Skip to content

Commit

Permalink
Add merge action to vendors
Browse files Browse the repository at this point in the history
  • Loading branch information
hillelcoren committed Dec 2, 2024
1 parent e954ad8 commit b8831d0
Show file tree
Hide file tree
Showing 7 changed files with 172 additions and 4 deletions.
4 changes: 4 additions & 0 deletions lib/data/models/vendor_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,10 @@ abstract class VendorEntity extends Object
}
*/

if (userCompany!.isAdmin && !multiselect) {
actions.add(EntityAction.merge);
}

if (actions.isNotEmpty && actions.last != null) {
actions.add(null);
}
Expand Down
18 changes: 18 additions & 0 deletions lib/data/repositories/vendor_repository.dart
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,24 @@ class VendorRepository {
return vendorResponse.data;
}

Future<VendorEntity> merge({
required Credentials credentials,
required String? vendorId,
required String? mergeIntoVendorId,
required String? password,
required String? idToken,
}) async {
final url = credentials.url + '/vendors/$mergeIntoVendorId/$vendorId/merge';

final dynamic response = await webClient.post(url, credentials.token,
password: password, idToken: idToken);

final VendorItemResponse clientResponse =
serializers.deserializeWith(VendorItemResponse.serializer, response)!;

return clientResponse.data;
}

Future<VendorEntity> uploadDocument(
Credentials credentials,
BaseEntity entity,
Expand Down
102 changes: 101 additions & 1 deletion lib/redux/vendor/vendor_actions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import 'package:invoiceninja_flutter/redux/app/app_actions.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/redux/document/document_actions.dart';
import 'package:invoiceninja_flutter/ui/app/entities/entity_actions_dialog.dart';
import 'package:invoiceninja_flutter/ui/app/forms/vendor_picker.dart';
import 'package:invoiceninja_flutter/utils/completers.dart';
import 'package:invoiceninja_flutter/utils/dialogs.dart';
import 'package:invoiceninja_flutter/utils/localization.dart';
Expand Down Expand Up @@ -175,6 +176,34 @@ class ArchiveVendorFailure implements StopSaving {
final List<VendorEntity?> vendors;
}

class MergeVendorsRequest implements StartSaving {
MergeVendorsRequest({
this.completer,
this.vendorId,
this.mergeIntoVendorId,
this.password,
this.idToken,
});

final Completer? completer;
final String? vendorId;
final String? mergeIntoVendorId;
final String? password;
final String? idToken;
}

class MergeVendorsSuccess implements StopSaving, PersistData {
MergeVendorsSuccess(this.vendorId);

final String? vendorId;
}

class MergeVendorsFailure implements StopSaving {
MergeVendorsFailure(this.vendors);

final List<VendorEntity> vendors;
}

class DeleteVendorRequest implements StartSaving {
DeleteVendorRequest(this.completer, this.vendorIds);

Expand Down Expand Up @@ -364,7 +393,14 @@ void handleVendorAction(BuildContext? context, List<BaseEntity> vendors,
),
);
break;

case EntityAction.merge:
showDialog<void>(
context: context,
builder: (context) => _MergVendorPicker(
vendor: vendor,
),
);
break;
case EntityAction.toggleMultiselect:
if (!store.state.vendorListState.isInMultiselect()) {
store.dispatch(StartVendorMultiselect());
Expand Down Expand Up @@ -473,3 +509,67 @@ class UpdateVendorTab implements PersistUI {

final int? tabIndex;
}

class _MergVendorPicker extends StatefulWidget {
const _MergVendorPicker({
Key? key,
required this.vendor,
}) : super(key: key);

final VendorEntity? vendor;

@override
State<_MergVendorPicker> createState() => __MergVendorPickerState();
}

class __MergVendorPickerState extends State<_MergVendorPicker> {
String? _mergeIntoVendorId;

@override
Widget build(BuildContext context) {
final localization = AppLocalization.of(context)!;
final store = StoreProvider.of<AppState>(context);
final state = store.state;

return AlertDialog(
title: Text(localization.mergeInto),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
VendorPicker(
vendorId: _mergeIntoVendorId,
vendorState: state.vendorState,
excludeIds: [widget.vendor!.id],
onSelected: (vendor) =>
setState(() => _mergeIntoVendorId = vendor?.id),
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text(localization.close),
),
TextButton(
onPressed: () {
passwordCallback(
context: context,
callback: (password, idToken) {
store.dispatch(MergeVendorsRequest(
vendorId: widget.vendor!.id,
idToken: idToken,
password: password,
mergeIntoVendorId: _mergeIntoVendorId,
completer: snackBarCompleter<Null>(
localization.mergedVendors,
),
));
Navigator.of(context).pop();
});
},
child: Text(localization.merge),
),
],
);
}
}
30 changes: 30 additions & 0 deletions lib/redux/vendor/vendor_middleware.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ List<Middleware<AppState>> createStoreVendorsMiddleware([
final loadVendors = _loadVendors(repository);
final loadVendor = _loadVendor(repository);
final saveVendor = _saveVendor(repository);
final mergeVendors = _mergeVendors(repository);
final archiveVendor = _archiveVendor(repository);
final deleteVendor = _deleteVendor(repository);
final restoreVendor = _restoreVendor(repository);
Expand All @@ -41,6 +42,7 @@ List<Middleware<AppState>> createStoreVendorsMiddleware([
TypedMiddleware<AppState, LoadVendors>(loadVendors),
TypedMiddleware<AppState, LoadVendor>(loadVendor),
TypedMiddleware<AppState, SaveVendorRequest>(saveVendor),
TypedMiddleware<AppState, MergeVendorsRequest>(mergeVendors),
TypedMiddleware<AppState, ArchiveVendorRequest>(archiveVendor),
TypedMiddleware<AppState, DeleteVendorRequest>(deleteVendor),
TypedMiddleware<AppState, RestoreVendorRequest>(restoreVendor),
Expand Down Expand Up @@ -118,6 +120,34 @@ Middleware<AppState> _archiveVendor(VendorRepository repository) {
};
}

Middleware<AppState> _mergeVendors(VendorRepository repository) {
return (Store<AppState> store, dynamic dynamicAction, NextDispatcher next) {
final action = dynamicAction as MergeVendorsRequest;
repository
.merge(
credentials: store.state.credentials,
vendorId: action.vendorId,
mergeIntoVendorId: action.mergeIntoVendorId,
idToken: action.idToken,
password: action.password,
)
.then((client) {
store.dispatch(MergeVendorsSuccess(action.vendorId));
store.dispatch(RefreshData());
if (action.completer != null) {
action.completer!.complete(null);
}
}).catchError((Object error) {
store.dispatch(MergeVendorsFailure(error as List<VendorEntity>));
if (action.completer != null) {
action.completer!.completeError(error);
}
});

next(action);
};
}

Middleware<AppState> _deleteVendor(VendorRepository repository) {
return (Store<AppState> store, dynamic dynamicAction, NextDispatcher next) {
final action = dynamicAction as DeleteVendorRequest;
Expand Down
8 changes: 8 additions & 0 deletions lib/redux/vendor/vendor_reducer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,7 @@ final vendorsReducer = combineReducers<VendorState>([
TypedReducer<VendorState, ArchiveVendorSuccess>(_archiveVendorSuccess),
TypedReducer<VendorState, DeleteVendorSuccess>(_deleteVendorSuccess),
TypedReducer<VendorState, RestoreVendorSuccess>(_restoreVendorSuccess),
TypedReducer<VendorState, MergeVendorsSuccess>(_mergeVendorSuccess),
]);

VendorState _archiveVendorSuccess(
Expand Down Expand Up @@ -324,3 +325,10 @@ VendorState _setLoadedCompany(
final company = action.userCompany.company;
return vendorState.loadVendors(company.vendors);
}

VendorState _mergeVendorSuccess(
VendorState vendorState, MergeVendorsSuccess action) {
return vendorState.rebuild((b) => b
..map.remove(action.vendorId)
..list.remove(action.vendorId));
}
9 changes: 6 additions & 3 deletions lib/ui/app/forms/vendor_picker.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,17 @@ class VendorPicker extends StatelessWidget {
required this.vendorId,
required this.vendorState,
required this.onSelected,
required this.onAddPressed,
this.onAddPressed,
this.autofocus,
this.excludeIds = const [],
});

final String vendorId;
final String? vendorId;
final VendorState vendorState;
final Function(SelectableEntity?) onSelected;
final Function(Completer<SelectableEntity> completer) onAddPressed;
final Function(Completer<SelectableEntity> completer)? onAddPressed;
final bool? autofocus;
final List<String> excludeIds;

@override
Widget build(BuildContext context) {
Expand All @@ -56,6 +58,7 @@ class VendorPicker extends StatelessWidget {
vendor: VendorEntity().rebuild((b) => b..name = name),
completer: completer));
},
excludeIds: excludeIds,
);
}
}
5 changes: 5 additions & 0 deletions lib/utils/i18n.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ mixin LocalizationsProvider on LocaleCodeAware {
static final Map<String, Map<String, String>> _localizedValues = {
'en': {
// STARTER: lang key - do not remove comment
'merged_vendors': 'Successfully merged vendors',
'activity_146': 'E-Invoice :invoice for :client was delivered',
'activity_147': 'E-Invoice :invoice for :client failed to send',
'activity_148': 'E-Exepnse :expense created',
Expand Down Expand Up @@ -121185,6 +121186,10 @@ mixin LocalizationsProvider on LocaleCodeAware {
_localizedValues[localeCode]!['ssl_host_override'] ??
_localizedValues['en']!['ssl_host_override']!;

String get mergedVendors =>
_localizedValues[localeCode]!['merged_vendors'] ??
_localizedValues['en']!['merged_vendors']!;

// STARTER: lang field - do not remove comment

String lookup(String? key, {String? overrideLocaleCode}) {
Expand Down

0 comments on commit b8831d0

Please sign in to comment.