From 8de4b3a85eda2c636c0e381b77cc712531b68ca3 Mon Sep 17 00:00:00 2001 From: Afsal Syed Date: Sun, 2 Aug 2026 02:32:59 +0530 Subject: [PATCH] test(stock): add test cases verifying stock over delivery role does not bypass order allowance (cherry picked from commit 0b271e24b60ed4f3a2f9e57bfd5f39f195a6b683) # Conflicts: # erpnext/buying/doctype/purchase_order/test_purchase_order.py # erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py --- .../purchase_order/test_purchase_order.py | 56 +++++++++++++++++++ .../blanket_order/test_blanket_order.py | 52 +++++++++++++++++ 2 files changed, 108 insertions(+) diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index c4394c066e0..3273a40ac7b 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -126,6 +126,62 @@ class TestPurchaseOrder(FrappeTestCase): frappe.db.set_value("Item", "_Test Item", "over_billing_allowance", 0) frappe.db.set_single_value("Accounts Settings", "over_billing_allowance", 0) +<<<<<<< HEAD +======= + def test_over_order_allowance_against_material_request(self) -> None: + """Over Order Allowance in Buying Settings must govern PO qty vs MR qty independently + from Over Delivery/Receipt Allowance which governs receipt/delivery against a PO.""" + mr = make_material_request(qty=100) + po = make_purchase_order(mr.name) + po.supplier = "_Test Supplier" + po.items[0].qty = 110 # 10% over the MR qty + + # Without any allowance, submitting should raise an OverAllowanceError + from erpnext.controllers.status_updater import OverAllowanceError + + frappe.db.set_single_value("Buying Settings", "over_order_allowance", 0) + frappe.db.set_single_value("Stock Settings", "over_delivery_receipt_allowance", 0) + self.assertRaises(OverAllowanceError, po.submit) + + # Granting 10% in Over Order Allowance (Buying Settings) must allow the submit + frappe.db.set_single_value("Buying Settings", "over_order_allowance", 10) + po.reload() + po.items[0].qty = 110 + po.submit() + self.assertEqual(po.docstatus, 1) + po.cancel() + + # Over Delivery/Receipt Allowance must remain independent — changing it must not + # affect the MR → PO validation when Over Order Allowance is 0. + frappe.db.set_single_value("Buying Settings", "over_order_allowance", 0) + frappe.db.set_single_value("Stock Settings", "over_delivery_receipt_allowance", 50) + + mr2 = make_material_request(qty=100) + po2 = make_purchase_order(mr2.name) + po2.supplier = "_Test Supplier" + 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) + +>>>>>>> 0b271e24b6 (test(stock): add test cases verifying stock over delivery role does not bypass order allowance) def test_update_remove_child_linked_to_mr(self): """Test impact on linked PO and MR on deleting/updating row.""" mr = make_material_request(qty=10) diff --git a/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py b/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py index a6eb18f47bc..ce7ef6b614a 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(FrappeTestCase): 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