From bba7fe98313c44c1478d5dca72aadc9ba555aa7e Mon Sep 17 00:00:00 2001 From: pandiyan Date: Wed, 24 Jun 2026 18:43:33 +0530 Subject: [PATCH 1/2] fix: skip qty over-allowance check for non-stock items only --- erpnext/controllers/status_updater.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py index 21e6d3ea8b9..50d272d7efb 100644 --- a/erpnext/controllers/status_updater.py +++ b/erpnext/controllers/status_updater.py @@ -275,6 +275,12 @@ class StatusUpdater(Document): item["idx"] = d.idx item["target_ref_field"] = args["target_ref_field"].replace("_", " ") + # skip qty over-allowance check for non-stock items + if "qty" in args.get("target_ref_field", "") and not frappe.get_cached_value( + "Item", item["item_code"], "is_stock_item" + ): + continue + # if not item[args['target_ref_field']]: # msgprint(_("Note: System will not check over-delivery and over-booking for Item {0} as quantity or amount is 0").format(item.item_code)) if args.get("no_allowance"): From 3f00a0ffa9c03e891643f17b86a9ca9182c6e7f6 Mon Sep 17 00:00:00 2001 From: pandiyan Date: Wed, 24 Jun 2026 18:43:39 +0530 Subject: [PATCH 2/2] test: add tests for non stock item over billing against so/po --- .../purchase_invoice/test_purchase_invoice.py | 18 ++++++++ .../sales_invoice/test_sales_invoice.py | 45 +++++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index bccd29c822a..5aa2faed1a1 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -2924,6 +2924,24 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin): # Test 4 - Since this PI is overbilled by 130% and only 120% is allowed, it will fail self.assertRaises(frappe.ValidationError, pi.submit) + @change_settings("Accounts Settings", {"over_billing_allowance": 0}) + def test_non_stock_item_over_billing_against_po_is_blocked(self): + service_item = create_item( + "_Test Service Item Non Stock PI", + is_stock_item=0, + is_purchase_item=1, + ).name + + po = create_purchase_order(item_code=service_item, qty=5, rate=100, do_not_save=False) + po.submit() + + pi = make_pi_from_po(po.name) + pi.items[0].qty = 10 # overbill by 100 % + pi.save() + + with self.assertRaises(frappe.ValidationError): + pi.submit() + def test_discount_percentage_not_set_when_amount_is_manually_set(self): pi = make_purchase_invoice(do_not_save=True) discount_amount = 7 diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 6f51e27f532..0b1f1e922bf 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -3700,6 +3700,51 @@ class TestSalesInvoice(FrappeTestCase): self.assertTrue("cannot overbill" in str(err.exception).lower()) dn.cancel() + @change_settings("Accounts Settings", {"over_billing_allowance": 0}) + def test_non_stock_item_over_billing_against_so_is_blocked(self): + from erpnext.selling.doctype.sales_order.sales_order import make_sales_invoice as make_si_from_so + from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order + + service_item = create_item( + "_Test Service Item Non Stock SI", + is_stock_item=0, + ).name + + so = make_sales_order(item_code=service_item, qty=5, rate=100) + so.submit() + + si = make_si_from_so(so.name) + si.items[0].qty = 10 # overbill by 100 % + si.save() + + with self.assertRaises(frappe.ValidationError): + si.submit() + + @change_settings("Accounts Settings", {"over_billing_allowance": 0}) + def test_non_stock_item_over_billing_against_so_from_quotation_is_blocked(self): + from erpnext.selling.doctype.quotation.quotation import make_sales_order as make_so_from_quotation + from erpnext.selling.doctype.quotation.test_quotation import make_quotation + from erpnext.selling.doctype.sales_order.sales_order import make_sales_invoice as make_si_from_so + + service_item = create_item( + "_Test Service Item Non Stock SI Quot", + is_stock_item=0, + ).name + + quotation = make_quotation(item_code=service_item, qty=5, rate=100) + + so = make_so_from_quotation(quotation.name) + so.delivery_date = frappe.utils.add_days(frappe.utils.today(), 7) + so.insert() + so.submit() + + si = make_si_from_so(so.name) + si.items[0].qty = 10 # overbill by 100 % + si.save() + + with self.assertRaises(frappe.ValidationError): + si.submit() + @change_settings( "Accounts Settings", {