mirror of
https://github.com/frappe/erpnext.git
synced 2026-08-12 22:21:50 +00:00
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:
@@ -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])
|
||||
Reference in New Issue
Block a user