From 2d7b4cc9381f69e7b006e937840c80704dad046a Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 31 Jan 2025 15:34:50 +0530 Subject: [PATCH 1/5] fix: '0' rate LDC's Invoice net totals should be ignored (cherry picked from commit 325c4e3536aaf84be0d7fb9ff9850360b2eeb2bd) # Conflicts: # erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py --- .../tax_withholding_category.py | 48 ++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) 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 2115d44322d2..06239de17373 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -237,7 +237,10 @@ def get_lower_deduction_certificate(company, posting_date, tax_details, pan_no): def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=None): vouchers, voucher_wise_amount = get_invoice_vouchers( - parties, tax_details, inv.company, party_type=party_type + parties, + tax_details, + inv.company, + party_type=party_type, ) payment_entry_vouchers = get_payment_entry_vouchers( @@ -308,12 +311,36 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"): +<<<<<<< HEAD doctype = "Purchase Invoice" if party_type == "Supplier" else "Sales Invoice" field = ( "base_tax_withholding_net_total as base_net_total" if party_type == "Supplier" else "base_net_total" ) voucher_wise_amount = {} vouchers = [] +======= + voucher_wise_amount = [] + vouchers = [] + + ldcs = frappe.db.get_all( + "Lower Deduction Certificate", + filters={ + "valid_from": [">=", tax_details.from_date], + "valid_upto": ["<=", tax_details.to_date], + "company": company, + "supplier": ["in", parties], + }, + fields=["supplier", "valid_from", "valid_upto", "rate"], + ) + + doctype = "Purchase Invoice" if party_type == "Supplier" else "Sales Invoice" + field = [ + "base_tax_withholding_net_total as base_net_total" if party_type == "Supplier" else "base_net_total", + "name", + "grand_total", + "posting_date", + ] +>>>>>>> 325c4e3536 (fix: '0' rate LDC's Invoice net totals should be ignored) filters = { "company": company, @@ -331,8 +358,27 @@ def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"): invoices_details = frappe.get_all(doctype, filters=filters, fields=["name", field]) for d in invoices_details: +<<<<<<< HEAD vouchers.append(d.name) voucher_wise_amount.update({d.name: {"amount": d.base_net_total, "voucher_type": doctype}}) +======= + d = frappe._dict( + { + "voucher_name": d.name, + "voucher_type": doctype, + "taxable_amount": d.base_net_total, + "grand_total": d.grand_total, + "posting_date": d.posting_date, + } + ) +>>>>>>> 325c4e3536 (fix: '0' rate LDC's Invoice net totals should be ignored) + + if ldc := [x for x in ldcs if d.posting_date >= x.valid_from and d.posting_date <= x.valid_upto]: + if ldc[0].supplier in parties and ldc[0].rate == 0: + d.update({"taxable_amount": 0}) + + vouchers.append(d.voucher_name) + voucher_wise_amount.append(d) journal_entries_details = frappe.db.sql( """ From d84e5590f11f436cc2a4ca324a802468aec56cc2 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 7 Feb 2025 12:18:01 +0530 Subject: [PATCH 2/5] test: ldc @ 0 rate (cherry picked from commit 0cdd346f8fd2000cba7591d3b03bf0db7b69f158) # Conflicts: # erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py --- .../test_tax_withholding_category.py | 59 +++++++++++++++++-- 1 file changed, 55 insertions(+), 4 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 d459b77865ce..380af7f595c0 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 @@ -6,8 +6,13 @@ import frappe from frappe.custom.doctype.custom_field.custom_field import create_custom_fields +<<<<<<< HEAD from frappe.tests.utils import FrappeTestCase, change_settings from frappe.utils import add_days, today +======= +from frappe.tests import IntegrationTestCase, UnitTestCase +from frappe.utils import add_days, add_months, today +>>>>>>> 0cdd346f8f (test: ldc @ 0 rate) from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry from erpnext.accounts.utils import get_fiscal_year @@ -614,6 +619,49 @@ def test_lower_deduction_certificate_application(self): pi2.cancel() pi3.cancel() + def test_ldc_at_0_rate(self): + frappe.db.set_value( + "Supplier", + "Test LDC Supplier", + { + "tax_withholding_category": "Test Service Category", + "pan": "ABCTY1234D", + }, + ) + + fiscal_year = get_fiscal_year(today(), company="_Test Company") + valid_from = fiscal_year[1] + valid_upto = add_months(valid_from, 1) + create_lower_deduction_certificate( + supplier="Test LDC Supplier", + certificate_no="1AE0423AAJ", + tax_withholding_category="Test Service Category", + tax_rate=0, + limit=50000, + valid_from=valid_from, + valid_upto=valid_upto, + ) + + pi1 = create_purchase_invoice( + supplier="Test LDC Supplier", rate=35000, posting_date=valid_from, set_posting_time=True + ) + pi1.submit() + self.assertEqual(pi1.taxes, []) + + pi2 = create_purchase_invoice( + supplier="Test LDC Supplier", + rate=35000, + posting_date=add_days(valid_upto, 1), + set_posting_time=True, + ) + pi2.submit() + self.assertEqual(len(pi2.taxes), 1) + # pi1 net total shouldn't be included as it lies within LDC at rate of '0' + self.assertEqual(pi2.taxes[0].tax_amount, 3500) + + pi1.cancel() + pi2.cancel() + def set_previous_fy_and_tax_category(self): test_company = "_Test Company" category = "Cumulative Threshold TDS" @@ -771,7 +819,8 @@ def create_purchase_invoice(**args): pi = frappe.get_doc( { "doctype": "Purchase Invoice", - "posting_date": today(), + "set_posting_time": args.set_posting_time or False, + "posting_date": args.posting_date or today(), "apply_tds": 0 if args.do_not_apply_tds else 1, "supplier": args.supplier, "company": "_Test Company", @@ -1099,7 +1148,9 @@ def create_tax_withholding_category( ).insert() -def create_lower_deduction_certificate(supplier, tax_withholding_category, tax_rate, certificate_no, limit): +def create_lower_deduction_certificate( + supplier, tax_withholding_category, tax_rate, certificate_no, limit, valid_from=None, valid_upto=None +): fiscal_year = get_fiscal_year(today(), company="_Test Company") if not frappe.db.exists("Lower Deduction Certificate", certificate_no): frappe.get_doc( @@ -1110,8 +1161,8 @@ def create_lower_deduction_certificate(supplier, tax_withholding_category, tax_r "certificate_no": certificate_no, "tax_withholding_category": tax_withholding_category, "fiscal_year": fiscal_year[0], - "valid_from": fiscal_year[1], - "valid_upto": fiscal_year[2], + "valid_from": valid_from or fiscal_year[1], + "valid_upto": valid_upto or fiscal_year[2], "rate": tax_rate, "certificate_limit": limit, } From 1ecbfe2de046d3c74a04a9bc2d5d43fe61d7fe40 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 7 Feb 2025 15:40:35 +0530 Subject: [PATCH 3/5] chore: resolve conflicts --- .../tax_withholding_category.py | 10 ---------- .../test_tax_withholding_category.py | 5 ----- 2 files changed, 15 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 06239de17373..86744819e24b 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -311,16 +311,12 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"): -<<<<<<< HEAD doctype = "Purchase Invoice" if party_type == "Supplier" else "Sales Invoice" field = ( "base_tax_withholding_net_total as base_net_total" if party_type == "Supplier" else "base_net_total" ) voucher_wise_amount = {} vouchers = [] -======= - voucher_wise_amount = [] - vouchers = [] ldcs = frappe.db.get_all( "Lower Deduction Certificate", @@ -340,7 +336,6 @@ def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"): "grand_total", "posting_date", ] ->>>>>>> 325c4e3536 (fix: '0' rate LDC's Invoice net totals should be ignored) filters = { "company": company, @@ -358,10 +353,6 @@ def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"): invoices_details = frappe.get_all(doctype, filters=filters, fields=["name", field]) for d in invoices_details: -<<<<<<< HEAD - vouchers.append(d.name) - voucher_wise_amount.update({d.name: {"amount": d.base_net_total, "voucher_type": doctype}}) -======= d = frappe._dict( { "voucher_name": d.name, @@ -371,7 +362,6 @@ def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"): "posting_date": d.posting_date, } ) ->>>>>>> 325c4e3536 (fix: '0' rate LDC's Invoice net totals should be ignored) if ldc := [x for x in ldcs if d.posting_date >= x.valid_from and d.posting_date <= x.valid_upto]: if ldc[0].supplier in parties and ldc[0].rate == 0: 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 380af7f595c0..6a7fc3c43d2b 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 @@ -6,13 +6,8 @@ import frappe from frappe.custom.doctype.custom_field.custom_field import create_custom_fields -<<<<<<< HEAD from frappe.tests.utils import FrappeTestCase, change_settings -from frappe.utils import add_days, today -======= -from frappe.tests import IntegrationTestCase, UnitTestCase from frappe.utils import add_days, add_months, today ->>>>>>> 0cdd346f8f (test: ldc @ 0 rate) from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry from erpnext.accounts.utils import get_fiscal_year From 11959a0747319459336f31b8023aaa4c4e43b4ff Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 7 Feb 2025 16:07:08 +0530 Subject: [PATCH 4/5] fix: incorrect parameters --- .../tax_withholding_category/tax_withholding_category.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 86744819e24b..d61c64ef5501 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -350,7 +350,7 @@ def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"): {"apply_tds": 1, "tax_withholding_category": tax_details.get("tax_withholding_category")} ) - invoices_details = frappe.get_all(doctype, filters=filters, fields=["name", field]) + invoices_details = frappe.get_all(doctype, filters=filters, fields=field) for d in invoices_details: d = frappe._dict( From 88070ee7b5baf33489210a79815d985aebca00e1 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 13 Feb 2025 11:42:56 +0530 Subject: [PATCH 5/5] fix: ignore 0 rate ldc invoices --- .../tax_withholding_category.py | 52 +++++++++++-------- 1 file changed, 29 insertions(+), 23 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 d61c64ef5501..da329324e95a 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -236,11 +236,8 @@ def get_lower_deduction_certificate(company, posting_date, tax_details, pan_no): def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=None): - vouchers, voucher_wise_amount = get_invoice_vouchers( - parties, - tax_details, - inv.company, - party_type=party_type, + vouchers, voucher_wise_amount, zero_rate_ldc_invoices = get_invoice_vouchers( + parties, tax_details, inv.company, party_type=party_type ) payment_entry_vouchers = get_payment_entry_vouchers( @@ -293,7 +290,8 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N # once tds is deducted, not need to add vouchers in the invoice voucher_wise_amount = {} else: - tax_amount = get_tds_amount(ldc, parties, inv, tax_details, vouchers) + taxable_vouchers = list(set(vouchers) - set(zero_rate_ldc_invoices)) + tax_amount = get_tds_amount(ldc, parties, inv, tax_details, taxable_vouchers) elif party_type == "Customer": if tax_deducted: @@ -312,9 +310,11 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"): doctype = "Purchase Invoice" if party_type == "Supplier" else "Sales Invoice" - field = ( - "base_tax_withholding_net_total as base_net_total" if party_type == "Supplier" else "base_net_total" - ) + field = [ + "name", + "base_tax_withholding_net_total as base_net_total" if party_type == "Supplier" else "base_net_total", + "posting_date", + ] voucher_wise_amount = {} vouchers = [] @@ -352,23 +352,29 @@ def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"): invoices_details = frappe.get_all(doctype, filters=filters, fields=field) + ldcs = frappe.db.get_all( + "Lower Deduction Certificate", + filters={ + "valid_from": [">=", tax_details.from_date], + "valid_upto": ["<=", tax_details.to_date], + "company": company, + "supplier": ["in", parties], + "rate": 0, + }, + fields=["name", "supplier", "valid_from", "valid_upto"], + ) + + zero_rate_ldc_invoices = [] for d in invoices_details: - d = frappe._dict( - { - "voucher_name": d.name, - "voucher_type": doctype, - "taxable_amount": d.base_net_total, - "grand_total": d.grand_total, - "posting_date": d.posting_date, - } - ) + vouchers.append(d.name) + _voucher_detail = {"amount": d.base_net_total, "voucher_type": doctype} if ldc := [x for x in ldcs if d.posting_date >= x.valid_from and d.posting_date <= x.valid_upto]: - if ldc[0].supplier in parties and ldc[0].rate == 0: - d.update({"taxable_amount": 0}) + if ldc[0].supplier in parties: + _voucher_detail.update({"amount": 0}) + zero_rate_ldc_invoices.append(d.name) - vouchers.append(d.voucher_name) - voucher_wise_amount.append(d) + voucher_wise_amount.update({d.name: _voucher_detail}) journal_entries_details = frappe.db.sql( """ @@ -399,7 +405,7 @@ def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"): vouchers.append(d.name) voucher_wise_amount.update({d.name: {"amount": d.amount, "voucher_type": "Journal Entry"}}) - return vouchers, voucher_wise_amount + return vouchers, voucher_wise_amount, zero_rate_ldc_invoices def get_payment_entry_vouchers(parties, tax_details, company, party_type="Supplier"):