diff --git a/erpnext/manufacturing/doctype/production_plan/services/reservation.py b/erpnext/manufacturing/doctype/production_plan/services/reservation.py index ee2a1bceb34..615c8b179f3 100644 --- a/erpnext/manufacturing/doctype/production_plan/services/reservation.py +++ b/erpnext/manufacturing/doctype/production_plan/services/reservation.py @@ -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: diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index 97d286e429d..d414755abd0 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -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 diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 419a42e0b52..4bc36ce882e 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -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 diff --git a/erpnext/patches/v16_0/recompute_production_plan_reserved_qty.py b/erpnext/patches/v16_0/recompute_production_plan_reserved_qty.py new file mode 100644 index 00000000000..c33d59eea8f --- /dev/null +++ b/erpnext/patches/v16_0/recompute_production_plan_reserved_qty.py @@ -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()