Skip to content

Commit be20698

Browse files
mergify[bot]vorasmitrohitwaghchaure
authored
fix: allow disassemble stock entry without work order (backport #51761) (#51835)
* fix: allow disassemble stock entry without work order (#51761) * fix: allow disassemble stock entry without work order * fix: use existing functionality to load fg item * chore: better dict update (cherry picked from commit 8391911) # Conflicts: # erpnext/stock/doctype/stock_entry/test_stock_entry.py * chore: fix conflicts Removed unused test functions related to stock entry and sample retention. * chore: fix linters issue --------- Co-authored-by: Smit Vora <[email protected]> Co-authored-by: rohitwaghchaure <[email protected]>
1 parent 6df8090 commit be20698

File tree

2 files changed

+90
-4
lines changed

2 files changed

+90
-4
lines changed

erpnext/stock/doctype/stock_entry/stock_entry.py

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -646,7 +646,7 @@ def validate_warehouse(self):
646646
"Material Transfer for Manufacture",
647647
]
648648

649-
validate_for_manufacture = any([d.bom_no for d in self.get("items")])
649+
has_bom = any([d.bom_no for d in self.get("items")])
650650

651651
if self.purpose in source_mandatory and self.purpose not in target_mandatory:
652652
self.to_warehouse = None
@@ -675,7 +675,7 @@ def validate_warehouse(self):
675675
frappe.throw(_("Target warehouse is mandatory for row {0}").format(d.idx))
676676

677677
if self.purpose == "Manufacture":
678-
if validate_for_manufacture:
678+
if has_bom:
679679
if d.is_finished_item or d.is_scrap_item:
680680
d.s_warehouse = None
681681
if not d.t_warehouse:
@@ -685,6 +685,17 @@ def validate_warehouse(self):
685685
if not d.s_warehouse:
686686
frappe.throw(_("Source warehouse is mandatory for row {0}").format(d.idx))
687687

688+
if self.purpose == "Disassemble":
689+
if has_bom:
690+
if d.is_finished_item:
691+
d.t_warehouse = None
692+
if not d.s_warehouse:
693+
frappe.throw(_("Source warehouse is mandatory for row {0}").format(d.idx))
694+
else:
695+
d.s_warehouse = None
696+
if not d.t_warehouse:
697+
frappe.throw(_("Target warehouse is mandatory for row {0}").format(d.idx))
698+
688699
if cstr(d.s_warehouse) == cstr(d.t_warehouse) and self.purpose not in [
689700
"Material Transfer for Manufacture",
690701
"Material Transfer",
@@ -1907,9 +1918,12 @@ def set_items_for_stock_in(self):
19071918
def get_items_for_disassembly(self):
19081919
"""Get items for Disassembly Order"""
19091920

1910-
if not self.work_order:
1911-
frappe.throw(_("The Work Order is mandatory for Disassembly Order"))
1921+
if self.work_order:
1922+
return self._add_items_for_disassembly_from_work_order()
1923+
1924+
return self._add_items_for_disassembly_from_bom()
19121925

1926+
def _add_items_for_disassembly_from_work_order(self):
19131927
items = self.get_items_from_manufacture_entry()
19141928

19151929
s_warehouse = frappe.db.get_value("Work Order", self.work_order, "fg_warehouse")
@@ -1941,6 +1955,23 @@ def get_items_for_disassembly(self):
19411955
child_row.t_warehouse = row.s_warehouse
19421956
child_row.is_finished_item = 0 if row.is_finished_item else 1
19431957

1958+
def _add_items_for_disassembly_from_bom(self):
1959+
if not self.bom_no or not self.fg_completed_qty:
1960+
frappe.throw(_("BOM and Finished Good Quantity is mandatory for Disassembly"))
1961+
1962+
# Raw Materials
1963+
item_dict = self.get_bom_raw_materials(self.fg_completed_qty)
1964+
1965+
for item_row in item_dict.values():
1966+
item_row["to_warehouse"] = self.to_warehouse
1967+
item_row["from_warehouse"] = ""
1968+
item_row["is_finished_item"] = 0
1969+
1970+
self.add_to_stock_entry_detail(item_dict)
1971+
1972+
# Finished goods
1973+
self.load_items_from_bom()
1974+
19441975
def get_items_from_manufacture_entry(self):
19451976
return frappe.get_all(
19461977
"Stock Entry",
@@ -2164,6 +2195,7 @@ def load_items_from_bom(self):
21642195
expense_account = item.get("expense_account")
21652196
if not expense_account:
21662197
expense_account = frappe.get_cached_value("Company", self.company, "stock_adjustment_account")
2198+
21672199
args = {
21682200
"to_warehouse": to_warehouse,
21692201
"from_warehouse": "",
@@ -2176,6 +2208,15 @@ def load_items_from_bom(self):
21762208
"is_finished_item": 1,
21772209
}
21782210

2211+
if self.purpose == "Disassemble":
2212+
args.update(
2213+
{
2214+
"from_warehouse": self.from_warehouse,
2215+
"to_warehouse": "",
2216+
"qty": flt(self.fg_completed_qty),
2217+
}
2218+
)
2219+
21792220
if (
21802221
self.work_order
21812222
and self.pro_doc.has_batch_no

erpnext/stock/doctype/stock_entry/test_stock_entry.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2187,6 +2187,51 @@ def test_transferred_qty_in_material_transfer(self):
21872187
material_request.reload()
21882188
self.assertEqual(material_request.transfer_status, "Completed")
21892189

2190+
def test_disassemble_entry_without_wo(self):
2191+
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
2192+
2193+
fg_item = make_item("_Disassemble Mobile", properties={"is_stock_item": 1}).name
2194+
rm_item1 = make_item("_Disassemble Temper Glass", properties={"is_stock_item": 1}).name
2195+
rm_item2 = make_item("_Disassemble Battery", properties={"is_stock_item": 1}).name
2196+
warehouse = "_Test Warehouse - _TC"
2197+
2198+
# Stock up the FG item (what we'll disassemble)
2199+
make_stock_entry(item_code=fg_item, target=warehouse, qty=5, purpose="Material Receipt")
2200+
2201+
bom_no = make_bom(item=fg_item, raw_materials=[rm_item1, rm_item2]).name
2202+
2203+
se = make_stock_entry(item_code=fg_item, qty=1, purpose="Disassemble", do_not_save=True)
2204+
se.from_bom = 1
2205+
se.use_multi_level_bom = 1
2206+
se.bom_no = bom_no
2207+
se.fg_completed_qty = 1
2208+
se.from_warehouse = warehouse
2209+
se.to_warehouse = warehouse
2210+
2211+
se.get_items()
2212+
2213+
# Verify FG as source (being consumed)
2214+
fg_items = [d for d in se.items if d.is_finished_item]
2215+
self.assertEqual(len(fg_items), 1)
2216+
self.assertEqual(fg_items[0].item_code, fg_item)
2217+
self.assertEqual(fg_items[0].qty, 1)
2218+
self.assertEqual(fg_items[0].s_warehouse, warehouse)
2219+
self.assertFalse(fg_items[0].t_warehouse)
2220+
2221+
# Verify RM as target (being received)
2222+
rm_items = {d.item_code: d for d in se.items if not d.is_finished_item}
2223+
self.assertEqual(len(rm_items), 2)
2224+
self.assertIn(rm_item1, rm_items)
2225+
self.assertIn(rm_item2, rm_items)
2226+
self.assertEqual(rm_items[rm_item1].qty, 1)
2227+
self.assertEqual(rm_items[rm_item2].qty, 1)
2228+
self.assertEqual(rm_items[rm_item1].t_warehouse, warehouse)
2229+
self.assertFalse(rm_items[rm_item1].s_warehouse)
2230+
2231+
se.calculate_rate_and_amount()
2232+
se.save()
2233+
se.submit()
2234+
21902235

21912236
def make_serialized_item(**args):
21922237
args = frappe._dict(args)

0 commit comments

Comments
 (0)