From 3e0d0b2d68372a629ffd489882024e9b35736772 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Sat, 8 Aug 2026 15:58:07 +0530 Subject: [PATCH] fix: derive operation FG items before material expansion, keep the final one the BOM's item The finished_good derivation ran in validate_semi_finished_goods, after set_materials_based_on_operation_bom had already expanded operation BOM materials. A single-pass insert-and-submit (API or import) with bom_no set but finished_good empty skipped the expansion, persisting a submitted BOM without the referenced components. The derivation also let a final operation inherit another item from its bom_no, so downstream job cards would produce the wrong item. Move the derivation into set_operation_finished_goods, called before the expansion, prefer the BOM's own item for the final operation, and reject a final operation whose FG item is not the BOM's item. (cherry picked from commit 1e2e87daaca9d5df11b732a80699b1ae3b896631) # Conflicts: # erpnext/manufacturing/doctype/bom/bom.py --- erpnext/manufacturing/doctype/bom/bom.py | 29 +++++++++++++++++++----- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 54fe063fcf0..e33e81d5aaa 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -282,6 +282,7 @@ class BOM(WebsiteGenerator): self.clear_inspection() self.validate_main_item() self.validate_currency() + self.set_operation_finished_goods() self.set_materials_based_on_operation_bom() self.set_conversion_rate() self.set_plc_conversion_rate() @@ -304,8 +305,23 @@ class BOM(WebsiteGenerator): self.set_fg_cost_allocation() self.validate_total_cost_allocation() +<<<<<<< HEAD if self.docstatus == 1: self.validate_raw_materials_of_operation() +======= + def set_operation_finished_goods(self): + """Fill each operation's FG item where it is unambiguous: the final operation produces + this BOM's item, an operation with a BOM produces that BOM's item. Runs before + set_materials_based_on_operation_bom so derived rows get their materials expanded.""" + if not self.track_semi_finished_goods: + return + + for row in self.operations: + if row.is_final_finished_good and not row.finished_good: + row.finished_good = self.item + elif row.bom_no and not row.finished_good: + row.finished_good = frappe.get_cached_value("BOM", row.bom_no, "item") +>>>>>>> 1e2e87daac (fix: derive operation FG items before material expansion, keep the final one the BOM's item) def validate_semi_finished_goods(self): if not self.track_semi_finished_goods or not self.operations: @@ -313,12 +329,6 @@ class BOM(WebsiteGenerator): fg_items = [] for row in self.operations: - if row.bom_no and not row.finished_good: - row.finished_good = frappe.get_cached_value("BOM", row.bom_no, "item") - - if row.is_final_finished_good and not row.finished_good: - row.finished_good = self.item - if not row.finished_good: frappe.throw( _( @@ -329,6 +339,13 @@ class BOM(WebsiteGenerator): if not row.is_final_finished_good: continue + if row.finished_good != self.item: + frappe.throw( + _( + "Row #{0}: The operation {1} has 'Is Final Finished Good' checked, so its FG / Semi FG Item must be {2}." + ).format(row.idx, bold(row.operation), bold(self.item)), + ) + fg_items.append(row.finished_good) if not fg_items: