Schedule send

This commit is contained in:
Hillel Coren 2023-03-21 18:16:15 +02:00
parent ee20546276
commit 43a9df00e5
10 changed files with 131 additions and 13 deletions

View File

@ -1034,6 +1034,9 @@ abstract class InvoiceEntity extends Object
actions.add(EntityAction.bulkSendEmail); actions.add(EntityAction.bulkSendEmail);
} else { } else {
actions.add(EntityAction.sendEmail); 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 isApplied => isCredit && statusId == kCreditStatusApplied;
bool get isUnpaid { bool get isUnpaid {
if (isQuote) { if (isPurchaseOrder) {
return !isApproved;
} else if (isQuote) {
return !isApproved; return !isApproved;
} else if (isCredit) { } else if (isCredit) {
return !isApplied; return !isApplied;

View File

@ -128,6 +128,7 @@ class EntityAction extends EnumClass {
static const EntityAction merge = _$merge; static const EntityAction merge = _$merge;
static const EntityAction bulkPrint = _$bulkPrint; static const EntityAction bulkPrint = _$bulkPrint;
static const EntityAction autoBill = _$autoBill; static const EntityAction autoBill = _$autoBill;
static const EntityAction schedule = _$schedule;
@override @override
String toString() { String toString() {

View File

@ -93,6 +93,7 @@ const EntityAction _$convertToProject =
const EntityAction _$merge = const EntityAction._('merge'); const EntityAction _$merge = const EntityAction._('merge');
const EntityAction _$bulkPrint = const EntityAction._('bulkPrint'); const EntityAction _$bulkPrint = const EntityAction._('bulkPrint');
const EntityAction _$autoBill = const EntityAction._('autoBill'); const EntityAction _$autoBill = const EntityAction._('autoBill');
const EntityAction _$schedule = const EntityAction._('schedule');
EntityAction _$valueOf(String name) { EntityAction _$valueOf(String name) {
switch (name) { switch (name) {
@ -248,6 +249,8 @@ EntityAction _$valueOf(String name) {
return _$bulkPrint; return _$bulkPrint;
case 'autoBill': case 'autoBill':
return _$autoBill; return _$autoBill;
case 'schedule':
return _$schedule;
default: default:
throw new ArgumentError(name); throw new ArgumentError(name);
} }
@ -331,6 +334,7 @@ final BuiltSet<EntityAction> _$values =
_$merge, _$merge,
_$bulkPrint, _$bulkPrint,
_$autoBill, _$autoBill,
_$schedule,
]); ]);
Serializer<EntityAction> _$entityActionSerializer = Serializer<EntityAction> _$entityActionSerializer =

View File

@ -10,6 +10,7 @@ import 'package:built_collection/built_collection.dart';
import 'package:flutter_redux/flutter_redux.dart'; import 'package:flutter_redux/flutter_redux.dart';
import 'package:http/http.dart'; import 'package:http/http.dart';
import 'package:invoiceninja_flutter/redux/document/document_actions.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'; import 'package:url_launcher/url_launcher.dart';
// Project imports: // Project imports:
@ -488,6 +489,7 @@ Future handleCreditAction(
break; break;
case EntityAction.sendEmail: case EntityAction.sendEmail:
case EntityAction.bulkSendEmail: case EntityAction.bulkSendEmail:
case EntityAction.schedule:
bool emailValid = true; bool emailValid = true;
credits.forEach((credit) { credits.forEach((credit) {
final client = state.clientState.get( final client = state.clientState.get(
@ -517,6 +519,29 @@ Future handleCreditAction(
snackBarCompleter<Null>(context, localization.emailedCredit), snackBarCompleter<Null>(context, localization.emailedCredit),
credit: credit, credit: credit,
context: context)); 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 { } else {
confirmCallback( confirmCallback(
context: context, context: context,

View File

@ -9,7 +9,9 @@ import 'package:flutter/material.dart';
import 'package:built_collection/built_collection.dart'; import 'package:built_collection/built_collection.dart';
import 'package:flutter_redux/flutter_redux.dart'; import 'package:flutter_redux/flutter_redux.dart';
import 'package:http/http.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/document/document_actions.dart';
import 'package:invoiceninja_flutter/redux/settings/settings_actions.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
// Project imports: // Project imports:
@ -607,6 +609,7 @@ void handleInvoiceAction(BuildContext context, List<BaseEntity> invoices,
break; break;
case EntityAction.sendEmail: case EntityAction.sendEmail:
case EntityAction.bulkSendEmail: case EntityAction.bulkSendEmail:
case EntityAction.schedule:
bool emailValid = true; bool emailValid = true;
invoices.forEach((invoice) { invoices.forEach((invoice) {
final client = state.clientState.get( final client = state.clientState.get(
@ -636,6 +639,29 @@ void handleInvoiceAction(BuildContext context, List<BaseEntity> invoices,
snackBarCompleter<Null>(context, localization.emailedInvoice), snackBarCompleter<Null>(context, localization.emailedInvoice),
invoice: invoice, invoice: invoice,
context: context)); 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 { } else {
confirmCallback( confirmCallback(
context: context, context: context,

View File

@ -5,11 +5,13 @@ import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart'; import 'package:flutter_redux/flutter_redux.dart';
import 'package:http/http.dart'; import 'package:http/http.dart';
import 'package:http/http.dart' as http; 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/models/models.dart';
import 'package:invoiceninja_flutter/data/web_client.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_actions.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart'; import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/redux/design/design_selectors.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/completers.dart';
import 'package:invoiceninja_flutter/utils/dialogs.dart'; import 'package:invoiceninja_flutter/utils/dialogs.dart';
import 'package:invoiceninja_flutter/utils/localization.dart'; import 'package:invoiceninja_flutter/utils/localization.dart';
@ -699,6 +701,7 @@ void handlePurchaseOrderAction(BuildContext context,
break; break;
case EntityAction.sendEmail: case EntityAction.sendEmail:
case EntityAction.bulkSendEmail: case EntityAction.bulkSendEmail:
case EntityAction.schedule:
bool emailValid = true; bool emailValid = true;
purchaseOrders.forEach((purchaseOrder) { purchaseOrders.forEach((purchaseOrder) {
final vendor = state.vendorState.get( final vendor = state.vendorState.get(
@ -729,6 +732,29 @@ void handlePurchaseOrderAction(BuildContext context,
context, localization.emailedPurchaseOrder), context, localization.emailedPurchaseOrder),
purchaseOrder: purchaseOrder, purchaseOrder: purchaseOrder,
context: context)); 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 { } else {
confirmCallback( confirmCallback(
context: context, context: context,

View File

@ -9,7 +9,9 @@ import 'package:flutter/material.dart';
import 'package:built_collection/built_collection.dart'; import 'package:built_collection/built_collection.dart';
import 'package:flutter_redux/flutter_redux.dart'; import 'package:flutter_redux/flutter_redux.dart';
import 'package:http/http.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/document/document_actions.dart';
import 'package:invoiceninja_flutter/redux/settings/settings_actions.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
// Project imports: // Project imports:
@ -551,6 +553,7 @@ Future handleQuoteAction(
break; break;
case EntityAction.sendEmail: case EntityAction.sendEmail:
case EntityAction.bulkSendEmail: case EntityAction.bulkSendEmail:
case EntityAction.schedule:
bool emailValid = true; bool emailValid = true;
quotes.forEach((quote) { quotes.forEach((quote) {
final client = state.clientState.get( final client = state.clientState.get(
@ -580,6 +583,29 @@ Future handleQuoteAction(
snackBarCompleter<Null>(context, localization.emailedQuote), snackBarCompleter<Null>(context, localization.emailedQuote),
quote: quote, quote: quote,
context: context)); 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 { } else {
confirmCallback( confirmCallback(
context: context, context: context,

View File

@ -164,7 +164,8 @@ class _ScheduleEditState extends State<ScheduleEdit> {
value: schedule.template, value: schedule.template,
onChanged: (dynamic value) { onChanged: (dynamic value) {
viewModel.onChanged( viewModel.onChanged(
schedule.rebuild((b) => b..template = value)); schedule.rebuild((b) => b..template = value),
);
}, },
items: ScheduleEntity.TEMPLATES items: ScheduleEntity.TEMPLATES
.map((entry) => DropdownMenuItem( .map((entry) => DropdownMenuItem(
@ -351,15 +352,13 @@ class _ScheduleEditState extends State<ScheduleEdit> {
EntityType.purchaseOrder EntityType.purchaseOrder
] ]
.map((entityType) => DropdownMenuItem<String>( .map((entityType) => DropdownMenuItem<String>(
value: entityType.toString(), value: entityType.apiValue,
child: Text( child: Text(
localization localization.lookup(entityType.apiValue),
.lookup(entityType.toString()),
), ),
)) ))
.toList()), .toList()),
if (parameters.entityType == if (parameters.entityType == EntityType.invoice.apiValue)
EntityType.invoice.toString())
EntityDropdown( EntityDropdown(
labelText: localization.invoice, labelText: localization.invoice,
entityType: EntityType.invoice, entityType: EntityType.invoice,
@ -371,7 +370,7 @@ class _ScheduleEditState extends State<ScheduleEdit> {
}, },
) )
else if (parameters.entityType == else if (parameters.entityType ==
EntityType.quote.toString()) EntityType.quote.apiValue)
EntityDropdown( EntityDropdown(
labelText: localization.quote, labelText: localization.quote,
entityType: EntityType.quote, entityType: EntityType.quote,
@ -383,7 +382,7 @@ class _ScheduleEditState extends State<ScheduleEdit> {
}, },
) )
else if (parameters.entityType == else if (parameters.entityType ==
EntityType.credit.toString()) EntityType.credit.apiValue)
EntityDropdown( EntityDropdown(
labelText: localization.credit, labelText: localization.credit,
entityType: EntityType.credit, entityType: EntityType.credit,
@ -395,7 +394,7 @@ class _ScheduleEditState extends State<ScheduleEdit> {
}, },
) )
else if (parameters.entityType == else if (parameters.entityType ==
EntityType.purchaseOrder.toString()) EntityType.purchaseOrder.apiValue)
EntityDropdown( EntityDropdown(
labelText: localization.purchaseOrder, labelText: localization.purchaseOrder,
entityType: EntityType.purchaseOrder, entityType: EntityType.purchaseOrder,

View File

@ -41,9 +41,13 @@ class ScheduleListItem extends StatelessWidget {
final filterMatch = filter != null && filter.isNotEmpty final filterMatch = filter != null && filter.isNotEmpty
? schedule.matchesFilterValue(filter) ? schedule.matchesFilterValue(filter)
: localization.lookup(schedule.template) + : (schedule.template == ScheduleEntity.TEMPLATE_SCHEDULE_ENTITY
'' + ? localization.emailRecord
localization.lookup(kFrequencies[schedule.frequencyId]); : localization.lookup(schedule.template)) +
(schedule.frequencyId.isEmpty
? ''
: '' +
localization.lookup(kFrequencies[schedule.frequencyId]));
final subtitle = filterMatch; final subtitle = filterMatch;
return DismissibleEntity( return DismissibleEntity(

View File

@ -44,6 +44,8 @@ IconData getEntityActionIcon(EntityAction entityAction) {
case EntityAction.resendInvite: case EntityAction.resendInvite:
case EntityAction.sendNow: case EntityAction.sendNow:
return Icons.send; return Icons.send;
case EntityAction.schedule:
return Icons.schedule;
case EntityAction.archive: case EntityAction.archive:
return Icons.archive; return Icons.archive;
case EntityAction.delete: case EntityAction.delete: