From 47ee1d126d32d746fa7b03009deaee8659823654 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 1 Jul 2026 22:53:54 +0530 Subject: [PATCH] test: add coverage for Work Order Consumed Materials report --- .../test_work_order_consumed_materials.py | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 erpnext/manufacturing/report/work_order_consumed_materials/test_work_order_consumed_materials.py diff --git a/erpnext/manufacturing/report/work_order_consumed_materials/test_work_order_consumed_materials.py b/erpnext/manufacturing/report/work_order_consumed_materials/test_work_order_consumed_materials.py new file mode 100644 index 00000000000..c879de4dc02 --- /dev/null +++ b/erpnext/manufacturing/report/work_order_consumed_materials/test_work_order_consumed_materials.py @@ -0,0 +1,105 @@ +# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +import frappe +from frappe.utils import add_days, nowdate + +from erpnext.manufacturing.doctype.work_order.mapper import make_stock_entry +from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record +from erpnext.manufacturing.report.work_order_consumed_materials.work_order_consumed_materials import execute +from erpnext.stock.doctype.stock_entry import test_stock_entry +from erpnext.tests.utils import ERPNextTestSuite + + +class TestWorkOrderConsumedMaterials(ERPNextTestSuite): + def run_report(self, **extra): + filters = frappe._dict( + { + "company": "_Test Company", + "from_date": add_days(nowdate(), -1), + "to_date": add_days(nowdate(), 1), + } + ) + filters.update(extra) + return execute(filters)[1] + + def make_manufactured_work_order(self, qty=2): + """Create a submitted WO, stock its raw materials, transfer and fully manufacture it.""" + wo = make_wo_order_test_record(production_item="_Test FG Item", qty=qty, company="_Test Company") + + for item in wo.required_items: + test_stock_entry.make_stock_entry( + item_code=item.item_code, + target=wo.wip_warehouse, + qty=item.required_qty, + basic_rate=100, + ) + + transfer = frappe.get_doc(make_stock_entry(wo.name, "Material Transfer for Manufacture", qty)) + transfer.insert() + transfer.submit() + + manufacture = frappe.get_doc(make_stock_entry(wo.name, "Manufacture", qty)) + manufacture.insert() + manufacture.submit() + + wo.reload() + return wo + + def get_wo_rows(self, data, work_order): + """The report blanks parent fields after the first raw-material row, so match by raw + material's parent work order instead of the (blanked) `name` column.""" + return [row for row in data if row.get("parent") == work_order] + + def test_consumed_materials_reported_after_manufacture(self): + wo = self.make_manufactured_work_order(qty=2) + + # fully producing the WO consumes exactly the required quantity of each raw material + self.assertEqual(wo.produced_qty, 2) + + data = self.run_report() + rows = self.get_wo_rows(data, wo.name) + + self.assertEqual(len(rows), len(wo.required_items)) + + reported = {row["raw_material_item_code"]: row for row in rows} + for item in wo.required_items: + row = reported[item.item_code] + self.assertEqual(row["required_qty"], item.required_qty) + self.assertEqual(row["transferred_qty"], item.required_qty) + self.assertEqual(row["consumed_qty"], item.required_qty) + # no over-consumption in a clean full manufacture + self.assertEqual(row["extra_consumed_qty"], 0.0) + self.assertEqual(row["returned_qty"], 0.0) + + # parent columns are populated on the first row only + first = rows[0] + self.assertEqual(first["status"], wo.status) + self.assertEqual(first["production_item"], "_Test FG Item") + self.assertEqual(first["qty"], 2) + self.assertEqual(first["produced_qty"], 2) + + def test_work_order_filter_scopes_output(self): + wo = self.make_manufactured_work_order(qty=1) + + data = self.run_report(name=wo.name) + + parents = {row.get("parent") for row in data} + self.assertEqual(parents, {wo.name}) + self.assertTrue(data) + + def test_draft_work_order_is_excluded(self): + # report only lists WOs in status In Process / Completed / Stopped + draft = make_wo_order_test_record( + production_item="_Test FG Item", qty=1, company="_Test Company", do_not_submit=True + ) + + data = self.run_report() + self.assertNotIn(draft.name, {row.get("parent") for row in data}) + + def test_date_range_filter_excludes_work_order(self): + wo = self.make_manufactured_work_order(qty=1) + + # a window that ends before the WO was created must not include it + data = self.run_report(from_date=add_days(nowdate(), -10), to_date=add_days(nowdate(), -5)) + self.assertNotIn(wo.name, {row.get("parent") for row in data})