fix: reinstate duplicate entry check for manufacture entries

The stock_entry.py split (#54466) dropped check_duplicate_entry_for_work_order
and DuplicateEntryForWorkOrderError with no replacement. The Work Order still
throws StockOverProductionError when submitted entries exceed the planned qty,
but nothing blocks saving another Manufacture entry, draft or submitted, once
existing entries already cover the full work order qty.

Restore the validation in the manufacture purpose handler, gated to work
orders without track_semi_finished_goods, matching the pre-split behaviour.
This commit is contained in:
Mihir Kandoi
2026-08-11 12:25:17 +05:30
parent fee2672bf9
commit 22fa520500
2 changed files with 46 additions and 0 deletions

View File

@@ -21,6 +21,10 @@ from .serial_batch import create_serial_and_batch_bundle
from .stock_entry_base import BaseStockEntry
class DuplicateEntryForWorkOrderError(frappe.ValidationError):
pass
class OperationsNotCompleteError(frappe.ValidationError):
pass
@@ -279,6 +283,7 @@ class ManufactureStockEntry(BaseManufactureStockEntry):
self.validate_warehouse()
self.validate_raw_materials_exists()
self.check_if_operations_completed()
self.check_duplicate_entry_for_work_order()
self.validate_component_and_quantities()
self.validate_finished_good_serial_batch_for_work_order()
@@ -430,6 +435,46 @@ class ManufactureStockEntry(BaseManufactureStockEntry):
OperationsNotCompleteError,
)
def check_duplicate_entry_for_work_order(self):
"""Block another manufacture entry once existing entries already cover the full work order qty."""
if not self.wo_doc or self.wo_doc.track_semi_finished_goods:
return
other_entries = frappe.get_all(
"Stock Entry",
filters={
"work_order": self.doc.work_order,
"purpose": self.doc.purpose,
"docstatus": ["!=", 2],
"name": ["!=", self.doc.name],
},
pluck="name",
)
if not other_entries:
return
if self.get_fg_qty_already_entered(other_entries) >= flt(self.wo_doc.qty):
frappe.throw(
_("Stock Entries already created for Work Order {0}: {1}").format(
self.doc.work_order, ", ".join(other_entries)
),
DuplicateEntryForWorkOrderError,
)
def get_fg_qty_already_entered(self, other_entries):
child = frappe.qb.DocType("Stock Entry Detail")
qty = (
frappe.qb.from_(child)
.select(Sum(child.transfer_qty))
.where(
child.parent.isin(other_entries)
& (child.item_code == self.wo_doc.production_item)
& (child.s_warehouse.isnull() | (child.s_warehouse == ""))
)
.run()
)[0][0]
return flt(qty)
def add_items(self):
self.add_raw_materials()
self.set_process_loss_qty()

View File

@@ -38,6 +38,7 @@ from erpnext.stock.utils import get_incoming_rate
from .services.disassemble import DisassembleStockEntry
from .services.manufacturing import (
DuplicateEntryForWorkOrderError,
ManufactureStockEntry,
MaterialConsumptionForManufactureStockEntry,
OperationsNotCompleteError,