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);
} 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;

View File

@ -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() {

View File

@ -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<EntityAction> _$values =
_$merge,
_$bulkPrint,
_$autoBill,
_$schedule,
]);
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: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<Null>(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,

View File

@ -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<BaseEntity> 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<BaseEntity> invoices,
snackBarCompleter<Null>(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,

View File

@ -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,

View File

@ -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<Null>(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,

View File

@ -164,7 +164,8 @@ class _ScheduleEditState extends State<ScheduleEdit> {
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<ScheduleEdit> {
EntityType.purchaseOrder
]
.map((entityType) => DropdownMenuItem<String>(
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<ScheduleEdit> {
},
)
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<ScheduleEdit> {
},
)
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<ScheduleEdit> {
},
)
else if (parameters.entityType ==
EntityType.purchaseOrder.toString())
EntityType.purchaseOrder.apiValue)
EntityDropdown(
labelText: localization.purchaseOrder,
entityType: EntityType.purchaseOrder,

View File

@ -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(

View File

@ -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: