fix: stop asking for a manufacturing entry when process loss explains the shortfall

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.
This commit is contained in:
Mihir Kandoi
2026-08-08 14:45:08 +05:30
parent f61f6523b9
commit 1e22695eae

View File

@@ -1464,12 +1464,12 @@ class JobCard(Document):
) )
if self.track_semi_finished_goods and previous_operations: if self.track_semi_finished_goods and previous_operations:
manufactured_qty = self.get_manufactured_qty_per_operation( totals = self.get_manufactured_qty_per_operation([row.name for row in previous_operations])
[row.name for row in previous_operations]
)
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 return previous_operations
@@ -1478,7 +1478,11 @@ class JobCard(Document):
data = ( data = (
frappe.qb.from_(job_card) 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( .where(
(job_card.work_order == self.work_order) (job_card.work_order == self.work_order)
& (job_card.docstatus == 1) & (job_card.docstatus == 1)
@@ -1486,9 +1490,9 @@ class JobCard(Document):
& (job_card.operation_id.isin(operation_ids)) & (job_card.operation_id.isin(operation_ids))
) )
.groupby(job_card.operation_id) .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): def get_current_operation_completed_qty(self):
current_operation_qty = 0.0 current_operation_qty = 0.0
@@ -1540,19 +1544,35 @@ class JobCard(Document):
OperationSequenceError, 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( 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( ).format(
bold(self.get_qty_with_uom(current_operation_qty)), bold(self.get_qty_with_uom(current_operation_qty)),
bold(self.operation), bold(self.operation),
bold(self.get_qty_with_uom(manufactured_qty, row.finished_good)), bold(self.get_qty_with_uom(manufactured_qty, row.finished_good)),
bold(row.operation), bold(row.operation),
bold(self.get_qty_with_uom(flt(row.process_loss_qty), row.finished_good)),
), ),
OperationSequenceError, 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): 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."))