fix(manufacturing): restore transfer allowance guard per submit

The redesign dropped the claim-sum StockOverProductionError entirely,
letting a single entry claim more than planned qty plus allowance
(test_allow_overproduction). Validate on stock entry submit instead:
already-recorded effective transferred qty plus the submitting entry's
For Quantity must stay within the allowance. Cross-entry claim sums no
longer block the honest remainder after an under-covered entry, because
the recorded base is row-derived.
This commit is contained in:
Mihir Kandoi
2026-08-13 12:04:51 +05:30
parent 4e7d5aaced
commit e5e2176da8
2 changed files with 31 additions and 2 deletions

View File

@@ -203,8 +203,8 @@ class StatusService:
return
if fieldname == "material_transferred_for_manufacturing":
# Owned by the net-coverage recomputation; the claimed fg_completed_qty is not
# validated here because item rows, not the claim, decide the stored value.
# Owned by the net-coverage recomputation; the per-entry allowance guard runs on
# stock entry submit, where the submitting entry's claim is known.
self.doc.refresh_material_transferred_for_manufacturing()
self.set_process_loss_qty()
self._update_produced_qty_in_so()

View File

@@ -382,6 +382,7 @@ class MaterialTransferForManufactureStockEntry(BaseMaterialTransferStockEntry):
if self.doc.fg_completed_qty:
if self.doc.docstatus == 1:
self.wo_doc.add_additional_items(self.doc)
self._validate_transfer_within_allowance()
else:
self.wo_doc.remove_additional_items(self.doc)
@@ -391,6 +392,34 @@ class MaterialTransferForManufactureStockEntry(BaseMaterialTransferStockEntry):
if not self.wo_doc.operations:
self.wo_doc.set_actual_dates()
def _validate_transfer_within_allowance(self):
"""Reject a transfer whose For Quantity, on top of the effective qty already
transferred, exceeds the planned qty plus the transfer allowance."""
from erpnext.manufacturing.doctype.work_order.services.status import StatusService
from erpnext.manufacturing.doctype.work_order.work_order import StockOverProductionError
if self.doc.is_return or self.doc.is_additional_transfer_entry:
return
if self.wo_doc.track_semi_finished_goods:
return
if self.wo_doc.operations and self.wo_doc.transfer_material_against == "Job Card":
return
allowance = StatusService(self.wo_doc).get_qty_allowance("Material Transfer for Manufacture")
allowed_qty = flt(self.wo_doc.qty) * (1.0 + allowance / 100.0)
transferred_qty = flt(self.wo_doc.material_transferred_for_manufacturing)
projected_qty = transferred_qty + flt(self.doc.fg_completed_qty)
precision = self.wo_doc.precision("material_transferred_for_manufacturing")
if flt(projected_qty, precision) <= flt(allowed_qty, precision):
return
frappe.throw(
_(
"For Quantity ({0}) with the already transferred quantity ({1}) cannot be greater than allowed quantity ({2}) in Work Order {3}"
).format(flt(self.doc.fg_completed_qty), transferred_qty, allowed_qty, self.wo_doc.name),
StockOverProductionError,
)
class MaterialRequestStockEntry(BaseMaterialTransferStockEntry):
def before_validate(self):