From 0428cddf5b61a3f71dd105dfe233294730fc9ebe Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Sat, 8 Aug 2026 16:47:07 +0530 Subject: [PATCH] fix: scale generated raw materials to the manufacture entry's production share Every generated entry copied each Job Card Item's full required_qty in the skip-transfer and BOM-backflush paths, so two entries for one job card consumed the requirement twice. Scale the rows to the share of production this entry accounts for and cap them at the requirement still unconsumed, dropping rows that have nothing left. An entry whose materials are exhausted then fails the existing at-least-one-raw-material check instead of minting finished goods from nothing. --- .../stock_entry_type/stock_entry_type.py | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry_type/stock_entry_type.py b/erpnext/stock/doctype/stock_entry_type/stock_entry_type.py index a10441106e0..d9ea63a9f82 100644 --- a/erpnext/stock/doctype/stock_entry_type/stock_entry_type.py +++ b/erpnext/stock/doctype/stock_entry_type/stock_entry_type.py @@ -126,6 +126,7 @@ class ManufactureEntry: if backflush_based_on != "BOM": available_serial_batches = self.get_transferred_serial_batches() + production_share = self.get_production_share() for item_code, _dict in item_dict.items(): _dict.s_warehouse = self.source_wh.get(item_code) or self.wip_warehouse _dict.t_warehouse = "" @@ -140,11 +141,29 @@ class ManufactureEntry: _dict.qty = calculated_qty self.update_available_serial_batches(_dict, available_serial_batches) - elif self.skip_material_transfer: - set_previous_operation_serial_batch(self.stock_entry, _dict) + else: + remaining_qty = max(flt(_dict.qty) - flt(_dict.consumed_qty), 0) + _dict.qty = min(flt(_dict.qty) * production_share, remaining_qty) + if not _dict.qty: + continue + + if self.skip_material_transfer: + set_previous_operation_serial_batch(self.stock_entry, _dict) self.stock_entry.append("items", _dict) + def get_production_share(self): + """Fraction of the job card's production this entry accounts for; raw materials are + generated proportionally so several partial entries never consume more than required.""" + for_quantity, pending_qty = frappe.db.get_value( + "Job Card", self.job_card, ["for_quantity", "pending_qty"] + ) + qty_to_produce = flt(for_quantity) - flt(pending_qty) + if not qty_to_produce: + return 1 + + return min(flt(self.for_quantity) / qty_to_produce, 1) + def parse_available_serial_batches(self, item_dict, available_serial_batches): key = (item_dict.item_code, item_dict.from_warehouse) if key not in available_serial_batches: