import 'dart:async'; import 'dart:convert'; import 'package:built_collection/built_collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter_redux/flutter_redux.dart'; import 'package:http/http.dart'; import 'package:http/http.dart' as http; import 'package:invoiceninja_flutter/constants.dart'; import 'package:invoiceninja_flutter/data/models/models.dart'; import 'package:invoiceninja_flutter/data/web_client.dart'; import 'package:invoiceninja_flutter/main_app.dart'; import 'package:invoiceninja_flutter/redux/app/app_actions.dart'; import 'package:invoiceninja_flutter/redux/app/app_state.dart'; import 'package:invoiceninja_flutter/redux/design/design_selectors.dart'; import 'package:invoiceninja_flutter/redux/settings/settings_actions.dart'; import 'package:invoiceninja_flutter/utils/completers.dart'; import 'package:invoiceninja_flutter/utils/dialogs.dart'; import 'package:invoiceninja_flutter/utils/files.dart'; import 'package:invoiceninja_flutter/utils/localization.dart'; import 'package:invoiceninja_flutter/ui/app/entities/entity_actions_dialog.dart'; import 'package:printing/printing.dart'; import 'package:url_launcher/url_launcher.dart'; class ViewPurchaseOrderList implements PersistUI { ViewPurchaseOrderList({ this.force = false, this.page = 0, }); final bool force; final int? page; } class ViewPurchaseOrder implements PersistUI, PersistPrefs { ViewPurchaseOrder({ required this.purchaseOrderId, this.force = false, }); final String? purchaseOrderId; final bool force; } class EditPurchaseOrder implements PersistUI, PersistPrefs { EditPurchaseOrder( {required this.purchaseOrder, this.completer, this.purchaseOrderItemIndex, this.cancelCompleter, this.force = false}); final InvoiceEntity purchaseOrder; final Completer? completer; final int? purchaseOrderItemIndex; final Completer? cancelCompleter; final bool force; } class ShowEmailPurchaseOrder { ShowEmailPurchaseOrder({this.purchaseOrder, this.context, this.completer}); final InvoiceEntity? purchaseOrder; final BuildContext? context; final Completer? completer; } class ShowPdfPurchaseOrder { ShowPdfPurchaseOrder({this.purchaseOrder, this.context, this.activityId}); final InvoiceEntity? purchaseOrder; final BuildContext? context; final String? activityId; } class EditPurchaseOrderItem implements PersistUI { EditPurchaseOrderItem([this.itemIndex]); final int? itemIndex; } class UpdatePurchaseOrder implements PersistUI { UpdatePurchaseOrder(this.purchaseOrder); final InvoiceEntity purchaseOrder; } class UpdatePurchaseOrderVendor implements PersistUI { UpdatePurchaseOrderVendor({this.vendor}); final VendorEntity? vendor; } class LoadPurchaseOrder { LoadPurchaseOrder({this.completer, this.purchaseOrderId}); final Completer? completer; final String? purchaseOrderId; } class LoadPurchaseOrderActivity { LoadPurchaseOrderActivity({this.completer, this.purchaseOrderId}); final Completer? completer; final String? purchaseOrderId; } class LoadPurchaseOrders { LoadPurchaseOrders({this.completer, this.page = 1}); final Completer? completer; final int page; } class LoadPurchaseOrderRequest implements StartLoading {} class LoadPurchaseOrderFailure implements StopLoading { LoadPurchaseOrderFailure(this.error); final dynamic error; @override String toString() { return 'LoadPurchaseOrderFailure{error: $error}'; } } class LoadPurchaseOrderSuccess implements StopLoading, PersistData { LoadPurchaseOrderSuccess(this.purchaseOrder); final InvoiceEntity purchaseOrder; @override String toString() { return 'LoadPurchaseOrderSuccess{purchaseOrder: $purchaseOrder}'; } } class LoadPurchaseOrdersRequest implements StartLoading {} class LoadPurchaseOrdersFailure implements StopLoading { LoadPurchaseOrdersFailure(this.error); final dynamic error; @override String toString() { return 'LoadPurchaseOrdersFailure{error: $error}'; } } class LoadPurchaseOrdersSuccess implements StopLoading { LoadPurchaseOrdersSuccess(this.purchaseOrders); final BuiltList purchaseOrders; @override String toString() { return 'LoadPurchaseOrdersSuccess{purchaseOrders: $purchaseOrders}'; } } class SavePurchaseOrderDocumentRequest implements StartSaving { SavePurchaseOrderDocumentRequest({ required this.isPrivate, required this.completer, required this.multipartFiles, required this.purchaseOrder, }); final bool? isPrivate; final Completer completer; final List multipartFiles; final InvoiceEntity purchaseOrder; } class SavePurchaseOrderDocumentSuccess implements StopSaving, PersistData, PersistUI { SavePurchaseOrderDocumentSuccess(this.document); final DocumentEntity document; } class SavePurchaseOrderDocumentFailure implements StopSaving { SavePurchaseOrderDocumentFailure(this.error); final Object error; } class SavePurchaseOrderRequest implements StartSaving { SavePurchaseOrderRequest({ required this.completer, required this.purchaseOrder, required this.action, }); final Completer completer; final InvoiceEntity purchaseOrder; final EntityAction? action; } class SavePurchaseOrderSuccess implements StopSaving, PersistData, PersistUI { SavePurchaseOrderSuccess(this.purchaseOrder); final InvoiceEntity purchaseOrder; } class AddPurchaseOrderSuccess implements StopSaving, PersistData, PersistUI { AddPurchaseOrderSuccess(this.purchaseOrder); final InvoiceEntity purchaseOrder; } class SavePurchaseOrderFailure implements StopSaving { SavePurchaseOrderFailure(this.error); final Object error; } class BulkEmailPurchaseOrdersRequest implements StartSaving { BulkEmailPurchaseOrdersRequest( {this.completer, this.purchaseOrderIds, this.template}); final Completer? completer; final List? purchaseOrderIds; final EmailTemplate? template; } class BulkEmailPurchaseOrdersSuccess implements StopSaving, PersistData { BulkEmailPurchaseOrdersSuccess(this.purchaseOrders); final List purchaseOrders; } class BulkEmailPurchaseOrdersFailure implements StopSaving { BulkEmailPurchaseOrdersFailure(this.error); final dynamic error; } class ArchivePurchaseOrdersRequest implements StartSaving { ArchivePurchaseOrdersRequest(this.completer, this.purchaseOrderIds); final Completer completer; final List purchaseOrderIds; } class ArchivePurchaseOrdersSuccess implements StopSaving, PersistData { ArchivePurchaseOrdersSuccess(this.purchaseOrders); final List purchaseOrders; } class ArchivePurchaseOrdersFailure implements StopSaving { ArchivePurchaseOrdersFailure(this.purchaseOrders); final List purchaseOrders; } class DeletePurchaseOrdersRequest implements StartSaving { DeletePurchaseOrdersRequest(this.completer, this.purchaseOrderIds); final Completer completer; final List purchaseOrderIds; } class DeletePurchaseOrdersSuccess implements StopSaving, PersistData { DeletePurchaseOrdersSuccess(this.purchaseOrders); final List purchaseOrders; } class DeletePurchaseOrdersFailure implements StopSaving { DeletePurchaseOrdersFailure(this.purchaseOrders); final List purchaseOrders; } class DownloadPurchaseOrdersRequest implements StartSaving { DownloadPurchaseOrdersRequest(this.completer, this.invoiceIds); final Completer completer; final List invoiceIds; } class DownloadPurchaseOrdersSuccess implements StopSaving {} class DownloadPurchaseOrdersFailure implements StopSaving { DownloadPurchaseOrdersFailure(this.error); final Object error; } class AcceptPurchaseOrdersRequest implements StartSaving { AcceptPurchaseOrdersRequest(this.completer, this.purchaseOrderIds); final List purchaseOrderIds; final Completer completer; } class AcceptPurchaseOrderSuccess implements StopSaving { AcceptPurchaseOrderSuccess(this.purchaseOrders); final List purchaseOrders; } class AcceptPurchaseOrderFailure implements StopSaving { AcceptPurchaseOrderFailure(this.error); final dynamic error; } class CancelPurchaseOrdersRequest implements StartSaving { CancelPurchaseOrdersRequest(this.completer, this.purchaseOrderIds); final List purchaseOrderIds; final Completer completer; } class CancelPurchaseOrderSuccess implements StopSaving { CancelPurchaseOrderSuccess(this.purchaseOrders); final List purchaseOrders; } class CancelPurchaseOrderFailure implements StopSaving { CancelPurchaseOrderFailure(this.error); final dynamic error; } class RestorePurchaseOrdersRequest implements StartSaving { RestorePurchaseOrdersRequest(this.completer, this.purchaseOrderIds); final Completer completer; final List purchaseOrderIds; } class RestorePurchaseOrdersSuccess implements StopSaving, PersistData { RestorePurchaseOrdersSuccess(this.purchaseOrders); final List purchaseOrders; } class RestorePurchaseOrdersFailure implements StopSaving { RestorePurchaseOrdersFailure(this.purchaseOrders); final List purchaseOrders; } class EmailPurchaseOrderRequest implements StartSaving { EmailPurchaseOrderRequest({ required this.completer, required this.purchaseOrderId, required this.template, required this.subject, required this.body, required this.ccEmail, }); final Completer completer; final String purchaseOrderId; final EmailTemplate template; final String subject; final String body; final String ccEmail; } class EmailPurchaseOrderSuccess implements StopSaving, PersistData { EmailPurchaseOrderSuccess(this.purchaseOrder); final InvoiceEntity purchaseOrder; } class EmailPurchaseOrderFailure implements StopSaving { EmailPurchaseOrderFailure(this.error); final dynamic error; } class MarkPurchaseOrdersSentRequest implements StartSaving { MarkPurchaseOrdersSentRequest(this.completer, this.purchaseOrderIds); final Completer completer; final List purchaseOrderIds; } class MarkPurchaseOrderSentSuccess implements StopSaving, PersistData { MarkPurchaseOrderSentSuccess(this.purchaseOrders); final List purchaseOrders; } class MarkPurchaseOrderSentFailure implements StopSaving { MarkPurchaseOrderSentFailure(this.error); final Object error; } class ConvertPurchaseOrdersToExpensesRequest implements StartSaving { ConvertPurchaseOrdersToExpensesRequest(this.completer, this.purchaseOrderIds); final Completer completer; final List purchaseOrderIds; } class ConvertPurchaseOrdersToExpensesSuccess implements StopSaving, PersistData { ConvertPurchaseOrdersToExpensesSuccess(this.purchaseOrders); final List purchaseOrders; } class ConvertPurchaseOrdersToExpensesFailure implements StopSaving { ConvertPurchaseOrdersToExpensesFailure(this.error); final Object error; } class AddPurchaseOrdersToInventoryRequest implements StartSaving { AddPurchaseOrdersToInventoryRequest(this.completer, this.purchaseOrderIds); final Completer completer; final List purchaseOrderIds; } class AddPurchaseOrdersToInventorySuccess implements StopSaving, PersistData { AddPurchaseOrdersToInventorySuccess(this.purchaseOrders); final List purchaseOrders; } class AddPurchaseOrdersToInventoryFailure implements StopSaving { AddPurchaseOrdersToInventoryFailure(this.error); final Object error; } class ApprovePurchaseOrders implements StartSaving { ApprovePurchaseOrders(this.completer, this.purchaseOrderIds); final List purchaseOrderIds; final Completer completer; } class ApprovePurchaseOrderSuccess implements StopSaving { ApprovePurchaseOrderSuccess({this.purchaseOrders}); final List? purchaseOrders; } class ApprovePurchaseOrderFailure implements StopSaving { ApprovePurchaseOrderFailure(this.error); final dynamic error; } class AddPurchaseOrderContact implements PersistUI { AddPurchaseOrderContact({this.contact, this.invitation}); final VendorContactEntity? contact; final InvitationEntity? invitation; } class RemovePurchaseOrderContact implements PersistUI { RemovePurchaseOrderContact({this.invitation}); final InvitationEntity? invitation; } class AddPurchaseOrderItem implements PersistUI { AddPurchaseOrderItem({this.purchaseOrderItem}); final InvoiceItemEntity? purchaseOrderItem; } class MovePurchaseOrderItem implements PersistUI { MovePurchaseOrderItem({ this.oldIndex, this.newIndex, }); final int? oldIndex; final int? newIndex; } class AddPurchaseOrderItems implements PersistUI { AddPurchaseOrderItems(this.lineItems); final List lineItems; } class UpdatePurchaseOrderItem implements PersistUI { UpdatePurchaseOrderItem({ required this.index, required this.purchaseOrderItem, }); final int index; final InvoiceItemEntity purchaseOrderItem; } class DeletePurchaseOrderItem implements PersistUI { DeletePurchaseOrderItem(this.index); final int index; } class FilterPurchaseOrders implements PersistUI { FilterPurchaseOrders(this.filter); final String? filter; } class SortPurchaseOrders implements PersistUI, PersistPrefs { SortPurchaseOrders(this.field); final String field; } class FilterPurchaseOrdersByState implements PersistUI { FilterPurchaseOrdersByState(this.state); final EntityState state; } class FilterPurchaseOrdersByStatus implements PersistUI { FilterPurchaseOrdersByStatus(this.status); final EntityStatus status; } class FilterPurchaseOrderDropdown { FilterPurchaseOrderDropdown(this.filter); final String? filter; } class FilterPurchaseOrdersByCustom1 implements PersistUI { FilterPurchaseOrdersByCustom1(this.value); final String value; } class FilterPurchaseOrdersByCustom2 implements PersistUI { FilterPurchaseOrdersByCustom2(this.value); final String value; } class FilterPurchaseOrdersByCustom3 implements PersistUI { FilterPurchaseOrdersByCustom3(this.value); final String value; } class FilterPurchaseOrdersByCustom4 implements PersistUI { FilterPurchaseOrdersByCustom4(this.value); final String value; } class StartPurchaseOrderMultiselect { StartPurchaseOrderMultiselect(); } class AddToPurchaseOrderMultiselect { AddToPurchaseOrderMultiselect({required this.entity}); final BaseEntity? entity; } class RemoveFromPurchaseOrderMultiselect { RemoveFromPurchaseOrderMultiselect({required this.entity}); final BaseEntity? entity; } class ClearPurchaseOrderMultiselect { ClearPurchaseOrderMultiselect(); } class UpdatePurchaseOrderTab implements PersistUI { UpdatePurchaseOrderTab({this.tabIndex}); final int? tabIndex; } void handlePurchaseOrderAction(BuildContext? context, List purchaseOrders, EntityAction? action) async { if (purchaseOrders.isEmpty) { return; } final store = StoreProvider.of(context!); final state = store.state; final localization = AppLocalization.of(context); final purchaseOrder = purchaseOrders.first as InvoiceEntity; final purchaseOrderIds = purchaseOrders.map((purchaseOrder) => purchaseOrder.id).toList(); switch (action) { case EntityAction.edit: editEntity(entity: purchaseOrder); break; case EntityAction.viewPdf: store.dispatch( ShowPdfPurchaseOrder(purchaseOrder: purchaseOrder, context: context)); break; case EntityAction.restore: store.dispatch(RestorePurchaseOrdersRequest( snackBarCompleter(localization!.restoredPurchaseOrder), purchaseOrderIds)); break; case EntityAction.archive: store.dispatch(ArchivePurchaseOrdersRequest( snackBarCompleter(localization!.archivedPurchaseOrder), purchaseOrderIds)); break; case EntityAction.delete: store.dispatch(DeletePurchaseOrdersRequest( snackBarCompleter(localization!.deletedPurchaseOrder), purchaseOrderIds)); break; case EntityAction.printPdf: final invitation = purchaseOrder.invitations.first; final url = invitation.downloadLink; store.dispatch(StartSaving()); final http.Response? response = await WebClient().get(url, state.token, rawResponse: true); store.dispatch(StopSaving()); await Printing.layoutPdf(onLayout: (_) => response!.bodyBytes); break; case EntityAction.bulkPrint: store.dispatch(StartSaving()); final url = state.credentials.url+ '/purchase_orders/bulk'; final data = json.encode({ 'ids': purchaseOrderIds, 'action': EntityAction.bulkPrint.toApiParam() }); final http.Response? response = await WebClient() .post(url, state.credentials.token, data: data, rawResponse: true); store.dispatch(StopSaving()); await Printing.layoutPdf(onLayout: (_) => response!.bodyBytes); break; case EntityAction.addToInventory: store.dispatch(AddPurchaseOrdersToInventoryRequest( snackBarCompleter(purchaseOrders.length == 1 ? localization!.addedPurchaseOrderToInventory : localization!.addedPurchaseOrdersToInventory), purchaseOrderIds)); break; case EntityAction.convertToExpense: store.dispatch(ConvertPurchaseOrdersToExpensesRequest( snackBarCompleter(purchaseOrders.length == 1 ? localization!.convertedToExpense : localization!.convertedToExpenses), purchaseOrderIds)); break; case EntityAction.viewExpense: viewEntityById( entityId: purchaseOrder.expenseId, entityType: EntityType.expense); break; case EntityAction.markSent: store.dispatch(MarkPurchaseOrdersSentRequest( snackBarCompleter(purchaseOrders.length == 1 ? localization!.markedPurchaseOrderAsSent : localization!.markedPurchaseOrdersAsSent), purchaseOrderIds)); break; case EntityAction.cancelInvoice: store.dispatch(CancelPurchaseOrdersRequest( snackBarCompleter(purchaseOrders.length == 1 ? localization!.cancelledPurchaseOrder : localization!.cancelledPurchaseOrders), purchaseOrderIds)); break; case EntityAction.accept: store.dispatch(AcceptPurchaseOrdersRequest( snackBarCompleter(purchaseOrders.length == 1 ? localization!.acceptedPurchaseOrder : localization!.acceptedPurchaseOrders), purchaseOrderIds)); break; case EntityAction.toggleMultiselect: if (!store.state.purchaseOrderListState.isInMultiselect()) { store.dispatch(StartPurchaseOrderMultiselect()); } if (purchaseOrders.isEmpty) { break; } for (final purchaseOrder in purchaseOrders) { if (!store.state.purchaseOrderListState.isSelected(purchaseOrder.id)) { store.dispatch(AddToPurchaseOrderMultiselect(entity: purchaseOrder)); } else { store.dispatch( RemoveFromPurchaseOrderMultiselect(entity: purchaseOrder)); } } break; case EntityAction.vendorPortal: launchUrl(Uri.parse(purchaseOrder.invitationSilentLink)); break; case EntityAction.sendEmail: case EntityAction.bulkSendEmail: case EntityAction.schedule: bool emailValid = true; purchaseOrders.forEach((purchaseOrder) { final vendor = state.vendorState.get( (purchaseOrder as InvoiceEntity).vendorId, ); if (!vendor.hasEmailAddress) { emailValid = false; } }); if (!emailValid) { showMessageDialog( message: localization!.vendorEmailNotSet, secondaryActions: [ TextButton( onPressed: () { Navigator.of(context).pop(); editEntity( entity: state.vendorState.get(purchaseOrder.vendorId)); }, child: Text(localization.editVendor.toUpperCase())) ]); return; } if (action == EntityAction.sendEmail) { store.dispatch( ShowEmailPurchaseOrder( completer: snackBarCompleter(localization!.emailedPurchaseOrder), purchaseOrder: purchaseOrder, context: navigatorKey.currentContext!, ), ); } else if (action == EntityAction.schedule) { if (!state.isProPlan) { showMessageDialog( message: localization!.upgradeToPaidPlanToSchedule, secondaryActions: [ TextButton( onPressed: () { store.dispatch( ViewSettings(section: kSettingsAccountManagement)); Navigator.of(context).pop(); }, child: Text(localization.upgrade.toUpperCase())), ]); return; } createEntity( entity: ScheduleEntity(ScheduleEntity.TEMPLATE_EMAIL_RECORD) .rebuild((b) => b ..parameters.entityType = EntityType.purchaseOrder.apiValue ..parameters.entityId = purchaseOrder.id)); } else { confirmCallback( context: navigatorKey.currentContext!, message: localization!.bulkEmailPurchaseOrders, callback: (_) { store.dispatch(BulkEmailPurchaseOrdersRequest( completer: snackBarCompleter(purchaseOrderIds.length == 1 ? localization.emailedPurchaseOrder : localization.emailedPurchaseOrders), purchaseOrderIds: purchaseOrderIds, )); }); } break; case EntityAction.cloneToQuote: final designId = getDesignIdForClientByEntity( state: state, clientId: purchaseOrder.clientId, entityType: EntityType.purchaseOrder); createEntity( entity: purchaseOrder.clone .rebuild((b) => b ..entityType = EntityType.quote ..designId = designId) .recreateInvitations(state)); break; case EntityAction.cloneToOther: cloneToDialog(invoice: purchaseOrder); break; case EntityAction.cloneToInvoice: final designId = getDesignIdForClientByEntity( state: state, clientId: purchaseOrder.clientId, entityType: EntityType.invoice); createEntity( entity: purchaseOrder.clone .rebuild((b) => b ..entityType = EntityType.invoice ..designId = designId) .recreateInvitations(state)); break; case EntityAction.clone: case EntityAction.cloneToPurchaseOrder: createEntity(entity: purchaseOrder.clone); break; case EntityAction.cloneToCredit: final designId = getDesignIdForClientByEntity( state: state, clientId: purchaseOrder.clientId, entityType: EntityType.credit); createEntity( entity: purchaseOrder.clone .rebuild((b) => b ..entityType = EntityType.credit ..designId = designId) .recreateInvitations(state)); break; case EntityAction.cloneToRecurring: final designId = getDesignIdForClientByEntity( state: state, clientId: purchaseOrder.clientId, entityType: EntityType.invoice); createEntity( entity: purchaseOrder.clone .rebuild((b) => b ..entityType = EntityType.recurringInvoice ..designId = designId) .recreateInvitations(state)); break; case EntityAction.download: await WebClient() .get(purchaseOrder.invitationDownloadLink, state.token, rawResponse: true) .then((response) { store.dispatch(StopLoading()); saveDownloadedFile(response.bodyBytes, localization!.purchaseOrder + '_' + purchaseOrder.number + '.pdf'); }).catchError((_) { store.dispatch(StopLoading()); }); break; case EntityAction.bulkDownload: store.dispatch(DownloadPurchaseOrdersRequest( snackBarCompleter(localization!.exportedData), purchaseOrderIds)); break; case EntityAction.more: showEntityActionsDialog( entities: [purchaseOrder], ); break; default: print('## ERROR: unhandled action $action in purchase_order_actions'); break; } }