Add support for variable currency precisions
This commit is contained in:
parent
32e11c5880
commit
db393aa146
|
|
@ -879,9 +879,9 @@ abstract class InvoiceEntity extends Object
|
|||
}
|
||||
|
||||
/// Gets taxes in the form { taxName1: { amount: 0, paid: 0} , ... }
|
||||
Map<String, Map<String, dynamic>> getTaxes() {
|
||||
Map<String, Map<String, dynamic>> getTaxes(int precision) {
|
||||
final taxes = <String, Map<String, dynamic>>{};
|
||||
final taxable = calculateTaxes(usesInclusiveTaxes);
|
||||
final taxable = calculateTaxes(useInclusiveTaxes: usesInclusiveTaxes, precision: precision);
|
||||
final paidAmount = amount - balance;
|
||||
|
||||
if (taxRate1 != 0) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:built_collection/built_collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:invoiceninja_flutter/data/models/invoice_model.dart';
|
||||
import 'package:invoiceninja_flutter/utils/formatting.dart';
|
||||
|
||||
|
|
@ -40,18 +41,19 @@ abstract class CalculateInvoiceTotal {
|
|||
BuiltList<InvoiceItemEntity> get lineItems;
|
||||
|
||||
double _calculateTaxAmount(
|
||||
double amount, double rate, bool useInclusiveTaxes) {
|
||||
double amount, double rate, bool useInclusiveTaxes, int precision) {
|
||||
double taxAmount;
|
||||
if (useInclusiveTaxes) {
|
||||
taxAmount = amount - (amount / (1 + (rate / 100)));
|
||||
} else {
|
||||
taxAmount = amount * rate / 100;
|
||||
}
|
||||
return round(taxAmount, 2);
|
||||
return round(taxAmount, precision);
|
||||
}
|
||||
|
||||
Map<String, double> calculateTaxes(bool useInclusiveTaxes) {
|
||||
double total = subtotal;
|
||||
Map<String, double> calculateTaxes(
|
||||
{@required bool useInclusiveTaxes, @required int precision}) {
|
||||
double total = calculateSubtotal(precision: precision);
|
||||
double taxAmount;
|
||||
final map = <String, double>{};
|
||||
|
||||
|
|
@ -59,20 +61,23 @@ abstract class CalculateInvoiceTotal {
|
|||
final double taxRate1 = round(item.taxRate1, 3);
|
||||
final double taxRate2 = round(item.taxRate2, 3);
|
||||
|
||||
final lineTotal = getItemTaxable(item, total);
|
||||
final lineTotal = getItemTaxable(item, total, precision);
|
||||
|
||||
if (taxRate1 != 0) {
|
||||
taxAmount = _calculateTaxAmount(lineTotal, taxRate1, useInclusiveTaxes);
|
||||
taxAmount = _calculateTaxAmount(
|
||||
lineTotal, taxRate1, useInclusiveTaxes, precision);
|
||||
map.update(item.taxName1, (value) => value + taxAmount,
|
||||
ifAbsent: () => taxAmount);
|
||||
}
|
||||
if (taxRate2 != 0) {
|
||||
taxAmount = _calculateTaxAmount(lineTotal, taxRate2, useInclusiveTaxes);
|
||||
taxAmount = _calculateTaxAmount(
|
||||
lineTotal, taxRate2, useInclusiveTaxes, precision);
|
||||
map.update(item.taxName2, (value) => value + taxAmount,
|
||||
ifAbsent: () => taxAmount);
|
||||
}
|
||||
if (taxRate3 != 0) {
|
||||
taxAmount = _calculateTaxAmount(lineTotal, taxRate3, useInclusiveTaxes);
|
||||
taxAmount = _calculateTaxAmount(
|
||||
lineTotal, taxRate3, useInclusiveTaxes, precision);
|
||||
map.update(item.taxName3, (value) => value + taxAmount,
|
||||
ifAbsent: () => taxAmount);
|
||||
}
|
||||
|
|
@ -80,34 +85,37 @@ abstract class CalculateInvoiceTotal {
|
|||
|
||||
if (discount != 0.0) {
|
||||
if (isAmountDiscount) {
|
||||
total -= round(discount, 2);
|
||||
total -= round(discount, precision);
|
||||
} else {
|
||||
total -= round(total * discount / 100, 2);
|
||||
total -= round(total * discount / 100, precision);
|
||||
}
|
||||
}
|
||||
|
||||
if (customSurcharge1 != 0.0 && customTaxes1) {
|
||||
total += round(customSurcharge1, 2);
|
||||
total += round(customSurcharge1, precision);
|
||||
}
|
||||
|
||||
if (customSurcharge2 != 0.0 && customTaxes2) {
|
||||
total += round(customSurcharge2, 2);
|
||||
total += round(customSurcharge2, precision);
|
||||
}
|
||||
|
||||
if (taxRate1 != 0) {
|
||||
taxAmount = _calculateTaxAmount(total, taxRate1, useInclusiveTaxes);
|
||||
taxAmount =
|
||||
_calculateTaxAmount(total, taxRate1, useInclusiveTaxes, precision);
|
||||
map.update(taxName1, (value) => value + taxAmount,
|
||||
ifAbsent: () => taxAmount);
|
||||
}
|
||||
|
||||
if (taxRate2 != 0) {
|
||||
taxAmount = _calculateTaxAmount(total, taxRate2, useInclusiveTaxes);
|
||||
taxAmount =
|
||||
_calculateTaxAmount(total, taxRate2, useInclusiveTaxes, precision);
|
||||
map.update(taxName2, (value) => value + taxAmount,
|
||||
ifAbsent: () => taxAmount);
|
||||
}
|
||||
|
||||
if (taxRate3 != 0) {
|
||||
taxAmount = _calculateTaxAmount(total, taxRate3, useInclusiveTaxes);
|
||||
taxAmount =
|
||||
_calculateTaxAmount(total, taxRate3, useInclusiveTaxes, precision);
|
||||
map.update(taxName3, (value) => value + taxAmount,
|
||||
ifAbsent: () => taxAmount);
|
||||
}
|
||||
|
|
@ -115,10 +123,11 @@ abstract class CalculateInvoiceTotal {
|
|||
return map;
|
||||
}
|
||||
|
||||
double getItemTaxable(InvoiceItemEntity item, double invoiceTotal) {
|
||||
double getItemTaxable(
|
||||
InvoiceItemEntity item, double invoiceTotal, int precision) {
|
||||
final double qty = round(item.quantity, 4);
|
||||
final double cost = round(item.cost, 4);
|
||||
final double itemDiscount = round(item.discount, 2);
|
||||
final double itemDiscount = round(item.discount, precision);
|
||||
double lineTotal = qty * cost;
|
||||
|
||||
if (discount != 0) {
|
||||
|
|
@ -137,17 +146,17 @@ abstract class CalculateInvoiceTotal {
|
|||
}
|
||||
}
|
||||
|
||||
return round(lineTotal, 2);
|
||||
return round(lineTotal, precision);
|
||||
}
|
||||
|
||||
double get calculateTotal {
|
||||
double total = subtotal;
|
||||
double calculateTotal({@required int precision}) {
|
||||
double total = calculateSubtotal(precision: precision);
|
||||
double itemTax = 0.0;
|
||||
|
||||
lineItems.forEach((item) {
|
||||
final double qty = round(item.quantity, 4);
|
||||
final double cost = round(item.cost, 4);
|
||||
final double itemDiscount = round(item.discount, 2);
|
||||
final double itemDiscount = round(item.discount, precision);
|
||||
final double taxRate1 = round(item.taxRate1, 3);
|
||||
final double taxRate2 = round(item.taxRate2, 3);
|
||||
double lineTotal = qty * cost;
|
||||
|
|
@ -168,54 +177,54 @@ abstract class CalculateInvoiceTotal {
|
|||
}
|
||||
}
|
||||
if (taxRate1 != 0) {
|
||||
itemTax += round(lineTotal * taxRate1 / 100, 2);
|
||||
itemTax += round(lineTotal * taxRate1 / 100, precision);
|
||||
}
|
||||
if (taxRate2 != 0) {
|
||||
itemTax += round(lineTotal * taxRate2 / 100, 2);
|
||||
itemTax += round(lineTotal * taxRate2 / 100, precision);
|
||||
}
|
||||
});
|
||||
|
||||
if (discount != 0.0) {
|
||||
if (isAmountDiscount) {
|
||||
total -= round(discount, 2);
|
||||
total -= round(discount, precision);
|
||||
} else {
|
||||
total -= round(total * discount / 100, 2);
|
||||
total -= round(total * discount / 100, precision);
|
||||
}
|
||||
}
|
||||
|
||||
if (customSurcharge1 != 0.0 && customTaxes1) {
|
||||
total += round(customSurcharge1, 2);
|
||||
total += round(customSurcharge1, precision);
|
||||
}
|
||||
|
||||
if (customSurcharge2 != 0.0 && customTaxes2) {
|
||||
total += round(customSurcharge2, 2);
|
||||
total += round(customSurcharge2, precision);
|
||||
}
|
||||
|
||||
if (!usesInclusiveTaxes) {
|
||||
final double taxAmount1 = round(total * taxRate1 / 100, 2);
|
||||
final double taxAmount2 = round(total * taxRate2 / 100, 2);
|
||||
final double taxAmount1 = round(total * taxRate1 / 100, precision);
|
||||
final double taxAmount2 = round(total * taxRate2 / 100, precision);
|
||||
|
||||
total += itemTax + taxAmount1 + taxAmount2;
|
||||
}
|
||||
|
||||
if (customSurcharge1 != 0.0 && !customTaxes1) {
|
||||
total += round(customSurcharge1, 2);
|
||||
total += round(customSurcharge1, precision);
|
||||
}
|
||||
|
||||
if (customSurcharge2 != 0.0 && !customTaxes2) {
|
||||
total += round(customSurcharge2, 2);
|
||||
total += round(customSurcharge2, precision);
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
double get subtotal {
|
||||
double calculateSubtotal({@required int precision}) {
|
||||
var total = 0.0;
|
||||
|
||||
lineItems.forEach((item) {
|
||||
final double qty = round(item.quantity, 4);
|
||||
final double cost = round(item.cost, 4);
|
||||
final double discount = round(item.discount, 2);
|
||||
final double discount = round(item.discount, precision);
|
||||
|
||||
double lineTotal = qty * cost;
|
||||
|
||||
|
|
@ -227,7 +236,7 @@ abstract class CalculateInvoiceTotal {
|
|||
}
|
||||
}
|
||||
|
||||
total += round(lineTotal, 2);
|
||||
total += round(lineTotal, precision);
|
||||
});
|
||||
|
||||
return total;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
|
||||
import 'package:invoiceninja_flutter/redux/static/static_state.dart';
|
||||
import 'package:memoize/memoize.dart';
|
||||
import 'package:built_collection/built_collection.dart';
|
||||
|
|
@ -216,6 +217,12 @@ EntityStats invoiceStatsForUser(
|
|||
return EntityStats(countActive: countActive, countArchived: countArchived);
|
||||
}
|
||||
|
||||
int precisionForInvoice(AppState state, InvoiceEntity invoice) {
|
||||
final client = state.clientState.get(invoice.clientId);
|
||||
final currency = state.staticState.currencyMap[client.currencyId];
|
||||
return currency.precision;
|
||||
}
|
||||
|
||||
bool hasInvoiceChanges(
|
||||
InvoiceEntity invoice, BuiltMap<String, InvoiceEntity> invoiceMap) =>
|
||||
invoice.isNew ? invoice.isChanged : invoice != invoiceMap[invoice.id];
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:invoiceninja_flutter/constants.dart';
|
||||
import 'package:invoiceninja_flutter/redux/invoice/invoice_selectors.dart';
|
||||
import 'package:invoiceninja_flutter/ui/app/app_border.dart';
|
||||
import 'package:invoiceninja_flutter/ui/invoice/edit/invoice_edit_vm.dart';
|
||||
import 'package:invoiceninja_flutter/ui/invoice/edit/invoice_item_selector.dart';
|
||||
|
|
@ -136,7 +137,7 @@ class _CreditEditState extends State<CreditEdit>
|
|||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
'${localization.total}: ${formatNumber(invoice.calculateTotal, context, clientId: viewModel.invoice.clientId)}',
|
||||
'${localization.total}: ${formatNumber(invoice.calculateTotal(precision: precisionForInvoice(state, invoice)), context, clientId: viewModel.invoice.clientId)}',
|
||||
style: TextStyle(
|
||||
//color: Theme.of(context).selectedRowColor,
|
||||
color: state.prefState.enableDarkMode
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:invoiceninja_flutter/constants.dart';
|
||||
import 'package:invoiceninja_flutter/redux/invoice/invoice_selectors.dart';
|
||||
import 'package:invoiceninja_flutter/ui/app/app_border.dart';
|
||||
import 'package:invoiceninja_flutter/ui/invoice/edit/invoice_edit_contacts_vm.dart';
|
||||
import 'package:invoiceninja_flutter/ui/invoice/edit/invoice_edit_details_vm.dart';
|
||||
|
|
@ -147,7 +148,7 @@ class _InvoiceEditState extends State<InvoiceEdit>
|
|||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
'${localization.lookup('${invoice.entityType}_total')}: ${formatNumber(invoice.calculateTotal, context, clientId: viewModel.invoice.clientId)}',
|
||||
'${localization.lookup('${invoice.entityType}_total')}: ${formatNumber(invoice.calculateTotal(precision: precisionForInvoice(state, invoice)), context, clientId: viewModel.invoice.clientId)}',
|
||||
style: TextStyle(
|
||||
//color: Theme.of(context).selectedRowColor,
|
||||
color: state.prefState.enableDarkMode
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import 'package:invoiceninja_flutter/constants.dart';
|
|||
import 'package:invoiceninja_flutter/data/models/company_model.dart';
|
||||
import 'package:invoiceninja_flutter/data/models/entities.dart';
|
||||
import 'package:invoiceninja_flutter/data/models/invoice_model.dart';
|
||||
import 'package:invoiceninja_flutter/redux/invoice/invoice_selectors.dart';
|
||||
import 'package:invoiceninja_flutter/ui/app/form_card.dart';
|
||||
import 'package:invoiceninja_flutter/ui/app/forms/app_dropdown_button.dart';
|
||||
import 'package:invoiceninja_flutter/ui/app/forms/app_tab_bar.dart';
|
||||
|
|
@ -646,8 +647,8 @@ class InvoiceEditDesktopState extends State<InvoiceEditDesktop>
|
|||
),
|
||||
textAlign: TextAlign.end,
|
||||
key: ValueKey(
|
||||
'__invoice_subtotal_${invoice.subtotal}_${invoice.clientId}__'),
|
||||
initialValue: formatNumber(invoice.subtotal, context,
|
||||
'__invoice_subtotal_${invoice.calculateSubtotal(precision: precisionForInvoice(state, invoice))}_${invoice.clientId}__'),
|
||||
initialValue: formatNumber(invoice.calculateSubtotal(precision: precisionForInvoice(state, invoice)), context,
|
||||
clientId: invoice.clientId),
|
||||
),
|
||||
if (invoice.isOld)
|
||||
|
|
@ -710,9 +711,9 @@ class InvoiceEditDesktopState extends State<InvoiceEditDesktop>
|
|||
),
|
||||
textAlign: TextAlign.end,
|
||||
key: ValueKey(
|
||||
'__invoice_total_${invoice.calculateTotal}_${invoice.clientId}__'),
|
||||
'__invoice_total_${invoice.calculateTotal(precision: precisionForInvoice(state, invoice))}_${invoice.clientId}__'),
|
||||
initialValue: formatNumber(
|
||||
invoice.calculateTotal - invoice.paidToDate, context,
|
||||
invoice.calculateTotal(precision: precisionForInvoice(state, invoice)) - invoice.paidToDate, context,
|
||||
clientId: invoice.clientId),
|
||||
),
|
||||
if (invoice.partial != 0)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,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/invoice/invoice_selectors.dart';
|
||||
import 'package:invoiceninja_flutter/redux/payment/payment_selectors.dart';
|
||||
import 'package:invoiceninja_flutter/redux/recurring_invoice/recurring_invoice_selectors.dart';
|
||||
import 'package:invoiceninja_flutter/ui/app/FieldGrid.dart';
|
||||
|
|
@ -340,7 +341,10 @@ class InvoiceOverview extends StatelessWidget {
|
|||
|
||||
widgets.addAll([
|
||||
SizedBox(height: 8),
|
||||
surchargeRow(localization.subtotal, invoice.calculateTotal),
|
||||
surchargeRow(
|
||||
localization.subtotal,
|
||||
invoice.calculateTotal(
|
||||
precision: precisionForInvoice(state, invoice))),
|
||||
surchargeRow(localization.paidToDate, invoice.paidToDate),
|
||||
]);
|
||||
|
||||
|
|
@ -357,7 +361,9 @@ class InvoiceOverview extends StatelessWidget {
|
|||
}
|
||||
|
||||
invoice
|
||||
.calculateTaxes(invoice.usesInclusiveTaxes)
|
||||
.calculateTaxes(
|
||||
useInclusiveTaxes: invoice.usesInclusiveTaxes,
|
||||
precision: precisionForInvoice(state, invoice))
|
||||
.forEach((taxName, taxAmount) {
|
||||
widgets.add(surchargeRow(taxName, taxAmount));
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:invoiceninja_flutter/constants.dart';
|
||||
import 'package:invoiceninja_flutter/redux/invoice/invoice_selectors.dart';
|
||||
import 'package:invoiceninja_flutter/ui/app/app_border.dart';
|
||||
import 'package:invoiceninja_flutter/ui/invoice/edit/invoice_edit_vm.dart';
|
||||
import 'package:invoiceninja_flutter/ui/invoice/edit/invoice_item_selector.dart';
|
||||
|
|
@ -136,7 +137,7 @@ class _QuoteEditState extends State<QuoteEdit>
|
|||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
'${localization.total}: ${formatNumber(invoice.calculateTotal, context, clientId: viewModel.invoice.clientId)}',
|
||||
'${localization.total}: ${formatNumber(invoice.calculateTotal(precision: precisionForInvoice(state, invoice)), context, clientId: viewModel.invoice.clientId)}',
|
||||
style: TextStyle(
|
||||
//color: Theme.of(context).selectedRowColor,
|
||||
color: state.prefState.enableDarkMode
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:invoiceninja_flutter/constants.dart';
|
||||
import 'package:invoiceninja_flutter/redux/invoice/invoice_selectors.dart';
|
||||
import 'package:invoiceninja_flutter/ui/app/app_border.dart';
|
||||
import 'package:invoiceninja_flutter/ui/app/edit_scaffold.dart';
|
||||
import 'package:invoiceninja_flutter/ui/invoice/edit/invoice_edit_vm.dart';
|
||||
|
|
@ -137,7 +138,7 @@ class _RecurringInvoiceEditState extends State<RecurringInvoiceEdit>
|
|||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
'${localization.total}: ${formatNumber(widget.viewModel.invoice.calculateTotal, context, clientId: viewModel.invoice.clientId)}',
|
||||
'${localization.total}: ${formatNumber(widget.viewModel.invoice.calculateTotal(precision: precisionForInvoice(state, invoice)), context, clientId: viewModel.invoice.clientId)}',
|
||||
style: TextStyle(
|
||||
//color: Theme.of(context).selectedRowColor,
|
||||
color: state.prefState.enableDarkMode
|
||||
|
|
|
|||
|
|
@ -76,7 +76,8 @@ ReportResult taxRateReport(
|
|||
//final invoiceTaxAmount = invoice.calculateTaxes(invoice.usesInclusiveTaxes);
|
||||
final invoicePaidAmount = invoice.amount - invoice.balance;
|
||||
|
||||
final taxes = invoice.getTaxes();
|
||||
final precision = staticState.currencyMap[client.currencyId].precision;
|
||||
final taxes = invoice.getTaxes(precision);
|
||||
for (final key in taxes.keys) {
|
||||
bool skip = false;
|
||||
final List<ReportElement> row = [];
|
||||
|
|
|
|||
Loading…
Reference in New Issue