From 6771daf6a1cd07b551ee60c098c996698545603a Mon Sep 17 00:00:00 2001 From: Umair Sayed Date: Fri, 12 Jun 2026 14:45:02 +0530 Subject: [PATCH 1/4] fix(bom): allow zero qty for secondary items (Co-Product, By-Product, Scrap, Additional Finished Good) Secondary output items in a BOM do not always guarantee output during manufacture. The actual qty is only known when manufacturing completes, so setting zero in the BOM is a valid way to express "output is non-deterministic". Changes: - Remove `reqd: 1` from the qty field in BOM Secondary Item so that 0 is accepted as an explicit value (non_negative constraint is kept, so negative values are still rejected). - Relax validate_secondary_items() in bom.py to only reject qty that is None/missing, not qty that is explicitly 0. - Add a qty event handler in bom.js that shows a blue informational alert when the user sets qty to 0, explaining that the actual output will be recorded at manufacture time. Fixes https://github.com/frappe/erpnext/issues/55401 Co-Authored-By: Claude Sonnet 4.6 --- erpnext/manufacturing/doctype/bom/bom.js | 13 +++++++++++++ erpnext/manufacturing/doctype/bom/bom.py | 4 ++-- .../bom_secondary_item/bom_secondary_item.json | 3 +-- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js index 9fbe4f1174c..3422ba1df7c 100644 --- a/erpnext/manufacturing/doctype/bom/bom.js +++ b/erpnext/manufacturing/doctype/bom/bom.js @@ -1012,6 +1012,19 @@ frappe.ui.form.on("BOM Secondary Item", { item_code(frm, cdt, cdn) { const { item_code } = locals[cdt][cdn]; }, + + qty(frm, cdt, cdn) { + const row = locals[cdt][cdn]; + if (flt(row.qty) === 0) { + frappe.show_alert({ + message: __( + "Row #{0}: Qty is set to zero for {1}. The actual output will be entered at the time of manufacture.", + [row.idx, frappe.bold(row.item_code)] + ), + indicator: "blue", + }); + } + }, }); function trigger_process_loss_qty_prompt(frm, cdt, cdn, item_code) { diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index d0a27613780..c20e8f3cd47 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -373,9 +373,9 @@ class BOM(WebsiteGenerator): ).format(item.idx, get_link_to_form("Item", item.item_code)) ) - if not item.qty: + if item.qty is None: frappe.throw( - _("Row #{0}: Quantity should be greater than 0 for {1} Item {2}").format( + _("Row #{0}: Quantity is required for {1} Item {2}").format( item.idx, item.secondary_item_type, get_link_to_form("Item", item.item_code) ) ) diff --git a/erpnext/manufacturing/doctype/bom_secondary_item/bom_secondary_item.json b/erpnext/manufacturing/doctype/bom_secondary_item/bom_secondary_item.json index 04ecebc0f4b..0e5363582e9 100644 --- a/erpnext/manufacturing/doctype/bom_secondary_item/bom_secondary_item.json +++ b/erpnext/manufacturing/doctype/bom_secondary_item/bom_secondary_item.json @@ -138,8 +138,7 @@ "fieldtype": "Float", "in_list_view": 1, "label": "Qty", - "non_negative": 1, - "reqd": 1 + "non_negative": 1 }, { "default": "0", From de3df6bcefa69f26990a6f5ab2e09af0182f686a Mon Sep 17 00:00:00 2001 From: Umair Sayed Date: Fri, 12 Jun 2026 16:04:12 +0530 Subject: [PATCH 2/4] fix(manufacture): preserve user-entered rate for secondary items with zero cost allocation When a BOM secondary item has cost_allocation_per = 0 (the default), the previous code unconditionally computed `0 / transfer_qty = 0`, wiping any rate the user had entered for the item. Now the allocation formula only runs when cost_allocation_per > 0, allowing the valuation-rate fallback (or a manually entered rate) to apply instead. Additionally, secondary items with transfer_qty = 0 now short-circuit the entire rate pipeline: they get rate = 0 and amount = 0 immediately, avoiding a ZeroDivisionError and the spurious "enter basic rate" prompt. Co-Authored-By: Claude Sonnet 4.6 --- erpnext/stock/doctype/stock_entry/stock_entry.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 80552b7c25f..048fa211b6f 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -554,6 +554,13 @@ class StockEntry(StockController, SubcontractingInwardController): for d in self.get("items"): if d.s_warehouse or d.set_basic_rate_manually: continue + + # Zero-qty secondary items carry no inventory value; skip rate calculation + if d.secondary_item_type and flt(d.transfer_qty) == 0: + d.basic_rate = 0.0 + d.basic_amount = 0.0 + continue + self._set_incoming_item_rate(d, outgoing_items_cost, raise_error_if_no_rate, zero_valuation_items) if zero_valuation_items: @@ -575,7 +582,10 @@ class StockEntry(StockController, SubcontractingInwardController): cost_allocation_per = frappe.get_value( "BOM Secondary Item", d.bom_secondary_item, "cost_allocation_per" ) - d.basic_rate = (outgoing_items_cost * (cost_allocation_per / 100)) / d.transfer_qty + # Only recalculate when cost is actually allocated; otherwise preserve the + # user-entered rate (or fall through to get_valuation_rate below) + if cost_allocation_per and flt(d.transfer_qty): + d.basic_rate = (outgoing_items_cost * (cost_allocation_per / 100)) / d.transfer_qty if not d.basic_rate and not d.allow_zero_valuation_rate: d.basic_rate = get_valuation_rate( From bc7c0de208a36cff3d4edbc3ec6c48dfb6707940 Mon Sep 17 00:00:00 2001 From: Umair Sayed Date: Fri, 12 Jun 2026 16:10:56 +0530 Subject: [PATCH 3/4] refactor(bom): remove qty=0 alert from BOM Secondary Item JS The informational toast is not required for the feature to work. The core fix (reqd removed from JSON, validation relaxed in bom.py) is sufficient to allow zero qty on BOM secondary items. Co-Authored-By: Claude Sonnet 4.6 --- erpnext/manufacturing/doctype/bom/bom.js | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js index 3422ba1df7c..9fbe4f1174c 100644 --- a/erpnext/manufacturing/doctype/bom/bom.js +++ b/erpnext/manufacturing/doctype/bom/bom.js @@ -1012,19 +1012,6 @@ frappe.ui.form.on("BOM Secondary Item", { item_code(frm, cdt, cdn) { const { item_code } = locals[cdt][cdn]; }, - - qty(frm, cdt, cdn) { - const row = locals[cdt][cdn]; - if (flt(row.qty) === 0) { - frappe.show_alert({ - message: __( - "Row #{0}: Qty is set to zero for {1}. The actual output will be entered at the time of manufacture.", - [row.idx, frappe.bold(row.item_code)] - ), - indicator: "blue", - }); - } - }, }); function trigger_process_loss_qty_prompt(frm, cdt, cdn, item_code) { From c1bef53f92ded95d80abd255f94102f372d44b70 Mon Sep 17 00:00:00 2001 From: Mohammad Umair Sayed Date: Mon, 15 Jun 2026 15:06:24 +0530 Subject: [PATCH 4/4] refactor(bom): drop redundant secondary item qty None check Float fields default to 0, so qty is never None. Per review feedback, remove the validation entirely. Co-Authored-By: Claude Opus 4.8 --- erpnext/manufacturing/doctype/bom/bom.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index c20e8f3cd47..ebe6e04ac03 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -373,13 +373,6 @@ class BOM(WebsiteGenerator): ).format(item.idx, get_link_to_form("Item", item.item_code)) ) - if item.qty is None: - frappe.throw( - _("Row #{0}: Quantity is required for {1} Item {2}").format( - item.idx, item.secondary_item_type, get_link_to_form("Item", item.item_code) - ) - ) - if item.process_loss_per >= 100: frappe.throw( _("Row #{0}: Process Loss Percentage should be less than 100% for {1} Item {2}").format(