test: additional items in stock entry considered with disassembly

(cherry picked from commit d32977e3a9)
This commit is contained in:
Smit Vora
2026-03-31 15:18:42 +05:30
committed by Mergify
parent 4c0ebee15b
commit 8444778f74

View File

@@ -2639,17 +2639,16 @@ class TestWorkOrder(ERPNextTestSuite):
def test_disassembly_with_additional_rm_not_in_bom(self): def test_disassembly_with_additional_rm_not_in_bom(self):
""" """
Test that disassembly correctly handles additional raw materials that were Test that SE-linked disassembly includes additional raw materials
manually added during manufacturing (not part of the BOM). that were manually added during manufacturing (not part of the BOM).
Scenario: Scenario:
1. Create Work Order for 10 units with 2 raw materials in BOM 1. Create Work Order for 10 units with 2 raw materials in BOM
2. Transfer raw materials for manufacture 2. Transfer raw materials for manufacture
3. Manufacture in 2 parts (3 units, then 7 units) 3. Manufacture in 2 parts (3 units, then 7 units)
4. In each manufacture entry, manually add an extra consumable item 4. In each manufacture entry, manually add an extra consumable item
(not in BOM) in proportion to the manufactured qty 5. Disassemble 3 units linked to first manufacture entry
5. Create Disassembly for 4 units 6. Verify additional RM is included with correct proportional qty from SE1
6. Verify that the additional RM is included in disassembly with proportional qty
""" """
from erpnext.stock.doctype.stock_entry.test_stock_entry import ( from erpnext.stock.doctype.stock_entry.test_stock_entry import (
make_stock_entry as make_stock_entry_test_record, make_stock_entry as make_stock_entry_test_record,
@@ -2685,9 +2684,8 @@ class TestWorkOrder(ERPNextTestSuite):
se_for_material_transfer.save() se_for_material_transfer.save()
se_for_material_transfer.submit() se_for_material_transfer.submit()
# First Manufacture Entry - 3 units # First Manufacture Entry - 3 units with additional RM
se_manufacture1 = frappe.get_doc(make_stock_entry(wo.name, "Manufacture", 3)) se_manufacture1 = frappe.get_doc(make_stock_entry(wo.name, "Manufacture", 3))
# Additional RM
se_manufacture1.append( se_manufacture1.append(
"items", "items",
{ {
@@ -2700,9 +2698,8 @@ class TestWorkOrder(ERPNextTestSuite):
se_manufacture1.save() se_manufacture1.save()
se_manufacture1.submit() se_manufacture1.submit()
# Second Manufacture Entry - 7 units # Second Manufacture Entry - 7 units with additional RM
se_manufacture2 = frappe.get_doc(make_stock_entry(wo.name, "Manufacture", 7)) se_manufacture2 = frappe.get_doc(make_stock_entry(wo.name, "Manufacture", 7))
# AAdditional RM
se_manufacture2.append( se_manufacture2.append(
"items", "items",
{ {
@@ -2718,13 +2715,15 @@ class TestWorkOrder(ERPNextTestSuite):
wo.reload() wo.reload()
self.assertEqual(wo.produced_qty, 10) self.assertEqual(wo.produced_qty, 10)
# Disassembly for 4 units # Disassemble 3 units linked to first manufacture entry
disassemble_qty = 4 disassemble_qty = 3
stock_entry = frappe.get_doc(make_stock_entry(wo.name, "Disassemble", disassemble_qty)) stock_entry = frappe.get_doc(
make_stock_entry(wo.name, "Disassemble", disassemble_qty, source_stock_entry=se_manufacture1.name)
)
stock_entry.save() stock_entry.save()
stock_entry.submit() stock_entry.submit()
# No duplicate # No duplicates
item_counts = {} item_counts = {}
for item in stock_entry.items: for item in stock_entry.items:
item_code = item.item_code item_code = item.item_code
@@ -2737,16 +2736,15 @@ class TestWorkOrder(ERPNextTestSuite):
f"Found duplicate items in disassembly stock entry: {duplicates}", f"Found duplicate items in disassembly stock entry: {duplicates}",
) )
# Additional RM qty # Additional RM should be included — qty proportional to SE1 (3 units -> 3 additional RM)
additional_rm_row = next((i for i in stock_entry.items if i.item_code == additional_rm), None) additional_rm_row = next((i for i in stock_entry.items if i.item_code == additional_rm), None)
self.assertIsNotNone( self.assertIsNotNone(
additional_rm_row, additional_rm_row,
f"Additional raw material {additional_rm} not found in disassembly", f"Additional raw material {additional_rm} not found in disassembly",
) )
# intentional full reversal as not part of BOM # SE1 had 3 additional RM for 3 manufactured units, disassembling all 3
# eg: dies or consumables used during manufacturing expected_additional_rm_qty = 3
expected_additional_rm_qty = 3 + 7
self.assertAlmostEqual( self.assertAlmostEqual(
additional_rm_row.qty, additional_rm_row.qty,
expected_additional_rm_qty, expected_additional_rm_qty,
@@ -2754,7 +2752,7 @@ class TestWorkOrder(ERPNextTestSuite):
msg=f"Additional RM qty mismatch: expected {expected_additional_rm_qty}, got {additional_rm_row.qty}", msg=f"Additional RM qty mismatch: expected {expected_additional_rm_qty}, got {additional_rm_row.qty}",
) )
# RM qty # BOM RM qty — scaled from SE1's rows
for bom_item in bom.items: for bom_item in bom.items:
expected_qty = (bom_item.qty / bom.quantity) * disassemble_qty expected_qty = (bom_item.qty / bom.quantity) * disassemble_qty
rm_row = next((i for i in stock_entry.items if i.item_code == bom_item.item_code), None) rm_row = next((i for i in stock_entry.items if i.item_code == bom_item.item_code), None)
@@ -2770,6 +2768,7 @@ class TestWorkOrder(ERPNextTestSuite):
fg_item_row = next((i for i in stock_entry.items if i.item_code == fg_item), None) 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) self.assertEqual(fg_item_row.qty, disassemble_qty)
# FG + 2 BOM RM + 1 additional RM = 4 items
expected_items = 4 expected_items = 4
self.assertEqual( self.assertEqual(
len(stock_entry.items), len(stock_entry.items),
@@ -2777,6 +2776,11 @@ class TestWorkOrder(ERPNextTestSuite):
f"Expected {expected_items} items, found {len(stock_entry.items)}", f"Expected {expected_items} items, found {len(stock_entry.items)}",
) )
# Verify traceability
for item in stock_entry.items:
self.assertEqual(item.against_stock_entry, se_manufacture1.name)
self.assertTrue(item.ste_detail)
def test_components_alternate_item_for_bom_based_manufacture_entry(self): def test_components_alternate_item_for_bom_based_manufacture_entry(self):
frappe.db.set_single_value("Manufacturing Settings", "backflush_raw_materials_based_on", "BOM") frappe.db.set_single_value("Manufacturing Settings", "backflush_raw_materials_based_on", "BOM")
frappe.db.set_single_value("Manufacturing Settings", "validate_components_quantities_per_bom", 1) frappe.db.set_single_value("Manufacturing Settings", "validate_components_quantities_per_bom", 1)