fix: reserve full BOM consumption for Production Plan raw materials

This commit is contained in:
Mihir Kandoi
2026-07-23 14:46:55 +05:30
parent da8bf368da
commit c0cb783603
4 changed files with 52 additions and 6 deletions

View File

@@ -3,7 +3,6 @@
"""Stock reservation for Production Plan (extracted from production_plan.py)."""
import frappe
from frappe import _
from frappe.model.document import Document
@@ -48,15 +47,11 @@ def get_reserved_qty_for_production_plan(item_code, warehouse):
def _production_plan_reserved_qty(item_code, warehouse, non_completed_production_plans):
table = frappe.qb.DocType("Production Plan")
child = frappe.qb.DocType("Material Request Plan Item")
qty = (
Case().when(child.quantity == 0, child.required_bom_qty).else_(child.quantity)
* child.conversion_factor
)
query = (
frappe.qb.from_(table)
.inner_join(child)
.on(table.name == child.parent)
.select(Sum(qty))
.select(Sum(child.required_bom_qty))
.where(_plan_reserved_filter(table, child, item_code, warehouse))
)
if non_completed_production_plans:

View File

@@ -1559,6 +1559,33 @@ class TestProductionPlan(ERPNextTestSuite):
reserved_qty_after_mr = flt(frappe.db.get_value("Bin", bin_name, "reserved_qty_for_production_plan"))
self.assertEqual(reserved_qty_after_mr, before_qty)
def test_reserved_qty_for_production_plan_with_partial_stock(self):
from erpnext.stock.utils import get_or_make_bin
fg_item = make_item(properties={"is_stock_item": 1}).name
rm_item = make_item(properties={"is_stock_item": 1}).name
make_bom(item=fg_item, raw_materials=[rm_item], source_warehouse="_Test Warehouse - _TC")
make_stock_entry(item_code=rm_item, qty=4, rate=100, target="_Test Warehouse - _TC")
bin_name = get_or_make_bin(rm_item, "_Test Warehouse - _TC")
before_qty = flt(frappe.db.get_value("Bin", bin_name, "reserved_qty_for_production_plan"))
pln = create_production_plan(item_code=fg_item, planned_qty=10, ignore_existing_ordered_qty=1)
row = next(d for d in pln.mr_items if d.item_code == rm_item)
self.assertEqual(row.required_bom_qty, 10)
self.assertEqual(row.quantity, 6)
after_qty = flt(frappe.db.get_value("Bin", bin_name, "reserved_qty_for_production_plan"))
self.assertEqual(after_qty - before_qty, 10)
pln.reload()
pln.cancel()
after_cancel = flt(frappe.db.get_value("Bin", bin_name, "reserved_qty_for_production_plan"))
self.assertEqual(after_cancel, before_qty)
def test_from_warehouse_for_purchase_material_request(self):
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
from erpnext.stock.utils import get_or_make_bin

View File

@@ -501,3 +501,4 @@ erpnext.patches.v16_0.crm_settings_handle_allowed_users_for_frappe_crm
erpnext.patches.v16_0.access_control_for_project_users
erpnext.patches.v16_0.enable_book_stock_expense_gl_entries
execute:frappe.db.set_single_value("Stock Settings", "use_inline_serial_batch_editor", 0)
erpnext.patches.v16_0.recompute_production_plan_reserved_qty

View File

@@ -0,0 +1,23 @@
import frappe
def execute():
plans = frappe.get_all(
"Production Plan",
filters={"docstatus": 1, "status": ("not in", ["Completed", "Closed"])},
pluck="name",
)
if not plans:
return
rows = frappe.get_all(
"Material Request Plan Item",
filters={"parent": ("in", plans)},
fields=["item_code", "warehouse"],
)
for item_code, warehouse in {(row.item_code, row.warehouse) for row in rows}:
bin_name = frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse})
if not bin_name:
continue
frappe.get_doc("Bin", bin_name, for_update=True).update_reserved_qty_for_production_plan()