From 82e12d2d5219f0a40a42555145f7fb4d4cfa0de8 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Wed, 27 May 2026 12:40:57 +0530 Subject: [PATCH] fix(tds): treat NULL and empty-string tax_withholding_group as equivalent (cherry picked from commit a85f8a64b109141756023615e1c7c70ba35a6463) --- .../tax_withholding_category.py | 9 ++-- .../test_tax_withholding_category.py | 41 +++++++++++++++++++ 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py index dd8caa60d7d..c29a7a88f17 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -48,7 +48,7 @@ class TaxWithholdingCategory(Document): for d in self.get("rates"): if getdate(d.from_date) >= getdate(d.to_date): frappe.throw(_("Row #{0}: From Date cannot be before To Date").format(d.idx)) - group_rates[d.tax_withholding_group].append(d) + group_rates[d.tax_withholding_group or ""].append(d) # Validate overlapping dates within each group for group, rates in group_rates.items(): @@ -92,10 +92,9 @@ class TaxWithholdingCategory(Document): def get_applicable_tax_row(self, posting_date, tax_withholding_group): for row in self.rates: - if ( - getdate(row.from_date) <= getdate(posting_date) <= getdate(row.to_date) - and row.tax_withholding_group == tax_withholding_group - ): + if getdate(row.from_date) <= getdate(posting_date) <= getdate(row.to_date) and ( + row.tax_withholding_group or "" + ) == (tax_withholding_group or ""): return row frappe.throw(_("No Tax Withholding data found for the current posting date.")) diff --git a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py index 13697084cbf..40de1933a34 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py @@ -999,6 +999,47 @@ class TestTaxWithholdingCategory(ERPNextTestSuite): self.cleanup_invoices(invoices) + def test_null_and_empty_tax_withholding_group_are_equivalent(self): + """ + NULL and empty-string `tax_withholding_group` must be treated as the + same value. + """ + category = frappe.get_doc("Tax Withholding Category", "Cumulative Threshold TDS") + original_row = category.rates[0] + original_row.tax_withholding_group = None + + # Part 1: validate_dates must detect overlap between NULL-group and + # empty-string-group rows covering the same date range. + category.append( + "rates", + { + "from_date": original_row.from_date, + "to_date": original_row.to_date, + "tax_withholding_group": "", + "tax_withholding_rate": original_row.tax_withholding_rate, + }, + ) + with self.assertRaises(frappe.ValidationError): + category.validate_dates() + category.rates.pop() + + # Part 2: get_applicable_tax_row must match NULL <-> "" in either direction. + posting_date = original_row.from_date + + row = category.get_applicable_tax_row(posting_date=posting_date, tax_withholding_group="") + self.assertEqual(row.name, original_row.name) + + row = category.get_applicable_tax_row(posting_date=posting_date, tax_withholding_group=None) + self.assertEqual(row.name, original_row.name) + + original_row.tax_withholding_group = "" + row = category.get_applicable_tax_row(posting_date=posting_date, tax_withholding_group=None) + self.assertEqual(row.name, original_row.name) + + original_row.tax_withholding_group = None + with self.assertRaises(frappe.ValidationError): + category.get_applicable_tax_row(posting_date=posting_date, tax_withholding_group="194R") + def test_tds_calculation_on_net_total(self): self.setup_party_with_category("Supplier", "Test TDS Supplier4", "Cumulative Threshold TDS") invoices = []