mirror of
https://github.com/frappe/erpnext.git
synced 2026-02-17 08:35:00 +00:00
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 83919119f8)
Co-authored-by: Smit Vora <smitvora203@gmail.com>
This commit is contained in:
@@ -724,7 +724,7 @@ class StockEntry(StockController, SubcontractingInwardController):
|
||||
"Subcontracting Return",
|
||||
]
|
||||
|
||||
validate_for_manufacture = any([d.bom_no for d in self.get("items")])
|
||||
has_bom = any([d.bom_no for d in self.get("items")])
|
||||
|
||||
if self.purpose in source_mandatory and self.purpose not in target_mandatory:
|
||||
self.to_warehouse = None
|
||||
@@ -753,7 +753,7 @@ class StockEntry(StockController, SubcontractingInwardController):
|
||||
frappe.throw(_("Target warehouse is mandatory for row {0}").format(d.idx))
|
||||
|
||||
if self.purpose == "Manufacture":
|
||||
if validate_for_manufacture:
|
||||
if has_bom:
|
||||
if d.is_finished_item or d.is_scrap_item:
|
||||
d.s_warehouse = None
|
||||
if not d.t_warehouse:
|
||||
@@ -763,6 +763,17 @@ class StockEntry(StockController, SubcontractingInwardController):
|
||||
if not d.s_warehouse:
|
||||
frappe.throw(_("Source warehouse is mandatory for row {0}").format(d.idx))
|
||||
|
||||
if self.purpose == "Disassemble":
|
||||
if has_bom:
|
||||
if d.is_finished_item:
|
||||
d.t_warehouse = None
|
||||
if not d.s_warehouse:
|
||||
frappe.throw(_("Source warehouse is mandatory for row {0}").format(d.idx))
|
||||
else:
|
||||
d.s_warehouse = None
|
||||
if not d.t_warehouse:
|
||||
frappe.throw(_("Target warehouse is mandatory for row {0}").format(d.idx))
|
||||
|
||||
if cstr(d.s_warehouse) == cstr(d.t_warehouse) and self.purpose not in [
|
||||
"Material Transfer for Manufacture",
|
||||
"Material Transfer",
|
||||
@@ -2162,9 +2173,12 @@ class StockEntry(StockController, SubcontractingInwardController):
|
||||
def get_items_for_disassembly(self):
|
||||
"""Get items for Disassembly Order"""
|
||||
|
||||
if not self.work_order:
|
||||
frappe.throw(_("The Work Order is mandatory for Disassembly Order"))
|
||||
if self.work_order:
|
||||
return self._add_items_for_disassembly_from_work_order()
|
||||
|
||||
return self._add_items_for_disassembly_from_bom()
|
||||
|
||||
def _add_items_for_disassembly_from_work_order(self):
|
||||
items = self.get_items_from_manufacture_entry()
|
||||
|
||||
s_warehouse = frappe.db.get_value("Work Order", self.work_order, "fg_warehouse")
|
||||
@@ -2196,6 +2210,23 @@ class StockEntry(StockController, SubcontractingInwardController):
|
||||
child_row.t_warehouse = row.s_warehouse
|
||||
child_row.is_finished_item = 0 if row.is_finished_item else 1
|
||||
|
||||
def _add_items_for_disassembly_from_bom(self):
|
||||
if not self.bom_no or not self.fg_completed_qty:
|
||||
frappe.throw(_("BOM and Finished Good Quantity is mandatory for Disassembly"))
|
||||
|
||||
# Raw Materials
|
||||
item_dict = self.get_bom_raw_materials(self.fg_completed_qty)
|
||||
|
||||
for item_row in item_dict.values():
|
||||
item_row["to_warehouse"] = self.to_warehouse
|
||||
item_row["from_warehouse"] = ""
|
||||
item_row["is_finished_item"] = 0
|
||||
|
||||
self.add_to_stock_entry_detail(item_dict)
|
||||
|
||||
# Finished goods
|
||||
self.load_items_from_bom()
|
||||
|
||||
def get_items_from_manufacture_entry(self):
|
||||
return frappe.get_all(
|
||||
"Stock Entry",
|
||||
@@ -2560,6 +2591,7 @@ class StockEntry(StockController, SubcontractingInwardController):
|
||||
expense_account = item.get("expense_account")
|
||||
if not expense_account:
|
||||
expense_account = frappe.get_cached_value("Company", self.company, "stock_adjustment_account")
|
||||
|
||||
args = {
|
||||
"to_warehouse": to_warehouse,
|
||||
"from_warehouse": "",
|
||||
@@ -2573,6 +2605,15 @@ class StockEntry(StockController, SubcontractingInwardController):
|
||||
"sample_quantity": item.get("sample_quantity"),
|
||||
}
|
||||
|
||||
if self.purpose == "Disassemble":
|
||||
args.update(
|
||||
{
|
||||
"from_warehouse": self.from_warehouse,
|
||||
"to_warehouse": "",
|
||||
"qty": flt(self.fg_completed_qty),
|
||||
}
|
||||
)
|
||||
|
||||
if (
|
||||
self.work_order
|
||||
and self.pro_doc.has_batch_no
|
||||
|
||||
@@ -2277,6 +2277,51 @@ class TestStockEntry(IntegrationTestCase):
|
||||
se.save()
|
||||
se.submit()
|
||||
|
||||
def test_disassemble_entry_without_wo(self):
|
||||
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
|
||||
|
||||
fg_item = make_item("_Disassemble Mobile", properties={"is_stock_item": 1}).name
|
||||
rm_item1 = make_item("_Disassemble Temper Glass", properties={"is_stock_item": 1}).name
|
||||
rm_item2 = make_item("_Disassemble Battery", properties={"is_stock_item": 1}).name
|
||||
warehouse = "_Test Warehouse - _TC"
|
||||
|
||||
# Stock up the FG item (what we'll disassemble)
|
||||
make_stock_entry(item_code=fg_item, target=warehouse, qty=5, purpose="Material Receipt")
|
||||
|
||||
bom_no = make_bom(item=fg_item, raw_materials=[rm_item1, rm_item2]).name
|
||||
|
||||
se = make_stock_entry(item_code=fg_item, qty=1, purpose="Disassemble", do_not_save=True)
|
||||
se.from_bom = 1
|
||||
se.use_multi_level_bom = 1
|
||||
se.bom_no = bom_no
|
||||
se.fg_completed_qty = 1
|
||||
se.from_warehouse = warehouse
|
||||
se.to_warehouse = warehouse
|
||||
|
||||
se.get_items()
|
||||
|
||||
# Verify FG as source (being consumed)
|
||||
fg_items = [d for d in se.items if d.is_finished_item]
|
||||
self.assertEqual(len(fg_items), 1)
|
||||
self.assertEqual(fg_items[0].item_code, fg_item)
|
||||
self.assertEqual(fg_items[0].qty, 1)
|
||||
self.assertEqual(fg_items[0].s_warehouse, warehouse)
|
||||
self.assertFalse(fg_items[0].t_warehouse)
|
||||
|
||||
# Verify RM as target (being received)
|
||||
rm_items = {d.item_code: d for d in se.items if not d.is_finished_item}
|
||||
self.assertEqual(len(rm_items), 2)
|
||||
self.assertIn(rm_item1, rm_items)
|
||||
self.assertIn(rm_item2, rm_items)
|
||||
self.assertEqual(rm_items[rm_item1].qty, 1)
|
||||
self.assertEqual(rm_items[rm_item2].qty, 1)
|
||||
self.assertEqual(rm_items[rm_item1].t_warehouse, warehouse)
|
||||
self.assertFalse(rm_items[rm_item1].s_warehouse)
|
||||
|
||||
se.calculate_rate_and_amount()
|
||||
se.save()
|
||||
se.submit()
|
||||
|
||||
@IntegrationTestCase.change_settings(
|
||||
"Stock Settings", {"sample_retention_warehouse": "_Test Warehouse 1 - _TC"}
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user