Support adding task/expenses to invoices

This commit is contained in:
Hillel Coren 2022-05-09 18:09:49 +03:00
parent 501668e44b
commit b143b869ce
9 changed files with 106 additions and 13 deletions

View File

@ -352,6 +352,9 @@ abstract class ExpenseEntity extends Object
shouldBeInvoiced && shouldBeInvoiced &&
userCompany.canCreate(EntityType.invoice)) { userCompany.canCreate(EntityType.invoice)) {
actions.add(EntityAction.invoiceExpense); actions.add(EntityAction.invoiceExpense);
if ((clientId ?? '').isNotEmpty) {
actions.add(EntityAction.addToInvoice);
}
} }
} }

View File

@ -109,6 +109,7 @@ class EntityAction extends EnumClass {
static const EntityAction disconnect = _$disconnect; static const EntityAction disconnect = _$disconnect;
static const EntityAction viewInvoice = _$viewInvoice; static const EntityAction viewInvoice = _$viewInvoice;
static const EntityAction changeStatus = _$changeStatus; static const EntityAction changeStatus = _$changeStatus;
static const EntityAction addToInvoice = _$addToInvoice;
@override @override
String toString() { String toString() {

View File

@ -75,6 +75,7 @@ const EntityAction _$resendInvite = const EntityAction._('resendInvite');
const EntityAction _$disconnect = const EntityAction._('disconnect'); const EntityAction _$disconnect = const EntityAction._('disconnect');
const EntityAction _$viewInvoice = const EntityAction._('viewInvoice'); const EntityAction _$viewInvoice = const EntityAction._('viewInvoice');
const EntityAction _$changeStatus = const EntityAction._('changeStatus'); const EntityAction _$changeStatus = const EntityAction._('changeStatus');
const EntityAction _$addToInvoice = const EntityAction._('addToInvoice');
EntityAction _$valueOf(String name) { EntityAction _$valueOf(String name) {
switch (name) { switch (name) {
@ -202,6 +203,8 @@ EntityAction _$valueOf(String name) {
return _$viewInvoice; return _$viewInvoice;
case 'changeStatus': case 'changeStatus':
return _$changeStatus; return _$changeStatus;
case 'addToInvoice':
return _$addToInvoice;
default: default:
throw new ArgumentError(name); throw new ArgumentError(name);
} }
@ -271,6 +274,7 @@ final BuiltSet<EntityAction> _$values =
_$disconnect, _$disconnect,
_$viewInvoice, _$viewInvoice,
_$changeStatus, _$changeStatus,
_$addToInvoice,
]); ]);
Serializer<EntityAction> _$entityActionSerializer = Serializer<EntityAction> _$entityActionSerializer =

View File

@ -608,6 +608,9 @@ abstract class TaskEntity extends Object
if (!isInvoiced && !isRunning) { if (!isInvoiced && !isRunning) {
if (userCompany.canCreate(EntityType.invoice)) { if (userCompany.canCreate(EntityType.invoice)) {
actions.add(EntityAction.invoiceTask); actions.add(EntityAction.invoiceTask);
if ((clientId ?? '').isNotEmpty) {
actions.add(EntityAction.addToInvoice);
}
} }
} }
} }

View File

@ -18,6 +18,7 @@ import 'package:invoiceninja_flutter/redux/document/document_actions.dart';
import 'package:invoiceninja_flutter/redux/expense/expense_selectors.dart'; import 'package:invoiceninja_flutter/redux/expense/expense_selectors.dart';
import 'package:invoiceninja_flutter/ui/app/entities/entity_actions_dialog.dart'; import 'package:invoiceninja_flutter/ui/app/entities/entity_actions_dialog.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/localization.dart'; import 'package:invoiceninja_flutter/utils/localization.dart';
class ViewExpenseList implements PersistUI { class ViewExpenseList implements PersistUI {
@ -277,6 +278,7 @@ void handleExpenseAction(
); );
break; break;
case EntityAction.invoiceExpense: case EntityAction.invoiceExpense:
case EntityAction.addToInvoice:
final availableExpenses = expenses.where((entity) { final availableExpenses = expenses.where((entity) {
final expense = entity as ExpenseEntity; final expense = entity as ExpenseEntity;
return !expense.isDeleted && !expense.isInvoiced; return !expense.isDeleted && !expense.isInvoiced;
@ -304,6 +306,7 @@ void handleExpenseAction(
)) ))
.toList(); .toList();
if (items.isNotEmpty) { if (items.isNotEmpty) {
if (action == EntityAction.invoiceExpense) {
createEntity( createEntity(
context: context, context: context,
entity: InvoiceEntity(state: state, client: client).rebuild( entity: InvoiceEntity(state: state, client: client).rebuild(
@ -313,6 +316,13 @@ void handleExpenseAction(
..vendorId = vendorId, ..vendorId = vendorId,
), ),
); );
} else {
addToInvoiceDialog(
context: context,
clientId: expense.clientId,
items: items,
);
}
} }
break; break;
case EntityAction.restore: case EntityAction.restore:

View File

@ -382,6 +382,7 @@ void handleTaskAction(
StopTasksRequest(snackBarCompleter<Null>(context, message), taskIds)); StopTasksRequest(snackBarCompleter<Null>(context, message), taskIds));
break; break;
case EntityAction.invoiceTask: case EntityAction.invoiceTask:
case EntityAction.addToInvoice:
tasks.sort((taskA, taskB) { tasks.sort((taskA, taskB) {
final taskAEntity = taskA as TaskEntity; final taskAEntity = taskA as TaskEntity;
final taskBEntity = taskB as TaskEntity; final taskBEntity = taskB as TaskEntity;
@ -435,11 +436,20 @@ void handleTaskAction(
}); });
if (items.isNotEmpty) { if (items.isNotEmpty) {
if (action == EntityAction.invoiceTask) {
createEntity( createEntity(
context: context, context: context,
entity: InvoiceEntity(state: state, client: client).rebuild((b) => b entity:
InvoiceEntity(state: state, client: client).rebuild((b) => b
..lineItems.addAll(items) ..lineItems.addAll(items)
..projectId = projectId)); ..projectId = projectId));
} else {
addToInvoiceDialog(
context: context,
clientId: task.clientId,
items: items,
);
}
} }
break; break;
case EntityAction.clone: case EntityAction.clone:

View File

@ -6,6 +6,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_redux/flutter_redux.dart'; import 'package:flutter_redux/flutter_redux.dart';
import 'package:invoiceninja_flutter/redux/task/task_actions.dart'; import 'package:invoiceninja_flutter/redux/task/task_actions.dart';
import 'package:invoiceninja_flutter/redux/task_status/task_status_selectors.dart'; import 'package:invoiceninja_flutter/redux/task_status/task_status_selectors.dart';
import 'package:invoiceninja_flutter/utils/formatting.dart';
import 'package:invoiceninja_flutter/utils/platforms.dart'; import 'package:invoiceninja_flutter/utils/platforms.dart';
import 'package:pointer_interceptor/pointer_interceptor.dart'; import 'package:pointer_interceptor/pointer_interceptor.dart';
@ -508,3 +509,53 @@ void changeTaskStatusDialog({
); );
}); });
} }
void addToInvoiceDialog({
@required BuildContext context,
@required String clientId,
@required List<InvoiceItemEntity> items,
}) {
final localization = AppLocalization.of(context);
final store = StoreProvider.of<AppState>(context);
final state = store.state;
final invoices = state.invoiceState.map.values.where((invoice) {
if (clientId != invoice.clientId) {
return false;
}
return invoice.isActive && !invoice.isPaid;
});
if (invoices.isEmpty) {
showMessageDialog(context: context, message: localization.noInvoicesFound);
return;
}
showDialog<AlertDialog>(
context: context,
builder: (BuildContext context) {
return SimpleDialog(
title: Text(localization.addToInvoice),
children: invoices.map((invoice) {
return SimpleDialogOption(
child: Row(children: [
Expanded(child: Text(invoice.number)),
Text(
formatNumber(invoice.amount, context,
clientId: invoice.clientId),
),
]),
onPressed: () {
editEntity(
context: context,
entity: invoice.rebuild(
(b) => b..lineItems.addAll(items),
));
Navigator.of(context).pop();
},
);
}).toList(),
);
});
}

View File

@ -16,6 +16,8 @@ mixin LocalizationsProvider on LocaleCodeAware {
static final Map<String, Map<String, String>> _localizedValues = { static final Map<String, Map<String, String>> _localizedValues = {
'en': { 'en': {
// STARTER: lang key - do not remove comment // STARTER: lang key - do not remove comment
'add_to_invoice': 'Add To Invoice',
'no_invoices_found': 'No invoices found',
'week': 'Week', 'week': 'Week',
'created_record': 'Successfully created record', 'created_record': 'Successfully created record',
'notification_invoice_sent': 'Invoice Sent', 'notification_invoice_sent': 'Invoice Sent',
@ -75046,6 +75048,14 @@ mixin LocalizationsProvider on LocaleCodeAware {
_localizedValues[localeCode]['week'] ?? _localizedValues[localeCode]['week'] ??
_localizedValues[localeCode]['week']; _localizedValues[localeCode]['week'];
String get addToInvoice =>
_localizedValues[localeCode]['add_to_invoice'] ??
_localizedValues[localeCode]['add_to_invoice'];
String get noInvoicesFound =>
_localizedValues[localeCode]['no_invoices_found'] ??
_localizedValues[localeCode]['no_invoices_found'];
// STARTER: lang field - do not remove comment // STARTER: lang field - do not remove comment
String lookup(String key) { String lookup(String key) {

View File

@ -70,6 +70,7 @@ IconData getEntityActionIcon(EntityAction entityAction) {
case EntityAction.invoiceTask: case EntityAction.invoiceTask:
case EntityAction.invoiceExpense: case EntityAction.invoiceExpense:
case EntityAction.invoiceProject: case EntityAction.invoiceProject:
case EntityAction.addToInvoice:
return Icons.add_circle_outline; return Icons.add_circle_outline;
case EntityAction.resume: case EntityAction.resume:
case EntityAction.start: case EntityAction.start: