From d06e06c41336fd39bbb2ee24049866088f6dad01 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Thu, 16 Jun 2022 10:33:25 +0300 Subject: [PATCH] Purchase orders --- lib/data/models/company_model.dart | 2 + .../purchase_order_actions.dart | 27 +++++ lib/ui/app/main_screen.dart | 33 ++++++ .../purchase_order_email_vm.dart | 102 ++++++++++++++++++ .../purchase_order/purchase_order_pdf_vm.dart | 62 +++++++++++ lib/utils/i18n.dart | 11 ++ 6 files changed, 237 insertions(+) create mode 100644 lib/ui/purchase_order/purchase_order_email_vm.dart create mode 100644 lib/ui/purchase_order/purchase_order_pdf_vm.dart diff --git a/lib/data/models/company_model.dart b/lib/data/models/company_model.dart index ff5f16a7d..65fd7370b 100644 --- a/lib/data/models/company_model.dart +++ b/lib/data/models/company_model.dart @@ -579,9 +579,11 @@ abstract class CompanyEntity extends Object ); bool isModuleEnabled(EntityType entityType) { + /* if (entityType == EntityType.purchaseOrder) { return false; } + */ if ((entityType == EntityType.invoice || entityType == EntityType.payment) && diff --git a/lib/redux/purchase_order/purchase_order_actions.dart b/lib/redux/purchase_order/purchase_order_actions.dart index 4359aef10..412ab39bf 100644 --- a/lib/redux/purchase_order/purchase_order_actions.dart +++ b/lib/redux/purchase_order/purchase_order_actions.dart @@ -194,6 +194,33 @@ class RestorePurchaseOrdersFailure implements StopSaving { final List purchaseOrders; } +class EmailPurchaseOrderRequest implements StartSaving { + EmailPurchaseOrderRequest( + {this.completer, + this.purchaseOrderId, + this.template, + this.subject, + this.body}); + + final Completer completer; + final String purchaseOrderId; + final EmailTemplate template; + final String subject; + final String body; +} + +class EmailPurchaseOrderSuccess implements StopSaving, PersistData { + EmailPurchaseOrderSuccess(this.quote); + + final InvoiceEntity quote; +} + +class EmailPurchaseOrderFailure implements StopSaving { + EmailPurchaseOrderFailure(this.error); + + final dynamic error; +} + class FilterPurchaseOrders implements PersistUI { FilterPurchaseOrders(this.filter); diff --git a/lib/ui/app/main_screen.dart b/lib/ui/app/main_screen.dart index f7158b6c0..85fd8d011 100644 --- a/lib/ui/app/main_screen.dart +++ b/lib/ui/app/main_screen.dart @@ -4,6 +4,12 @@ import 'package:flutter/material.dart'; // Package imports: import 'package:flutter_redux/flutter_redux.dart'; import 'package:invoiceninja_flutter/ui/app/app_title_bar.dart'; +import 'package:invoiceninja_flutter/ui/purchase_order/edit/purchase_order_edit_vm.dart'; +import 'package:invoiceninja_flutter/ui/purchase_order/purchase_order_email_vm.dart'; +import 'package:invoiceninja_flutter/ui/purchase_order/purchase_order_pdf_vm.dart'; +import 'package:invoiceninja_flutter/ui/purchase_order/purchase_order_screen.dart'; +import 'package:invoiceninja_flutter/ui/purchase_order/purchase_order_screen_vm.dart'; +import 'package:invoiceninja_flutter/ui/purchase_order/view/purchase_order_view_vm.dart'; import 'package:invoiceninja_flutter/ui/settings/payment_settings_vm.dart'; import 'package:invoiceninja_flutter/utils/platforms.dart'; import 'package:redux/redux.dart'; @@ -169,6 +175,12 @@ class MainScreen extends StatelessWidget { editingFilterEntity: editingFilterEntity, ); break; + case PurchaseOrderScreen.route: + screen = EntityScreens( + entityType: EntityType.purchaseOrder, + editingFilterEntity: editingFilterEntity, + ); + break; case ProjectScreen.route: screen = EntityScreens( entityType: EntityType.project, @@ -392,6 +404,13 @@ class EntityScreens extends StatelessWidget { ? CreditEmailScreen() : CreditEditScreen(); break; + case PurchaseOrderScreen.route: + child = isPdf + ? PurchaseOrderPdfScreen() + : isEmail + ? PurchaseOrderEmailScreen() + : PurchaseOrderEditScreen(); + break; case RecurringInvoiceScreen.route: child = isPdf ? RecurringInvoicePdfScreen() @@ -447,6 +466,9 @@ class EntityScreens extends StatelessWidget { case EntityType.credit: child = CreditEditScreen(); break; + case EntityType.purchaseOrder: + child = PurchaseOrderEditScreen(); + break; case EntityType.project: child = ProjectEditScreen(); break; @@ -497,6 +519,9 @@ class EntityScreens extends StatelessWidget { case EntityType.credit: child = CreditViewScreen(); break; + case EntityType.purchaseOrder: + child = PurchaseOrderViewScreen(); + break; case EntityType.project: child = ProjectViewScreen(); break; @@ -562,6 +587,11 @@ class EntityScreens extends StatelessWidget { ? CreditViewScreen() : CreditViewScreen(isFilter: true); break; + case EntityType.purchaseOrder: + leftFilterChild = editingFilterEntity && !uiState.isEditing + ? PurchaseOrderViewScreen() + : PurchaseOrderViewScreen(isFilter: true); + break; case EntityType.payment: leftFilterChild = editingFilterEntity && !uiState.isEditing ? PaymentEditScreen() @@ -650,6 +680,9 @@ class EntityScreens extends StatelessWidget { case EntityType.credit: listWidget = CreditScreenBuilder(); break; + case EntityType.purchaseOrder: + listWidget = PurchaseOrderScreenBuilder(); + break; case EntityType.project: listWidget = ProjectScreenBuilder(); break; diff --git a/lib/ui/purchase_order/purchase_order_email_vm.dart b/lib/ui/purchase_order/purchase_order_email_vm.dart new file mode 100644 index 000000000..32b1e4b16 --- /dev/null +++ b/lib/ui/purchase_order/purchase_order_email_vm.dart @@ -0,0 +1,102 @@ +// Flutter imports: +import 'package:flutter/material.dart'; + +// Package imports: +import 'package:flutter_redux/flutter_redux.dart'; +import 'package:redux/redux.dart'; + +// Project imports: +import 'package:invoiceninja_flutter/data/models/models.dart'; +import 'package:invoiceninja_flutter/redux/app/app_actions.dart'; +import 'package:invoiceninja_flutter/redux/app/app_state.dart'; +import 'package:invoiceninja_flutter/redux/client/client_actions.dart'; +import 'package:invoiceninja_flutter/redux/purchase_order/purchase_order_actions.dart'; +import 'package:invoiceninja_flutter/ui/app/invoice/invoice_email_view.dart'; +import 'package:invoiceninja_flutter/ui/invoice/invoice_email_vm.dart'; +import 'package:invoiceninja_flutter/utils/completers.dart'; +import 'package:invoiceninja_flutter/utils/localization.dart'; +import 'package:invoiceninja_flutter/utils/platforms.dart'; + +class PurchaseOrderEmailScreen extends StatelessWidget { + const PurchaseOrderEmailScreen({Key key}) : super(key: key); + + static const String route = '/purchase_order/email'; + + @override + Widget build(BuildContext context) { + return StoreConnector( + onInit: (Store store) { + final state = store.state; + final purchaseOrderId = state.uiState.purchaseOrderUIState.selectedId; + final purchaseOrder = state.purchaseOrderState.map[purchaseOrderId]; + final client = state.clientState.map[purchaseOrder.clientId]; + if (client.isStale) { + store.dispatch(LoadClient(clientId: client.id)); + } + }, + converter: (Store store) { + final state = store.state; + final purchaseOrderId = state.uiState.purchaseOrderUIState.selectedId; + final purchaseOrder = state.purchaseOrderState.map[purchaseOrderId]; + return EmailPurchaseOrderVM.fromStore(store, purchaseOrder); + }, + builder: (context, viewModel) { + return InvoiceEmailView( + viewModel: viewModel, + ); + }, + ); + } +} + +class EmailPurchaseOrderVM extends EmailEntityVM { + EmailPurchaseOrderVM({ + @required AppState state, + @required bool isLoading, + @required bool isSaving, + @required CompanyEntity company, + @required InvoiceEntity invoice, + @required ClientEntity client, + @required + Function(BuildContext, EmailTemplate, String, String) onSendPressed, + }) : super( + state: state, + isLoading: isLoading, + isSaving: isSaving, + company: company, + invoice: invoice, + client: client, + onSendPressed: onSendPressed, + ); + + factory EmailPurchaseOrderVM.fromStore( + Store store, InvoiceEntity purchaseOrder) { + final state = store.state; + + return EmailPurchaseOrderVM( + state: state, + isLoading: state.isLoading, + isSaving: state.isSaving, + company: state.company, + invoice: purchaseOrder, + client: state.clientState.map[purchaseOrder.clientId], + onSendPressed: (context, template, subject, body) { + final completer = snackBarCompleter( + context, AppLocalization.of(context).emailedPurchaseOrder, + shouldPop: isMobile(context)); + if (!isMobile(context)) { + completer.future.then((value) { + viewEntity(entity: purchaseOrder); + }); + } + store.dispatch(EmailPurchaseOrderRequest( + completer: completer, + purchaseOrderId: purchaseOrder.id, + template: template, + subject: subject, + body: body, + )); + }, + ); + } +} diff --git a/lib/ui/purchase_order/purchase_order_pdf_vm.dart b/lib/ui/purchase_order/purchase_order_pdf_vm.dart new file mode 100644 index 000000000..b13b0cbf9 --- /dev/null +++ b/lib/ui/purchase_order/purchase_order_pdf_vm.dart @@ -0,0 +1,62 @@ +// Flutter imports: +import 'package:flutter/material.dart'; + +// Package imports: +import 'package:flutter_redux/flutter_redux.dart'; +import 'package:redux/redux.dart'; + +// Project imports: +import 'package:invoiceninja_flutter/data/models/models.dart'; +import 'package:invoiceninja_flutter/redux/app/app_state.dart'; +import 'package:invoiceninja_flutter/ui/invoice/invoice_pdf.dart'; +import 'package:invoiceninja_flutter/ui/invoice/invoice_pdf_vm.dart'; + +class PurchaseOrderPdfScreen extends StatelessWidget { + const PurchaseOrderPdfScreen({Key key, this.showAppBar = true}) + : super(key: key); + + final bool showAppBar; + + static const String route = '/purchase_order/pdf'; + + @override + Widget build(BuildContext context) { + return StoreConnector( + converter: (Store store) { + return PurchaseOrderPdfVM.fromStore(store); + }, + builder: (context, vm) { + return InvoicePdfView( + key: ValueKey('__purchase_order_pdf_${vm.invoice.id}__'), + viewModel: vm, + showAppBar: showAppBar, + ); + }, + ); + } +} + +class PurchaseOrderPdfVM extends EntityPdfVM { + PurchaseOrderPdfVM({ + AppState state, + InvoiceEntity invoice, + String activityId, + }) : super( + state: state, + invoice: invoice, + activityId: activityId, + ); + + factory PurchaseOrderPdfVM.fromStore(Store store) { + final state = store.state; + final purchaseOrderUIState = state.uiState.purchaseOrderUIState; + final invoiceId = purchaseOrderUIState.selectedId; + final invoice = state.purchaseOrderState.get(invoiceId); + + return PurchaseOrderPdfVM( + state: state, + invoice: invoice, + //activityId: purchaseOrderUIState.historyActivityId, + ); + } +} diff --git a/lib/utils/i18n.dart b/lib/utils/i18n.dart index 707b0fb0b..0f248f3e8 100644 --- a/lib/utils/i18n.dart +++ b/lib/utils/i18n.dart @@ -16,6 +16,9 @@ mixin LocalizationsProvider on LocaleCodeAware { static final Map> _localizedValues = { 'en': { // STARTER: lang key - do not remove comment + 'emailed_purchase_order': 'Successfully queued purchase order to be sent', + 'emailed_purchase_orders': + 'Successfully queued purchase orders to be sent', 'enable_react_app': 'Change to the React web app', 'purchase_order_design': 'Purchase Order Design', 'purchase_order_terms': 'Purchase Order Terms', @@ -70688,6 +70691,14 @@ mixin LocalizationsProvider on LocaleCodeAware { _localizedValues[localeCode]['enable_react_app'] ?? _localizedValues['en']['enable_react_app']; + String get emailedPurchaseOrder => + _localizedValues[localeCode]['emailed_purchase_orderk'] ?? + _localizedValues['en']['emailed_purchase_order']; + + String get emailedPurchaseOrders => + _localizedValues[localeCode]['emailed_purchase_orders'] ?? + _localizedValues['en']['emailed_purchase_orders']; + // STARTER: lang field - do not remove comment String lookup(String key) {