fix: add support to fetch items based on manufacture stock entry; fix how it's done from work order

(cherry picked from commit 1ed0124ad7)

# Conflicts:
#	erpnext/stock/doctype/stock_entry/stock_entry.py
This commit is contained in:
Smit Vora
2026-03-30 18:19:19 +05:30
committed by Mergify
parent 583c7b9819
commit e9ce0a41e6

View File

@@ -28,7 +28,6 @@ from erpnext.buying.utils import check_on_hold_or_closed_status
from erpnext.controllers.taxes_and_totals import init_landed_taxes_and_totals from erpnext.controllers.taxes_and_totals import init_landed_taxes_and_totals
from erpnext.manufacturing.doctype.bom.bom import ( from erpnext.manufacturing.doctype.bom.bom import (
add_additional_cost, add_additional_cost,
get_bom_items_as_dict,
get_op_cost_from_sub_assemblies, get_op_cost_from_sub_assemblies,
get_scrap_items_from_sub_assemblies, get_scrap_items_from_sub_assemblies,
validate_bom_no, validate_bom_no,
@@ -1984,45 +1983,108 @@ class StockEntry(StockController):
) )
def get_items_for_disassembly(self): def get_items_for_disassembly(self):
"""Get items for Disassembly Order""" """Get items for Disassembly Order.
Priority:
1. From a specific Manufacture Stock Entry (exact reversal)
2. From Work Order required_items (reflects WO changes)
3. From BOM (standalone disassembly)
"""
if self.get("source_stock_entry"):
return self._add_items_for_disassembly_from_stock_entry()
if self.work_order: if self.work_order:
return self._add_items_for_disassembly_from_work_order() return self._add_items_for_disassembly_from_work_order()
return self._add_items_for_disassembly_from_bom() return self._add_items_for_disassembly_from_bom()
def _add_items_for_disassembly_from_stock_entry(self):
source_fg_qty = frappe.db.get_value("Stock Entry", self.source_stock_entry, "fg_completed_qty")
if not source_fg_qty:
frappe.throw(
_("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)
for source_row in self.get_items_from_manufacture_stock_entry(self.source_stock_entry):
if source_row.is_finished_item:
qty = flt(self.fg_completed_qty)
s_warehouse = self.from_warehouse or source_row.t_warehouse
t_warehouse = ""
else:
qty = flt(source_row.qty * scale_factor)
s_warehouse = ""
t_warehouse = self.to_warehouse or source_row.s_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,
"against_stock_entry": self.source_stock_entry,
"ste_detail": source_row.name,
"batch_no": source_row.batch_no,
"serial_no": source_row.serial_no,
"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):
items = self.get_items_from_manufacture_entry() wo = frappe.get_doc("Work Order", self.work_order)
s_warehouse = frappe.db.get_value("Work Order", self.work_order, "fg_warehouse") if not wo.required_items:
return self._add_items_for_disassembly_from_bom()
items_dict = get_bom_items_as_dict( scale_factor = flt(self.fg_completed_qty) / flt(wo.qty) if flt(wo.qty) else 0
self.bom_no,
self.company, # RMs
self.fg_completed_qty, for ri in wo.required_items:
fetch_exploded=self.use_multi_level_bom, self.append(
fetch_qty_in_stock_uom=False, "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,
"t_warehouse": ri.source_warehouse or wo.source_warehouse or self.to_warehouse,
"s_warehouse": "",
"is_finished_item": 0,
},
)
# 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,
},
) )
for row in items:
child_row = self.append("items", {})
for field, value in row.items():
if value is not None:
child_row.set(field, value)
# update qty and amount from BOM items
bom_items = items_dict.get(row.item_code)
if bom_items:
child_row.qty = bom_items.get("qty", child_row.qty)
child_row.amount = bom_items.get("amount", child_row.amount)
if row.is_finished_item:
child_row.qty = self.fg_completed_qty
child_row.s_warehouse = (self.from_warehouse or s_warehouse) if row.is_finished_item else ""
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): 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"))
@@ -2040,6 +2102,7 @@ class StockEntry(StockController):
# Finished goods # Finished goods
self.load_items_from_bom() self.load_items_from_bom()
<<<<<<< HEAD
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",
@@ -2068,6 +2131,38 @@ class StockEntry(StockController):
], ],
order_by="`tabStock Entry Detail`.`idx` desc, `tabStock Entry Detail`.`is_finished_item` desc", order_by="`tabStock Entry Detail`.`idx` desc, `tabStock Entry Detail`.`is_finished_item` desc",
group_by="`tabStock Entry Detail`.`item_code`", group_by="`tabStock Entry Detail`.`item_code`",
=======
def get_items_from_manufacture_stock_entry(self, stock_entry):
SE = frappe.qb.DocType("Stock Entry")
SED = frappe.qb.DocType("Stock Entry Detail")
return (
frappe.qb.from_(SED)
.join(SE)
.on(SED.parent == SE.name)
.select(
SED.name,
SED.item_code,
SED.item_name,
SED.description,
SED.qty,
SED.transfer_qty,
SED.stock_uom,
SED.uom,
SED.basic_rate,
SED.conversion_factor,
SED.is_finished_item,
SED.batch_no,
SED.serial_no,
SED.use_serial_batch_fields,
SED.s_warehouse,
SED.t_warehouse,
)
.where(SE.name == stock_entry)
.where(SE.docstatus == 1)
.orderby(SED.idx)
.run(as_dict=True)
>>>>>>> 1ed0124ad7 (fix: add support to fetch items based on manufacture stock entry; fix how it's done from work order)
) )
@frappe.whitelist() @frappe.whitelist()