Support group/client default rates

This commit is contained in:
Hillel Coren 2021-10-25 18:27:18 +03:00
parent 9a417c41be
commit 549a546b9e
12 changed files with 145 additions and 110 deletions

View File

@ -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,

View File

@ -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,
);
}

View File

@ -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) {

View File

@ -127,6 +127,22 @@ List<String> 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<String, ClientEntity> clientMap) =>
client.isNew ? client.isChanged : client != clientMap[client.id];

View File

@ -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;

View File

@ -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<InvoiceEmailView>
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,

View File

@ -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<InvoiceEditDesktop>
!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;

View File

@ -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<InvoiceEditDetails> {
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;

View File

@ -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<InvoiceEditNotes> {
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: <Widget>[

View File

@ -120,11 +120,10 @@ class _SettingsListState extends State<SettingsList> {
section: kSettingsOnlinePayments,
viewModel: widget.viewModel,
),
if (showAll)
SettingsListTile(
section: kSettingsTaxSettings,
viewModel: widget.viewModel,
),
SettingsListTile(
section: kSettingsTaxSettings,
viewModel: widget.viewModel,
),
if (showAll)
SettingsListTile(
section: kSettingsProducts,

View File

@ -56,76 +56,74 @@ class _TaxSettingsState extends State<TaxSettings> {
formKey: _formKey,
focusNode: _focusNode,
children: <Widget>[
FormCard(
children: <Widget>[
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: <Widget>[
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: <Widget>[
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),
),
),
],
),
);

View File

@ -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<Null>(
context, AppLocalization.of(context).savedSettings);
store.dispatch(SaveCompanyRequest(
completer: completer, company: settingsUIState.company));
switch (settingsUIState.entityType) {
case EntityType.company:
final completer = snackBarCompleter<Null>(
context, AppLocalization.of(context).savedSettings);
store.dispatch(SaveCompanyRequest(
completer: completer, company: settingsUIState.company));
break;
case EntityType.group:
final completer = snackBarCompleter<GroupEntity>(
context, AppLocalization.of(context).savedSettings);
store.dispatch(SaveGroupRequest(
completer: completer, group: settingsUIState.group));
break;
case EntityType.client:
final completer = snackBarCompleter<ClientEntity>(
context, AppLocalization.of(context).savedSettings);
store.dispatch(SaveClientRequest(
completer: completer, client: settingsUIState.client));
break;
}
});
},
onConfigureRatesPressed: (context) {