diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index 631baa4229c..374caf369ea 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -2519,13 +2519,11 @@ class TestWorkOrder(FrappeTestCase): scrap_item = make_item("Test Scrap for Multi Batch Disassembly", {"is_stock_item": 1}).name fg_item = make_item("Test FG for Multi Batch Disassembly", {"is_stock_item": 1}).name bom = make_bom( - item=fg_item, - quantity=1, - raw_materials=[raw_item1, raw_item2], - rm_qty=2, - scrap_items=[scrap_item], - scrap_qty=10, + item=fg_item, quantity=1, raw_materials=[raw_item1, raw_item2], rm_qty=2, do_not_submit=True ) + # add scrap item + bom.append("scrap_items", {"item_code": scrap_item, "stock_qty": 10}) + bom.submit() # Create WO wo = make_wo_order_test_record(production_item=fg_item, qty=10, bom_no=bom.name, status="Not Started") @@ -2611,16 +2609,15 @@ class TestWorkOrder(FrappeTestCase): fg_item_row = next((i for i in stock_entry.items if i.item_code == fg_item), None) self.assertEqual(fg_item_row.qty, disassemble_qty) - # Secondary/Scrap item: should be taken from scrap warehouse in disassembly + # Scrap item: should be taken from scrap warehouse in disassembly scrap_row = next((i for i in stock_entry.items if i.item_code == scrap_item), None) self.assertIsNotNone(scrap_row) - self.assertEqual(scrap_row.type, "Scrap") + self.assertEqual(scrap_row.is_scrap_item, 1) self.assertTrue(scrap_row.s_warehouse) self.assertFalse(scrap_row.t_warehouse) self.assertEqual(scrap_row.s_warehouse, wo.scrap_warehouse) - # BOM has scrap_qty=10/FG but also process_loss_per=10%, so actual scrap per FG = 9 - # Total produced = 9*3 + 9*7 = 90, disassemble 4/10 → 36 - self.assertEqual(scrap_row.qty, 36) + # BOM has scrap_qty=10/FG, total produced = 10*10 = 100, disassemble 4/10 → 40 + self.assertEqual(scrap_row.qty, 40) # RM quantities for bom_item in bom.items: @@ -2665,11 +2662,11 @@ class TestWorkOrder(FrappeTestCase): bom_scrap_row = next((i for i in bom_se.items if i.item_code == scrap_item), None) self.assertIsNotNone(bom_scrap_row, "Scrap item must appear in BOM-path disassembly") - # Without fix 3: qty = 10 * 2 = 20; with fix 3 (process_loss_per=10%): qty = 9 * 2 = 18 + # v15: BOM scrap_qty=10/FG, no process_loss_per field → qty = 10 * 2 = 20 self.assertEqual( bom_scrap_row.qty, - 18, - f"BOM-path disassembly must apply process_loss_per; expected 18, got {bom_scrap_row.qty}", + 20, + f"BOM-path disassembly scrap qty mismatch; expected 20, got {bom_scrap_row.qty}", ) def test_disassembly_with_additional_rm_not_in_bom(self): diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index e2b4309cd9a..f7b49839fdc 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -784,7 +784,7 @@ class StockEntry(StockController): if self.purpose == "Disassemble": if has_bom: - if d.is_finished_item or d.type or d.is_legacy_scrap_item: + if d.is_finished_item or d.is_scrap_item: d.t_warehouse = None if not d.s_warehouse: frappe.throw(_("Source warehouse is mandatory for row {0}").format(d.idx)) @@ -2136,9 +2136,7 @@ class StockEntry(StockController): "s_warehouse": s_warehouse, "t_warehouse": t_warehouse, "is_finished_item": source_row.is_finished_item, - "type": source_row.type, - "is_legacy_scrap_item": source_row.is_legacy_scrap_item, - "bom_secondary_item": source_row.bom_secondary_item, + "is_scrap_item": source_row.is_scrap_item, "bom_no": source_row.bom_no, # batch and serial bundles built on submit "use_serial_batch_fields": 1 if (source_row.batch_no or source_row.serial_no) else 0, @@ -2168,9 +2166,9 @@ class StockEntry(StockController): self.add_to_stock_entry_detail(item_dict) - # Secondary/Scrap items (reverse of what set_secondary_items does for Manufacture) - secondary_items = self.get_secondary_items(self.fg_completed_qty) - if secondary_items: + # Scrap items (reverse: take scrap FROM scrap warehouse instead of producing TO it) + scrap_items = self.get_bom_scrap_material(self.fg_completed_qty) + if scrap_items: scrap_warehouse = self.from_warehouse if self.work_order: wo_values = frappe.db.get_value( @@ -2178,18 +2176,12 @@ class StockEntry(StockController): ) scrap_warehouse = wo_values.scrap_warehouse or scrap_warehouse or wo_values.fg_warehouse - for item in secondary_items.values(): + for item in scrap_items.values(): item["from_warehouse"] = scrap_warehouse item["to_warehouse"] = "" item["is_finished_item"] = 0 - if item.get("process_loss_per"): - item["qty"] -= flt( - item["qty"] * (item["process_loss_per"] / 100), - self.precision("fg_completed_qty"), - ) - - self.add_to_stock_entry_detail(secondary_items, bom_no=self.bom_no) + self.add_to_stock_entry_detail(scrap_items, bom_no=self.bom_no) # Finished goods self.load_items_from_bom() @@ -2208,9 +2200,7 @@ class StockEntry(StockController): SED.basic_rate, SED.conversion_factor, SED.is_finished_item, - SED.type, - SED.is_legacy_scrap_item, - SED.bom_secondary_item, + SED.is_scrap_item, SED.batch_no, SED.serial_no, SED.use_serial_batch_fields,