From c69077cd3a09ab88b4295123615d0d12c6dda4d3 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Fri, 26 Jun 2026 10:32:58 +0530 Subject: [PATCH 1/3] test: add coverage for BOM Search report Co-Authored-By: Claude Opus 4.8 (1M context) --- .../report/bom_search/test_bom_search.py | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 erpnext/stock/report/bom_search/test_bom_search.py diff --git a/erpnext/stock/report/bom_search/test_bom_search.py b/erpnext/stock/report/bom_search/test_bom_search.py new file mode 100644 index 00000000000..b3a0d84a713 --- /dev/null +++ b/erpnext/stock/report/bom_search/test_bom_search.py @@ -0,0 +1,28 @@ +# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +import frappe + +from erpnext.stock.doctype.item.test_item import make_item +from erpnext.stock.report.bom_search.bom_search import execute +from erpnext.tests.utils import ERPNextTestSuite + + +class TestBomSearch(ERPNextTestSuite): + def run_report(self, **extra): + filters = frappe._dict({"search_sub_assemblies": 0}) + filters.update(extra) + return execute(filters)[1] + + def test_bom_found_by_contained_item(self): + raw_material = make_item(properties={"is_stock_item": 1}).name + finished_good = make_item(properties={"is_stock_item": 1}).name + + bom = frappe.get_doc(doctype="BOM", item=finished_good, company="_Test Company", currency="INR") + bom.append("items", {"item_code": raw_material, "qty": 1}) + bom.insert() + bom.submit() + + rows = self.run_report(item1=raw_material) + bom_names = [row[0] for row in rows] + self.assertIn(bom.name, bom_names) From f2d64d1a2a179e8fc010ad126e91dd0107fe43de Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Fri, 26 Jun 2026 13:26:14 +0530 Subject: [PATCH 2/3] test: reuse BootStrapTestData master data to reduce runtime Co-Authored-By: Claude Opus 4.8 (1M context) --- erpnext/stock/report/bom_search/test_bom_search.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/report/bom_search/test_bom_search.py b/erpnext/stock/report/bom_search/test_bom_search.py index b3a0d84a713..060b3a06d4c 100644 --- a/erpnext/stock/report/bom_search/test_bom_search.py +++ b/erpnext/stock/report/bom_search/test_bom_search.py @@ -3,7 +3,6 @@ import frappe -from erpnext.stock.doctype.item.test_item import make_item from erpnext.stock.report.bom_search.bom_search import execute from erpnext.tests.utils import ERPNextTestSuite @@ -15,8 +14,8 @@ class TestBomSearch(ERPNextTestSuite): return execute(filters)[1] def test_bom_found_by_contained_item(self): - raw_material = make_item(properties={"is_stock_item": 1}).name - finished_good = make_item(properties={"is_stock_item": 1}).name + raw_material = "_Test Item" + finished_good = "_Test FG Item" bom = frappe.get_doc(doctype="BOM", item=finished_good, company="_Test Company", currency="INR") bom.append("items", {"item_code": raw_material, "qty": 1}) From 0458446a06ce3b1ce66c4b92143c051c974e9362 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Fri, 26 Jun 2026 14:57:26 +0530 Subject: [PATCH 3/3] test: cover search_sub_assemblies filter in BOM Search report --- .../report/bom_search/test_bom_search.py | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/erpnext/stock/report/bom_search/test_bom_search.py b/erpnext/stock/report/bom_search/test_bom_search.py index 060b3a06d4c..344663f594f 100644 --- a/erpnext/stock/report/bom_search/test_bom_search.py +++ b/erpnext/stock/report/bom_search/test_bom_search.py @@ -25,3 +25,27 @@ class TestBomSearch(ERPNextTestSuite): rows = self.run_report(item1=raw_material) bom_names = [row[0] for row in rows] self.assertIn(bom.name, bom_names) + + def test_search_sub_assemblies_finds_top_level_bom(self): + raw_material = "_Test Item" + sub_assembly = "_Test FG Item" # its default BOM contains _Test Item + finished_good = "_Test FG Item 2" + + # top-level BOM uses the sub-assembly (it does NOT list the raw material directly). + # the bootstrap sub-assembly BOM is in USD, so match its currency. + top_bom = frappe.get_doc( + doctype="BOM", item=finished_good, company="_Test Company", currency="USD", conversion_rate=1 + ) + top_bom.append("items", {"item_code": sub_assembly, "qty": 1}) + top_bom.insert() + top_bom.submit() + + # search_sub_assemblies=1 scans the exploded tree, so the raw material buried in the + # sub-assembly surfaces the top-level BOM + deep = [row[0] for row in self.run_report(search_sub_assemblies=1, item1=raw_material)] + self.assertIn(top_bom.name, deep) + + # search_sub_assemblies=0 scans only direct BOM Items, so the top-level BOM (which lists + # the sub-assembly, not the raw material) is not returned for the raw material + direct = [row[0] for row in self.run_report(search_sub_assemblies=0, item1=raw_material)] + self.assertNotIn(top_bom.name, direct)