From a10a2f90c40e7f6ef7db7ac625cb0c68fa9d4bfc Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Mon, 24 Aug 2020 12:43:35 +0300 Subject: [PATCH] Added exchange rate --- lib/data/models/invoice_model.dart | 2 +- lib/redux/app/app_state.dart | 2 ++ lib/redux/group/group_state.dart | 8 +++++ .../credit/edit/credit_edit_details_vm.dart | 8 ++++- lib/ui/invoice/edit/invoice_edit_desktop.dart | 35 ++++++++++++++----- .../invoice/edit/invoice_edit_details_vm.dart | 9 ++++- .../edit/invoice_edit_items_desktop.dart | 7 ++-- lib/ui/quote/edit/quote_edit_details_vm.dart | 10 ++++-- lib/utils/formatting.dart | 2 ++ lib/utils/money.dart | 3 ++ 10 files changed, 70 insertions(+), 16 deletions(-) diff --git a/lib/data/models/invoice_model.dart b/lib/data/models/invoice_model.dart index 9690a5bbe..d649d021b 100644 --- a/lib/data/models/invoice_model.dart +++ b/lib/data/models/invoice_model.dart @@ -159,7 +159,7 @@ abstract class InvoiceEntity extends Object reminder2Sent: '', reminder3Sent: '', reminderLastSent: '', - exchangeRate: 0, + exchangeRate: 1, ); } diff --git a/lib/redux/app/app_state.dart b/lib/redux/app/app_state.dart index bf461e2ca..2fab3d79e 100644 --- a/lib/redux/app/app_state.dart +++ b/lib/redux/app/app_state.dart @@ -636,6 +636,8 @@ abstract class AppState implements Built { //return '${clientState.map[clientUIState.selectedId].gatewayTokens}'; //return 'gatewayId: ${companyGatewayState.map[companyGatewayUIState.selectedId].gatewayId}'; //return 'Language Id: ${company.settings.languageId}'; + //return 'Rates: ${staticState.currencyMap.keys.map((key) => 'Rate: ${staticState.currencyMap[key].exchangeRate}').join(',')}'; + return 'Client ID: ${invoiceUIState?.editing?.clientId ?? 'NULL'}'; return '\n\nURL: ${authState.url}\nRoute: ${uiState.currentRoute}\nPrev: ${uiState.previousRoute}\nIs Loaded: ${isLoaded ? 'Yes' : 'No'}\nis Large: ${(company?.isLarge ?? false) ? 'Yes' : 'No'}\nCompany: $companyUpdated${userCompanyState.isStale ? ' [S]' : ''}\nStatic: $staticUpdated${staticState.isStale ? ' [S]' : ''}\n'; } } diff --git a/lib/redux/group/group_state.dart b/lib/redux/group/group_state.dart index c2dc7b9a1..acc72a16a 100644 --- a/lib/redux/group/group_state.dart +++ b/lib/redux/group/group_state.dart @@ -22,6 +22,14 @@ abstract class GroupState implements Built { @memoized int get hashCode; + GroupEntity get(String groupId) { + if (map.containsKey(groupId)) { + return map[groupId]; + } else { + return GroupEntity(id: groupId); + } + } + BuiltMap get map; BuiltList get list; diff --git a/lib/ui/credit/edit/credit_edit_details_vm.dart b/lib/ui/credit/edit/credit_edit_details_vm.dart index e2eea36ec..a6b9b5e72 100644 --- a/lib/ui/credit/edit/credit_edit_details_vm.dart +++ b/lib/ui/credit/edit/credit_edit_details_vm.dart @@ -72,15 +72,21 @@ class CreditEditDetailsVM extends EntityEditDetailsVM { factory CreditEditDetailsVM.fromStore(Store store) { final AppState state = store.state; final credit = state.creditUIState.editing; + final company = state.company; return CreditEditDetailsVM( state: state, - company: state.company, + company: company, invoice: credit, onChanged: (InvoiceEntity credit) => store.dispatch(UpdateCredit(credit)), clientMap: state.clientState.map, clientList: state.clientState.list, onClientChanged: (invoice, client) { + if (company.convertProductExchangeRate && client != null) { + store.dispatch(UpdateCredit(credit.rebuild((b) => b + ..exchangeRate = state + .staticState.currencyMap[client.currencyId].exchangeRate))); + } store.dispatch(UpdateCreditClient(client: client)); }, onAddClientPressed: (context, completer) { diff --git a/lib/ui/invoice/edit/invoice_edit_desktop.dart b/lib/ui/invoice/edit/invoice_edit_desktop.dart index a0629c305..077d5b7a4 100644 --- a/lib/ui/invoice/edit/invoice_edit_desktop.dart +++ b/lib/ui/invoice/edit/invoice_edit_desktop.dart @@ -2,6 +2,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:invoiceninja_flutter/constants.dart'; +import 'package:invoiceninja_flutter/data/models/client_model.dart'; import 'package:invoiceninja_flutter/data/models/entities.dart'; import 'package:invoiceninja_flutter/ui/app/form_card.dart'; import 'package:invoiceninja_flutter/ui/app/forms/app_tab_bar.dart'; @@ -62,7 +63,6 @@ class InvoiceEditDesktopState extends State final _privateNotesController = TextEditingController(); final _termsController = TextEditingController(); final _footerController = TextEditingController(); - final _exchangeRateController = TextEditingController(); List _controllers = []; final _debouncer = Debouncer(); @@ -94,7 +94,6 @@ class InvoiceEditDesktopState extends State _privateNotesController, _termsController, _footerController, - _exchangeRateController, ]; _controllers @@ -123,8 +122,6 @@ class InvoiceEditDesktopState extends State _privateNotesController.text = invoice.privateNotes; _termsController.text = invoice.terms; _footerController.text = invoice.footer; - _exchangeRateController.text = formatNumber(invoice.exchangeRate, context, - formatNumberType: FormatNumberType.input); _controllers .forEach((dynamic controller) => controller.addListener(_onChanged)); @@ -162,8 +159,7 @@ class InvoiceEditDesktopState extends State ..publicNotes = _publicNotesController.text.trim() ..privateNotes = _privateNotesController.text.trim() ..terms = _termsController.text.trim() - ..footer = _footerController.text.trim() - ..exchangeRate = parseDouble(_exchangeRateController.text)); + ..footer = _footerController.text.trim()); if (invoice != widget.viewModel.invoice) { widget.viewModel.onChanged(invoice); } @@ -201,8 +197,20 @@ class InvoiceEditDesktopState extends State autofocus: kIsWeb, clientId: invoice.clientId, clientState: viewModel.state.clientState, - onSelected: (client) => - viewModel.onClientChanged(invoice, client), + onSelected: (client) { + viewModel.onClientChanged(invoice, client); + /* + final currencyId = (client as ClientEntity)?.currencyId; + if (company.convertProductExchangeRate && + client != null) { + _exchangeRateController.text = formatNumber( + viewModel.state.staticState + .currencyMap[currencyId].exchangeRate, + context, + formatNumberType: FormatNumberType.input); + } + */ + }, onAddPressed: (completer) => viewModel.onAddClientPressed(context, completer), ) @@ -441,8 +449,16 @@ class InvoiceEditDesktopState extends State children: [ Expanded( child: DecoratedFormField( + key: ValueKey('__rate_${invoice.clientId}__'), label: localization.exchangeRate, - controller: _exchangeRateController, + initialValue: formatNumber( + invoice.exchangeRate, context, + formatNumberType: + FormatNumberType.input), + onChanged: (value) => viewModel.onChanged( + invoice.rebuild((b) => b + ..exchangeRate = + parseDouble(value))), keyboardType: TextInputType.numberWithOptions( decimal: true), @@ -534,6 +550,7 @@ class InvoiceEditDesktopState extends State decoration: InputDecoration( labelText: localization.total, ), + textAlign: TextAlign.end, key: ValueKey( '__invoice_total_${invoiceTotal}_${invoice.clientId}__'), initialValue: formatNumber(invoiceTotal, context, diff --git a/lib/ui/invoice/edit/invoice_edit_details_vm.dart b/lib/ui/invoice/edit/invoice_edit_details_vm.dart index 8edf33adb..918780fb6 100644 --- a/lib/ui/invoice/edit/invoice_edit_details_vm.dart +++ b/lib/ui/invoice/edit/invoice_edit_details_vm.dart @@ -91,16 +91,23 @@ class InvoiceEditDetailsVM extends EntityEditDetailsVM { factory InvoiceEditDetailsVM.fromStore(Store store) { final AppState state = store.state; final invoice = state.invoiceUIState.editing; + final company = state.company; return InvoiceEditDetailsVM( state: state, - company: state.company, + company: company, invoice: invoice, onChanged: (InvoiceEntity invoice) => store.dispatch(UpdateInvoice(invoice)), clientMap: state.clientState.map, clientList: state.clientState.list, onClientChanged: (invoice, client) { + print('## onClientChanged: ${client?.id}'); + if (company.convertProductExchangeRate && client != null) { + store.dispatch(UpdateInvoice(invoice.rebuild((b) => b + ..exchangeRate = state + .staticState.currencyMap[client.currencyId].exchangeRate))); + } store.dispatch(UpdateInvoiceClient(client: client)); }, onAddClientPressed: (context, completer) { diff --git a/lib/ui/invoice/edit/invoice_edit_items_desktop.dart b/lib/ui/invoice/edit/invoice_edit_items_desktop.dart index 6a48c45c0..9481ab541 100644 --- a/lib/ui/invoice/edit/invoice_edit_items_desktop.dart +++ b/lib/ui/invoice/edit/invoice_edit_items_desktop.dart @@ -228,6 +228,9 @@ class _InvoiceEditItemsDesktopState extends State { final item = lineItems[index]; final product = productState.map[suggestion]; final client = state.clientState.get(invoice.clientId); + final currencyId = client.getCurrencyId( + company: company, + group: state.groupState.get(client.groupId)); double cost = product.price; if (company.convertProductExchangeRate && @@ -239,7 +242,7 @@ class _InvoiceEditItemsDesktopState extends State { toCurrencyId: client.currencyId); cost = round( cost, - state.staticState.currencyMap[client.currencyId] + state.staticState.currencyMap[currencyId] .precision); } @@ -428,7 +431,7 @@ class _InvoiceEditItemsDesktopState extends State { padding: const EdgeInsets.only(right: kTableColumnGap), child: TextFormField( key: ValueKey( - '__total_${index}_${lineItems[index].total}__'), + '__total_${index}_${lineItems[index].total}_${invoice.clientId}__'), readOnly: true, enabled: false, initialValue: formatNumber( diff --git a/lib/ui/quote/edit/quote_edit_details_vm.dart b/lib/ui/quote/edit/quote_edit_details_vm.dart index 2c39a661b..4b6879925 100644 --- a/lib/ui/quote/edit/quote_edit_details_vm.dart +++ b/lib/ui/quote/edit/quote_edit_details_vm.dart @@ -71,15 +71,21 @@ class QuoteEditDetailsVM extends EntityEditDetailsVM { factory QuoteEditDetailsVM.fromStore(Store store) { final AppState state = store.state; final quote = state.quoteUIState.editing; + final company = state.company; return QuoteEditDetailsVM( state: state, - company: state.company, + company: company, invoice: quote, onChanged: (InvoiceEntity quote) => store.dispatch(UpdateQuote(quote)), clientMap: state.clientState.map, clientList: state.clientState.list, - onClientChanged: (invoice, client) { + onClientChanged: (quote, client) { + if (company.convertProductExchangeRate && client != null) { + store.dispatch(UpdateQuote(quote.rebuild((b) => b + ..exchangeRate = state + .staticState.currencyMap[client.currencyId].exchangeRate))); + } store.dispatch(UpdateQuoteClient(client: client)); }, onAddClientPressed: (context, completer) { diff --git a/lib/utils/formatting.dart b/lib/utils/formatting.dart index b034d96a4..3acec7023 100644 --- a/lib/utils/formatting.dart +++ b/lib/utils/formatting.dart @@ -107,6 +107,8 @@ String formatNumber( return ''; } + print('## Formatting... CLIENT: ${client?.name}, CURRENCY: ${currency.name}'); + if (formatNumberType == FormatNumberType.money) { value = round(value, currency.precision); } diff --git a/lib/utils/money.dart b/lib/utils/money.dart index dfa6344c3..146bfecb6 100644 --- a/lib/utils/money.dart +++ b/lib/utils/money.dart @@ -15,6 +15,9 @@ double getExchangeRate(BuildContext context, double getExchangeRateWithMap(BuiltMap currencyMap, {String fromCurrencyId, String toCurrencyId}) { + if (fromCurrencyId == null || toCurrencyId == null) { + return 1; + } final fromCurrency = currencyMap[fromCurrencyId]; final toCurrency = currencyMap[toCurrencyId]; // TODO replace with data from server