mirror of
https://github.com/frappe/erpnext.git
synced 2026-02-18 00:55:02 +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",
|
"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:
|
if self.purpose in source_mandatory and self.purpose not in target_mandatory:
|
||||||
self.to_warehouse = None
|
self.to_warehouse = None
|
||||||
@@ -753,7 +753,7 @@ class StockEntry(StockController, SubcontractingInwardController):
|
|||||||
frappe.throw(_("Target warehouse is mandatory for row {0}").format(d.idx))
|
frappe.throw(_("Target warehouse is mandatory for row {0}").format(d.idx))
|
||||||
|
|
||||||
if self.purpose == "Manufacture":
|
if self.purpose == "Manufacture":
|
||||||
if validate_for_manufacture:
|
if has_bom:
|
||||||
if d.is_finished_item or d.is_scrap_item:
|
if d.is_finished_item or d.is_scrap_item:
|
||||||
d.s_warehouse = None
|
d.s_warehouse = None
|
||||||
if not d.t_warehouse:
|
if not d.t_warehouse:
|
||||||
@@ -763,6 +763,17 @@ class StockEntry(StockController, SubcontractingInwardController):
|
|||||||
if not d.s_warehouse:
|
if not d.s_warehouse:
|
||||||
frappe.throw(_("Source warehouse is mandatory for row {0}").format(d.idx))
|
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 [
|
if cstr(d.s_warehouse) == cstr(d.t_warehouse) and self.purpose not in [
|
||||||
"Material Transfer for Manufacture",
|
"Material Transfer for Manufacture",
|
||||||
"Material Transfer",
|
"Material Transfer",
|
||||||
@@ -2162,9 +2173,12 @@ class StockEntry(StockController, SubcontractingInwardController):
|
|||||||
def get_items_for_disassembly(self):
|
def get_items_for_disassembly(self):
|
||||||
"""Get items for Disassembly Order"""
|
"""Get items for Disassembly Order"""
|
||||||
|
|
||||||
if not self.work_order:
|
if self.work_order:
|
||||||
frappe.throw(_("The Work Order is mandatory for Disassembly 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()
|
items = self.get_items_from_manufacture_entry()
|
||||||
|
|
||||||
s_warehouse = frappe.db.get_value("Work Order", self.work_order, "fg_warehouse")
|
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.t_warehouse = row.s_warehouse
|
||||||
child_row.is_finished_item = 0 if row.is_finished_item else 1
|
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):
|
def get_items_from_manufacture_entry(self):
|
||||||
return frappe.get_all(
|
return frappe.get_all(
|
||||||
"Stock Entry",
|
"Stock Entry",
|
||||||
@@ -2560,6 +2591,7 @@ class StockEntry(StockController, SubcontractingInwardController):
|
|||||||
expense_account = item.get("expense_account")
|
expense_account = item.get("expense_account")
|
||||||
if not expense_account:
|
if not expense_account:
|
||||||
expense_account = frappe.get_cached_value("Company", self.company, "stock_adjustment_account")
|
expense_account = frappe.get_cached_value("Company", self.company, "stock_adjustment_account")
|
||||||
|
|
||||||
args = {
|
args = {
|
||||||
"to_warehouse": to_warehouse,
|
"to_warehouse": to_warehouse,
|
||||||
"from_warehouse": "",
|
"from_warehouse": "",
|
||||||
@@ -2573,6 +2605,15 @@ class StockEntry(StockController, SubcontractingInwardController):
|
|||||||
"sample_quantity": item.get("sample_quantity"),
|
"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 (
|
if (
|
||||||
self.work_order
|
self.work_order
|
||||||
and self.pro_doc.has_batch_no
|
and self.pro_doc.has_batch_no
|
||||||
|
|||||||
@@ -2277,6 +2277,51 @@ class TestStockEntry(IntegrationTestCase):
|
|||||||
se.save()
|
se.save()
|
||||||
se.submit()
|
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(
|
@IntegrationTestCase.change_settings(
|
||||||
"Stock Settings", {"sample_retention_warehouse": "_Test Warehouse 1 - _TC"}
|
"Stock Settings", {"sample_retention_warehouse": "_Test Warehouse 1 - _TC"}
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user