From 6ad5e8960782f761ce4dcca305c2c94ea88b11fc Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Mon, 30 Mar 2026 19:37:59 +0530 Subject: [PATCH] fix(taxes): improve tax calculation accuracy and update test assertions (cherry picked from commit a18196f584d4d234e669e23842223f6042360c1d) --- erpnext/controllers/taxes_and_totals.py | 29 +++++++++++++++---- .../tests/test_item_wise_tax_details.py | 7 ++--- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index fa55aa1daa5..f0da61ad900 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -285,6 +285,13 @@ class calculate_taxes_and_totals: self.doc._item_wise_tax_details = item_wise_tax_details self.doc.item_wise_tax_details = [] + for tax in self.doc.get("taxes"): + if not tax.get("dont_recompute_tax"): + tax._running_txn_tax_total = 0.0 + tax._running_base_tax_total = 0.0 + tax._running_txn_taxable_total = 0.0 + tax._running_base_taxable_total = 0.0 + def determine_exclusive_rate(self): if not any(cint(tax.included_in_print_rate) for tax in self.doc.get("taxes")): return @@ -521,8 +528,7 @@ class calculate_taxes_and_totals: actual_breakup = tax._total_tax_breakup diff = flt(expected_amount - actual_breakup, 5) - # TODO: fix rounding difference issues - if abs(diff) <= 1: + if abs(diff) <= 0.5: detail_row = self.doc._item_wise_tax_details[last_idx] detail_row["amount"] = flt(detail_row["amount"] + diff, 5) @@ -597,14 +603,25 @@ class calculate_taxes_and_totals: def set_item_wise_tax(self, item, tax, tax_rate, current_tax_amount, current_net_amount): # store tax breakup for each item multiplier = -1 if tax.get("add_deduct_tax") == "Deduct" else 1 - item_wise_tax_amount = flt( - current_tax_amount * self.doc.conversion_rate * multiplier, tax.precision("tax_amount") + + # Error diffusion: derive each item's base amount as a delta of the running cumulative total + # so the sum always equals base_tax_amount_after_discount_amount. + tax._running_txn_tax_total += current_tax_amount * multiplier + new_base_tax_total = flt( + flt(tax._running_txn_tax_total, tax.precision("tax_amount")) * self.doc.conversion_rate, + tax.precision("base_tax_amount"), ) + item_wise_tax_amount = new_base_tax_total - tax._running_base_tax_total + tax._running_base_tax_total = new_base_tax_total if tax.charge_type != "On Item Quantity": - item_wise_taxable_amount = flt( - current_net_amount * self.doc.conversion_rate * multiplier, tax.precision("tax_amount") + tax._running_txn_taxable_total += current_net_amount * multiplier + new_base_taxable_total = flt( + flt(tax._running_txn_taxable_total, tax.precision("net_amount")) * self.doc.conversion_rate, + tax.precision("base_net_amount"), ) + item_wise_taxable_amount = new_base_taxable_total - tax._running_base_taxable_total + tax._running_base_taxable_total = new_base_taxable_total else: item_wise_taxable_amount = 0.0 diff --git a/erpnext/controllers/tests/test_item_wise_tax_details.py b/erpnext/controllers/tests/test_item_wise_tax_details.py index 6b259429cc5..cce4f2ce024 100644 --- a/erpnext/controllers/tests/test_item_wise_tax_details.py +++ b/erpnext/controllers/tests/test_item_wise_tax_details.py @@ -179,14 +179,11 @@ class TestTaxesAndTotals(ERPNextTestSuite): details_by_tax = {} for detail in doc.item_wise_tax_details: - bucket = details_by_tax.setdefault(detail.tax_row, {"amount": 0.0, "taxable_amount": 0.0}) + bucket = details_by_tax.setdefault(detail.tax_row, {"amount": 0.0}) bucket["amount"] += detail.amount for tax in doc.taxes: - detail_totals = details_by_tax[tax.name] - self.assertAlmostEqual( - detail_totals["amount"], tax.base_tax_amount_after_discount_amount, places=2 - ) + self.assertEqual(details_by_tax[tax.name]["amount"], tax.base_tax_amount_after_discount_amount) def test_item_wise_tax_detail_with_multi_currency_with_single_item(self): """