From 00880eb657f3b31ec29ea4f0d4ce18d828ed56b2 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Mon, 8 Jun 2026 13:17:32 +0530 Subject: [PATCH] fix: disallow BOM finished good item in secondary items table (#55710) The FG item produced by a BOM should not also appear as a secondary item (Co-Product/By-Product/Scrap/Additional Finished Good). When an Additional Finished Good shared the main FG's item code, the resulting Stock Entry ended up with two rows of the same item carrying different valuation rates. Validate against it instead, exempting legacy rows so migrated BOMs can still be re-saved. Co-authored-by: Claude Opus 4.8 --- erpnext/manufacturing/doctype/bom/bom.py | 7 ++++++ erpnext/manufacturing/doctype/bom/test_bom.py | 24 +++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 95f3aae57a1..d0a27613780 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -366,6 +366,13 @@ class BOM(WebsiteGenerator): def validate_secondary_items(self): for item in self.secondary_items: + if not item.is_legacy and item.item_code == self.item: + frappe.throw( + _( + "Row #{0}: Finished Good Item {1} cannot be added in the Secondary Items table." + ).format(item.idx, get_link_to_form("Item", item.item_code)) + ) + if not item.qty: frappe.throw( _("Row #{0}: Quantity should be greater than 0 for {1} Item {2}").format( diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py index 3335eb5dec7..041a3d3899e 100644 --- a/erpnext/manufacturing/doctype/bom/test_bom.py +++ b/erpnext/manufacturing/doctype/bom/test_bom.py @@ -444,6 +444,30 @@ class TestBOM(ERPNextTestSuite): # Items with whole UOMs can't be PL Items self.assertRaises(frappe.ValidationError, bom_doc.submit) + @timeout + def test_fg_item_not_allowed_in_secondary_items(self): + fg_item = make_item(properties={"is_stock_item": 1, "valuation_rate": 100}).name + rm_item = make_item(properties={"is_stock_item": 1, "valuation_rate": 100}).name + + bom_doc = frappe.new_doc("BOM") + bom_doc.item = fg_item + bom_doc.quantity = 1 + bom_doc.company = "_Test Company" + bom_doc.currency = "INR" + bom_doc.append("items", {"item_code": rm_item, "qty": 1, "rate": 100.0}) + bom_doc.append( + "secondary_items", + { + "item_code": fg_item, + "secondary_item_type": "Additional Finished Good", + "qty": 1, + "cost_allocation_per": 10, + }, + ) + + # FG item of the BOM cannot also be a secondary item + self.assertRaises(frappe.ValidationError, bom_doc.save) + @timeout def test_bom_item_query(self): query = partial(