test(manufacturing): cover MRP chart date range

(cherry picked from commit 592924cc0d)

# Conflicts:
#	erpnext/manufacturing/report/material_requirements_planning_report/test_material_requirements_planning_report.py
This commit is contained in:
Mihir Kandoi
2026-08-12 11:39:53 +05:30
committed by Mergify
parent de06cb7f45
commit 5b68db0156

View File

@@ -0,0 +1,205 @@
# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import frappe
from frappe.utils import add_days, flt, formatdate, today
from erpnext.accounts.doctype.tax_rule.test_tax_rule import make_tax_rule
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
from erpnext.manufacturing.report.material_requirements_planning_report.material_requirements_planning_report import (
MaterialRequirementsPlanningReport,
execute,
get_item_lead_time,
make_order,
)
from erpnext.stock.doctype.item.test_item import make_item
from erpnext.tests.utils import ERPNextTestSuite
COMPANY = "_Test Company"
WAREHOUSE = "_Test Warehouse - _TC"
SUPPLIER = "_Test Supplier"
TAX_TEMPLATE = "_Test Purchase Taxes and Charges Template - _TC"
class TestMaterialRequirementsPlanningReport(ERPNextTestSuite):
def test_detailed_chart_includes_full_date_range(self):
start_date = add_days(today(), 1)
delivery_dates = [add_days(start_date, offset) for offset in range(12)]
rows = [make_chart_row(delivery_date) for delivery_date in delivery_dates]
rows.append(make_chart_row(delivery_dates[-1], planned_qty=2))
chart = MaterialRequirementsPlanningReport(frappe._dict()).get_detailed_view_chart_data(rows)
self.assertEqual(
chart["data"]["labels"],
[formatdate(delivery_date, "dd MMM") for delivery_date in delivery_dates],
)
self.assertEqual(chart["data"]["datasets"][0]["values"], [1] * 11 + [3])
def test_detailed_chart_excludes_past_and_empty_delivery_dates(self):
delivery_dates = [today(), add_days(today(), 1)]
rows = [
make_chart_row(add_days(today(), -1)),
make_chart_row(None),
*[make_chart_row(delivery_date) for delivery_date in delivery_dates],
]
chart = MaterialRequirementsPlanningReport(frappe._dict()).get_detailed_view_chart_data(rows)
self.assertEqual(
chart["data"]["labels"],
[formatdate(delivery_date, "dd MMM") for delivery_date in delivery_dates],
)
def test_manufacture_lead_time_is_not_int_truncated(self):
"""lead_time = 1440 / manufacturing_time_in_mins + buffer_time. Both columns are Int;
integer/integer division truncates on Postgres (1440/7 -> 205) while MariaDB yields a
decimal, so the computed lead time (and the derived release date) diverged by engine."""
item = make_item("_Test MRP Lead Time Item", {"is_stock_item": 1}).name
frappe.get_doc(
{
"doctype": "Item Lead Time",
"item_code": item,
"manufacturing_time_in_mins": 7,
"buffer_time": 2,
}
).insert()
lead_time = get_item_lead_time(item, "Manufacture")
# 1440 / 7 + 2 = 207.714...; a truncating integer division on Postgres would give 207.
self.assertAlmostEqual(float(lead_time), 1440 / 7 + 2, places=2)
def test_make_order_creates_draft_purchase_and_work_orders(self):
plan = make_mrp_plan(self)
make_order(plan.rows, COMPANY, warehouse=WAREHOUSE, mps=plan.mps)
purchase_order = get_created_order(plan.mps, "Purchase Order")
self.assertEqual(purchase_order.docstatus, 0)
self.assertEqual(purchase_order.supplier, SUPPLIER)
self.assertEqual([d.item_code for d in purchase_order.items], [plan.rm_item])
self.assertEqual(purchase_order.items[0].qty, plan.planned_qty * plan.rm_qty)
work_order = get_created_order(plan.mps, "Work Order")
self.assertEqual(work_order.docstatus, 0)
self.assertEqual(work_order.production_item, plan.fg_item)
self.assertEqual(work_order.bom_no, plan.bom)
self.assertEqual(work_order.qty, plan.planned_qty)
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)
frappe.get_doc(
{
"doctype": "Item Price",
"item_code": plan.rm_item,
"price_list": "Standard Buying",
"price_list_rate": 100,
}
).insert()
make_order(plan.rows, COMPANY, warehouse=WAREHOUSE, mps=plan.mps)
purchase_order = get_created_order(plan.mps, "Purchase Order")
self.assertEqual(purchase_order.buying_price_list, "Standard Buying")
self.assertEqual(purchase_order.items[0].rate, 100)
template = frappe.get_doc("Purchase Taxes and Charges Template", TAX_TEMPLATE)
self.assertEqual(purchase_order.taxes_and_charges, TAX_TEMPLATE)
self.assertEqual([d.rate for d in purchase_order.taxes], [d.rate for d in template.taxes])
net_total = flt(purchase_order.net_total)
self.assertEqual(
purchase_order.grand_total, net_total + net_total * flt(template.taxes[0].rate) / 100
)
def make_chart_row(delivery_date, planned_qty=1):
return frappe._dict(
{
"delivery_date": delivery_date,
"planned_qty": planned_qty,
"in_hand_qty": 0,
"po_ordered_qty": 0,
"wo_ordered_qty": 0,
}
)
def make_mrp_plan(test_case, planned_qty=10, rm_qty=2):
"""Build a finished good with a submitted BOM and an MPS demanding it, then return the
report's own output rows -- the same payload the report's client sends to `make_order`."""
rm_item = make_item(
properties={
"is_stock_item": 1,
"is_purchase_item": 1,
"item_defaults": [
{"company": COMPANY, "default_warehouse": WAREHOUSE, "default_supplier": SUPPLIER}
],
}
).name
fg_item = make_item(
properties={
"is_stock_item": 1,
"item_defaults": [{"company": COMPANY, "default_warehouse": WAREHOUSE}],
}
).name
# on_submit sets Item.default_bom, which is how the report finds the raw materials
bom = make_bom(item=fg_item, raw_materials=[rm_item], rm_qty=rm_qty, rate=100).name
mps = frappe.get_doc(
{
"doctype": "Master Production Schedule",
"company": COMPANY,
"posting_date": today(),
"from_date": today(),
"parent_warehouse": WAREHOUSE,
"items": [
{
"item_code": fg_item,
"warehouse": WAREHOUSE,
"delivery_date": add_days(today(), 30),
"planned_qty": planned_qty,
"uom": frappe.get_cached_value("Item", fg_item, "stock_uom"),
}
],
}
)
# left in draft: on_submit enqueues MRP Log creation in a background job
mps.insert()
_, data, _, _ = execute(
frappe._dict(
{
"company": COMPANY,
"from_date": today(),
"to_date": add_days(today(), 90),
"warehouse": WAREHOUSE,
"mps": mps.name,
"type_of_material": "All",
"add_safety_stock": 0,
}
)
)
# the report separates each finished good with a blank row
rows = [row for row in data if row.get("item_code")]
test_case.assertTrue(rows, msg="the report returned no rows to create orders from")
return frappe._dict(
rm_item=rm_item,
fg_item=fg_item,
bom=bom,
mps=mps.name,
planned_qty=planned_qty,
rm_qty=rm_qty,
rows=rows,
)
def get_created_order(mps, doctype):
names = frappe.get_all(doctype, filters={"mps": mps}, pluck="name")
if len(names) != 1:
frappe.throw(f"Expected exactly one {doctype} for {mps}, got {names}")
return frappe.get_doc(doctype, names[0])