mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-27 08:54:45 +00:00
fix: validate work order consistency in stock entry
(cherry picked from commit ea392b2009)
# Conflicts:
# erpnext/stock/doctype/stock_entry/stock_entry.py
This commit is contained in:
@@ -848,6 +848,16 @@ class StockEntry(StockController):
|
|||||||
if not self.get("source_stock_entry"):
|
if not self.get("source_stock_entry"):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if self.work_order:
|
||||||
|
source_wo = frappe.db.get_value("Stock Entry", self.source_stock_entry, "work_order")
|
||||||
|
if source_wo and source_wo != self.work_order:
|
||||||
|
frappe.throw(
|
||||||
|
_(
|
||||||
|
"Source Stock Entry {0} belongs to Work Order {1}, not {2}. Please use a manufacture entry from the same Work Order."
|
||||||
|
).format(self.source_stock_entry, source_wo, self.work_order),
|
||||||
|
title=_("Work Order Mismatch"),
|
||||||
|
)
|
||||||
|
|
||||||
from erpnext.manufacturing.doctype.work_order.work_order import get_disassembly_available_qty
|
from erpnext.manufacturing.doctype.work_order.work_order import get_disassembly_available_qty
|
||||||
|
|
||||||
available_qty = get_disassembly_available_qty(self.source_stock_entry, self.name)
|
available_qty = get_disassembly_available_qty(self.source_stock_entry, self.name)
|
||||||
@@ -2401,7 +2411,161 @@ class StockEntry(StockController):
|
|||||||
if self.pro_doc and self.pro_doc.scrap_warehouse:
|
if self.pro_doc and self.pro_doc.scrap_warehouse:
|
||||||
item["to_warehouse"] = self.pro_doc.scrap_warehouse
|
item["to_warehouse"] = self.pro_doc.scrap_warehouse
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
self.add_to_stock_entry_detail(scrap_item_dict, bom_no=self.bom_no)
|
self.add_to_stock_entry_detail(scrap_item_dict, bom_no=self.bom_no)
|
||||||
|
=======
|
||||||
|
if (
|
||||||
|
self.purpose not in ["Material Transfer for Manufacture"]
|
||||||
|
and frappe.db.get_single_value("Manufacturing Settings", "backflush_raw_materials_based_on")
|
||||||
|
!= "BOM"
|
||||||
|
and not skip_transfer
|
||||||
|
):
|
||||||
|
return
|
||||||
|
|
||||||
|
reservation_entries = self.get_available_reserved_materials()
|
||||||
|
if not reservation_entries:
|
||||||
|
return
|
||||||
|
|
||||||
|
new_items_to_add = []
|
||||||
|
for d in self.items:
|
||||||
|
if d.serial_and_batch_bundle or d.serial_no or d.batch_no:
|
||||||
|
continue
|
||||||
|
|
||||||
|
key = (d.item_code, d.s_warehouse)
|
||||||
|
if details := reservation_entries.get(key):
|
||||||
|
original_qty = d.qty
|
||||||
|
if batches := details.get("batch_no"):
|
||||||
|
for batch_no, qty in batches.items():
|
||||||
|
if original_qty <= 0:
|
||||||
|
break
|
||||||
|
|
||||||
|
if qty <= 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if d.batch_no and original_qty > 0:
|
||||||
|
new_row = frappe.copy_doc(d)
|
||||||
|
new_row.name = None
|
||||||
|
new_row.batch_no = batch_no
|
||||||
|
new_row.qty = qty
|
||||||
|
new_row.idx = d.idx + 1
|
||||||
|
if new_row.batch_no and details.get("batchwise_sn"):
|
||||||
|
new_row.serial_no = "\n".join(
|
||||||
|
details.get("batchwise_sn")[new_row.batch_no][: cint(new_row.qty)]
|
||||||
|
)
|
||||||
|
|
||||||
|
new_items_to_add.append(new_row)
|
||||||
|
original_qty -= qty
|
||||||
|
batches[batch_no] -= qty
|
||||||
|
|
||||||
|
if qty >= d.qty and not d.batch_no:
|
||||||
|
d.batch_no = batch_no
|
||||||
|
batches[batch_no] -= d.qty
|
||||||
|
if d.batch_no and details.get("batchwise_sn"):
|
||||||
|
d.serial_no = "\n".join(
|
||||||
|
details.get("batchwise_sn")[d.batch_no][: cint(d.qty)]
|
||||||
|
)
|
||||||
|
elif not d.batch_no:
|
||||||
|
d.batch_no = batch_no
|
||||||
|
d.qty = qty
|
||||||
|
original_qty -= qty
|
||||||
|
batches[batch_no] = 0
|
||||||
|
|
||||||
|
if d.batch_no and details.get("batchwise_sn"):
|
||||||
|
d.serial_no = "\n".join(
|
||||||
|
details.get("batchwise_sn")[d.batch_no][: cint(d.qty)]
|
||||||
|
)
|
||||||
|
|
||||||
|
if details.get("serial_no"):
|
||||||
|
d.serial_no = "\n".join(details.get("serial_no")[: cint(d.qty)])
|
||||||
|
|
||||||
|
d.use_serial_batch_fields = 1
|
||||||
|
|
||||||
|
for new_row in new_items_to_add:
|
||||||
|
self.append("items", new_row)
|
||||||
|
|
||||||
|
sorted_items = sorted(self.items, key=lambda x: x.item_code)
|
||||||
|
if self.purpose == "Manufacture":
|
||||||
|
# ensure finished item at last
|
||||||
|
sorted_items = sorted(sorted_items, key=lambda x: x.t_warehouse)
|
||||||
|
|
||||||
|
idx = 0
|
||||||
|
for row in sorted_items:
|
||||||
|
idx += 1
|
||||||
|
row.idx = idx
|
||||||
|
self.set("items", sorted_items)
|
||||||
|
|
||||||
|
def get_available_reserved_materials(self):
|
||||||
|
reserved_entries = self.get_reserved_materials()
|
||||||
|
if not reserved_entries:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
itemwise_serial_batch_qty = frappe._dict()
|
||||||
|
|
||||||
|
for d in reserved_entries:
|
||||||
|
key = (d.item_code, d.warehouse)
|
||||||
|
if key not in itemwise_serial_batch_qty:
|
||||||
|
itemwise_serial_batch_qty[key] = frappe._dict(
|
||||||
|
{
|
||||||
|
"serial_no": [],
|
||||||
|
"batch_no": defaultdict(float),
|
||||||
|
"batchwise_sn": defaultdict(list),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
details = itemwise_serial_batch_qty[key]
|
||||||
|
if d.batch_no:
|
||||||
|
details.batch_no[d.batch_no] += d.qty
|
||||||
|
if d.serial_no:
|
||||||
|
details.batchwise_sn[d.batch_no].extend(d.serial_no.split("\n"))
|
||||||
|
elif d.serial_no:
|
||||||
|
details.serial_no.append(d.serial_no)
|
||||||
|
|
||||||
|
return itemwise_serial_batch_qty
|
||||||
|
|
||||||
|
def get_reserved_materials(self):
|
||||||
|
doctype = frappe.qb.DocType("Stock Reservation Entry")
|
||||||
|
serial_batch_doc = frappe.qb.DocType("Serial and Batch Entry")
|
||||||
|
|
||||||
|
query = (
|
||||||
|
frappe.qb.from_(doctype)
|
||||||
|
.inner_join(serial_batch_doc)
|
||||||
|
.on(doctype.name == serial_batch_doc.parent)
|
||||||
|
.select(
|
||||||
|
serial_batch_doc.serial_no,
|
||||||
|
serial_batch_doc.batch_no,
|
||||||
|
serial_batch_doc.qty,
|
||||||
|
doctype.item_code,
|
||||||
|
doctype.warehouse,
|
||||||
|
doctype.name,
|
||||||
|
doctype.transferred_qty,
|
||||||
|
doctype.consumed_qty,
|
||||||
|
)
|
||||||
|
.where(
|
||||||
|
(doctype.docstatus == 1)
|
||||||
|
& (doctype.voucher_no == (self.work_order or self.subcontracting_order))
|
||||||
|
& (serial_batch_doc.delivered_qty < serial_batch_doc.qty)
|
||||||
|
)
|
||||||
|
.orderby(serial_batch_doc.idx)
|
||||||
|
)
|
||||||
|
|
||||||
|
return query.run(as_dict=True)
|
||||||
|
|
||||||
|
def set_secondary_items(self):
|
||||||
|
if self.purpose in ["Manufacture", "Repack"]:
|
||||||
|
secondary_items_dict = self.get_secondary_items(self.fg_completed_qty)
|
||||||
|
for item in secondary_items_dict.values():
|
||||||
|
if self.pro_doc and item.type:
|
||||||
|
if self.pro_doc.scrap_warehouse and item.type == "Scrap":
|
||||||
|
item["to_warehouse"] = self.pro_doc.scrap_warehouse
|
||||||
|
|
||||||
|
if item.process_loss_per:
|
||||||
|
item["qty"] -= flt(
|
||||||
|
item["qty"] * (item.process_loss_per / 100),
|
||||||
|
self.precision("fg_completed_qty"),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.add_to_stock_entry_detail(secondary_items_dict, bom_no=self.bom_no)
|
||||||
|
>>>>>>> ea392b2009 (fix: validate work order consistency in stock entry)
|
||||||
|
|
||||||
def set_process_loss_qty(self):
|
def set_process_loss_qty(self):
|
||||||
if self.purpose not in ("Manufacture", "Repack"):
|
if self.purpose not in ("Manufacture", "Repack"):
|
||||||
|
|||||||
Reference in New Issue
Block a user