mirror of
https://github.com/frappe/erpnext.git
synced 2026-09-18 19:07:55 +00:00
fix(job_card): block next operation until previous operation is manufactured
With track semi finished goods, Work Order Operation completed_qty is set from the submitted job cards' total completed qty, so a job card of the next operation could be started and completed even when no Manufacture entry existed for the previous operation. The semi-finished goods it consumes were never produced. Validate the sequence against the qty actually manufactured against the previous operations' job cards (Manufacture entries / Subcontracting Receipts) when the work order tracks semi finished goods.
This commit is contained in:
@@ -1418,15 +1418,46 @@ class JobCard(Document):
|
|||||||
|
|
||||||
current_operation_qty = self.get_current_operation_completed_qty()
|
current_operation_qty = self.get_current_operation_completed_qty()
|
||||||
|
|
||||||
|
for row in self.get_previous_operations():
|
||||||
|
if self.track_semi_finished_goods:
|
||||||
|
self.validate_previous_operation_manufactured_qty(row, current_operation_qty)
|
||||||
|
else:
|
||||||
|
self.validate_previous_operation(row, current_operation_qty)
|
||||||
|
|
||||||
|
def get_previous_operations(self):
|
||||||
previous_operations = frappe.get_all(
|
previous_operations = frappe.get_all(
|
||||||
"Work Order Operation",
|
"Work Order Operation",
|
||||||
fields=["operation", "status", "completed_qty", "sequence_id"],
|
fields=["name", "operation", "status", "completed_qty", "sequence_id"],
|
||||||
filters={"docstatus": 1, "parent": self.work_order, "sequence_id": ("<", self.sequence_id)},
|
filters={"docstatus": 1, "parent": self.work_order, "sequence_id": ("<", self.sequence_id)},
|
||||||
order_by="sequence_id, idx",
|
order_by="sequence_id, idx",
|
||||||
)
|
)
|
||||||
|
|
||||||
for row in previous_operations:
|
if self.track_semi_finished_goods and previous_operations:
|
||||||
self.validate_previous_operation(row, current_operation_qty)
|
manufactured_qty = self.get_manufactured_qty_per_operation(
|
||||||
|
[row.name for row in previous_operations]
|
||||||
|
)
|
||||||
|
|
||||||
|
for row in previous_operations:
|
||||||
|
row.manufactured_qty = flt(manufactured_qty.get(row.name))
|
||||||
|
|
||||||
|
return previous_operations
|
||||||
|
|
||||||
|
def get_manufactured_qty_per_operation(self, operation_ids):
|
||||||
|
job_card = frappe.qb.DocType("Job Card")
|
||||||
|
|
||||||
|
data = (
|
||||||
|
frappe.qb.from_(job_card)
|
||||||
|
.select(job_card.operation_id, Sum(job_card.manufactured_qty))
|
||||||
|
.where(
|
||||||
|
(job_card.work_order == self.work_order)
|
||||||
|
& (job_card.docstatus == 1)
|
||||||
|
& (IfNull(job_card.is_corrective_job_card, 0) == 0)
|
||||||
|
& (job_card.operation_id.isin(operation_ids))
|
||||||
|
)
|
||||||
|
.groupby(job_card.operation_id)
|
||||||
|
).run()
|
||||||
|
|
||||||
|
return dict(data)
|
||||||
|
|
||||||
def get_current_operation_completed_qty(self):
|
def get_current_operation_completed_qty(self):
|
||||||
current_operation_qty = 0.0
|
current_operation_qty = 0.0
|
||||||
@@ -1462,6 +1493,35 @@ class JobCard(Document):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def validate_previous_operation_manufactured_qty(self, row, current_operation_qty):
|
||||||
|
manufactured_qty = flt(row.manufactured_qty)
|
||||||
|
|
||||||
|
if not manufactured_qty:
|
||||||
|
frappe.throw(
|
||||||
|
_(
|
||||||
|
"Job Card {0}: As per the sequence of the operations in the work order {1}, submit the manufacturing entry for the operation {2} before the operation {3}."
|
||||||
|
).format(
|
||||||
|
bold(self.name),
|
||||||
|
bold(get_link_to_form("Work Order", self.work_order)),
|
||||||
|
bold(row.operation),
|
||||||
|
bold(self.operation),
|
||||||
|
),
|
||||||
|
OperationSequenceError,
|
||||||
|
)
|
||||||
|
|
||||||
|
if manufactured_qty < current_operation_qty:
|
||||||
|
frappe.throw(
|
||||||
|
_(
|
||||||
|
"The completed quantity {0} of an operation {1} cannot be greater than the manufactured quantity {2} of a previous operation {3}. Submit the manufacturing entry for the operation {3} first."
|
||||||
|
).format(
|
||||||
|
bold(current_operation_qty),
|
||||||
|
bold(self.operation),
|
||||||
|
bold(manufactured_qty),
|
||||||
|
bold(row.operation),
|
||||||
|
),
|
||||||
|
OperationSequenceError,
|
||||||
|
)
|
||||||
|
|
||||||
def validate_work_order(self):
|
def validate_work_order(self):
|
||||||
if self.is_work_order_closed():
|
if self.is_work_order_closed():
|
||||||
frappe.throw(_("You cannot make any changes to Job Card since Work Order is closed."))
|
frappe.throw(_("You cannot make any changes to Job Card since Work Order is closed."))
|
||||||
|
|||||||
Reference in New Issue
Block a user