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/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index 09b8b5eaaf5..2cd3fde5f84 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -7,6 +7,7 @@ from frappe.utils import add_days, cstr, flt, get_time, getdate, nowtime, today from erpnext.accounts.doctype.account.test_account import get_inventory_account from erpnext.controllers.accounts_controller import InvalidQtyError +from erpnext.exceptions import QualityInspectionRequiredError from erpnext.stock.doctype.item.test_item import ( create_item, make_item, @@ -2728,6 +2729,36 @@ class TestStockEntry(ERPNextTestSuite): self.assertEqual(fg_sle.incoming_rate, 0) self.assertEqual(fg_sle.stock_value_difference, 0) + def test_secondary_item_type_does_not_waive_inspection_outside_manufacturing(self): + """A stray secondary item type must not let a QI-required item through a receipt.""" + item = make_item( + properties={ + "is_stock_item": 1, + "valuation_rate": 50, + "inspection_required_before_purchase": 1, + } + ).name + + def receipt(secondary_item_type): + se = frappe.new_doc("Stock Entry") + se.purpose = se.stock_entry_type = "Material Receipt" + se.company = "_Test Company" + se.inspection_required = 1 + se.append( + "items", + { + "item_code": item, + "t_warehouse": "_Test Warehouse - _TC", + "qty": 10, + "conversion_factor": 1, + "secondary_item_type": secondary_item_type, + }, + ) + return se + + self.assertRaises(QualityInspectionRequiredError, receipt("").submit) + self.assertRaises(QualityInspectionRequiredError, receipt("Scrap").submit) + def _make_wo_for_free_raw_material(self, rm_item, fg_item, bom_no): from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record from erpnext.manufacturing.doctype.work_order.work_order import ( 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