From 1e22695eaef905ad141cb0e3f57fc7513e81c1ce Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Sat, 8 Aug 2026 14:45:08 +0530 Subject: [PATCH] fix: stop asking for a manufacturing entry when process loss explains the shortfall MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a previous operation manufactured less than the current job card is completing, the error always said 'Submit the manufacturing entry for the operation first' — even when the entry was already submitted and the missing quantity was booked as process loss, which made the advice a dead end. Sum the process loss of the previous operation's job cards alongside the manufactured quantity. When manufactured + process loss covers the requested quantity, say the shortfall is process loss so the user knows to reduce the completed quantity; keep the submit-first message for genuinely pending manufacturing entries. --- .../doctype/job_card/job_card.py | 38 ++++++++++++++----- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index b9b3e93f5ec..f253d2061e6 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -1464,12 +1464,12 @@ class JobCard(Document): ) if self.track_semi_finished_goods and previous_operations: - manufactured_qty = self.get_manufactured_qty_per_operation( - [row.name for row in previous_operations] - ) + totals = 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)) + operation_totals = totals.get(row.name) + row.manufactured_qty = flt(operation_totals and operation_totals.manufactured_qty) + row.process_loss_qty = flt(operation_totals and operation_totals.process_loss_qty) return previous_operations @@ -1478,7 +1478,11 @@ class JobCard(Document): data = ( frappe.qb.from_(job_card) - .select(job_card.operation_id, Sum(job_card.manufactured_qty)) + .select( + job_card.operation_id, + Sum(job_card.manufactured_qty).as_("manufactured_qty"), + Sum(job_card.process_loss_qty).as_("process_loss_qty"), + ) .where( (job_card.work_order == self.work_order) & (job_card.docstatus == 1) @@ -1486,9 +1490,9 @@ class JobCard(Document): & (job_card.operation_id.isin(operation_ids)) ) .groupby(job_card.operation_id) - ).run() + ).run(as_dict=True) - return dict(data) + return {row.operation_id: row for row in data} def get_current_operation_completed_qty(self): current_operation_qty = 0.0 @@ -1540,19 +1544,35 @@ class JobCard(Document): OperationSequenceError, ) - if manufactured_qty < current_operation_qty: + if manufactured_qty >= current_operation_qty: + return + + if manufactured_qty + flt(row.process_loss_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." + "The completed quantity {0} of an operation {1} cannot be greater than the manufactured quantity {2} of a previous operation {3}, as {4} was booked as process loss there." ).format( bold(self.get_qty_with_uom(current_operation_qty)), bold(self.operation), bold(self.get_qty_with_uom(manufactured_qty, row.finished_good)), bold(row.operation), + bold(self.get_qty_with_uom(flt(row.process_loss_qty), row.finished_good)), ), OperationSequenceError, ) + 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(self.get_qty_with_uom(current_operation_qty)), + bold(self.operation), + bold(self.get_qty_with_uom(manufactured_qty, row.finished_good)), + bold(row.operation), + ), + OperationSequenceError, + ) + def validate_work_order(self): if self.is_work_order_closed(): frappe.throw(_("You cannot make any changes to Job Card since Work Order is closed."))