From dfec7bd5c703086c203b4e62eb1d75b13cfdcae7 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Mon, 3 Aug 2026 13:55:26 +0530 Subject: [PATCH] fix(stock): stop a secondary item type from waiving quality inspection The inspection skip for secondary rows applied to every purpose, and in validate_inspection it skipped the row even when the item itself mandated inspection. Secondary Item Type is only meaningful on the purposes that produce secondary items, but nothing clears it elsewhere, since mark_finished_and_secondary_items runs for Manufacture and Repack alone. A Material Receipt of an item marked Inspection Required Before Purchase is blocked without an inspection. Setting Secondary Item Type on the row submitted it clean. Limit the exemption to the purposes that produce secondary items, and to other doctypes such as Subcontracting Receipt, which carry the field with its intended meaning. The client-side mirror is kept in sync. --- erpnext/public/js/controllers/transaction.js | 7 ++++++- .../services/quality_inspection_service.py | 20 +++++++++++++++++-- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 6d2de472ce0..615d3302c5e 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -18,10 +18,15 @@ erpnext.stock.qi_outgoing_purposes = [ "Subcontracting Delivery", "Disassemble", ]; +erpnext.stock.secondary_item_purposes = ["Manufacture", "Repack", "Disassemble"]; erpnext.stock.is_incoming_qi_purpose = (purpose) => purpose === "Manufacture" || erpnext.stock.qi_incoming_purposes.includes(purpose); erpnext.stock.row_requires_quality_inspection = (purpose, row) => { - if (row.secondary_item_type || row.is_legacy_scrap_item) return false; + if ( + erpnext.stock.secondary_item_purposes.includes(purpose) && + (row.secondary_item_type || row.is_legacy_scrap_item) + ) + return false; if (purpose === "Manufacture") return !!row.is_finished_item; if (erpnext.stock.qi_incoming_purposes.includes(purpose)) return !!row.t_warehouse; if (erpnext.stock.qi_outgoing_purposes.includes(purpose)) diff --git a/erpnext/stock/services/quality_inspection_service.py b/erpnext/stock/services/quality_inspection_service.py index 3784725a4d2..b9fab051038 100644 --- a/erpnext/stock/services/quality_inspection_service.py +++ b/erpnext/stock/services/quality_inspection_service.py @@ -50,9 +50,25 @@ QI_OUTGOING_PURPOSES = ( ) +SECONDARY_ITEM_PURPOSES = ("Manufacture", "Repack", "Disassemble") + + +def is_inspection_exempt_secondary_row(doc, row) -> bool: + """Whether the row is a secondary item on a document that produces secondary items.""" + if not (row.get("secondary_item_type") or row.get("is_legacy_scrap_item")): + return False + + if doc.doctype == "Stock Entry": + return doc.purpose in SECONDARY_ITEM_PURPOSES + + return True + + def stock_entry_row_requires_inspection(purpose, row): """Check if this Stock Entry row need a Quality Inspection.""" - if row.get("secondary_item_type") or row.get("is_legacy_scrap_item"): + if purpose in SECONDARY_ITEM_PURPOSES and ( + row.get("secondary_item_type") or row.get("is_legacy_scrap_item") + ): return False if purpose == "Manufacture": return bool(row.is_finished_item) @@ -88,7 +104,7 @@ class QualityInspectionService: elif self.doc.doctype == "Stock Entry": qi_required = stock_entry_row_requires_inspection(self.doc.purpose, row) - if row.get("secondary_item_type") or row.get("is_legacy_scrap_item"): + if is_inspection_exempt_secondary_row(self.doc, row): continue if qi_required: # validate row only if inspection is required on item level