From a2f877cee6a286e358e37d128d9c9d46a8404d51 Mon Sep 17 00:00:00 2001 From: yash14023 Date: Tue, 26 May 2026 01:54:06 +0530 Subject: [PATCH 1/3] fix(accounts): prevent update_stock on Debit Notes Extracted validation into validate_debit_note_with_update_stock(). Hide update_stock in JS via set_dynamic_labels() and is_debit_note handler. Added unit test asserting ValidationError on save. Fixes #54891 --- .../accounts/doctype/sales_invoice/sales_invoice.js | 13 +++++++++++++ .../accounts/doctype/sales_invoice/sales_invoice.py | 11 +++++++++++ .../doctype/sales_invoice/test_sales_invoice.py | 6 ++++++ 3 files changed, 30 insertions(+) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 097d4f1ad03..be3e08a17c9 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -567,6 +567,9 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends ( set_dynamic_labels() { super.set_dynamic_labels(); this.frm.events.hide_fields(this.frm); + if (this.frm.doc.is_debit_note) { + this.frm.set_df_property("update_stock", "hidden", 1); + } } items_on_form_rendered() { @@ -1155,6 +1158,16 @@ frappe.ui.form.on("Sales Invoice", { ); }, + is_debit_note: function (frm) { + if (frm.doc.is_debit_note) { + frm.set_value("update_stock", 0); + frm.set_df_property("update_stock", "hidden", 1); + } else { + frm.set_df_property("update_stock", "hidden", 0); + } + frm.refresh_field("update_stock"); + }, + refresh: function (frm) { if (frm.doc.is_debit_note) { frm.set_df_property("return_against", "label", __("Adjustment Against")); diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 86f84be0973..08a9a35c4e0 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -315,6 +315,7 @@ class SalesInvoice(SellingController): self.validate_uom_is_integer("uom", "qty") self.check_sales_order_on_hold_or_close("sales_order") self.validate_debit_to_acc() + self.validate_debit_note_with_update_stock() self.clear_unallocated_advances("Sales Invoice Advance", "advances") self.validate_fixed_asset() self.set_income_account_for_fixed_assets() @@ -1284,6 +1285,16 @@ class SalesInvoice(SellingController): if flt(self.change_amount) and not self.account_for_change_amount: msgprint(_("Please enter Account for Change Amount"), raise_exception=1) + def validate_debit_note_with_update_stock(self): + if self.is_debit_note and cint(self.update_stock): + frappe.throw( + _( + "You cannot update stock for a Debit Note. A Debit Note is a financial " + "document that should not affect inventory. Please disable 'Update Stock'." + ), + title=_("Invalid Configuration"), + ) + def validate_dropship_item(self): """If items are drop shipped, stock cannot be updated.""" if not cint(self.update_stock): diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index b2e4ea875d0..f56562ab330 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -4862,6 +4862,12 @@ class TestSalesInvoice(ERPNextTestSuite): frappe.db.set_value("Company", "_Test Company 1", "cost_center", cost_center) + def test_debit_note_with_update_stock_validation(self): + si = create_sales_invoice(do_not_save=True) + si.is_debit_note = 1 + si.update_stock = 1 + self.assertRaises(frappe.ValidationError, si.save) + def make_item_for_si(item_code, properties=None): from erpnext.stock.doctype.item.test_item import make_item From d57786caa242d2167fa4ff95be80c2f5e978ffcd Mon Sep 17 00:00:00 2001 From: yash14023 Date: Tue, 26 May 2026 10:24:26 +0530 Subject: [PATCH 2/3] fix(accounts): unify update_stock visibility logic in JS --- erpnext/accounts/doctype/sales_invoice/sales_invoice.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index be3e08a17c9..bd11f5316ce 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -567,9 +567,7 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends ( set_dynamic_labels() { super.set_dynamic_labels(); this.frm.events.hide_fields(this.frm); - if (this.frm.doc.is_debit_note) { - this.frm.set_df_property("update_stock", "hidden", 1); - } + this.frm.set_df_property("update_stock", "hidden", cint(this.frm.doc.is_debit_note)); } items_on_form_rendered() { @@ -1161,10 +1159,8 @@ frappe.ui.form.on("Sales Invoice", { is_debit_note: function (frm) { if (frm.doc.is_debit_note) { frm.set_value("update_stock", 0); - frm.set_df_property("update_stock", "hidden", 1); - } else { - frm.set_df_property("update_stock", "hidden", 0); } + frm.set_df_property("update_stock", "hidden", cint(frm.doc.is_debit_note)); frm.refresh_field("update_stock"); }, From 9084570d18cbd9927e043703083364b3d0f0e8c7 Mon Sep 17 00:00:00 2001 From: yash14023 Date: Sun, 31 May 2026 11:22:32 +0530 Subject: [PATCH 3/3] fix: add docstrings and unify update_stock visibility in JS --- erpnext/accounts/doctype/sales_invoice/sales_invoice.js | 8 ++++---- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 1 + .../accounts/doctype/sales_invoice/test_sales_invoice.py | 1 + 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index bd11f5316ce..b7de176c502 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -567,7 +567,8 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends ( set_dynamic_labels() { super.set_dynamic_labels(); this.frm.events.hide_fields(this.frm); - this.frm.set_df_property("update_stock", "hidden", cint(this.frm.doc.is_debit_note)); + const hide_update_stock = cint(this.frm.doc.is_debit_note) || cint(this.frm.doc.has_subcontracted); + this.frm.set_df_property("update_stock", "hidden", hide_update_stock); } items_on_form_rendered() { @@ -1160,8 +1161,8 @@ frappe.ui.form.on("Sales Invoice", { if (frm.doc.is_debit_note) { frm.set_value("update_stock", 0); } - frm.set_df_property("update_stock", "hidden", cint(frm.doc.is_debit_note)); - frm.refresh_field("update_stock"); + // visibility handled by set_dynamic_labels() + frm.cscript.set_dynamic_labels(); }, refresh: function (frm) { @@ -1170,7 +1171,6 @@ frappe.ui.form.on("Sales Invoice", { } frm.set_df_property("update_stock", "read_only", frm.doc.has_subcontracted); - frm.toggle_display("update_stock", !frm.doc.has_subcontracted); }, }); diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 08a9a35c4e0..d622ae8cc3d 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1286,6 +1286,7 @@ class SalesInvoice(SellingController): msgprint(_("Please enter Account for Change Amount"), raise_exception=1) def validate_debit_note_with_update_stock(self): + """Prevent stock update when Sales Invoice is marked as Debit Note.""" if self.is_debit_note and cint(self.update_stock): frappe.throw( _( diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index f56562ab330..c9688955ffc 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -4863,6 +4863,7 @@ class TestSalesInvoice(ERPNextTestSuite): frappe.db.set_value("Company", "_Test Company 1", "cost_center", cost_center) def test_debit_note_with_update_stock_validation(self): + """Test that saving a Debit Note with Update Stock enabled raises ValidationError.""" si = create_sales_invoice(do_not_save=True) si.is_debit_note = 1 si.update_stock = 1