diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index e214d2f4416e..ad5120ec590b 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -363,9 +363,11 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying. }, function () { me.apply_pricing_rule(); - me.frm.doc.apply_tds = - me.frm.tax_withholding_category || me.frm.tax_withholding_group ? 1 : 0; - me.frm.clear_table("tax_withholding_entries"); + me.update_item_tax_withholding_categories(me.frm.tax_withholding_category); + me.frm.set_value( + "apply_tds", + me.frm.tax_withholding_category || me.frm.tax_withholding_group ? 1 : 0 + ); // while duplicating, don't change payment terms if (me.frm.doc.__run_link_triggers === false) { @@ -376,11 +378,6 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying. ); } - apply_tds(frm) { - var me = this; - me.frm.clear_table("tax_withholding_entries"); - } - credit_to() { var me = this; if (this.frm.doc.credit_to) { diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json index 7399e987fa79..e737141964fd 100644 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json @@ -999,6 +999,7 @@ "fieldname": "tax_withholding_category", "fieldtype": "Link", "label": "Tax Withholding Category", + "mandatory_depends_on": "eval:doc.apply_tds", "options": "Tax Withholding Category", "print_hide": 1 } @@ -1007,7 +1008,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2026-04-07 15:40:45.687554", + "modified": "2026-04-23 15:24:07.707385", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice Item", diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 90c0da74f267..4732e3607962 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -474,9 +474,11 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends ( ), }, function () { - me.frm.doc.apply_tds = - me.frm.tax_withholding_category || me.frm.tax_withholding_group ? 1 : 0; - me.frm.clear_table("tax_withholding_entries"); + me.update_item_tax_withholding_categories(me.frm.tax_withholding_category); + me.frm.set_value( + "apply_tds", + me.frm.tax_withholding_category || me.frm.tax_withholding_group ? 1 : 0 + ); me.apply_pricing_rule(); } ); @@ -694,10 +696,6 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends ( this.calculate_taxes_and_totals(); } - apply_tds(frm) { - this.frm.clear_table("tax_withholding_entries"); - } - is_return() { this.toggle_get_items(); } diff --git a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json index cd18994d0c5d..a5cfaea79edc 100644 --- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json +++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json @@ -1000,6 +1000,7 @@ "fieldname": "tax_withholding_category", "fieldtype": "Link", "label": "Tax Withholding Category", + "mandatory_depends_on": "eval:doc.apply_tds", "options": "Tax Withholding Category", "print_hide": 1 }, @@ -1008,15 +1009,14 @@ "fieldname": "apply_tds", "fieldtype": "Check", "label": "Consider for Tax Withholding", - "print_hide": 1, - "read_only": 1 + "print_hide": 1 } ], "grid_page_length": 50, "idx": 1, "istable": 1, "links": [], - "modified": "2026-02-24 14:37:16.853941", + "modified": "2026-04-23 15:24:07.707385", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice Item", diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 2eab035147c7..265701f3c431 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1104,6 +1104,8 @@ def set_missing_item_details(self, for_validate=False): ): if not item.get("tax_withholding_category") and ret.get("tax_withholding_category"): item.set("tax_withholding_category", ret.get("tax_withholding_category")) + apply_tds = ret.get("apply_tds") if ret.get("apply_tds") is not None else 1 + item.set("apply_tds", apply_tds) # Double check for cost center # Items add via promotional scheme may not have cost center set diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index bc22b56db11f..cdf44dfcf681 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -3183,6 +3183,41 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe } } + apply_tds(doc, cdt, cdn) { + if (!["Purchase Invoice", "Sales Invoice"].includes(cdt)) return; + + var me = this; + me.frm.clear_table("tax_withholding_entries"); + $.each(this.frm.doc.items || [], function (i, item) { + item.apply_tds = me.frm.doc.apply_tds; + }); + me.frm.refresh_field("tax_withholding_entries"); + me.frm.refresh_field("items"); + } + + /** + * Called on party (customer/supplier) change to update tax_withholding_category + */ + update_item_tax_withholding_categories(party_category) { + var me = this; + var item_codes = [...new Set((this.frm.doc.items || []).map((i) => i.item_code).filter(Boolean))]; + if (!item_codes.length) return; + + frappe.call({ + method: "erpnext.stock.get_item_details.get_item_tax_withholding_categories", + args: { item_codes: item_codes, doctype: me.frm.doc.doctype }, + callback: function (r) { + if (r.exc || !r.message) return; + var item_categories = r.message; + $.each(me.frm.doc.items || [], function (i, item) { + // Item master takes priority; fall back to party-level category + item.tax_withholding_category = item_categories[item.item_code] || party_category || null; + }); + me.frm.refresh_field("items"); + }, + }); + } + against_blanket_order(doc, cdt, cdn) { var item = locals[cdt][cdn]; if (!item.against_blanket_order) { diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 703c4c9f2f94..f68f9d1e9284 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -95,6 +95,7 @@ def get_item_details( if doc: ctx.transaction_date = doc.get("transaction_date") or doc.get("posting_date") + ctx.apply_tds = doc.get("apply_tds") if doc.get("doctype") == "Purchase Invoice": ctx.bill_date = doc.get("bill_date") @@ -1394,6 +1395,28 @@ def get_tax_withholding_category(ctx: ItemDetailsCtx, item_doc, out: ItemDetails ) out.tax_withholding_category = tax_withholding_category + if ctx.get("apply_tds") is not None: + out.apply_tds = ctx.get("apply_tds") + + +@frappe.whitelist() +def get_item_tax_withholding_categories(item_codes: list | str, doctype: str) -> dict: + """ + Return a mapping of {item_code: tax_withholding_category} from the Item master. + """ + item_codes = frappe.parse_json(item_codes) + field = ( + "sales_tax_withholding_category" if doctype in sales_doctypes else "purchase_tax_withholding_category" + ) + + return frappe._dict( + frappe.get_list( + "Item", + filters={"name": ["in", item_codes]}, + fields=["name", field], + as_list=1, + ) + ) @erpnext.normalize_ctx_input(ItemDetailsCtx)