mirror of
https://github.com/frappe/erpnext.git
synced 2026-08-17 08:28:44 +00:00
Merge pull request #56825 from aerele/backport-56806
fix: skip stock reservation for opted-out production plans
This commit is contained in:
@@ -2322,6 +2322,145 @@ class TestProductionPlan(ERPNextTestSuite):
|
||||
self.assertTrue(len(reserved_entries) == 0)
|
||||
frappe.db.set_single_value("Stock Settings", "enable_stock_reservation", 0)
|
||||
|
||||
def test_no_stock_reservation_via_purchase_receipt_when_reserve_stock_disabled(self):
|
||||
from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_receipt
|
||||
from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom
|
||||
from erpnext.stock.doctype.material_request.material_request import make_purchase_order
|
||||
|
||||
frappe.db.set_single_value("Stock Settings", "enable_stock_reservation", 1)
|
||||
frappe.db.set_single_value("Stock Settings", "auto_reserve_stock", 0)
|
||||
|
||||
bom_tree = {"FG For SR No Auto Reserve": {"RM For SR No Auto Reserve": {}}}
|
||||
parent_bom = create_nested_bom(bom_tree, prefix="")
|
||||
|
||||
warehouse = "_Test Warehouse - _TC"
|
||||
|
||||
# reserve_stock is deliberately left unset (defaults to 0): this is what happens when
|
||||
# "Auto Reserve Stock" is off and nobody ticks "Reserve Stock" on the Production Plan by hand.
|
||||
plan = create_production_plan(
|
||||
item_code=parent_bom.item,
|
||||
planned_qty=5,
|
||||
ignore_existing_ordered_qty=1,
|
||||
do_not_submit=1,
|
||||
warehouse=warehouse,
|
||||
for_warehouse=warehouse,
|
||||
)
|
||||
plan.get_sub_assembly_items()
|
||||
plan.set("mr_items", [])
|
||||
for d in get_items_for_material_requests(plan.as_dict()):
|
||||
plan.append("mr_items", d)
|
||||
plan.save()
|
||||
|
||||
self.assertEqual(plan.reserve_stock, 0)
|
||||
plan.submit()
|
||||
|
||||
plan.submit_material_request = 1
|
||||
plan.make_material_request()
|
||||
|
||||
material_requests = frappe.get_all(
|
||||
"Material Request", filters={"production_plan": plan.name}, pluck="name"
|
||||
)
|
||||
self.assertGreater(len(material_requests), 0)
|
||||
|
||||
for mr_name in list(set(material_requests)):
|
||||
po = make_purchase_order(mr_name)
|
||||
po.supplier = "_Test Supplier"
|
||||
po.submit()
|
||||
|
||||
pr = make_purchase_receipt(po.name)
|
||||
pr.submit()
|
||||
|
||||
sre = StockReservation(plan)
|
||||
reserved_entries = sre.get_reserved_entries("Production Plan", plan.name)
|
||||
self.assertEqual(len(reserved_entries), 0)
|
||||
|
||||
frappe.db.set_single_value("Stock Settings", "enable_stock_reservation", 0)
|
||||
|
||||
def test_stock_reservation_ignores_production_plans_with_reserve_stock_off_on_shared_purchase_order(self):
|
||||
from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_receipt
|
||||
from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom
|
||||
|
||||
frappe.db.set_single_value("Stock Settings", "enable_stock_reservation", 1)
|
||||
frappe.db.set_single_value("Stock Settings", "auto_reserve_stock", 0)
|
||||
|
||||
warehouse = "_Test Warehouse - _TC"
|
||||
|
||||
bom_reserve = create_nested_bom({"FG SR Mixed Reserve": {"RM SR Mixed Reserve": {}}}, prefix="")
|
||||
bom_skip = create_nested_bom({"FG SR Mixed Skip": {"RM SR Mixed Skip": {}}}, prefix="")
|
||||
|
||||
def make_submitted_plan(item_code, reserve_stock):
|
||||
plan = create_production_plan(
|
||||
item_code=item_code,
|
||||
planned_qty=5,
|
||||
ignore_existing_ordered_qty=1,
|
||||
do_not_submit=1,
|
||||
warehouse=warehouse,
|
||||
for_warehouse=warehouse,
|
||||
reserve_stock=reserve_stock,
|
||||
)
|
||||
plan.get_sub_assembly_items()
|
||||
plan.set("mr_items", [])
|
||||
for d in get_items_for_material_requests(plan.as_dict()):
|
||||
plan.append("mr_items", d)
|
||||
plan.save()
|
||||
plan.submit()
|
||||
plan.submit_material_request = 1
|
||||
plan.make_material_request()
|
||||
return plan
|
||||
|
||||
plan_reserve = make_submitted_plan(bom_reserve.item, reserve_stock=1)
|
||||
plan_skip = make_submitted_plan(bom_skip.item, reserve_stock=0)
|
||||
|
||||
self.assertEqual(plan_reserve.reserve_stock, 1)
|
||||
self.assertEqual(plan_skip.reserve_stock, 0)
|
||||
|
||||
mr_reserve = frappe.get_all(
|
||||
"Material Request", filters={"production_plan": plan_reserve.name}, pluck="name"
|
||||
)[0]
|
||||
mr_skip = frappe.get_all(
|
||||
"Material Request", filters={"production_plan": plan_skip.name}, pluck="name"
|
||||
)[0]
|
||||
|
||||
# One Purchase Order pulling rows from both Material Requests, so the Purchase Receipt made
|
||||
# from it has both a reservable and a non-reservable Production Plan reference in `doc.items`.
|
||||
po = frappe.new_doc("Purchase Order")
|
||||
po.supplier = "_Test Supplier"
|
||||
po.company = plan_reserve.company
|
||||
po.schedule_date = nowdate()
|
||||
|
||||
for mr_name in (mr_reserve, mr_skip):
|
||||
mr = frappe.get_doc("Material Request", mr_name)
|
||||
for item in mr.items:
|
||||
po.append(
|
||||
"items",
|
||||
{
|
||||
"item_code": item.item_code,
|
||||
"qty": item.qty,
|
||||
"rate": 100,
|
||||
"schedule_date": nowdate(),
|
||||
"warehouse": warehouse,
|
||||
"material_request": mr.name,
|
||||
"material_request_item": item.name,
|
||||
},
|
||||
)
|
||||
|
||||
po.submit()
|
||||
|
||||
pr = make_purchase_receipt(po.name)
|
||||
pr.submit()
|
||||
|
||||
reserved_for_plan_reserve = StockReservation(plan_reserve).get_reserved_entries(
|
||||
"Production Plan", plan_reserve.name
|
||||
)
|
||||
reserved_for_plan_skip = StockReservation(plan_skip).get_reserved_entries(
|
||||
"Production Plan", plan_skip.name
|
||||
)
|
||||
|
||||
self.assertGreater(len(reserved_for_plan_reserve), 0)
|
||||
self.assertEqual(len(reserved_for_plan_skip), 0)
|
||||
|
||||
frappe.db.set_single_value("Stock Settings", "enable_stock_reservation", 0)
|
||||
|
||||
def test_stock_reservation_of_serial_nos_against_production_plan(self):
|
||||
from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_receipt
|
||||
from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom
|
||||
|
||||
@@ -1034,6 +1034,13 @@ class PurchaseReceipt(BuyingController):
|
||||
return
|
||||
|
||||
production_plan_references = self.get_production_plan_references()
|
||||
if not production_plan_references:
|
||||
return
|
||||
|
||||
reservable_plans = self.get_reservable_production_plans(production_plan_references)
|
||||
if not reservable_plans:
|
||||
return
|
||||
|
||||
production_plan_items = []
|
||||
self.reload()
|
||||
|
||||
@@ -1041,6 +1048,9 @@ class PurchaseReceipt(BuyingController):
|
||||
for row in self.items:
|
||||
if row.material_request_item and row.material_request_item in production_plan_references:
|
||||
_ref = production_plan_references[row.material_request_item]
|
||||
if _ref.production_plan not in reservable_plans:
|
||||
continue
|
||||
|
||||
docnames.append(_ref.production_plan)
|
||||
row.update(
|
||||
{
|
||||
@@ -1066,6 +1076,25 @@ class PurchaseReceipt(BuyingController):
|
||||
docnames, from_doctype="Production Plan", to_doctype="Work Order"
|
||||
)
|
||||
|
||||
def get_reservable_production_plans(self, production_plan_references) -> set:
|
||||
"""Production Plans that opted into stock reservation (``reserve_stock``).
|
||||
|
||||
A Production Plan only gets this flag set if "Auto Reserve Stock" was enabled in
|
||||
Stock Settings when it was created, or the user ticked "Reserve Stock" manually.
|
||||
Without this check, a Purchase Receipt would auto-reserve stock for every
|
||||
Production Plan whenever "Enable Stock Reservation" is on, ignoring both of those.
|
||||
"""
|
||||
plan_names = {ref.production_plan for ref in production_plan_references.values()}
|
||||
return {
|
||||
p.name
|
||||
for p in frappe.get_all(
|
||||
"Production Plan",
|
||||
filters={"name": ["in", list(plan_names)]},
|
||||
fields=["name", "reserve_stock"],
|
||||
)
|
||||
if p.reserve_stock
|
||||
}
|
||||
|
||||
def get_production_plan_references(self):
|
||||
production_plan_references = frappe._dict()
|
||||
material_request_items = []
|
||||
|
||||
Reference in New Issue
Block a user