diff --git a/lib/data/models/invoice_model.dart b/lib/data/models/invoice_model.dart index 32fe50d3d..894e77f8d 100644 --- a/lib/data/models/invoice_model.dart +++ b/lib/data/models/invoice_model.dart @@ -1210,30 +1210,42 @@ abstract class InvoiceItemEntity @nullable int get createdAt; - /* - double taxAmount(bool useInclusiveTaxes, int precision) { + double taxAmount(InvoiceEntity invoice, int precision) { double calculateTaxAmount(double rate) { double taxAmount; if (rate == 0) { return 0; } - if (useInclusiveTaxes) { - taxAmount = total - (total / (1 + (rate / 100))); + final lineTotal = total(invoice); + if (invoice.usesInclusiveTaxes) { + taxAmount = lineTotal - (lineTotal / (1 + (rate / 100))); } else { - taxAmount = total * rate / 100; + taxAmount = lineTotal * rate / 100; } return round(taxAmount, precision); } - return calculateTaxAmount(taxRate1) + calculateTaxAmount(taxRate2) + + return calculateTaxAmount(taxRate1) + + calculateTaxAmount(taxRate2) + calculateTaxAmount(taxRate3); } - double netTotal(bool useInclusiveTaxes, int precision) => - total - taxAmount(useInclusiveTaxes, precision); - */ + double netTotal(InvoiceEntity invoice, int precision) => + total(invoice) - taxAmount(invoice, precision); - double get total => round(quantity * cost, 2); + double total(InvoiceEntity invoice) { + var total = quantity * cost; + + if (discount != 0) { + if (invoice.isAmountDiscount) { + total = total - discount; + } else { + total = total - (discount / 100 * total); + } + } + + return round(total, 2); + } bool get isTask => typeId == TYPE_TASK; @@ -1245,9 +1257,31 @@ abstract class InvoiceItemEntity cost == 0 && quantity == 0 && customValue1.isEmpty && - customValue2.isEmpty; + customValue2.isEmpty && + customValue3.isEmpty && + customValue4.isEmpty; - // TODO add custom 3 and 4 + bool get hasTaxes => + taxRate1 != 0 || + taxRate2 != 0 || + taxRate3 != 0 || + taxName1.isNotEmpty || + taxName2.isNotEmpty || + taxName3.isNotEmpty; + + String get taxRates { + final parts = []; + if (taxName1.isNotEmpty) { + parts.add(taxName1); + } + if (taxName2.isNotEmpty) { + parts.add(taxName2); + } + if (taxName3.isNotEmpty) { + parts.add(taxName3); + } + return parts.join(', '); + } InvoiceItemEntity applyTax(TaxRateEntity taxRate, {bool isSecond = false, bool isThird = false}) { diff --git a/lib/ui/app/invoice/invoice_item_view.dart b/lib/ui/app/invoice/invoice_item_view.dart index 5ed4a868a..5f2a04d7e 100644 --- a/lib/ui/app/invoice/invoice_item_view.dart +++ b/lib/ui/app/invoice/invoice_item_view.dart @@ -93,7 +93,7 @@ class InvoiceItemListTile extends StatelessWidget { title: Row( children: [ Expanded(child: Text(invoiceItem.productKey)), - Text(formatNumber(invoiceItem.total, context, + Text(formatNumber(invoiceItem.total(invoice), context, clientId: invoice.clientId)), ], ), diff --git a/lib/ui/app/presenters/entity_presenter.dart b/lib/ui/app/presenters/entity_presenter.dart index 08cb07a2a..a5579ed05 100644 --- a/lib/ui/app/presenters/entity_presenter.dart +++ b/lib/ui/app/presenters/entity_presenter.dart @@ -133,6 +133,7 @@ class EntityPresenter { 'calculated_rate', 'duration', 'net_amount', + 'net_total', ].contains(field); return value; diff --git a/lib/ui/invoice/edit/invoice_edit_items_desktop.dart b/lib/ui/invoice/edit/invoice_edit_items_desktop.dart index a6af0e3f2..d27040579 100644 --- a/lib/ui/invoice/edit/invoice_edit_items_desktop.dart +++ b/lib/ui/invoice/edit/invoice_edit_items_desktop.dart @@ -576,7 +576,7 @@ class _InvoiceEditItemsDesktopState extends State { readOnly: true, enabled: false, initialValue: formatNumber( - lineItems[index].total, context, + lineItems[index].total(invoice), context, clientId: invoice.clientId), textAlign: TextAlign.right, ), diff --git a/lib/ui/reports/invoice_item_report.dart b/lib/ui/reports/invoice_item_report.dart index ebd48fc0e..5d4c14712 100644 --- a/lib/ui/reports/invoice_item_report.dart +++ b/lib/ui/reports/invoice_item_report.dart @@ -17,7 +17,7 @@ enum InvoiceItemReportFields { cost, quantity, profit, - lineTotal, + total, discount, custom1, custom2, @@ -26,6 +26,11 @@ enum InvoiceItemReportFields { invoiceNumber, invoiceDate, client, + dueDate, + hasTaxes, + taxRates, + taxAmount, + netTotal, } var memoizedInvoiceItemReport = memo6(( @@ -81,6 +86,7 @@ ReportResult lineItemReport( for (var entry in invoiceMap.entries) { final invoice = entry.value; final client = clientMap[invoice.clientId]; + final precision = staticState.currencyMap[client.currencyId].precision; if (invoice.isDeleted || client.isDeleted) { continue; @@ -105,9 +111,8 @@ ReportResult lineItemReport( value = productId == null ? 0.0 : productMap[productId].cost; break; case InvoiceItemReportFields.profit: - value = productId == null - ? 0.0 - : lineItem.total - productMap[productId].cost; + value = lineItem.netTotal(invoice, precision) - + (productId == null ? 0.0 : productMap[productId].cost); break; case InvoiceItemReportFields.custom1: value = lineItem.customValue1; @@ -124,7 +129,7 @@ ReportResult lineItemReport( case InvoiceItemReportFields.notes: value = lineItem.notes; break; - case InvoiceItemReportFields.lineTotal: + case InvoiceItemReportFields.total: value = lineItem.total; break; case InvoiceItemReportFields.productKey: @@ -142,6 +147,21 @@ ReportResult lineItemReport( case InvoiceItemReportFields.client: value = client.displayName; break; + case InvoiceItemReportFields.dueDate: + value = invoice.dueDate; + break; + case InvoiceItemReportFields.hasTaxes: + value = lineItem.hasTaxes; + break; + case InvoiceItemReportFields.taxRates: + value = lineItem.taxRates; + break; + case InvoiceItemReportFields.taxAmount: + value = lineItem.taxAmount(invoice, precision); + break; + case InvoiceItemReportFields.netTotal: + value = lineItem.netTotal(invoice, precision); + break; } if (!ReportResult.matchField( diff --git a/lib/ui/reports/quote_item_report.dart b/lib/ui/reports/quote_item_report.dart index 4889b5065..27768cd14 100644 --- a/lib/ui/reports/quote_item_report.dart +++ b/lib/ui/reports/quote_item_report.dart @@ -17,7 +17,7 @@ enum QuoteItemReportFields { cost, quantity, profit, - lineTotal, + total, discount, custom1, custom2, @@ -26,6 +26,11 @@ enum QuoteItemReportFields { quoteNumber, quoteDate, client, + validUntil, + hasTaxes, + taxRates, + taxAmount, + netTotal, } var memoizedQuoteItemReport = memo6(( @@ -81,6 +86,7 @@ ReportResult lineItemReport( for (var entry in invoiceMap.entries) { final invoice = entry.value; final client = clientMap[invoice.clientId]; + final precision = staticState.currencyMap[client.currencyId].precision; if (invoice.isDeleted || client.isDeleted) { continue; @@ -105,9 +111,8 @@ ReportResult lineItemReport( value = productId == null ? 0.0 : productMap[productId].cost; break; case QuoteItemReportFields.profit: - value = productId == null - ? 0.0 - : lineItem.total - productMap[productId].cost; + value = lineItem.netTotal(invoice, precision) - + (productId == null ? 0.0 : productMap[productId].cost); break; case QuoteItemReportFields.custom1: value = lineItem.customValue1; @@ -124,7 +129,7 @@ ReportResult lineItemReport( case QuoteItemReportFields.notes: value = lineItem.notes; break; - case QuoteItemReportFields.lineTotal: + case QuoteItemReportFields.total: value = lineItem.total; break; case QuoteItemReportFields.productKey: @@ -142,6 +147,21 @@ ReportResult lineItemReport( case QuoteItemReportFields.client: value = client.displayName; break; + case QuoteItemReportFields.validUntil: + value = invoice.dueDate; + break; + case QuoteItemReportFields.hasTaxes: + value = lineItem.hasTaxes; + break; + case QuoteItemReportFields.taxRates: + value = lineItem.taxRates; + break; + case QuoteItemReportFields.taxAmount: + value = lineItem.taxAmount(invoice, precision); + break; + case QuoteItemReportFields.netTotal: + value = lineItem.netTotal(invoice, precision); + break; } if (!ReportResult.matchField( diff --git a/lib/ui/reports/reports_screen.dart b/lib/ui/reports/reports_screen.dart index 910657ed2..7eb1405d0 100644 --- a/lib/ui/reports/reports_screen.dart +++ b/lib/ui/reports/reports_screen.dart @@ -534,6 +534,8 @@ enum ReportColumnType { } ReportColumnType getReportColumnType(String column, BuildContext context) { + column = toSnakeCase(column); + ReportColumnType convertCustomFieldType(String type) { if (type == kFieldTypeDate) { return ReportColumnType.date; @@ -1125,6 +1127,9 @@ class ReportResult { final sortedColumns = columns.toList() ..sort((String str1, String str2) => str1.compareTo(str2)); + for (String column in sortedColumns) + print('## $column => ${getReportColumnType(column, context)}'); + final totalColumns = [ DataColumn( label: Text(localization.currency), diff --git a/lib/utils/i18n.dart b/lib/utils/i18n.dart index c4b413861..1436d89ec 100644 --- a/lib/utils/i18n.dart +++ b/lib/utils/i18n.dart @@ -15,6 +15,8 @@ mixin LocalizationsProvider on LocaleCodeAware { static final Map> _localizedValues = { 'en': { // STARTER: lang key - do not remove comment + 'net_total': 'Net Total', + 'has_taxes': 'Has Taxes', 'import_customers': 'Import Customers', 'imported_customers': 'Successfully started importing customers', 'login_success': 'Successful Login', @@ -60405,6 +60407,14 @@ mixin LocalizationsProvider on LocaleCodeAware { _localizedValues[localeCode]['imported_customers'] ?? _localizedValues['en']['imported_customers']; + String get hasTaxes => + _localizedValues[localeCode]['has_taxes'] ?? + _localizedValues['en']['has_taxes']; + + String get netTotal => + _localizedValues[localeCode]['net_total'] ?? + _localizedValues['en']['net_total']; + String lookup(String key) { final lookupKey = toSnakeCase(key);