From 719d982a07e6888bbf24e9077cff50c0251e7b47 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Thu, 23 Apr 2026 13:35:46 +0530 Subject: [PATCH] fix: ensure tax withholding entries respect date range of category (cherry picked from commit 9ead8d4e3f55432baf68d31088933482fea36658) --- .../test_tax_withholding_category.py | 196 ++++++++++++++++-- .../tax_withholding_entry.py | 1 + 2 files changed, 179 insertions(+), 18 deletions(-) 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 2d0450107bc..13697084cbf 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 @@ -5,8 +5,7 @@ import datetime from unittest.mock import patch import frappe -from frappe.custom.doctype.custom_field.custom_field import create_custom_fields -from frappe.utils import add_days, add_months, today +from frappe.utils import add_days, add_months, getdate, today from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry from erpnext.accounts.utils import get_fiscal_year @@ -1922,7 +1921,6 @@ class TestTaxWithholdingCategory(ERPNextTestSuite): def set_previous_fy_and_tax_category(self): test_company = "_Test Company" - category = "Cumulative Threshold TDS" def add_company_to_fy(fy, company): if not [x.company for x in fy.companies if x.company == company]: @@ -1948,20 +1946,6 @@ class TestTaxWithholdingCategory(ERPNextTestSuite): ) self.prev_fy.save() - # setup tax withholding category for previous fiscal year - cat = frappe.get_doc("Tax Withholding Category", category) - cat.append( - "rates", - { - "from_date": self.prev_fy.year_start_date, - "to_date": self.prev_fy.year_end_date, - "tax_withholding_rate": 10, - "single_threshold": 0, - "cumulative_threshold": 30000, - }, - ) - cat.save() - def test_tds_across_fiscal_year(self): """ Advance TDS on previous fiscal year should be properly allocated on Invoices in upcoming fiscal year @@ -1972,6 +1956,14 @@ class TestTaxWithholdingCategory(ERPNextTestSuite): supplier = "Test TDS Supplier" # Cumulative threshold 30000 and tax rate 10% category = "Cumulative Threshold TDS" + create_tax_withholding_category( + category_name=category, + rate=10, + from_date=self.prev_fy.year_start_date, + to_date=self.prev_fy.year_end_date, + account="TDS - _TC", + cumulative_threshold=30000, + ) frappe.db.set_value( "Supplier", supplier, @@ -2043,6 +2035,158 @@ class TestTaxWithholdingCategory(ERPNextTestSuite): self.assertEqual(pi2.taxes, []) self.assertEqual(payment.taxes[0].tax_amount, 6000) + def test_threshold_resets_in_new_fiscal_year(self): + """ + Threshold entries from a previous FY must not carry over into the new FY. + """ + self.set_previous_fy_and_tax_category() + invoices = [] + supplier = "Test TDS Supplier" + category = "Cumulative Threshold TDS" + create_tax_withholding_category( + category_name=category, + rate=10, + from_date=self.prev_fy.year_start_date, + to_date=self.prev_fy.year_end_date, + account="TDS - _TC", + cumulative_threshold=30000, + ) + self.setup_party_with_category("Supplier", supplier, category) + prev_fy_date = add_days(self.prev_fy.year_end_date, -10) + + # Previous FY: 3 invoices to cross the 30000 cumulative threshold + for _ in range(3): + pi = create_purchase_invoice(supplier=supplier, posting_date=prev_fy_date, set_posting_time=True) + pi.submit() + invoices.append(pi) + + # Third invoice crosses the threshold - 3000 TDS deducted across all three + self.validate_tax_deduction(invoices[-1], 3000) + + # Current FY: 10000 invoice - must be Under Withheld, threshold resets + pi_curr = create_purchase_invoice(supplier=supplier) + pi_curr.submit() + invoices.append(pi_curr) + self.validate_tax_deduction(pi_curr, 0) + + self.validate_tax_withholding_entries( + "Purchase Invoice", + pi_curr.name, + [ + self.get_tax_withholding_entry( + tax_withholding_category=category, + party_type="Supplier", + party=supplier, + taxable_doctype="Purchase Invoice", + taxable_name=pi_curr.name, + tax_rate=10.0, + taxable_amount=10000.0, + withholding_amount=0.0, + status="Under Withheld", + withholding_doctype=None, + withholding_name=None, + under_withheld_reason=None, + ) + ], + ) + self.cleanup_invoices(invoices) + + def test_tax_on_excess_threshold_resets_in_new_fiscal_year(self): + """ + For tax-on-excess categories, unused threshold must reset each FY. + """ + self.set_previous_fy_and_tax_category() + invoices = [] + supplier = "Test TDS Supplier3" + category = "New TDS Category" + create_tax_withholding_category( + category_name=category, + rate=10, + from_date=self.prev_fy.year_start_date, + to_date=self.prev_fy.year_end_date, + account="TDS - _TC", + cumulative_threshold=30000, + tax_on_excess_amount=1, + round_off_tax_amount=1, + ) + self.setup_party_with_category("Supplier", supplier, category) + prev_fy_date = add_days(self.prev_fy.year_end_date, -10) + + for _ in range(2): + pi = create_purchase_invoice(supplier=supplier, posting_date=prev_fy_date, set_posting_time=True) + pi.submit() + invoices.append(pi) + + pi3 = create_purchase_invoice( + supplier=supplier, rate=20000, posting_date=prev_fy_date, set_posting_time=True + ) + pi3.submit() + invoices.append(pi3) + + self.validate_tax_deduction(pi3, 1000) + self.validate_tax_withholding_entries( + "Purchase Invoice", + pi3.name, + [ + self.get_tax_withholding_entry( + tax_withholding_category=category, + party_type="Supplier", + party=supplier, + taxable_doctype="Purchase Invoice", + taxable_name=pi3.name, + tax_rate=10.0, + taxable_amount=10000.0, + withholding_amount=0.0, + status="Settled", + withholding_doctype="Purchase Invoice", + withholding_name=pi3.name, + under_withheld_reason="Threshold Exemption", + ), + self.get_tax_withholding_entry( + tax_withholding_category=category, + party_type="Supplier", + party=supplier, + taxable_doctype="Purchase Invoice", + taxable_name=pi3.name, + tax_rate=10.0, + taxable_amount=10000.0, + withholding_amount=1000.0, + status="Settled", + withholding_doctype="Purchase Invoice", + withholding_name=pi3.name, + under_withheld_reason=None, + ), + ], + ) + + # no excess, so no TDS + pi_curr = create_purchase_invoice(supplier=supplier, rate=30000) + pi_curr.submit() + invoices.append(pi_curr) + self.validate_tax_deduction(pi_curr, 0) + + self.validate_tax_withholding_entries( + "Purchase Invoice", + pi_curr.name, + [ + self.get_tax_withholding_entry( + tax_withholding_category=category, + party_type="Supplier", + party=supplier, + taxable_doctype="Purchase Invoice", + taxable_name=pi_curr.name, + tax_rate=10.0, + taxable_amount=30000.0, + withholding_amount=0.0, + status="Settled", + withholding_doctype="Purchase Invoice", + withholding_name=pi_curr.name, + under_withheld_reason="Threshold Exemption", + ), + ], + ) + self.cleanup_invoices(invoices) + @ERPNextTestSuite.change_settings("Accounts Settings", {"delete_linked_ledger_entries": 1}) def test_tds_payment_entry_cancellation(self): """ @@ -3997,7 +4141,7 @@ def create_tax_withholding_category( tax_deduction_basis="Net Total", ): if not frappe.db.exists("Tax Withholding Category", category_name): - frappe.get_doc( + doc = frappe.get_doc( { "doctype": "Tax Withholding Category", "name": category_name, @@ -4018,6 +4162,22 @@ def create_tax_withholding_category( "accounts": [{"company": "_Test Company", "account": account}], } ).insert() + else: + doc = frappe.get_doc("Tax Withholding Category", category_name) + if not any(getdate(r.from_date) == getdate(from_date) for r in doc.rates): + doc.append( + "rates", + { + "from_date": from_date, + "to_date": to_date, + "tax_withholding_rate": rate, + "single_threshold": single_threshold, + "cumulative_threshold": cumulative_threshold, + }, + ) + doc.save() + + return doc def create_lower_deduction_certificate( diff --git a/erpnext/accounts/doctype/tax_withholding_entry/tax_withholding_entry.py b/erpnext/accounts/doctype/tax_withholding_entry/tax_withholding_entry.py index 8f8ee7898af..941d9da08f7 100644 --- a/erpnext/accounts/doctype/tax_withholding_entry/tax_withholding_entry.py +++ b/erpnext/accounts/doctype/tax_withholding_entry/tax_withholding_entry.py @@ -640,6 +640,7 @@ class TaxWithholdingController: .where(entry.tax_withholding_category == category.name) .where(entry.company == self.doc.company) .where(entry.docstatus == 1) + .where(entry.taxable_date.between(category.from_date, category.to_date)) .groupby(entry.status) )