fix: skip covered rows when ordering from the mrp report (#58249) (#58312)

This commit is contained in:
Pandiyan P
2026-08-20 07:43:16 +05:30
committed by GitHub
parent 53b61e5ac6
commit dfc2301f08
2 changed files with 83 additions and 1 deletions

View File

@@ -1298,7 +1298,7 @@ def get_item_capacity(item_code, bucket_size):
@frappe.whitelist()
def make_order(selected_rows, company, warehouse=None, mps=None):
def make_order(selected_rows: str | list, company: str, warehouse: str | None = None, mps: str | None = None):
if not frappe.has_permission("Purchase Order", "create"):
frappe.throw(_("Not permitted to make Purchase Orders"), frappe.PermissionError)
@@ -1308,16 +1308,33 @@ def make_order(selected_rows, company, warehouse=None, mps=None):
if not frappe.db.exists("Company", company):
frappe.throw(_("Company {0} does not exist").format(company))
qty_precision = frappe.get_precision("Purchase Order Item", "qty")
purchase_orders = {}
work_orders = []
covered_rows = 0
for row in selected_rows:
row = frappe._dict(row)
# what is left to order once stock and the orders already placed are counted. rounding
# to the precision an order is stored in, so what is left of a covered row after all the
# subtracting does not become an order line of its own
if flt(row.required_qty, qty_precision) <= 0:
covered_rows += 1
continue
if row.type_of_material == "Purchase":
purchase_orders.setdefault((row.default_supplier, row.release_date), []).append(row)
if row.type_of_material == "Manufacture" and row.bom_no:
work_orders.append(row)
if not purchase_orders and not work_orders:
frappe.msgprint(
_("Nothing to order, the selected rows are already covered by stock or existing orders")
if covered_rows
else _("Nothing to order from the selected rows")
)
return
if purchase_orders:
make_purchase_orders(purchase_orders, company, warehouse=warehouse, mps=mps)

View File

@@ -20,6 +20,7 @@ COMPANY = "_Test Company"
WAREHOUSE = "_Test Warehouse - _TC"
SUPPLIER = "_Test Supplier"
TAX_TEMPLATE = "_Test Purchase Taxes and Charges Template - _TC"
WIP_WAREHOUSE = "_Test Warehouse 1 - _TC"
class TestMaterialRequirementsPlanningReport(ERPNextTestSuite):
@@ -105,6 +106,62 @@ class TestMaterialRequirementsPlanningReport(ERPNextTestSuite):
self.assertEqual(work_order.bom_no, plan.bom)
self.assertEqual(work_order.qty, plan.planned_qty)
def test_make_order_skips_rows_that_are_already_covered(self):
"""
A row whose requirement is met by stock or by an order placed earlier has nothing left
to order. It must be left out instead of failing, and the rows beside it must still be
created.
"""
plan = make_mrp_plan(self)
covered_row, remaining_rows = plan.rows[0], plan.rows[1:]
self.assertTrue(remaining_rows, msg="the plan needs a second row to order")
covered_row.required_qty = 0
make_order(plan.rows, COMPANY, warehouse=WAREHOUSE, mps=plan.mps)
ordered_items = [
row.item_code
for doctype in ("Purchase Order", "Work Order")
for order in frappe.get_all(doctype, filters={"mps": plan.mps}, pluck="name")
for row in get_ordered_items(doctype, order)
]
self.assertNotIn(covered_row.item_code, ordered_items)
self.assertEqual(sorted(ordered_items), sorted([row.item_code for row in remaining_rows]))
def test_make_order_creates_nothing_when_every_row_is_covered(self):
plan = make_mrp_plan(self)
for row in plan.rows:
row.required_qty = 0
make_order(plan.rows, COMPANY, warehouse=WAREHOUSE, mps=plan.mps)
for doctype in ("Purchase Order", "Work Order"):
self.assertFalse(frappe.get_all(doctype, filters={"mps": plan.mps}, pluck="name"))
def test_make_order_ignores_a_requirement_left_over_by_rounding(self):
"""What is left of a covered row after subtracting is not a quantity worth ordering."""
plan = make_mrp_plan(self)
for row in plan.rows:
row.required_qty = 0.0000000001
make_order(plan.rows, COMPANY, warehouse=WAREHOUSE, mps=plan.mps)
for doctype in ("Purchase Order", "Work Order"):
self.assertFalse(frappe.get_all(doctype, filters={"mps": plan.mps}, pluck="name"))
def test_work_order_keeps_the_company_wip_warehouse(self):
"""
The item's own warehouse is where the finished goods go, the work in progress warehouse
stays the one the company keeps for it.
"""
plan = make_mrp_plan(self)
frappe.db.set_value("Company", COMPANY, "default_wip_warehouse", WIP_WAREHOUSE)
make_order(plan.rows, COMPANY, warehouse=WAREHOUSE, mps=plan.mps)
work_order = get_created_order(plan.mps, "Work Order")
self.assertEqual(work_order.wip_warehouse, WIP_WAREHOUSE)
def test_purchase_order_gets_defaults_from_set_missing_values(self):
plan = make_mrp_plan(self)
make_tax_rule(tax_type="Purchase", purchase_tax_template=TAX_TEMPLATE, priority=1, save=1)
@@ -204,6 +261,14 @@ def make_mrp_plan(test_case, planned_qty=10, rm_qty=2):
)
def get_ordered_items(doctype, order):
child_doctype = "Purchase Order Item" if doctype == "Purchase Order" else None
if not child_doctype:
return frappe.get_all(doctype, filters={"name": order}, fields=["production_item as item_code"])
return frappe.get_all(child_doctype, filters={"parent": order}, fields=["item_code"])
def get_created_order(mps, doctype):
names = frappe.get_all(doctype, filters={"mps": mps}, pluck="name")
if len(names) != 1: