mirror of
https://github.com/frappe/erpnext.git
synced 2026-04-12 11:25:09 +00:00
fix: avg stock entries for disassembly from WO
(cherry picked from commit 71fd18bdf9)
This commit is contained in:
@@ -2618,7 +2618,9 @@ class TestWorkOrder(FrappeTestCase):
|
|||||||
self.assertTrue(scrap_row.s_warehouse)
|
self.assertTrue(scrap_row.s_warehouse)
|
||||||
self.assertFalse(scrap_row.t_warehouse)
|
self.assertFalse(scrap_row.t_warehouse)
|
||||||
self.assertEqual(scrap_row.s_warehouse, wo.scrap_warehouse)
|
self.assertEqual(scrap_row.s_warehouse, wo.scrap_warehouse)
|
||||||
self.assertEqual(scrap_row.qty, 40)
|
# 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)
|
||||||
|
|
||||||
# RM quantities
|
# RM quantities
|
||||||
for bom_item in bom.items:
|
for bom_item in bom.items:
|
||||||
|
|||||||
@@ -2060,7 +2060,7 @@ class StockEntry(StockController):
|
|||||||
|
|
||||||
Priority:
|
Priority:
|
||||||
1. From a specific Manufacture Stock Entry (exact reversal)
|
1. From a specific Manufacture Stock Entry (exact reversal)
|
||||||
2. From Work Order required_items (reflects WO changes)
|
2. From Work Order Manufacture Stock Entries (averaged reversal)
|
||||||
3. From BOM (standalone disassembly)
|
3. From BOM (standalone disassembly)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -2094,104 +2094,79 @@ class StockEntry(StockController):
|
|||||||
_("Source Stock Entry {0} has no finished goods quantity").format(self.source_stock_entry)
|
_("Source Stock Entry {0} has no finished goods quantity").format(self.source_stock_entry)
|
||||||
)
|
)
|
||||||
|
|
||||||
scale_factor = flt(self.fg_completed_qty) / flt(source_fg_qty)
|
disassemble_qty = flt(self.fg_completed_qty)
|
||||||
|
scale_factor = disassemble_qty / flt(source_fg_qty)
|
||||||
|
|
||||||
for source_row in self.get_items_from_manufacture_stock_entry(self.source_stock_entry):
|
self._append_disassembly_row_from_source(
|
||||||
if source_row.is_finished_item:
|
disassemble_qty=disassemble_qty,
|
||||||
qty = flt(self.fg_completed_qty)
|
scale_factor=scale_factor,
|
||||||
s_warehouse = self.from_warehouse or source_row.t_warehouse
|
source_stock_entry=self.source_stock_entry,
|
||||||
t_warehouse = ""
|
)
|
||||||
elif source_row.s_warehouse:
|
|
||||||
# RM: was consumed FROM s_warehouse → return TO s_warehouse
|
|
||||||
qty = flt(source_row.qty * scale_factor)
|
|
||||||
s_warehouse = ""
|
|
||||||
t_warehouse = self.to_warehouse or source_row.s_warehouse
|
|
||||||
else:
|
|
||||||
# Scrap/secondary: was produced TO t_warehouse → take FROM t_warehouse
|
|
||||||
qty = flt(source_row.qty * scale_factor)
|
|
||||||
s_warehouse = source_row.t_warehouse
|
|
||||||
t_warehouse = ""
|
|
||||||
|
|
||||||
use_serial_batch_fields = 1 if (source_row.batch_no or source_row.serial_no) else 0
|
|
||||||
|
|
||||||
self.append(
|
|
||||||
"items",
|
|
||||||
{
|
|
||||||
"item_code": source_row.item_code,
|
|
||||||
"item_name": source_row.item_name,
|
|
||||||
"description": source_row.description,
|
|
||||||
"stock_uom": source_row.stock_uom,
|
|
||||||
"uom": source_row.uom,
|
|
||||||
"conversion_factor": source_row.conversion_factor,
|
|
||||||
"basic_rate": source_row.basic_rate,
|
|
||||||
"qty": qty,
|
|
||||||
"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,
|
|
||||||
"against_stock_entry": self.source_stock_entry,
|
|
||||||
"ste_detail": source_row.name,
|
|
||||||
# batch and serial bundles built on submit
|
|
||||||
"use_serial_batch_fields": use_serial_batch_fields,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
def _add_items_for_disassembly_from_work_order(self):
|
def _add_items_for_disassembly_from_work_order(self):
|
||||||
wo = frappe.get_doc("Work Order", self.work_order)
|
wo = frappe.get_doc("Work Order", self.work_order)
|
||||||
|
|
||||||
if not wo.required_items:
|
wo_produced_qty = flt(wo.produced_qty)
|
||||||
return self._add_items_for_disassembly_from_bom()
|
if wo_produced_qty <= 0:
|
||||||
|
frappe.throw(_("Work Order {0} has no produced qty").format(self.work_order))
|
||||||
|
|
||||||
scale_factor = flt(self.fg_completed_qty) / flt(wo.qty) if flt(wo.qty) else 0
|
disassemble_qty = flt(self.fg_completed_qty)
|
||||||
|
if disassemble_qty <= 0:
|
||||||
|
frappe.throw(_("Disassemble Qty cannot be less than or equal to 0."))
|
||||||
|
|
||||||
# RMs
|
scale_factor = disassemble_qty / wo_produced_qty
|
||||||
for ri in wo.required_items:
|
|
||||||
self.append(
|
|
||||||
"items",
|
|
||||||
{
|
|
||||||
"item_code": ri.item_code,
|
|
||||||
"item_name": ri.item_name,
|
|
||||||
"description": ri.description,
|
|
||||||
"qty": flt(ri.required_qty * scale_factor),
|
|
||||||
"stock_uom": ri.stock_uom,
|
|
||||||
"uom": ri.stock_uom,
|
|
||||||
"conversion_factor": 1,
|
|
||||||
# manufacture transfers RMs from WIP (not source warehouse)
|
|
||||||
"t_warehouse": self.to_warehouse or wo.wip_warehouse,
|
|
||||||
"s_warehouse": "",
|
|
||||||
"is_finished_item": 0,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
# Secondary/Scrap items
|
self._append_disassembly_row_from_source(
|
||||||
secondary_items = self.get_secondary_items(self.fg_completed_qty)
|
disassemble_qty=disassemble_qty,
|
||||||
if secondary_items:
|
scale_factor=scale_factor,
|
||||||
scrap_warehouse = wo.scrap_warehouse or self.from_warehouse or wo.fg_warehouse
|
|
||||||
for item in secondary_items.values():
|
|
||||||
item["from_warehouse"] = scrap_warehouse
|
|
||||||
item["to_warehouse"] = ""
|
|
||||||
item["is_finished_item"] = 0
|
|
||||||
self.add_to_stock_entry_detail(secondary_items, bom_no=self.bom_no)
|
|
||||||
|
|
||||||
# FG
|
|
||||||
self.append(
|
|
||||||
"items",
|
|
||||||
{
|
|
||||||
"item_code": wo.production_item,
|
|
||||||
"item_name": wo.item_name,
|
|
||||||
"description": wo.description,
|
|
||||||
"qty": flt(self.fg_completed_qty),
|
|
||||||
"stock_uom": wo.stock_uom,
|
|
||||||
"uom": wo.stock_uom,
|
|
||||||
"conversion_factor": 1,
|
|
||||||
"s_warehouse": self.from_warehouse or wo.fg_warehouse,
|
|
||||||
"t_warehouse": "",
|
|
||||||
"is_finished_item": 1,
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _append_disassembly_row_from_source(self, disassemble_qty, scale_factor, source_stock_entry=None):
|
||||||
|
for source_row in self.get_items_from_manufacture_stock_entry(self.source_stock_entry):
|
||||||
|
if source_row.is_finished_item:
|
||||||
|
qty = disassemble_qty
|
||||||
|
s_warehouse = self.from_warehouse or source_row.t_warehouse
|
||||||
|
t_warehouse = ""
|
||||||
|
elif source_row.s_warehouse:
|
||||||
|
# RM: was consumed FROM s_warehouse -> return TO s_warehouse
|
||||||
|
qty = flt(source_row.qty * scale_factor)
|
||||||
|
s_warehouse = ""
|
||||||
|
t_warehouse = self.to_warehouse or source_row.s_warehouse
|
||||||
|
else:
|
||||||
|
# Scrap/secondary: was produced TO t_warehouse -> take FROM t_warehouse
|
||||||
|
qty = flt(source_row.qty * scale_factor)
|
||||||
|
s_warehouse = source_row.t_warehouse
|
||||||
|
t_warehouse = ""
|
||||||
|
|
||||||
|
item = {
|
||||||
|
"item_code": source_row.item_code,
|
||||||
|
"item_name": source_row.item_name,
|
||||||
|
"description": source_row.description,
|
||||||
|
"stock_uom": source_row.stock_uom,
|
||||||
|
"uom": source_row.uom,
|
||||||
|
"conversion_factor": source_row.conversion_factor,
|
||||||
|
"basic_rate": source_row.basic_rate,
|
||||||
|
"qty": qty,
|
||||||
|
"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,
|
||||||
|
# batch and serial bundles built on submit
|
||||||
|
"use_serial_batch_fields": 1 if (source_row.batch_no or source_row.serial_no) else 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
if source_stock_entry:
|
||||||
|
item.update(
|
||||||
|
{
|
||||||
|
"against_stock_entry": source_stock_entry,
|
||||||
|
"ste_detail": source_row.name,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
self.append("items", item)
|
||||||
|
|
||||||
def _add_items_for_disassembly_from_bom(self):
|
def _add_items_for_disassembly_from_bom(self):
|
||||||
if not self.bom_no or not self.fg_completed_qty:
|
if not self.bom_no or not self.fg_completed_qty:
|
||||||
frappe.throw(_("BOM and Finished Good Quantity is mandatory for Disassembly"))
|
frappe.throw(_("BOM and Finished Good Quantity is mandatory for Disassembly"))
|
||||||
|
|||||||
Reference in New Issue
Block a user