From 446ec6030a73573f24500f9ff9def1f78a1d387e Mon Sep 17 00:00:00 2001 From: Afsal Syed Date: Sun, 2 Aug 2026 02:31:21 +0530 Subject: [PATCH 1/4] fix(stock): validate over delivery/receipt allowance in stock settings --- .../stock/doctype/stock_settings/stock_settings.json | 11 +++++++---- .../stock/doctype/stock_settings/stock_settings.py | 7 ++++++- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.json b/erpnext/stock/doctype/stock_settings/stock_settings.json index 5c2111b8f7c..3de206f9a18 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.json +++ b/erpnext/stock/doctype/stock_settings/stock_settings.json @@ -125,7 +125,8 @@ "description": "The percentage you are allowed to receive or deliver more against the quantity ordered. For example, if you have ordered 100 units, and your Allowance is 10%, then you are allowed to receive 110 units.", "fieldname": "over_delivery_receipt_allowance", "fieldtype": "Float", - "label": "Over Delivery/Receipt Allowance (%)" + "label": "Over Delivery/Receipt Allowance (%)", + "non_negative": 1 }, { "default": "Stop", @@ -276,7 +277,8 @@ "description": "The percentage you are allowed to transfer more against the quantity ordered. For example, if you have ordered 100 units, and your Allowance is 10%, then you are allowed transfer 110 units.", "fieldname": "mr_qty_allowance", "fieldtype": "Float", - "label": "Over Transfer Allowance (%)" + "label": "Over Transfer Allowance (%)", + "non_negative": 1 }, { "default": "0", @@ -437,7 +439,8 @@ "description": "The percentage you are allowed to pick more items in the pick list than the ordered quantity.", "fieldname": "over_picking_allowance", "fieldtype": "Percent", - "label": "Over Picking Allowance (%)" + "label": "Over Picking Allowance (%)", + "non_negative": 1 }, { "default": "1", @@ -590,7 +593,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2026-07-16 17:00:00.000000", + "modified": "2026-08-01 23:35:02.896836", "modified_by": "Administrator", "module": "Stock", "name": "Stock Settings", diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.py b/erpnext/stock/doctype/stock_settings/stock_settings.py index f3890cc9dfe..cefb321c791 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.py +++ b/erpnext/stock/doctype/stock_settings/stock_settings.py @@ -68,7 +68,7 @@ class StockSettings(Document): use_naming_series: DF.Check use_serial_batch_fields: DF.Check validate_material_transfer_warehouses: DF.Check - valuation_method: DF.Literal["FIFO", "Moving Average", "LIFO"] + valuation_method: DF.Literal["FIFO", "Moving Average", "LIFO", "Standard Cost"] # end: auto-generated types def validate(self): @@ -101,6 +101,7 @@ class StockSettings(Document): validate_fields_for_doctype=False, ) + self.validate_over_delivery_receipt_allowance() self.validate_serial_and_batch_no_settings() self.cant_change_valuation_method() self.validate_clean_description_html() @@ -112,6 +113,10 @@ class StockSettings(Document): self.change_precision_for_stock_entry() self.validate_do_not_use_batchwise_valuation() + def validate_over_delivery_receipt_allowance(self): + if not self.over_delivery_receipt_allowance: + self.role_allowed_to_over_deliver_receive = None + def validate_do_not_use_batchwise_valuation(self): doc_before_save = self.get_doc_before_save() if not doc_before_save: From 248873034df67a2953ef7948ddc44d28f202ea97 Mon Sep 17 00:00:00 2001 From: Afsal Syed Date: Sun, 2 Aug 2026 02:31:36 +0530 Subject: [PATCH 2/4] fix(stock): scope over deliver/receive role check to delivery and receipt overflow --- erpnext/controllers/status_updater.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py index eed56008547..41614f93327 100644 --- a/erpnext/controllers/status_updater.py +++ b/erpnext/controllers/status_updater.py @@ -446,11 +446,12 @@ class StatusUpdater(Document): else (0, {}, None, None) ) - role_allowed_to_over_deliver_receive = frappe.get_single_value( - "Stock Settings", "role_allowed_to_over_deliver_receive" - ) - role_allowed_to_over_bill = frappe.get_single_value("Accounts Settings", "role_allowed_to_over_bill") - role = role_allowed_to_over_deliver_receive if qty_or_amount == "qty" else role_allowed_to_over_bill + role = None + if qty_or_amount == "qty": + if args.get("overflow_type") in ("delivery", "receipt"): + role = frappe.get_single_value("Stock Settings", "role_allowed_to_over_deliver_receive") + else: + role = frappe.get_single_value("Accounts Settings", "role_allowed_to_over_bill") overflow_percent = ( (item[args["target_field"]] - item[args["target_ref_field"]]) / item[args["target_ref_field"]] From 0b271e24b60ed4f3a2f9e57bfd5f39f195a6b683 Mon Sep 17 00:00:00 2001 From: Afsal Syed Date: Sun, 2 Aug 2026 02:32:59 +0530 Subject: [PATCH 3/4] test(stock): add test cases verifying stock over delivery role does not bypass order allowance --- .../purchase_order/test_purchase_order.py | 15 +++++++++++ .../blanket_order/test_blanket_order.py | 26 +++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index 37ccf275cdc..b84ca5ca9dd 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -216,6 +216,21 @@ class TestPurchaseOrder(ERPNextTestSuite): po2.items[0].qty = 110 self.assertRaises(OverAllowanceError, po2.submit) + # Stock over-delivery role must not bypass over-ordering against Material Request. + with self.change_settings( + "Stock Settings", {"role_allowed_to_over_deliver_receive": "Stock Manager"} + ): + test_user = frappe.get_doc("User", "test@example.com") + test_user.add_roles("Stock Manager") + + mr3 = make_material_request(qty=100) + po3 = make_purchase_order(mr3.name) + po3.supplier = "_Test Supplier" + po3.items[0].qty = 110 + with self.set_user("test@example.com"): + po3.flags.ignore_permissions = True + self.assertRaises(OverAllowanceError, po3.submit) + # cleanup frappe.db.set_single_value("Buying Settings", "over_order_allowance", 0) frappe.db.set_single_value("Stock Settings", "over_delivery_receipt_allowance", 0) diff --git a/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py b/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py index d6b94ca7bae..a2babf8d845 100644 --- a/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py +++ b/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py @@ -91,6 +91,32 @@ class TestBlanketOrder(ERPNextTestSuite): frappe.db.set_single_value("Buying Settings", "blanket_order_allowance", 10) po.submit() + @ERPNextTestSuite.change_settings("Selling Settings", {"blanket_order_allowance": 0}) + @ERPNextTestSuite.change_settings("Buying Settings", {"blanket_order_allowance": 0}) + @ERPNextTestSuite.change_settings( + "Stock Settings", + {"over_delivery_receipt_allowance": 10, "role_allowed_to_over_deliver_receive": "Stock Manager"}, + ) + def test_stock_over_delivery_role_does_not_bypass_blanket_order_allowance(self): + test_user = frappe.get_doc("User", "test@example.com") + test_user.add_roles("Stock Manager") + + frappe.clear_cache() + for blanket_order_type, doctype, date_field in ( + ("Selling", "Sales Order", "delivery_date"), + ("Purchasing", "Purchase Order", "schedule_date"), + ): + bo = make_blanket_order(blanket_order_type=blanket_order_type, quantity=100) + frappe.flags.args.doctype = doctype + order = make_order(bo.name) + order.currency = get_company_currency(order.company) + setattr(order, date_field, today()) + order.items[0].qty = 110 + + with self.set_user("test@example.com"): + order.flags.ignore_permissions = True + self.assertRaises(frappe.ValidationError, order.submit) + def test_blanket_order_over_order_aggregated_across_rows(self): # the over-order check should sum the same item across multiple order rows frappe.db.set_single_value("Selling Settings", "blanket_order_allowance", 0) From 99630f40eb5fcef78b889278e001f68de70caa04 Mon Sep 17 00:00:00 2001 From: Afsal Syed Date: Sun, 2 Aug 2026 02:33:42 +0530 Subject: [PATCH 4/4] test(stock): prevent settings leakage in purchase order tests --- .../buying/doctype/purchase_order/test_purchase_order.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index b84ca5ca9dd..d36dedb200a 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -1059,6 +1059,8 @@ class TestPurchaseOrder(ERPNextTestSuite): # self.assertEqual(po.payment_terms_template, pi.payment_terms_template) compare_payment_schedules(self, po, pi) + @ERPNextTestSuite.change_settings("Selling Settings", {"maintain_same_sales_rate": 1}) + @ERPNextTestSuite.change_settings("Buying Settings", {"maintain_same_rate": 1}) def test_internal_transfer_flow(self): from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center from erpnext.accounts.doctype.sales_invoice.mapper import ( @@ -1070,9 +1072,6 @@ class TestPurchaseOrder(ERPNextTestSuite): ) from erpnext.stock.doctype.delivery_note.mapper import make_inter_company_purchase_receipt - frappe.db.set_single_value("Selling Settings", "maintain_same_sales_rate", 1) - frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 1) - prepare_data_for_internal_transfer() supplier = "_Test Internal Supplier 2" @@ -1511,6 +1510,7 @@ class TestPurchaseOrder(ERPNextTestSuite): self.assertEqual(pi_2.status, "Paid") self.assertEqual(po.status, "Completed") + @ERPNextTestSuite.change_settings("Buying Settings", {"maintain_same_rate": 0}) def test_purchase_order_over_billing_missing_item(self): item1 = make_item( "_Test Item for Overbilling",