From 3f3292ca4aedf48dd356c8a6ca8c6b1ecbac1330 Mon Sep 17 00:00:00 2001 From: Afsal Syed Date: Sun, 2 Aug 2026 02:31:21 +0530 Subject: [PATCH 1/5] fix(stock): validate over delivery/receipt allowance in stock settings (cherry picked from commit 446ec6030a73573f24500f9ff9def1f78a1d387e) # Conflicts: # erpnext/stock/doctype/stock_settings/stock_settings.json # erpnext/stock/doctype/stock_settings/stock_settings.py --- .../doctype/stock_settings/stock_settings.json | 13 ++++++++++--- .../stock/doctype/stock_settings/stock_settings.py | 10 +++++++++- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.json b/erpnext/stock/doctype/stock_settings/stock_settings.json index f9b46cf6e4f..5e23d5cb4c5 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.json +++ b/erpnext/stock/doctype/stock_settings/stock_settings.json @@ -140,7 +140,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", @@ -291,7 +292,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", @@ -452,7 +454,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", @@ -602,7 +605,11 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], +<<<<<<< HEAD "modified": "2026-06-13 12:38:02.202183", +======= + "modified": "2026-08-01 23:35:02.896836", +>>>>>>> 446ec6030a (fix(stock): validate over delivery/receipt allowance in stock settings) "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 e7373802b1e..1377d72de16 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.py +++ b/erpnext/stock/doctype/stock_settings/stock_settings.py @@ -70,7 +70,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): @@ -104,7 +104,11 @@ class StockSettings(Document): validate_fields_for_doctype=False, ) +<<<<<<< HEAD self.validate_warehouses() +======= + self.validate_over_delivery_receipt_allowance() +>>>>>>> 446ec6030a (fix(stock): validate over delivery/receipt allowance in stock settings) self.validate_serial_and_batch_no_settings() self.cant_change_valuation_method() self.validate_clean_description_html() @@ -116,6 +120,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 4713ddd55b2c4846b87134e16df1eeec49f60401 Mon Sep 17 00:00:00 2001 From: Afsal Syed Date: Sun, 2 Aug 2026 02:31:36 +0530 Subject: [PATCH 2/5] fix(stock): scope over deliver/receive role check to delivery and receipt overflow (cherry picked from commit 248873034df67a2953ef7948ddc44d28f202ea97) --- 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 b9d56c9d92d..ddea50fb7ff 100644 --- a/erpnext/controllers/status_updater.py +++ b/erpnext/controllers/status_updater.py @@ -445,11 +445,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 3a0f988a9e9e57aaea8b327ada018fedd5143143 Mon Sep 17 00:00:00 2001 From: Afsal Syed Date: Sun, 2 Aug 2026 02:32:59 +0530 Subject: [PATCH 3/5] test(stock): add test cases verifying stock over delivery role does not bypass order allowance (cherry picked from commit 0b271e24b60ed4f3a2f9e57bfd5f39f195a6b683) # Conflicts: # erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py --- .../purchase_order/test_purchase_order.py | 15 ++++++ .../blanket_order/test_blanket_order.py | 52 +++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index 7f934489b13..5691d790c50 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -162,6 +162,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 ac7c860dd1b..095a3e4e065 100644 --- a/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py +++ b/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py @@ -91,6 +91,58 @@ class TestBlanketOrder(ERPNextTestSuite): frappe.db.set_single_value("Buying Settings", "blanket_order_allowance", 10) po.submit() +<<<<<<< HEAD +======= + @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) + bo = make_blanket_order(blanket_order_type="Selling", quantity=100) + + frappe.flags.args.doctype = "Sales Order" + so = make_order(bo.name) + so.currency = get_company_currency(so.company) + so.delivery_date = today() + so.items[0].qty = 60 + so.append( + "items", + { + "item_code": so.items[0].item_code, + "qty": 50, # 60 + 50 = 110 > 100 blanket qty + "rate": so.items[0].rate, + "delivery_date": today(), + "against_blanket_order": 1, + "blanket_order": bo.name, + }, + ) + self.assertRaises(frappe.ValidationError, so.submit) + +>>>>>>> 0b271e24b6 (test(stock): add test cases verifying stock over delivery role does not bypass order allowance) def test_party_item_code(self): item_doc = make_item("_Test Item 1 for Blanket Order") item_code = item_doc.name From 6cbf73a326d2eb8802f1a6329ab7adc864478653 Mon Sep 17 00:00:00 2001 From: Afsal Syed Date: Sun, 2 Aug 2026 02:33:42 +0530 Subject: [PATCH 4/5] test(stock): prevent settings leakage in purchase order tests (cherry picked from commit 99630f40eb5fcef78b889278e001f68de70caa04) --- .../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 5691d790c50..b021925bd9d 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -1011,6 +1011,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.sales_invoice import ( @@ -1022,9 +1024,6 @@ class TestPurchaseOrder(ERPNextTestSuite): ) from erpnext.stock.doctype.delivery_note.delivery_note 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" @@ -1463,6 +1462,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", From 697f68d1d2cefc3b99927021be67a3e8e31fabc9 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Mon, 3 Aug 2026 12:55:08 +0530 Subject: [PATCH 5/5] fix: resolve version-16 backport conflicts Keep validate_warehouses() alongside the new validate_over_delivery_receipt_allowance() call. Drop test_blanket_order_over_order_aggregated_across_rows: it is develop-only context the cherry-pick swallowed into the conflict, not part of #57725. Revert the valuation_method literal to the three options this branch offers - Standard Cost rode along from a regenerated develop type block. --- .../blanket_order/test_blanket_order.py | 26 ------------------- .../stock_settings/stock_settings.json | 4 --- .../doctype/stock_settings/stock_settings.py | 5 +--- 3 files changed, 1 insertion(+), 34 deletions(-) diff --git a/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py b/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py index 095a3e4e065..fa346cd9e6b 100644 --- a/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py +++ b/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py @@ -91,8 +91,6 @@ class TestBlanketOrder(ERPNextTestSuite): frappe.db.set_single_value("Buying Settings", "blanket_order_allowance", 10) po.submit() -<<<<<<< HEAD -======= @ERPNextTestSuite.change_settings("Selling Settings", {"blanket_order_allowance": 0}) @ERPNextTestSuite.change_settings("Buying Settings", {"blanket_order_allowance": 0}) @ERPNextTestSuite.change_settings( @@ -119,30 +117,6 @@ class TestBlanketOrder(ERPNextTestSuite): 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) - bo = make_blanket_order(blanket_order_type="Selling", quantity=100) - - frappe.flags.args.doctype = "Sales Order" - so = make_order(bo.name) - so.currency = get_company_currency(so.company) - so.delivery_date = today() - so.items[0].qty = 60 - so.append( - "items", - { - "item_code": so.items[0].item_code, - "qty": 50, # 60 + 50 = 110 > 100 blanket qty - "rate": so.items[0].rate, - "delivery_date": today(), - "against_blanket_order": 1, - "blanket_order": bo.name, - }, - ) - self.assertRaises(frappe.ValidationError, so.submit) - ->>>>>>> 0b271e24b6 (test(stock): add test cases verifying stock over delivery role does not bypass order allowance) def test_party_item_code(self): item_doc = make_item("_Test Item 1 for Blanket Order") item_code = item_doc.name diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.json b/erpnext/stock/doctype/stock_settings/stock_settings.json index 5e23d5cb4c5..6188f1a6f4c 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.json +++ b/erpnext/stock/doctype/stock_settings/stock_settings.json @@ -605,11 +605,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], -<<<<<<< HEAD - "modified": "2026-06-13 12:38:02.202183", -======= "modified": "2026-08-01 23:35:02.896836", ->>>>>>> 446ec6030a (fix(stock): validate over delivery/receipt allowance in stock settings) "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 1377d72de16..b6c4a458c00 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.py +++ b/erpnext/stock/doctype/stock_settings/stock_settings.py @@ -70,7 +70,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", "Standard Cost"] + valuation_method: DF.Literal["FIFO", "Moving Average", "LIFO"] # end: auto-generated types def validate(self): @@ -104,11 +104,8 @@ class StockSettings(Document): validate_fields_for_doctype=False, ) -<<<<<<< HEAD self.validate_warehouses() -======= self.validate_over_delivery_receipt_allowance() ->>>>>>> 446ec6030a (fix(stock): validate over delivery/receipt allowance in stock settings) self.validate_serial_and_batch_no_settings() self.cant_change_valuation_method() self.validate_clean_description_html()