From 2f8d588093aa7ea9b395c45e92783e6ba3570ca1 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Thu, 16 Jul 2026 12:46:21 +0530 Subject: [PATCH 1/2] fix: consider min order qty in the purchase/transfer flow of production plan The transfer flow ignored Consider Minimum Order Qty twice: the JS handler force-reset the checkbox before fetching items, and the purchase remainder left after allocating transfers from other warehouses was never raised to min_order_qty (the check runs on the total requirement before the split). Drop the JS reset and apply min order qty to the purchase remainder, in stock UOM before the purchase UOM conversion. --- .../production_plan/production_plan.js | 2 -- .../services/material_request.py | 19 +++++++++++++++---- .../production_plan/test_production_plan.py | 11 +++++++++++ 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js index 3bef5d30712..5cba4e50eb4 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.js +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js @@ -471,8 +471,6 @@ frappe.ui.form.on("Production Plan", { frappe.throw(__("Select the Warehouse")); } - frm.set_value("consider_minimum_order_qty", 0); - if (!frm.doc.ignore_existing_ordered_qty) { frm.events.get_items_for_material_requests(frm); } else { diff --git a/erpnext/manufacturing/doctype/production_plan/services/material_request.py b/erpnext/manufacturing/doctype/production_plan/services/material_request.py index fc2ad13df73..1b6cc8a850b 100644 --- a/erpnext/manufacturing/doctype/production_plan/services/material_request.py +++ b/erpnext/manufacturing/doctype/production_plan/services/material_request.py @@ -453,7 +453,13 @@ def _apply_other_locations(doc, mr_items, warehouses, ignore_ordered_qty, get_pa new_mr_items = [] for item in mr_items: - get_materials_from_other_locations(item, warehouses, new_mr_items, doc.get("company")) + get_materials_from_other_locations( + item, + warehouses, + new_mr_items, + doc.get("company"), + consider_minimum_order_qty=doc.get("consider_minimum_order_qty"), + ) return new_mr_items @@ -573,7 +579,9 @@ def _material_request_item_row( } -def get_materials_from_other_locations(item, warehouses, new_mr_items, company): +def get_materials_from_other_locations( + item, warehouses, new_mr_items, company, consider_minimum_order_qty=False +): from erpnext.stock.doctype.pick_list.pick_list import get_available_item_locations locations = get_available_item_locations( @@ -590,7 +598,7 @@ def get_materials_from_other_locations(item, warehouses, new_mr_items, company): required_qty = required_qty * item.get("conversion_factor") required_qty = _transfer_from_locations(item, locations, new_mr_items, required_qty) - _add_remaining_purchase_request(item, new_mr_items, required_qty) + _add_remaining_purchase_request(item, new_mr_items, required_qty, consider_minimum_order_qty) def _transfer_from_locations(item, locations, new_mr_items, required_qty): @@ -615,12 +623,15 @@ def _transfer_from_locations(item, locations, new_mr_items, required_qty): return required_qty -def _add_remaining_purchase_request(item, new_mr_items, required_qty): +def _add_remaining_purchase_request(item, new_mr_items, required_qty, consider_minimum_order_qty=False): # raise purchase request for remaining qty precision = frappe.get_precision("Material Request Plan Item", "quantity") if flt(required_qty, precision) <= 0: return + if consider_minimum_order_qty: + required_qty = max(required_qty, flt(item.get("min_order_qty"))) + purchase_uom = frappe.db.get_value("Item", item.get("item_code"), "purchase_uom") if frappe.db.get_value("UOM", purchase_uom, "must_be_whole_number"): required_qty = ceil(required_qty) diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index e63bc8a2b09..ef27532c619 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -2129,6 +2129,17 @@ class TestProductionPlan(ERPNextTestSuite): for d in mr_items: self.assertEqual(d.get("quantity"), 1000.0) + source_warehouse = create_warehouse("MOQ Source Warehouse", company="_Test Company") + make_stock_entry(item_code=rm_item, qty=7, rate=100, target=source_warehouse) + + pln.ignore_existing_ordered_qty = 1 + mr_items = get_items_for_material_requests( + pln.as_dict(), warehouses=[{"warehouse": source_warehouse}] + ) + items_by_type = {d.get("material_request_type"): d for d in mr_items} + self.assertEqual(items_by_type["Material Transfer"].get("quantity"), 7.0) + self.assertEqual(items_by_type["Purchase"].get("quantity"), 1000.0) + def test_fg_item_quantity(self): fg_item = make_item(properties={"is_stock_item": 1}).name rm_item = make_item(properties={"is_stock_item": 1}).name From 448316fe8e77dfe8e7d98826c7bb787275d61e2c Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Thu, 16 Jul 2026 12:56:11 +0530 Subject: [PATCH 2/2] test: assert row count in the min order qty split scenario --- .../doctype/production_plan/test_production_plan.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index ef27532c619..97d286e429d 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -2136,6 +2136,7 @@ class TestProductionPlan(ERPNextTestSuite): mr_items = get_items_for_material_requests( pln.as_dict(), warehouses=[{"warehouse": source_warehouse}] ) + self.assertEqual(len(mr_items), 2) items_by_type = {d.get("material_request_type"): d for d in mr_items} self.assertEqual(items_by_type["Material Transfer"].get("quantity"), 7.0) self.assertEqual(items_by_type["Purchase"].get("quantity"), 1000.0)