diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 9d98eed668d..fa55aa1daa5 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -522,7 +522,7 @@ class calculate_taxes_and_totals: diff = flt(expected_amount - actual_breakup, 5) # TODO: fix rounding difference issues - if abs(diff) <= 0.5: + if abs(diff) <= 1: detail_row = self.doc._item_wise_tax_details[last_idx] detail_row["amount"] = flt(detail_row["amount"] + diff, 5) diff --git a/erpnext/controllers/tests/test_item_wise_tax_details.py b/erpnext/controllers/tests/test_item_wise_tax_details.py index f6d94c61eca..d4a560284ef 100644 --- a/erpnext/controllers/tests/test_item_wise_tax_details.py +++ b/erpnext/controllers/tests/test_item_wise_tax_details.py @@ -124,3 +124,107 @@ class TestTaxesAndTotals(ERPNextTestSuite): ] self.assertEqual(actual_values, expected_values) + + def test_item_wise_tax_detail_with_multi_currency(self): + """ + For multi-item, multi-currency invoices, item-wise tax breakup should + still reconcile with base tax totals. + """ + doc = frappe.get_doc( + { + "doctype": "Sales Invoice", + "customer": "_Test Customer", + "company": "_Test Company", + "currency": "USD", + "debit_to": "_Test Receivable USD - _TC", + "conversion_rate": 129.99, + "items": [ + { + "item_code": "_Test Item", + "qty": 1, + "rate": 47.41, + "income_account": "Sales - _TC", + "expense_account": "Cost of Goods Sold - _TC", + "cost_center": "_Test Cost Center - _TC", + }, + { + "item_code": "_Test Item", + "qty": 2, + "rate": 33.33, + "income_account": "Sales - _TC", + "expense_account": "Cost of Goods Sold - _TC", + "cost_center": "_Test Cost Center - _TC", + }, + ], + "taxes": [ + { + "charge_type": "On Net Total", + "account_head": "_Test Account VAT - _TC", + "cost_center": "_Test Cost Center - _TC", + "description": "VAT", + "rate": 16, + }, + { + "charge_type": "On Previous Row Amount", + "account_head": "_Test Account Service Tax - _TC", + "cost_center": "_Test Cost Center - _TC", + "description": "Service Tax", + "rate": 10, + "row_id": 1, + }, + ], + } + ) + doc.save() + + 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["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 + ) + + def test_item_wise_tax_detail_with_multi_currency_with_single_item(self): + """ + When the tax amount (in transaction currency) has more decimals than + the field precision, rounding must happen *before* multiplying by + conversion_rate — the same order used by _set_in_company_currency. + """ + doc = frappe.get_doc( + { + "doctype": "Sales Invoice", + "customer": "_Test Customer", + "company": "_Test Company", + "currency": "USD", + "debit_to": "_Test Receivable USD - _TC", + "conversion_rate": 129.99, + "items": [ + { + "item_code": "_Test Item", + "qty": 1, + "rate": 47.41, + "income_account": "Sales - _TC", + "expense_account": "Cost of Goods Sold - _TC", + "cost_center": "_Test Cost Center - _TC", + } + ], + "taxes": [ + { + "charge_type": "On Net Total", + "account_head": "_Test Account VAT - _TC", + "cost_center": "_Test Cost Center - _TC", + "description": "VAT", + "rate": 16, + }, + ], + } + ) + doc.save() + + tax = doc.taxes[0] + detail = doc.item_wise_tax_details[0] + self.assertEqual(detail.amount, tax.base_tax_amount_after_discount_amount)