Purchase orders

This commit is contained in:
Hillel Coren 2022-06-16 16:30:45 +03:00
parent c41a7ed225
commit 958e5fa0c7
8 changed files with 179 additions and 84 deletions

View File

@ -597,7 +597,7 @@ const Map<int, String> kModules = {
kModuleProjects: 'projects', kModuleProjects: 'projects',
kModuleTasks: 'tasks', kModuleTasks: 'tasks',
kModuleVendors: 'vendors', kModuleVendors: 'vendors',
//kModulePurchaseOrders: 'purchase_orders', kModulePurchaseOrders: 'purchase_orders',
kModuleExpenses: 'expenses', kModuleExpenses: 'expenses',
kModuleRecurringExpenses: 'recurring_expenses', kModuleRecurringExpenses: 'recurring_expenses',
//kModuleTickets: 'tickets', //kModuleTickets: 'tickets',

View File

@ -699,15 +699,12 @@ abstract class AppState implements Built<AppState, AppStateBuilder> {
case PurchaseOrderEditScreen.route: case PurchaseOrderEditScreen.route:
return hasPurchaseOrderChanges( return hasPurchaseOrderChanges(
purchaseOrderUIState.editing, purchaseOrderState.map); purchaseOrderUIState.editing, purchaseOrderState.map);
case RecurringExpenseEditScreen.route: case RecurringExpenseEditScreen.route:
return hasRecurringExpenseChanges( return hasRecurringExpenseChanges(
recurringExpenseUIState.editing, recurringExpenseState.map); recurringExpenseUIState.editing, recurringExpenseState.map);
case SubscriptionEditScreen.route: case SubscriptionEditScreen.route:
return hasSubscriptionChanges( return hasSubscriptionChanges(
subscriptionUIState.editing, subscriptionState.map); subscriptionUIState.editing, subscriptionState.map);
case TaskStatusEditScreen.route: case TaskStatusEditScreen.route:
return hasTaskStatusChanges( return hasTaskStatusChanges(
taskStatusUIState.editing, taskStatusState.map); taskStatusUIState.editing, taskStatusState.map);

View File

@ -38,6 +38,28 @@ class EditPurchaseOrder implements PersistUI, PersistPrefs {
final bool force; final bool force;
} }
class ShowEmailPurchaseOrder {
ShowEmailPurchaseOrder({this.purchaseOrder, this.context, this.completer});
final InvoiceEntity purchaseOrder;
final BuildContext context;
final Completer completer;
}
class ShowPdfPurchaseOrder {
ShowPdfPurchaseOrder({this.purchaseOrder, this.context, this.activityId});
final InvoiceEntity purchaseOrder;
final BuildContext context;
final String activityId;
}
class EditPurchaseOrderItem implements PersistUI {
EditPurchaseOrderItem([this.itemIndex]);
final int itemIndex;
}
class UpdatePurchaseOrder implements PersistUI { class UpdatePurchaseOrder implements PersistUI {
UpdatePurchaseOrder(this.purchaseOrder); UpdatePurchaseOrder(this.purchaseOrder);
@ -113,10 +135,15 @@ class LoadPurchaseOrdersSuccess implements StopLoading {
} }
class SavePurchaseOrderRequest implements StartSaving { class SavePurchaseOrderRequest implements StartSaving {
SavePurchaseOrderRequest({this.completer, this.purchaseOrder}); SavePurchaseOrderRequest({
this.completer,
this.purchaseOrder,
this.action,
});
final Completer completer; final Completer completer;
final InvoiceEntity purchaseOrder; final InvoiceEntity purchaseOrder;
final EntityAction action;
} }
class SavePurchaseOrderSuccess implements StopSaving, PersistData, PersistUI { class SavePurchaseOrderSuccess implements StopSaving, PersistData, PersistUI {

View File

@ -72,6 +72,10 @@ abstract class PurchaseOrderUIState extends Object
@nullable @nullable
InvoiceEntity get editing; InvoiceEntity get editing;
@nullable
@BuiltValueField(serialize: false)
int get editingItemIndex;
@override @override
bool get isCreatingNew => editing.isNew; bool get isCreatingNew => editing.isNew;

View File

@ -264,6 +264,8 @@ class _$PurchaseOrderUIState extends PurchaseOrderUIState {
@override @override
final InvoiceEntity editing; final InvoiceEntity editing;
@override @override
final int editingItemIndex;
@override
final ListUIState listUIState; final ListUIState listUIState;
@override @override
final String selectedId; final String selectedId;
@ -282,6 +284,7 @@ class _$PurchaseOrderUIState extends PurchaseOrderUIState {
_$PurchaseOrderUIState._( _$PurchaseOrderUIState._(
{this.editing, {this.editing,
this.editingItemIndex,
this.listUIState, this.listUIState,
this.selectedId, this.selectedId,
this.forceSelected, this.forceSelected,
@ -309,6 +312,7 @@ class _$PurchaseOrderUIState extends PurchaseOrderUIState {
if (identical(other, this)) return true; if (identical(other, this)) return true;
return other is PurchaseOrderUIState && return other is PurchaseOrderUIState &&
editing == other.editing && editing == other.editing &&
editingItemIndex == other.editingItemIndex &&
listUIState == other.listUIState && listUIState == other.listUIState &&
selectedId == other.selectedId && selectedId == other.selectedId &&
forceSelected == other.forceSelected && forceSelected == other.forceSelected &&
@ -324,7 +328,11 @@ class _$PurchaseOrderUIState extends PurchaseOrderUIState {
$jc( $jc(
$jc( $jc(
$jc( $jc(
$jc($jc($jc(0, editing.hashCode), listUIState.hashCode), $jc(
$jc(
$jc($jc(0, editing.hashCode),
editingItemIndex.hashCode),
listUIState.hashCode),
selectedId.hashCode), selectedId.hashCode),
forceSelected.hashCode), forceSelected.hashCode),
tabIndex.hashCode), tabIndex.hashCode),
@ -336,6 +344,7 @@ class _$PurchaseOrderUIState extends PurchaseOrderUIState {
String toString() { String toString() {
return (newBuiltValueToStringHelper('PurchaseOrderUIState') return (newBuiltValueToStringHelper('PurchaseOrderUIState')
..add('editing', editing) ..add('editing', editing)
..add('editingItemIndex', editingItemIndex)
..add('listUIState', listUIState) ..add('listUIState', listUIState)
..add('selectedId', selectedId) ..add('selectedId', selectedId)
..add('forceSelected', forceSelected) ..add('forceSelected', forceSelected)
@ -355,6 +364,11 @@ class PurchaseOrderUIStateBuilder
_$this._editing ??= new InvoiceEntityBuilder(); _$this._editing ??= new InvoiceEntityBuilder();
set editing(InvoiceEntityBuilder editing) => _$this._editing = editing; set editing(InvoiceEntityBuilder editing) => _$this._editing = editing;
int _editingItemIndex;
int get editingItemIndex => _$this._editingItemIndex;
set editingItemIndex(int editingItemIndex) =>
_$this._editingItemIndex = editingItemIndex;
ListUIStateBuilder _listUIState; ListUIStateBuilder _listUIState;
ListUIStateBuilder get listUIState => ListUIStateBuilder get listUIState =>
_$this._listUIState ??= new ListUIStateBuilder(); _$this._listUIState ??= new ListUIStateBuilder();
@ -390,6 +404,7 @@ class PurchaseOrderUIStateBuilder
final $v = _$v; final $v = _$v;
if ($v != null) { if ($v != null) {
_editing = $v.editing?.toBuilder(); _editing = $v.editing?.toBuilder();
_editingItemIndex = $v.editingItemIndex;
_listUIState = $v.listUIState.toBuilder(); _listUIState = $v.listUIState.toBuilder();
_selectedId = $v.selectedId; _selectedId = $v.selectedId;
_forceSelected = $v.forceSelected; _forceSelected = $v.forceSelected;
@ -419,6 +434,7 @@ class PurchaseOrderUIStateBuilder
_$result = _$v ?? _$result = _$v ??
new _$PurchaseOrderUIState._( new _$PurchaseOrderUIState._(
editing: _editing?.build(), editing: _editing?.build(),
editingItemIndex: editingItemIndex,
listUIState: listUIState.build(), listUIState: listUIState.build(),
selectedId: selectedId, selectedId: selectedId,
forceSelected: forceSelected, forceSelected: forceSelected,
@ -431,6 +447,7 @@ class PurchaseOrderUIStateBuilder
try { try {
_$failedField = 'editing'; _$failedField = 'editing';
_editing?.build(); _editing?.build();
_$failedField = 'listUIState'; _$failedField = 'listUIState';
listUIState.build(); listUIState.build();
} catch (e) { } catch (e) {

View File

@ -151,7 +151,7 @@ InvoiceEntity _clearEditing(InvoiceEntity quote, dynamic action) {
} }
InvoiceEntity _updateEditing(InvoiceEntity quote, dynamic action) { InvoiceEntity _updateEditing(InvoiceEntity quote, dynamic action) {
return action.quote; return action.purchaseOrder;
} }
InvoiceEntity _addQuoteItem(InvoiceEntity quote, AddQuoteItem action) { InvoiceEntity _addQuoteItem(InvoiceEntity quote, AddQuoteItem action) {
@ -367,7 +367,7 @@ QuoteState _addQuote(QuoteState quoteState, AddQuoteSuccess action) {
} }
QuoteState _updateQuote(QuoteState invoiceState, dynamic action) { QuoteState _updateQuote(QuoteState invoiceState, dynamic action) {
final InvoiceEntity quote = action.quote; final InvoiceEntity quote = action.purchaseOrder;
return invoiceState.rebuild((b) => b return invoiceState.rebuild((b) => b
..map[quote.id] = quote ..map[quote.id] = quote
.rebuild((b) => b..loadedAt = DateTime.now().millisecondsSinceEpoch)); .rebuild((b) => b..loadedAt = DateTime.now().millisecondsSinceEpoch));

View File

@ -57,7 +57,7 @@ class _PurchaseOrderEditState extends State<PurchaseOrderEdit> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final viewModel = widget.viewModel; final viewModel = widget.viewModel;
final localization = AppLocalization.of(context); final localization = AppLocalization.of(context);
final purchaseOrder = viewModel.purchaseOrder; final purchaseOrder = viewModel.invoice;
return EditScaffold( return EditScaffold(
title: purchaseOrder.isNew title: purchaseOrder.isNew

View File

@ -1,21 +1,31 @@
// Dart imports:
import 'dart:async'; import 'dart:async';
// Flutter imports:
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
// Package imports:
import 'package:flutter_redux/flutter_redux.dart'; import 'package:flutter_redux/flutter_redux.dart';
import 'package:invoiceninja_flutter/redux/app/app_actions.dart';
import 'package:invoiceninja_flutter/redux/ui/ui_actions.dart';
import 'package:redux/redux.dart';
import 'package:invoiceninja_flutter/utils/completers.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/ui/app/dialogs/error_dialog.dart';
import 'package:invoiceninja_flutter/ui/purchase_order/view/purchase_order_view_vm.dart';
import 'package:invoiceninja_flutter/redux/purchase_order/purchase_order_actions.dart';
import 'package:invoiceninja_flutter/ui/purchase_order/edit/purchase_order_edit.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:flutter_styled_toast/flutter_styled_toast.dart'; import 'package:flutter_styled_toast/flutter_styled_toast.dart';
import 'package:invoiceninja_flutter/utils/localization.dart'; import 'package:invoiceninja_flutter/redux/purchase_order/purchase_order_actions.dart';
import 'package:invoiceninja_flutter/redux/purchase_order/purchase_order_selectors.dart';
import 'package:invoiceninja_flutter/ui/purchase_order/edit/purchase_order_edit.dart';
import 'package:invoiceninja_flutter/ui/purchase_order/view/purchase_order_view_vm.dart';
import 'package:redux/redux.dart';
// Project imports:
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/main_app.dart';
import 'package:invoiceninja_flutter/redux/app/app_actions.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/redux/ui/ui_actions.dart';
import 'package:invoiceninja_flutter/ui/app/dialogs/error_dialog.dart';
import 'package:invoiceninja_flutter/ui/invoice/edit/invoice_edit_vm.dart';
import 'package:invoiceninja_flutter/utils/completers.dart';
class PurchaseOrderEditScreen extends StatelessWidget { class PurchaseOrderEditScreen extends StatelessWidget {
const PurchaseOrderEditScreen({Key key}) : super(key: key); const PurchaseOrderEditScreen({Key key}) : super(key: key);
static const String route = '/purchase_order/edit'; static const String route = '/purchase_order/edit';
@override @override
@ -27,90 +37,130 @@ class PurchaseOrderEditScreen extends StatelessWidget {
builder: (context, viewModel) { builder: (context, viewModel) {
return PurchaseOrderEdit( return PurchaseOrderEdit(
viewModel: viewModel, viewModel: viewModel,
key: ValueKey(viewModel.purchaseOrder.updatedAt), key: ValueKey(viewModel.invoice.updatedAt),
); );
}, },
); );
} }
} }
class PurchaseOrderEditVM { class PurchaseOrderEditVM extends AbstractInvoiceEditVM {
PurchaseOrderEditVM({ PurchaseOrderEditVM({
@required this.state, AppState state,
@required this.purchaseOrder, CompanyEntity company,
@required this.company, InvoiceEntity purchaseOrder,
@required this.onChanged, int invoiceItemIndex,
@required this.isSaving, InvoiceEntity origInvoice,
@required this.origPurchaseOrder, Function(BuildContext) onSavePressed,
@required this.onSavePressed, Function(List<InvoiceItemEntity>, String, String) onItemsAdded,
@required this.onCancelPressed, bool isSaving,
@required this.isLoading, Function(BuildContext) onCancelPressed,
}); }) : super(
state: state,
company: company,
invoice: purchaseOrder,
invoiceItemIndex: invoiceItemIndex,
origInvoice: origInvoice,
onSavePressed: onSavePressed,
onItemsAdded: onItemsAdded,
isSaving: isSaving,
onCancelPressed: onCancelPressed,
);
factory PurchaseOrderEditVM.fromStore(Store<AppState> store) { factory PurchaseOrderEditVM.fromStore(Store<AppState> store) {
final state = store.state; final AppState state = store.state;
final purchaseOrder = state.purchaseOrderUIState.editing; final purchaseOrder = state.purchaseOrderUIState.editing;
return PurchaseOrderEditVM( return PurchaseOrderEditVM(
state: state, state: state,
isLoading: state.isLoading,
isSaving: state.isSaving,
origPurchaseOrder: state.purchaseOrderState.map[purchaseOrder.id],
purchaseOrder: purchaseOrder,
company: state.company, company: state.company,
onChanged: (InvoiceEntity purchaseOrder) { isSaving: state.isSaving,
store.dispatch(UpdatePurchaseOrder(purchaseOrder)); purchaseOrder: purchaseOrder,
invoiceItemIndex: state.purchaseOrderUIState.editingItemIndex,
origInvoice: store.state.purchaseOrderState.map[purchaseOrder.id],
onSavePressed: (BuildContext context, [EntityAction action]) {
Debouncer.runOnComplete(() {
final purchaseOrder = store.state.purchaseOrderUIState.editing;
final localization = navigatorKey.localization;
final navigator = navigatorKey.currentState;
if (purchaseOrder.clientId.isEmpty) {
showDialog<ErrorDialog>(
context: navigatorKey.currentContext,
builder: (BuildContext context) {
return ErrorDialog(localization.pleaseSelectAClient);
});
return null;
}
if (purchaseOrder.isOld &&
!hasPurchaseOrderChanges(
purchaseOrder, state.purchaseOrderState.map) &&
action != null &&
action.isClientSide) {
handleEntityAction(purchaseOrder, action);
} else {
final Completer<InvoiceEntity> completer =
Completer<InvoiceEntity>();
store.dispatch(SavePurchaseOrderRequest(
completer: completer,
purchaseOrder: purchaseOrder,
action: action,
));
return completer.future.then((savedPurchaseOrder) {
showToast(purchaseOrder.isNew
? localization.createdPurchaseOrder
: localization.updatedPurchaseOrder);
if (state.prefState.isMobile) {
store.dispatch(
UpdateCurrentRoute(PurchaseOrderViewScreen.route));
if (purchaseOrder.isNew) {
navigator.pushReplacementNamed(PurchaseOrderViewScreen.route);
} else {
navigator.pop(savedPurchaseOrder);
}
} else {
if (!state.prefState.isPreviewVisible) {
store.dispatch(TogglePreviewSidebar());
}
viewEntity(entity: savedPurchaseOrder);
if (state.prefState.isEditorFullScreen(EntityType.invoice) &&
state.prefState.editAfterSaving) {
editEntity(entity: savedPurchaseOrder);
}
}
if (action != null && action.isClientSide) {
handleEntityAction(savedPurchaseOrder, action);
} else if (action != null && action.requiresSecondRequest) {
handleEntityAction(savedPurchaseOrder, action);
viewEntity(entity: savedPurchaseOrder, force: true);
}
}).catchError((Object error) {
showDialog<ErrorDialog>(
context: navigatorKey.currentContext,
builder: (BuildContext context) {
return ErrorDialog(error);
});
});
}
});
},
onItemsAdded: (items, clientId, projectId) {
if (items.length == 1) {
store.dispatch(EditPurchaseOrderItem(purchaseOrder.lineItems.length));
}
store.dispatch(AddPurchaseOrderItems(items));
}, },
onCancelPressed: (BuildContext context) { onCancelPressed: (BuildContext context) {
createEntity(context: context, entity: InvoiceEntity(), force: true); if (['pdf', 'email'].contains(state.uiState.previousSubRoute)) {
if (state.purchaseOrderUIState.cancelCompleter != null) { viewEntitiesByType(entityType: EntityType.purchaseOrder);
state.purchaseOrderUIState.cancelCompleter.complete();
} else { } else {
createEntity(context: context, entity: InvoiceEntity(), force: true);
store.dispatch(UpdateCurrentRoute(state.uiState.previousRoute)); store.dispatch(UpdateCurrentRoute(state.uiState.previousRoute));
} }
}, },
onSavePressed: (BuildContext context) {
Debouncer.runOnComplete(() {
final purchaseOrder = store.state.purchaseOrderUIState.editing;
final localization = AppLocalization.of(context);
final Completer<InvoiceEntity> completer =
new Completer<InvoiceEntity>();
store.dispatch(SavePurchaseOrderRequest(
completer: completer, purchaseOrder: purchaseOrder));
return completer.future.then((savedPurchaseOrder) {
showToast(purchaseOrder.isNew
? localization.createdPurchaseOrder
: localization.updatedPurchaseOrder);
if (state.prefState.isMobile) {
store.dispatch(UpdateCurrentRoute(PurchaseOrderViewScreen.route));
if (purchaseOrder.isNew) {
Navigator.of(context)
.pushReplacementNamed(PurchaseOrderViewScreen.route);
} else {
Navigator.of(context).pop(savedPurchaseOrder);
}
} else {
viewEntity(entity: savedPurchaseOrder, force: true);
}
}).catchError((Object error) {
showDialog<ErrorDialog>(
context: context,
builder: (BuildContext context) {
return ErrorDialog(error);
});
});
});
},
); );
} }
final InvoiceEntity purchaseOrder;
final CompanyEntity company;
final Function(InvoiceEntity) onChanged;
final Function(BuildContext) onSavePressed;
final Function(BuildContext) onCancelPressed;
final bool isLoading;
final bool isSaving;
final InvoiceEntity origPurchaseOrder;
final AppState state;
} }