diff --git a/lib/data/models/invoice_model.dart b/lib/data/models/invoice_model.dart index 5cb097e7a..b057ff990 100644 --- a/lib/data/models/invoice_model.dart +++ b/lib/data/models/invoice_model.dart @@ -1034,6 +1034,9 @@ abstract class InvoiceEntity extends Object actions.add(EntityAction.bulkSendEmail); } else { actions.add(EntityAction.sendEmail); + if (isUnpaid) { + actions.add(EntityAction.schedule); + } } } } @@ -1278,7 +1281,9 @@ abstract class InvoiceEntity extends Object bool get isApplied => isCredit && statusId == kCreditStatusApplied; bool get isUnpaid { - if (isQuote) { + if (isPurchaseOrder) { + return !isApproved; + } else if (isQuote) { return !isApproved; } else if (isCredit) { return !isApplied; diff --git a/lib/data/models/models.dart b/lib/data/models/models.dart index 14db96837..8f5836cfd 100644 --- a/lib/data/models/models.dart +++ b/lib/data/models/models.dart @@ -128,6 +128,7 @@ class EntityAction extends EnumClass { static const EntityAction merge = _$merge; static const EntityAction bulkPrint = _$bulkPrint; static const EntityAction autoBill = _$autoBill; + static const EntityAction schedule = _$schedule; @override String toString() { diff --git a/lib/data/models/models.g.dart b/lib/data/models/models.g.dart index ffd891003..56a062127 100644 --- a/lib/data/models/models.g.dart +++ b/lib/data/models/models.g.dart @@ -93,6 +93,7 @@ const EntityAction _$convertToProject = const EntityAction _$merge = const EntityAction._('merge'); const EntityAction _$bulkPrint = const EntityAction._('bulkPrint'); const EntityAction _$autoBill = const EntityAction._('autoBill'); +const EntityAction _$schedule = const EntityAction._('schedule'); EntityAction _$valueOf(String name) { switch (name) { @@ -248,6 +249,8 @@ EntityAction _$valueOf(String name) { return _$bulkPrint; case 'autoBill': return _$autoBill; + case 'schedule': + return _$schedule; default: throw new ArgumentError(name); } @@ -331,6 +334,7 @@ final BuiltSet _$values = _$merge, _$bulkPrint, _$autoBill, + _$schedule, ]); Serializer _$entityActionSerializer = diff --git a/lib/redux/credit/credit_actions.dart b/lib/redux/credit/credit_actions.dart index 813e29d39..609b5261b 100644 --- a/lib/redux/credit/credit_actions.dart +++ b/lib/redux/credit/credit_actions.dart @@ -10,6 +10,7 @@ import 'package:built_collection/built_collection.dart'; import 'package:flutter_redux/flutter_redux.dart'; import 'package:http/http.dart'; import 'package:invoiceninja_flutter/redux/document/document_actions.dart'; +import 'package:invoiceninja_flutter/redux/settings/settings_actions.dart'; import 'package:url_launcher/url_launcher.dart'; // Project imports: @@ -488,6 +489,7 @@ Future handleCreditAction( break; case EntityAction.sendEmail: case EntityAction.bulkSendEmail: + case EntityAction.schedule: bool emailValid = true; credits.forEach((credit) { final client = state.clientState.get( @@ -517,6 +519,29 @@ Future handleCreditAction( snackBarCompleter(context, localization.emailedCredit), credit: credit, context: context)); + } else if (action == EntityAction.schedule) { + if (!state.isProPlan) { + showMessageDialog( + context: context, + message: localization.upgradeToPaidPlanToSchedule, + secondaryActions: [ + TextButton( + onPressed: () { + store.dispatch( + ViewSettings(section: kSettingsAccountManagement)); + Navigator.of(context).pop(); + }, + child: Text(localization.upgrade.toUpperCase())), + ]); + return; + } + + createEntity( + context: context, + entity: ScheduleEntity().rebuild((b) => b + ..template = ScheduleEntity.TEMPLATE_SCHEDULE_ENTITY + ..parameters.entityType = EntityType.credit.apiValue + ..parameters.entityId = credit.id)); } else { confirmCallback( context: context, diff --git a/lib/redux/invoice/invoice_actions.dart b/lib/redux/invoice/invoice_actions.dart index 4066611cb..67adcaf7d 100644 --- a/lib/redux/invoice/invoice_actions.dart +++ b/lib/redux/invoice/invoice_actions.dart @@ -9,7 +9,9 @@ import 'package:flutter/material.dart'; import 'package:built_collection/built_collection.dart'; import 'package:flutter_redux/flutter_redux.dart'; import 'package:http/http.dart'; +import 'package:invoiceninja_flutter/constants.dart'; import 'package:invoiceninja_flutter/redux/document/document_actions.dart'; +import 'package:invoiceninja_flutter/redux/settings/settings_actions.dart'; import 'package:url_launcher/url_launcher.dart'; // Project imports: @@ -607,6 +609,7 @@ void handleInvoiceAction(BuildContext context, List invoices, break; case EntityAction.sendEmail: case EntityAction.bulkSendEmail: + case EntityAction.schedule: bool emailValid = true; invoices.forEach((invoice) { final client = state.clientState.get( @@ -636,6 +639,29 @@ void handleInvoiceAction(BuildContext context, List invoices, snackBarCompleter(context, localization.emailedInvoice), invoice: invoice, context: context)); + } else if (action == EntityAction.schedule) { + if (!state.isProPlan) { + showMessageDialog( + context: context, + message: localization.upgradeToPaidPlanToSchedule, + secondaryActions: [ + TextButton( + onPressed: () { + store.dispatch( + ViewSettings(section: kSettingsAccountManagement)); + Navigator.of(context).pop(); + }, + child: Text(localization.upgrade.toUpperCase())), + ]); + return; + } + + createEntity( + context: context, + entity: ScheduleEntity().rebuild((b) => b + ..template = ScheduleEntity.TEMPLATE_SCHEDULE_ENTITY + ..parameters.entityType = EntityType.invoice.apiValue + ..parameters.entityId = invoice.id)); } else { confirmCallback( context: context, diff --git a/lib/redux/purchase_order/purchase_order_actions.dart b/lib/redux/purchase_order/purchase_order_actions.dart index 5165fecaf..4147dd871 100644 --- a/lib/redux/purchase_order/purchase_order_actions.dart +++ b/lib/redux/purchase_order/purchase_order_actions.dart @@ -5,11 +5,13 @@ 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/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/localization.dart'; @@ -699,6 +701,7 @@ void handlePurchaseOrderAction(BuildContext context, break; case EntityAction.sendEmail: case EntityAction.bulkSendEmail: + case EntityAction.schedule: bool emailValid = true; purchaseOrders.forEach((purchaseOrder) { final vendor = state.vendorState.get( @@ -729,6 +732,29 @@ void handlePurchaseOrderAction(BuildContext context, context, localization.emailedPurchaseOrder), purchaseOrder: purchaseOrder, context: context)); + } else if (action == EntityAction.schedule) { + if (!state.isProPlan) { + showMessageDialog( + context: context, + message: localization.upgradeToPaidPlanToSchedule, + secondaryActions: [ + TextButton( + onPressed: () { + store.dispatch( + ViewSettings(section: kSettingsAccountManagement)); + Navigator.of(context).pop(); + }, + child: Text(localization.upgrade.toUpperCase())), + ]); + return; + } + + createEntity( + context: context, + entity: ScheduleEntity().rebuild((b) => b + ..template = ScheduleEntity.TEMPLATE_SCHEDULE_ENTITY + ..parameters.entityType = EntityType.purchaseOrder.apiValue + ..parameters.entityId = purchaseOrder.id)); } else { confirmCallback( context: context, diff --git a/lib/redux/quote/quote_actions.dart b/lib/redux/quote/quote_actions.dart index 0c14989e4..3c6ad7673 100644 --- a/lib/redux/quote/quote_actions.dart +++ b/lib/redux/quote/quote_actions.dart @@ -9,7 +9,9 @@ import 'package:flutter/material.dart'; import 'package:built_collection/built_collection.dart'; import 'package:flutter_redux/flutter_redux.dart'; import 'package:http/http.dart'; +import 'package:invoiceninja_flutter/constants.dart'; import 'package:invoiceninja_flutter/redux/document/document_actions.dart'; +import 'package:invoiceninja_flutter/redux/settings/settings_actions.dart'; import 'package:url_launcher/url_launcher.dart'; // Project imports: @@ -551,6 +553,7 @@ Future handleQuoteAction( break; case EntityAction.sendEmail: case EntityAction.bulkSendEmail: + case EntityAction.schedule: bool emailValid = true; quotes.forEach((quote) { final client = state.clientState.get( @@ -580,6 +583,29 @@ Future handleQuoteAction( snackBarCompleter(context, localization.emailedQuote), quote: quote, context: context)); + } else if (action == EntityAction.schedule) { + if (!state.isProPlan) { + showMessageDialog( + context: context, + message: localization.upgradeToPaidPlanToSchedule, + secondaryActions: [ + TextButton( + onPressed: () { + store.dispatch( + ViewSettings(section: kSettingsAccountManagement)); + Navigator.of(context).pop(); + }, + child: Text(localization.upgrade.toUpperCase())), + ]); + return; + } + + createEntity( + context: context, + entity: ScheduleEntity().rebuild((b) => b + ..template = ScheduleEntity.TEMPLATE_SCHEDULE_ENTITY + ..parameters.entityType = EntityType.quote.apiValue + ..parameters.entityId = quote.id)); } else { confirmCallback( context: context, diff --git a/lib/ui/schedule/edit/schedule_edit.dart b/lib/ui/schedule/edit/schedule_edit.dart index 5d586b12b..eb31136e7 100644 --- a/lib/ui/schedule/edit/schedule_edit.dart +++ b/lib/ui/schedule/edit/schedule_edit.dart @@ -164,7 +164,8 @@ class _ScheduleEditState extends State { value: schedule.template, onChanged: (dynamic value) { viewModel.onChanged( - schedule.rebuild((b) => b..template = value)); + schedule.rebuild((b) => b..template = value), + ); }, items: ScheduleEntity.TEMPLATES .map((entry) => DropdownMenuItem( @@ -351,15 +352,13 @@ class _ScheduleEditState extends State { EntityType.purchaseOrder ] .map((entityType) => DropdownMenuItem( - value: entityType.toString(), + value: entityType.apiValue, child: Text( - localization - .lookup(entityType.toString()), + localization.lookup(entityType.apiValue), ), )) .toList()), - if (parameters.entityType == - EntityType.invoice.toString()) + if (parameters.entityType == EntityType.invoice.apiValue) EntityDropdown( labelText: localization.invoice, entityType: EntityType.invoice, @@ -371,7 +370,7 @@ class _ScheduleEditState extends State { }, ) else if (parameters.entityType == - EntityType.quote.toString()) + EntityType.quote.apiValue) EntityDropdown( labelText: localization.quote, entityType: EntityType.quote, @@ -383,7 +382,7 @@ class _ScheduleEditState extends State { }, ) else if (parameters.entityType == - EntityType.credit.toString()) + EntityType.credit.apiValue) EntityDropdown( labelText: localization.credit, entityType: EntityType.credit, @@ -395,7 +394,7 @@ class _ScheduleEditState extends State { }, ) else if (parameters.entityType == - EntityType.purchaseOrder.toString()) + EntityType.purchaseOrder.apiValue) EntityDropdown( labelText: localization.purchaseOrder, entityType: EntityType.purchaseOrder, diff --git a/lib/ui/schedule/schedule_list_item.dart b/lib/ui/schedule/schedule_list_item.dart index bdedf092a..c64b9b0e0 100644 --- a/lib/ui/schedule/schedule_list_item.dart +++ b/lib/ui/schedule/schedule_list_item.dart @@ -41,9 +41,13 @@ class ScheduleListItem extends StatelessWidget { final filterMatch = filter != null && filter.isNotEmpty ? schedule.matchesFilterValue(filter) - : localization.lookup(schedule.template) + - ' • ' + - localization.lookup(kFrequencies[schedule.frequencyId]); + : (schedule.template == ScheduleEntity.TEMPLATE_SCHEDULE_ENTITY + ? localization.emailRecord + : localization.lookup(schedule.template)) + + (schedule.frequencyId.isEmpty + ? '' + : ' • ' + + localization.lookup(kFrequencies[schedule.frequencyId])); final subtitle = filterMatch; return DismissibleEntity( diff --git a/lib/utils/icons.dart b/lib/utils/icons.dart index abf1e83d6..92e532de7 100644 --- a/lib/utils/icons.dart +++ b/lib/utils/icons.dart @@ -44,6 +44,8 @@ IconData getEntityActionIcon(EntityAction entityAction) { case EntityAction.resendInvite: case EntityAction.sendNow: return Icons.send; + case EntityAction.schedule: + return Icons.schedule; case EntityAction.archive: return Icons.archive; case EntityAction.delete: