mirror of
https://github.com/frappe/erpnext.git
synced 2026-08-27 13:55:19 +00:00
Merge pull request #58004 from mihir-kandoi/restore-duplicate-entry-check
fix: reinstate duplicate entry check for manufacture entries
This commit is contained in:
@@ -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,50 @@ class ManufactureStockEntry(BaseManufactureStockEntry):
|
||||
OperationsNotCompleteError,
|
||||
)
|
||||
|
||||
def check_duplicate_entry_for_work_order(self):
|
||||
"""Block another manufacture entry once existing entries already cover the work order qty plus allowance."""
|
||||
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
|
||||
|
||||
allowance_percentage = flt(
|
||||
frappe.db.get_single_value("Manufacturing Settings", "overproduction_percentage_for_work_order")
|
||||
)
|
||||
allowed_qty = flt(self.wo_doc.qty) + (allowance_percentage / 100 * flt(self.wo_doc.qty))
|
||||
if self.get_fg_qty_already_entered(other_entries) >= allowed_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()
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -28,6 +28,7 @@ from erpnext.stock.doctype.serial_and_batch_bundle.test_serial_and_batch_bundle
|
||||
)
|
||||
from erpnext.stock.doctype.serial_no.serial_no import *
|
||||
from erpnext.stock.doctype.stock_entry.stock_entry import (
|
||||
DuplicateEntryForWorkOrderError,
|
||||
FinishedGoodError,
|
||||
get_pending_work_orders,
|
||||
make_stock_in_entry,
|
||||
@@ -1272,6 +1273,36 @@ class TestStockEntry(ERPNextTestSuite):
|
||||
se_ok.submit()
|
||||
self.assertEqual(se_ok.docstatus, 1)
|
||||
|
||||
def test_duplicate_entry_for_work_order(self):
|
||||
from erpnext.manufacturing.doctype.work_order.mapper import (
|
||||
make_stock_entry as make_wo_stock_entry,
|
||||
)
|
||||
from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record
|
||||
|
||||
wo = make_wo_order_test_record(qty=1)
|
||||
make_stock_entry(item_code="_Test Item", target="Stores - _TC", qty=10, basic_rate=100)
|
||||
make_stock_entry(
|
||||
item_code="_Test Item Home Desktop 100", target="Stores - _TC", qty=10, basic_rate=100
|
||||
)
|
||||
|
||||
transfer = frappe.get_doc(make_wo_stock_entry(wo.name, "Material Transfer for Manufacture", 1))
|
||||
for d in transfer.get("items"):
|
||||
d.s_warehouse = "Stores - _TC"
|
||||
transfer.insert()
|
||||
transfer.submit()
|
||||
|
||||
mfg = frappe.get_doc(make_wo_stock_entry(wo.name, "Manufacture", 1))
|
||||
mfg.insert()
|
||||
|
||||
duplicate = frappe.get_doc(make_wo_stock_entry(wo.name, "Manufacture", 1))
|
||||
self.assertRaises(DuplicateEntryForWorkOrderError, duplicate.insert)
|
||||
|
||||
with self.change_settings(
|
||||
"Manufacturing Settings", {"overproduction_percentage_for_work_order": 100}
|
||||
):
|
||||
within_allowance = frappe.get_doc(make_wo_stock_entry(wo.name, "Manufacture", 1))
|
||||
within_allowance.insert()
|
||||
|
||||
@ERPNextTestSuite.change_settings("Stock Settings", {"action_if_quality_inspection_is_rejected": "Stop"})
|
||||
def test_quality_inspection_required_for_manufacture(self):
|
||||
from erpnext.exceptions import QualityInspectionRejectedError, QualityInspectionRequiredError
|
||||
|
||||
Reference in New Issue
Block a user