Compare commits

...

6 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
69075c7db0 fix(accounts): add account to GROUP BY for PostgreSQL compatibility, fix ruff formatting 2026-06-10 05:34:47 +00:00
copilot-swe-agent[bot]
ffd0dbdfd9 Initial plan 2026-06-10 05:29:00 +00:00
Nabin Hait
3ad32f4030 Merge pull request #55274 from yash14023/fix/debit-note-prevent-update-stock
fix(accounts): prevent update_stock on Debit Notes
2026-06-10 10:44:20 +05:30
yash14023
9084570d18 fix: add docstrings and unify update_stock visibility in JS 2026-05-31 11:23:39 +05:30
yash14023
d57786caa2 fix(accounts): unify update_stock visibility logic in JS 2026-05-26 10:24:26 +05:30
yash14023
a2f877cee6 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
2026-05-26 01:54:06 +05:30
4 changed files with 31 additions and 3 deletions

View File

@@ -586,6 +586,8 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
set_dynamic_labels() {
super.set_dynamic_labels();
this.frm.events.hide_fields(this.frm);
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() {
@@ -1174,13 +1176,20 @@ frappe.ui.form.on("Sales Invoice", {
);
},
is_debit_note: function (frm) {
if (frm.doc.is_debit_note) {
frm.set_value("update_stock", 0);
}
// visibility handled by set_dynamic_labels()
frm.cscript.set_dynamic_labels();
},
refresh: function (frm) {
if (frm.doc.is_debit_note) {
frm.set_df_property("return_against", "label", __("Adjustment Against"));
}
frm.set_df_property("update_stock", "read_only", frm.doc.has_subcontracted);
frm.toggle_display("update_stock", !frm.doc.has_subcontracted);
},
});

View File

@@ -304,6 +304,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")
FixedAssetService(self).validate_fixed_asset()
FixedAssetService(self).set_income_account_for_fixed_assets()
@@ -960,6 +961,17 @@ 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):
"""Prevent stock update when Sales Invoice is marked as Debit Note."""
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):

View File

@@ -5213,6 +5213,13 @@ 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
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

View File

@@ -2384,7 +2384,7 @@ class QueryPaymentLedger:
.where(Criterion.all(self.common_filter))
.where(Criterion.all(self.dimensions_filter))
.where(Criterion.all(self.voucher_posting_date))
.groupby(ple.voucher_type, ple.voucher_no, ple.party_type, ple.party)
.groupby(ple.account, ple.voucher_type, ple.voucher_no, ple.party_type, ple.party)
)
# build query for voucher outstanding
@@ -2405,7 +2405,7 @@ class QueryPaymentLedger:
.where(ple.delinked == 0)
.where(Criterion.all(filter_on_against_voucher_no))
.where(Criterion.all(self.common_filter))
.groupby(ple.against_voucher_type, ple.against_voucher_no, ple.party_type, ple.party)
.groupby(ple.account, ple.against_voucher_type, ple.against_voucher_no, ple.party_type, ple.party)
)
# build CTE for combining voucher amount and outstanding