diff --git a/lib/data/models/invoice_model.dart b/lib/data/models/invoice_model.dart index 9239c1064..a5510d72d 100644 --- a/lib/data/models/invoice_model.dart +++ b/lib/data/models/invoice_model.dart @@ -9,6 +9,7 @@ import 'package:invoiceninja_flutter/data/models/models.dart'; import 'package:invoiceninja_flutter/data/models/quote_model.dart'; import 'package:invoiceninja_flutter/data/models/recurring_invoice_model.dart'; import 'package:invoiceninja_flutter/redux/app/app_state.dart'; +import 'package:invoiceninja_flutter/redux/client/client_selectors.dart'; import 'package:invoiceninja_flutter/utils/formatting.dart'; import 'package:invoiceninja_flutter/utils/money.dart'; import 'package:invoiceninja_flutter/utils/strings.dart'; @@ -119,6 +120,8 @@ abstract class InvoiceEntity extends Object EntityType entityType, }) { final company = state?.company; + final settings = getClientSettings(state, client); + double exchangeRate = 1; if ((client?.currencyId ?? '').isNotEmpty) { exchangeRate = getExchangeRate( @@ -148,22 +151,22 @@ abstract class InvoiceEntity extends Object footer: '', designId: '', taxName1: (company?.numberOfInvoiceTaxRates ?? 0) >= 1 - ? company?.settings?.defaultTaxName1 ?? '' + ? settings.defaultTaxName1 ?? '' : '', taxRate1: (company?.numberOfInvoiceTaxRates ?? 0) >= 1 - ? company?.settings?.defaultTaxRate1 ?? 0.0 + ? settings.defaultTaxRate1 ?? 0.0 : 0, taxName2: (company?.numberOfInvoiceTaxRates ?? 0) >= 2 - ? company?.settings?.defaultTaxName2 ?? '' + ? settings.defaultTaxName2 ?? '' : '', taxRate2: (company?.numberOfInvoiceTaxRates ?? 0) >= 2 - ? company?.settings?.defaultTaxRate2 ?? 0.0 + ? settings.defaultTaxRate2 ?? 0.0 : 0, taxName3: (company?.numberOfInvoiceTaxRates ?? 0) >= 3 - ? company?.settings?.defaultTaxName3 ?? '' + ? settings.defaultTaxName3 ?? '' : '', taxRate3: (company?.numberOfInvoiceTaxRates ?? 0) >= 3 - ? company?.settings?.defaultTaxRate3 ?? 0.0 + ? settings.defaultTaxRate3 ?? 0.0 : 0, isAmountDiscount: false, partial: 0.0, diff --git a/lib/data/models/settings_model.dart b/lib/data/models/settings_model.dart index 608709ec2..dca076342 100644 --- a/lib/data/models/settings_model.dart +++ b/lib/data/models/settings_model.dart @@ -59,6 +59,24 @@ abstract class SettingsEntity defaultValidUntil: clientSettings?.defaultValidUntil ?? groupSettings?.defaultValidUntil ?? companySettings?.defaultValidUntil, + defaultTaxRate1: clientSettings?.defaultTaxRate1 ?? + groupSettings?.defaultTaxRate1 ?? + companySettings?.defaultTaxRate1, + defaultTaxName1: clientSettings?.defaultTaxName1 ?? + groupSettings?.defaultTaxName1 ?? + companySettings?.defaultTaxName1, + defaultTaxRate2: clientSettings?.defaultTaxRate2 ?? + groupSettings?.defaultTaxRate2 ?? + companySettings?.defaultTaxRate2, + defaultTaxName2: clientSettings?.defaultTaxName2 ?? + groupSettings?.defaultTaxName2 ?? + companySettings?.defaultTaxName2, + defaultTaxRate3: clientSettings?.defaultTaxRate3 ?? + groupSettings?.defaultTaxRate3 ?? + companySettings?.defaultTaxRate3, + defaultTaxName3: clientSettings?.defaultTaxName3 ?? + groupSettings?.defaultTaxName3 ?? + companySettings?.defaultTaxName3, ); } diff --git a/lib/redux/app/app_actions.dart b/lib/redux/app/app_actions.dart index 5c0bc7229..a7a8b7a20 100644 --- a/lib/redux/app/app_actions.dart +++ b/lib/redux/app/app_actions.dart @@ -13,6 +13,7 @@ import 'package:invoiceninja_flutter/data/models/static/static_data_model.dart'; import 'package:invoiceninja_flutter/main_app.dart'; import 'package:invoiceninja_flutter/redux/app/app_state.dart'; import 'package:invoiceninja_flutter/redux/client/client_actions.dart'; +import 'package:invoiceninja_flutter/redux/client/client_selectors.dart'; import 'package:invoiceninja_flutter/redux/company_gateway/company_gateway_actions.dart'; import 'package:invoiceninja_flutter/redux/credit/credit_actions.dart'; import 'package:invoiceninja_flutter/redux/dashboard/dashboard_actions.dart'; @@ -1110,11 +1111,7 @@ void editEntity({ case EntityType.invoice: final invoice = entity as InvoiceEntity; final client = state.clientState.get(invoice.clientId); - final settings = SettingsEntity( - clientSettings: client.settings, - groupSettings: state.groupState.get(client.groupId).settings, - companySettings: state.company.settings, - ); + final settings = getClientSettings(state, client); if (settings.lockInvoices == SettingsEntity.LOCK_INVOICES_PAID && invoice.isPaid) { diff --git a/lib/redux/client/client_selectors.dart b/lib/redux/client/client_selectors.dart index ed8bd764b..d9e8ccf55 100644 --- a/lib/redux/client/client_selectors.dart +++ b/lib/redux/client/client_selectors.dart @@ -127,6 +127,22 @@ List filteredClientsSelector( return list; } +SettingsEntity getClientSettings(AppState state, ClientEntity client) { + if (state == null) { + return SettingsEntity(); + } + + client ??= ClientEntity(); + final company = state.company; + final group = state.groupState.get(client.groupId); + + return SettingsEntity( + clientSettings: client.settings, + groupSettings: group.settings, + companySettings: company.settings, + ); +} + bool hasClientChanges( ClientEntity client, BuiltMap clientMap) => client.isNew ? client.isChanged : client != clientMap[client.id]; diff --git a/lib/redux/design/design_selectors.dart b/lib/redux/design/design_selectors.dart index 453ad06e1..174c79209 100644 --- a/lib/redux/design/design_selectors.dart +++ b/lib/redux/design/design_selectors.dart @@ -1,5 +1,6 @@ import 'package:invoiceninja_flutter/data/models/design_model.dart'; import 'package:invoiceninja_flutter/redux/app/app_state.dart'; +import 'package:invoiceninja_flutter/redux/client/client_selectors.dart'; import 'package:memoize/memoize.dart'; import 'package:built_collection/built_collection.dart'; import 'package:invoiceninja_flutter/data/models/models.dart'; @@ -68,12 +69,7 @@ bool hasDesignChanges( String getDesignIdForClientByEntity( {AppState state, String clientId, EntityType entityType}) { final client = state.clientState.get(clientId); - final group = state.groupState.get(client.groupId); - final settings = SettingsEntity( - clientSettings: client.settings, - groupSettings: group.settings, - companySettings: state.company.settings, - ); + final settings = getClientSettings(state, client); switch (entityType) { case EntityType.invoice: return settings.defaultInvoiceDesignId; diff --git a/lib/ui/app/invoice/invoice_email_view.dart b/lib/ui/app/invoice/invoice_email_view.dart index 6aea8f135..e7f16dbc1 100644 --- a/lib/ui/app/invoice/invoice_email_view.dart +++ b/lib/ui/app/invoice/invoice_email_view.dart @@ -2,6 +2,7 @@ import 'package:invoiceninja_flutter/constants.dart'; import 'package:invoiceninja_flutter/data/models/entities.dart'; import 'package:invoiceninja_flutter/data/models/models.dart'; import 'package:invoiceninja_flutter/redux/app/app_actions.dart'; +import 'package:invoiceninja_flutter/redux/client/client_selectors.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/decorated_form_field.dart'; @@ -159,11 +160,7 @@ class _InvoiceEmailViewState extends State final invoice = widget.viewModel.invoice; final client = viewModel.client; final state = viewModel.state; - final settings = SettingsEntity( - clientSettings: client.settings, - groupSettings: state.groupState.get(client.groupId).settings, - companySettings: state.company.settings, - ); + final settings = getClientSettings(state, client); final contacts = invoice.invitations .map((invitation) => client.contacts.firstWhere( (contact) => contact.id == invitation.contactId, diff --git a/lib/ui/invoice/edit/invoice_edit_desktop.dart b/lib/ui/invoice/edit/invoice_edit_desktop.dart index 48677b8d8..366ca31e0 100644 --- a/lib/ui/invoice/edit/invoice_edit_desktop.dart +++ b/lib/ui/invoice/edit/invoice_edit_desktop.dart @@ -6,6 +6,7 @@ import 'package:flutter_redux/flutter_redux.dart'; import 'package:invoiceninja_flutter/data/models/serializers.dart'; import 'package:invoiceninja_flutter/redux/app/app_actions.dart'; import 'package:invoiceninja_flutter/redux/app/app_state.dart'; +import 'package:invoiceninja_flutter/redux/client/client_selectors.dart'; import 'package:invoiceninja_flutter/ui/app/app_scrollbar.dart'; import 'package:invoiceninja_flutter/ui/app/buttons/elevated_button.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; @@ -212,12 +213,7 @@ class InvoiceEditDesktopState extends State !item.isEmpty && item.typeId == InvoiceItemEntity.TYPE_TASK) .length; - final settings = SettingsEntity( - clientSettings: client.settings, - groupSettings: state.groupState.get(client.groupId).settings, - companySettings: state.company.settings, - ); - + final settings = getClientSettings(state, client); final terms = entityType == EntityType.quote ? settings.defaultValidUntil : settings.defaultPaymentTerms; diff --git a/lib/ui/invoice/edit/invoice_edit_details.dart b/lib/ui/invoice/edit/invoice_edit_details.dart index 085a81ed9..4d6724c54 100644 --- a/lib/ui/invoice/edit/invoice_edit_details.dart +++ b/lib/ui/invoice/edit/invoice_edit_details.dart @@ -1,5 +1,6 @@ import 'package:invoiceninja_flutter/constants.dart'; import 'package:invoiceninja_flutter/data/models/models.dart'; +import 'package:invoiceninja_flutter/redux/client/client_selectors.dart'; import 'package:invoiceninja_flutter/redux/invoice/invoice_selectors.dart'; import 'package:invoiceninja_flutter/ui/app/forms/app_dropdown_button.dart'; import 'package:invoiceninja_flutter/ui/app/forms/client_picker.dart'; @@ -137,11 +138,7 @@ class InvoiceEditDetailsState extends State { state.getEntity(invoice.entityType, invoice.id) as InvoiceEntity; final client = state.clientState.get(invoice.clientId); - final settings = SettingsEntity( - clientSettings: client.settings, - groupSettings: state.groupState.get(client.groupId).settings, - companySettings: state.company.settings, - ); + final settings = getClientSettings(state, client); final terms = widget.entityType == EntityType.quote ? settings.defaultValidUntil : settings.defaultPaymentTerms; diff --git a/lib/ui/invoice/edit/invoice_edit_notes.dart b/lib/ui/invoice/edit/invoice_edit_notes.dart index 5d81e13d3..b9bd63965 100644 --- a/lib/ui/invoice/edit/invoice_edit_notes.dart +++ b/lib/ui/invoice/edit/invoice_edit_notes.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:invoiceninja_flutter/data/models/entities.dart'; import 'package:invoiceninja_flutter/data/models/models.dart'; +import 'package:invoiceninja_flutter/redux/client/client_selectors.dart'; import 'package:invoiceninja_flutter/ui/app/form_card.dart'; import 'package:invoiceninja_flutter/ui/app/forms/decorated_form_field.dart'; import 'package:invoiceninja_flutter/ui/app/scrollable_listview.dart'; @@ -83,12 +84,7 @@ class InvoiceEditNotesState extends State { final state = viewModel.state; final invoice = viewModel.invoice; final client = state.clientState.get(invoice.clientId); - - final settings = SettingsEntity( - clientSettings: client.settings, - groupSettings: state.groupState.get(client.groupId).settings, - companySettings: state.company.settings, - ); + final settings = getClientSettings(state, client); return ScrollableListView( children: [ diff --git a/lib/ui/settings/settings_list.dart b/lib/ui/settings/settings_list.dart index 0d28d80d5..ceae77f7f 100644 --- a/lib/ui/settings/settings_list.dart +++ b/lib/ui/settings/settings_list.dart @@ -120,11 +120,10 @@ class _SettingsListState extends State { section: kSettingsOnlinePayments, viewModel: widget.viewModel, ), - if (showAll) - SettingsListTile( - section: kSettingsTaxSettings, - viewModel: widget.viewModel, - ), + SettingsListTile( + section: kSettingsTaxSettings, + viewModel: widget.viewModel, + ), if (showAll) SettingsListTile( section: kSettingsProducts, diff --git a/lib/ui/settings/tax_settings.dart b/lib/ui/settings/tax_settings.dart index dc6473141..632c5ce36 100644 --- a/lib/ui/settings/tax_settings.dart +++ b/lib/ui/settings/tax_settings.dart @@ -56,76 +56,74 @@ class _TaxSettingsState extends State { formKey: _formKey, focusNode: _focusNode, children: [ - FormCard( - children: [ - NumberOfRatesSelector( - label: localization.invoiceTaxRates, - numberOfRates: company.numberOfInvoiceTaxRates, - onChanged: (value) => viewModel.onCompanyChanged( - company.rebuild((b) => b..numberOfInvoiceTaxRates = value)), - ), - NumberOfRatesSelector( - label: localization.itemTaxRates, - numberOfRates: company.numberOfItemTaxRates, - onChanged: (value) => viewModel.onCompanyChanged( - company.rebuild((b) => b..numberOfItemTaxRates = value)), - ), - SizedBox(height: 16), - BoolDropdownButton( - iconData: MdiIcons.percent, - label: localization.inclusiveTaxes, - value: settings.enableInclusiveTaxes, - onChanged: (value) => viewModel.onSettingsChanged( - settings.rebuild((b) => b..enableInclusiveTaxes = value)), - helpLabel: - '\n${localization.exclusive}: 100 + 10% = 100 + 10\n${localization.inclusive}: 100 + 10% = 90.91 + 9.09', - ), - ], - ), - if (company.enableFirstInvoiceTaxRate && - state.taxRateState.list.isNotEmpty) + if (!state.settingsUIState.isFiltered) FormCard( children: [ + NumberOfRatesSelector( + label: localization.invoiceTaxRates, + numberOfRates: company.numberOfInvoiceTaxRates, + onChanged: (value) => viewModel.onCompanyChanged(company + .rebuild((b) => b..numberOfInvoiceTaxRates = value)), + ), + NumberOfRatesSelector( + label: localization.itemTaxRates, + numberOfRates: company.numberOfItemTaxRates, + onChanged: (value) => viewModel.onCompanyChanged( + company.rebuild((b) => b..numberOfItemTaxRates = value)), + ), + SizedBox(height: 16), + BoolDropdownButton( + iconData: MdiIcons.percent, + label: localization.inclusiveTaxes, + value: settings.enableInclusiveTaxes, + onChanged: (value) => viewModel.onSettingsChanged( + settings.rebuild((b) => b..enableInclusiveTaxes = value)), + helpLabel: + '\n${localization.exclusive}: 100 + 10% = 100 + 10\n${localization.inclusive}: 100 + 10% = 90.91 + 9.09', + ), + ], + ), + FormCard( + children: [ + TaxRateDropdown( + onSelected: (taxRate) => + viewModel.onSettingsChanged(settings.rebuild((b) => b + ..defaultTaxName1 = taxRate.name + ..defaultTaxRate1 = taxRate.rate)), + labelText: localization.defaultTaxRate, + initialTaxName: settings.defaultTaxName1, + initialTaxRate: settings.defaultTaxRate1, + ), + if (company.enableSecondInvoiceTaxRate) TaxRateDropdown( onSelected: (taxRate) => viewModel.onSettingsChanged(settings.rebuild((b) => b - ..defaultTaxName1 = taxRate.name - ..defaultTaxRate1 = taxRate.rate)), + ..defaultTaxName2 = taxRate.name + ..defaultTaxRate2 = taxRate.rate)), labelText: localization.defaultTaxRate, - initialTaxName: settings.defaultTaxName1, - initialTaxRate: settings.defaultTaxRate1, + initialTaxName: settings.defaultTaxName2, + initialTaxRate: settings.defaultTaxRate2, ), - if (company.enableSecondInvoiceTaxRate) - TaxRateDropdown( - onSelected: (taxRate) => - viewModel.onSettingsChanged(settings.rebuild((b) => b - ..defaultTaxName2 = taxRate.name - ..defaultTaxRate2 = taxRate.rate)), - labelText: localization.defaultTaxRate, - initialTaxName: settings.defaultTaxName2, - initialTaxRate: settings.defaultTaxRate2, - ), - if (company.enableThirdInvoiceTaxRate) - TaxRateDropdown( - onSelected: (taxRate) => - viewModel.onSettingsChanged(settings.rebuild((b) => b - ..defaultTaxName3 = taxRate.name - ..defaultTaxRate3 = taxRate.rate)), - labelText: localization.defaultTaxRate, - initialTaxName: settings.defaultTaxName3, - initialTaxRate: settings.defaultTaxRate3, - ), - ], - ), - if (!state.uiState.settingsUIState.isFiltered) - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), - child: AppButton( - iconData: Icons.settings, - label: localization.configureRates.toUpperCase(), - onPressed: () => viewModel.onConfigureRatesPressed(context), - ), + if (company.enableThirdInvoiceTaxRate) + TaxRateDropdown( + onSelected: (taxRate) => + viewModel.onSettingsChanged(settings.rebuild((b) => b + ..defaultTaxName3 = taxRate.name + ..defaultTaxRate3 = taxRate.rate)), + labelText: localization.defaultTaxRate, + initialTaxName: settings.defaultTaxName3, + initialTaxRate: settings.defaultTaxRate3, + ), + ], + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: AppButton( + iconData: Icons.settings, + label: localization.configureRates.toUpperCase(), + onPressed: () => viewModel.onConfigureRatesPressed(context), ), + ), ], ), ); diff --git a/lib/ui/settings/tax_settings_vm.dart b/lib/ui/settings/tax_settings_vm.dart index 175cc8a05..7858121dc 100644 --- a/lib/ui/settings/tax_settings_vm.dart +++ b/lib/ui/settings/tax_settings_vm.dart @@ -2,9 +2,14 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_redux/flutter_redux.dart'; import 'package:invoiceninja_flutter/constants.dart'; +import 'package:invoiceninja_flutter/data/models/client_model.dart'; import 'package:invoiceninja_flutter/data/models/company_model.dart'; +import 'package:invoiceninja_flutter/data/models/entities.dart'; +import 'package:invoiceninja_flutter/data/models/group_model.dart'; import 'package:invoiceninja_flutter/data/models/settings_model.dart'; +import 'package:invoiceninja_flutter/redux/client/client_actions.dart'; import 'package:invoiceninja_flutter/redux/company/company_actions.dart'; +import 'package:invoiceninja_flutter/redux/group/group_actions.dart'; import 'package:invoiceninja_flutter/redux/settings/settings_actions.dart'; import 'package:invoiceninja_flutter/ui/settings/tax_settings.dart'; import 'package:invoiceninja_flutter/utils/completers.dart'; @@ -56,10 +61,27 @@ class TaxSettingsVM { onSavePressed: (context) { Debouncer.runOnComplete(() { final settingsUIState = store.state.uiState.settingsUIState; - final completer = snackBarCompleter( - context, AppLocalization.of(context).savedSettings); - store.dispatch(SaveCompanyRequest( - completer: completer, company: settingsUIState.company)); + + switch (settingsUIState.entityType) { + case EntityType.company: + final completer = snackBarCompleter( + context, AppLocalization.of(context).savedSettings); + store.dispatch(SaveCompanyRequest( + completer: completer, company: settingsUIState.company)); + break; + case EntityType.group: + final completer = snackBarCompleter( + context, AppLocalization.of(context).savedSettings); + store.dispatch(SaveGroupRequest( + completer: completer, group: settingsUIState.group)); + break; + case EntityType.client: + final completer = snackBarCompleter( + context, AppLocalization.of(context).savedSettings); + store.dispatch(SaveClientRequest( + completer: completer, client: settingsUIState.client)); + break; + } }); }, onConfigureRatesPressed: (context) {