From f003b3c3787488deee4c8238ef94fc0122e6e234 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 20 Jan 2026 13:27:14 +0530 Subject: [PATCH] fix: validation to check at-least one raw material for manufacture entry --- .../stock/doctype/stock_entry/stock_entry.py | 21 ++++++++++++++++++ .../doctype/stock_entry/test_stock_entry.py | 22 +++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 5d973cdc3a0..31a4081fc9d 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -278,9 +278,30 @@ class StockEntry(StockController, SubcontractingInwardController): self.validate_closed_subcontracting_order() self.validate_subcontract_order() + self.validate_raw_materials_exists() super().validate_subcontracting_inward() + def validate_raw_materials_exists(self): + if self.purpose not in ["Manufacture", "Repack", "Disassemble"]: + return + + if frappe.db.get_single_value("Manufacturing Settings", "material_consumption"): + return + + raw_materials = [] + for row in self.items: + if row.s_warehouse: + raw_materials.append(row.item_code) + + if not raw_materials: + frappe.throw( + _( + "At least one raw material item must be present in the stock entry for the type {0}" + ).format(bold(self.purpose)), + title=_("Raw Materials Missing"), + ) + def set_serial_batch_for_disassembly(self): if self.purpose != "Disassemble": return diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index 216858229fb..86e4211afa3 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -2361,6 +2361,28 @@ class TestStockEntry(IntegrationTestCase): self.assertEqual(target_sabb.entries[0].batch_no, batch) self.assertEqual([entry.serial_no for entry in target_sabb.entries], serial_nos[:2]) + def test_raw_material_missing_validation(self): + original_value = frappe.db.get_single_value("Manufacturing Settings", "material_consumption") + frappe.db.set_single_value("Manufacturing Settings", "material_consumption", 0) + + stock_entry = make_stock_entry( + item_code="_Test Item", + qty=1, + target="_Test Warehouse - _TC", + do_not_save=True, + ) + + stock_entry.purpose = "Manufacture" + stock_entry.stock_entry_type = "Manufacture" + stock_entry.items[0].is_finished_item = 1 + + self.assertRaises( + frappe.ValidationError, + stock_entry.save, + ) + + frappe.db.set_single_value("Manufacturing Settings", "material_consumption", original_value) + def make_serialized_item(self, **args): args = frappe._dict(args)