Added exchange rate

This commit is contained in:
Hillel Coren 2020-08-24 12:43:35 +03:00
parent 92a7d72d5f
commit a10a2f90c4
10 changed files with 70 additions and 16 deletions

View File

@ -159,7 +159,7 @@ abstract class InvoiceEntity extends Object
reminder2Sent: '', reminder2Sent: '',
reminder3Sent: '', reminder3Sent: '',
reminderLastSent: '', reminderLastSent: '',
exchangeRate: 0, exchangeRate: 1,
); );
} }

View File

@ -636,6 +636,8 @@ abstract class AppState implements Built<AppState, AppStateBuilder> {
//return '${clientState.map[clientUIState.selectedId].gatewayTokens}'; //return '${clientState.map[clientUIState.selectedId].gatewayTokens}';
//return 'gatewayId: ${companyGatewayState.map[companyGatewayUIState.selectedId].gatewayId}'; //return 'gatewayId: ${companyGatewayState.map[companyGatewayUIState.selectedId].gatewayId}';
//return 'Language Id: ${company.settings.languageId}'; //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'; 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';
} }
} }

View File

@ -22,6 +22,14 @@ abstract class GroupState implements Built<GroupState, GroupStateBuilder> {
@memoized @memoized
int get hashCode; int get hashCode;
GroupEntity get(String groupId) {
if (map.containsKey(groupId)) {
return map[groupId];
} else {
return GroupEntity(id: groupId);
}
}
BuiltMap<String, GroupEntity> get map; BuiltMap<String, GroupEntity> get map;
BuiltList<String> get list; BuiltList<String> get list;

View File

@ -72,15 +72,21 @@ class CreditEditDetailsVM extends EntityEditDetailsVM {
factory CreditEditDetailsVM.fromStore(Store<AppState> store) { factory CreditEditDetailsVM.fromStore(Store<AppState> store) {
final AppState state = store.state; final AppState state = store.state;
final credit = state.creditUIState.editing; final credit = state.creditUIState.editing;
final company = state.company;
return CreditEditDetailsVM( return CreditEditDetailsVM(
state: state, state: state,
company: state.company, company: company,
invoice: credit, invoice: credit,
onChanged: (InvoiceEntity credit) => store.dispatch(UpdateCredit(credit)), onChanged: (InvoiceEntity credit) => store.dispatch(UpdateCredit(credit)),
clientMap: state.clientState.map, clientMap: state.clientState.map,
clientList: state.clientState.list, clientList: state.clientState.list,
onClientChanged: (invoice, client) { 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)); store.dispatch(UpdateCreditClient(client: client));
}, },
onAddClientPressed: (context, completer) { onAddClientPressed: (context, completer) {

View File

@ -2,6 +2,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:invoiceninja_flutter/constants.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/data/models/entities.dart';
import 'package:invoiceninja_flutter/ui/app/form_card.dart'; import 'package:invoiceninja_flutter/ui/app/form_card.dart';
import 'package:invoiceninja_flutter/ui/app/forms/app_tab_bar.dart'; import 'package:invoiceninja_flutter/ui/app/forms/app_tab_bar.dart';
@ -62,7 +63,6 @@ class InvoiceEditDesktopState extends State<InvoiceEditDesktop>
final _privateNotesController = TextEditingController(); final _privateNotesController = TextEditingController();
final _termsController = TextEditingController(); final _termsController = TextEditingController();
final _footerController = TextEditingController(); final _footerController = TextEditingController();
final _exchangeRateController = TextEditingController();
List<TextEditingController> _controllers = []; List<TextEditingController> _controllers = [];
final _debouncer = Debouncer(); final _debouncer = Debouncer();
@ -94,7 +94,6 @@ class InvoiceEditDesktopState extends State<InvoiceEditDesktop>
_privateNotesController, _privateNotesController,
_termsController, _termsController,
_footerController, _footerController,
_exchangeRateController,
]; ];
_controllers _controllers
@ -123,8 +122,6 @@ class InvoiceEditDesktopState extends State<InvoiceEditDesktop>
_privateNotesController.text = invoice.privateNotes; _privateNotesController.text = invoice.privateNotes;
_termsController.text = invoice.terms; _termsController.text = invoice.terms;
_footerController.text = invoice.footer; _footerController.text = invoice.footer;
_exchangeRateController.text = formatNumber(invoice.exchangeRate, context,
formatNumberType: FormatNumberType.input);
_controllers _controllers
.forEach((dynamic controller) => controller.addListener(_onChanged)); .forEach((dynamic controller) => controller.addListener(_onChanged));
@ -162,8 +159,7 @@ class InvoiceEditDesktopState extends State<InvoiceEditDesktop>
..publicNotes = _publicNotesController.text.trim() ..publicNotes = _publicNotesController.text.trim()
..privateNotes = _privateNotesController.text.trim() ..privateNotes = _privateNotesController.text.trim()
..terms = _termsController.text.trim() ..terms = _termsController.text.trim()
..footer = _footerController.text.trim() ..footer = _footerController.text.trim());
..exchangeRate = parseDouble(_exchangeRateController.text));
if (invoice != widget.viewModel.invoice) { if (invoice != widget.viewModel.invoice) {
widget.viewModel.onChanged(invoice); widget.viewModel.onChanged(invoice);
} }
@ -201,8 +197,20 @@ class InvoiceEditDesktopState extends State<InvoiceEditDesktop>
autofocus: kIsWeb, autofocus: kIsWeb,
clientId: invoice.clientId, clientId: invoice.clientId,
clientState: viewModel.state.clientState, clientState: viewModel.state.clientState,
onSelected: (client) => onSelected: (client) {
viewModel.onClientChanged(invoice, 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) => onAddPressed: (completer) =>
viewModel.onAddClientPressed(context, completer), viewModel.onAddClientPressed(context, completer),
) )
@ -441,8 +449,16 @@ class InvoiceEditDesktopState extends State<InvoiceEditDesktop>
children: [ children: [
Expanded( Expanded(
child: DecoratedFormField( child: DecoratedFormField(
key: ValueKey('__rate_${invoice.clientId}__'),
label: localization.exchangeRate, 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: keyboardType:
TextInputType.numberWithOptions( TextInputType.numberWithOptions(
decimal: true), decimal: true),
@ -534,6 +550,7 @@ class InvoiceEditDesktopState extends State<InvoiceEditDesktop>
decoration: InputDecoration( decoration: InputDecoration(
labelText: localization.total, labelText: localization.total,
), ),
textAlign: TextAlign.end,
key: ValueKey( key: ValueKey(
'__invoice_total_${invoiceTotal}_${invoice.clientId}__'), '__invoice_total_${invoiceTotal}_${invoice.clientId}__'),
initialValue: formatNumber(invoiceTotal, context, initialValue: formatNumber(invoiceTotal, context,

View File

@ -91,16 +91,23 @@ class InvoiceEditDetailsVM extends EntityEditDetailsVM {
factory InvoiceEditDetailsVM.fromStore(Store<AppState> store) { factory InvoiceEditDetailsVM.fromStore(Store<AppState> store) {
final AppState state = store.state; final AppState state = store.state;
final invoice = state.invoiceUIState.editing; final invoice = state.invoiceUIState.editing;
final company = state.company;
return InvoiceEditDetailsVM( return InvoiceEditDetailsVM(
state: state, state: state,
company: state.company, company: company,
invoice: invoice, invoice: invoice,
onChanged: (InvoiceEntity invoice) => onChanged: (InvoiceEntity invoice) =>
store.dispatch(UpdateInvoice(invoice)), store.dispatch(UpdateInvoice(invoice)),
clientMap: state.clientState.map, clientMap: state.clientState.map,
clientList: state.clientState.list, clientList: state.clientState.list,
onClientChanged: (invoice, client) { 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)); store.dispatch(UpdateInvoiceClient(client: client));
}, },
onAddClientPressed: (context, completer) { onAddClientPressed: (context, completer) {

View File

@ -228,6 +228,9 @@ class _InvoiceEditItemsDesktopState extends State<InvoiceEditItemsDesktop> {
final item = lineItems[index]; final item = lineItems[index];
final product = productState.map[suggestion]; final product = productState.map[suggestion];
final client = state.clientState.get(invoice.clientId); final client = state.clientState.get(invoice.clientId);
final currencyId = client.getCurrencyId(
company: company,
group: state.groupState.get(client.groupId));
double cost = product.price; double cost = product.price;
if (company.convertProductExchangeRate && if (company.convertProductExchangeRate &&
@ -239,7 +242,7 @@ class _InvoiceEditItemsDesktopState extends State<InvoiceEditItemsDesktop> {
toCurrencyId: client.currencyId); toCurrencyId: client.currencyId);
cost = round( cost = round(
cost, cost,
state.staticState.currencyMap[client.currencyId] state.staticState.currencyMap[currencyId]
.precision); .precision);
} }
@ -428,7 +431,7 @@ class _InvoiceEditItemsDesktopState extends State<InvoiceEditItemsDesktop> {
padding: const EdgeInsets.only(right: kTableColumnGap), padding: const EdgeInsets.only(right: kTableColumnGap),
child: TextFormField( child: TextFormField(
key: ValueKey( key: ValueKey(
'__total_${index}_${lineItems[index].total}__'), '__total_${index}_${lineItems[index].total}_${invoice.clientId}__'),
readOnly: true, readOnly: true,
enabled: false, enabled: false,
initialValue: formatNumber( initialValue: formatNumber(

View File

@ -71,15 +71,21 @@ class QuoteEditDetailsVM extends EntityEditDetailsVM {
factory QuoteEditDetailsVM.fromStore(Store<AppState> store) { factory QuoteEditDetailsVM.fromStore(Store<AppState> store) {
final AppState state = store.state; final AppState state = store.state;
final quote = state.quoteUIState.editing; final quote = state.quoteUIState.editing;
final company = state.company;
return QuoteEditDetailsVM( return QuoteEditDetailsVM(
state: state, state: state,
company: state.company, company: company,
invoice: quote, invoice: quote,
onChanged: (InvoiceEntity quote) => store.dispatch(UpdateQuote(quote)), onChanged: (InvoiceEntity quote) => store.dispatch(UpdateQuote(quote)),
clientMap: state.clientState.map, clientMap: state.clientState.map,
clientList: state.clientState.list, 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)); store.dispatch(UpdateQuoteClient(client: client));
}, },
onAddClientPressed: (context, completer) { onAddClientPressed: (context, completer) {

View File

@ -107,6 +107,8 @@ String formatNumber(
return ''; return '';
} }
print('## Formatting... CLIENT: ${client?.name}, CURRENCY: ${currency.name}');
if (formatNumberType == FormatNumberType.money) { if (formatNumberType == FormatNumberType.money) {
value = round(value, currency.precision); value = round(value, currency.precision);
} }

View File

@ -15,6 +15,9 @@ double getExchangeRate(BuildContext context,
double getExchangeRateWithMap(BuiltMap<String, CurrencyEntity> currencyMap, double getExchangeRateWithMap(BuiltMap<String, CurrencyEntity> currencyMap,
{String fromCurrencyId, String toCurrencyId}) { {String fromCurrencyId, String toCurrencyId}) {
if (fromCurrencyId == null || toCurrencyId == null) {
return 1;
}
final fromCurrency = currencyMap[fromCurrencyId]; final fromCurrency = currencyMap[fromCurrencyId];
final toCurrency = currencyMap[toCurrencyId]; final toCurrency = currencyMap[toCurrencyId];
// TODO replace with data from server // TODO replace with data from server