From 650f874fbdd7cc949c6e2088ed91df8249119e91 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 20 Jan 2026 13:27:14 +0530 Subject: [PATCH 1/3] fix: validation to check at-least one raw material for manufacture entry (cherry picked from commit f003b3c3787488deee4c8238ef94fc0122e6e234) # Conflicts: # erpnext/stock/doctype/stock_entry/stock_entry.py # erpnext/stock/doctype/stock_entry/test_stock_entry.py --- .../stock/doctype/stock_entry/stock_entry.py | 29 +++++++++ .../doctype/stock_entry/test_stock_entry.py | 61 +++++++++++++++++++ 2 files changed, 90 insertions(+) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 21f3245bbc4..a4a699ab1de 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -244,6 +244,35 @@ class StockEntry(StockController): self.validate_same_source_target_warehouse_during_material_transfer() +<<<<<<< HEAD +======= + 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"), + ) + +>>>>>>> f003b3c378 (fix: validation to check at-least one raw material for manufacture entry) 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 0be02756207..076231193fa 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -2233,7 +2233,68 @@ class TestStockEntry(FrappeTestCase): se.submit() +<<<<<<< HEAD def make_serialized_item(**args): +======= + warehouse = "_Test Warehouse - _TC" + retain_sample_item = make_item( + "Retain Sample Item", + properties={ + "is_stock_item": 1, + "retain_sample": 1, + "sample_quantity": 2, + "has_batch_no": 1, + "has_serial_no": 1, + "create_new_batch": 1, + "batch_number_series": "SAMPLE-RET-.#####", + "serial_no_series": "SAMPLE-RET-SN-.#####", + }, + ) + material_receipt = make_stock_entry( + item_code=retain_sample_item.item_code, target=warehouse, qty=10, purpose="Material Receipt" + ) + + source_sabb = frappe.get_doc( + "Serial and Batch Bundle", material_receipt.items[0].serial_and_batch_bundle + ) + batch = source_sabb.entries[0].batch_no + serial_nos = [entry.serial_no for entry in source_sabb.entries] + + sample_entry = frappe.get_doc( + move_sample_to_retention_warehouse(material_receipt.company, material_receipt.items) + ) + sample_entry.submit() + target_sabb = frappe.get_doc("Serial and Batch Bundle", sample_entry.items[0].serial_and_batch_bundle) + + self.assertEqual(sample_entry.items[0].transfer_qty, 2) + 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): +>>>>>>> f003b3c378 (fix: validation to check at-least one raw material for manufacture entry) args = frappe._dict(args) se = frappe.copy_doc(test_records[0]) From a4b099e481a6fb3acf18518469b7323f3c9bef1e Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Sat, 24 Jan 2026 13:50:33 +0530 Subject: [PATCH 2/3] chore: fix conflicts Removed subcontracting order validation methods from stock entry. --- erpnext/stock/doctype/stock_entry/stock_entry.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index a4a699ab1de..d313c037e02 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -243,15 +243,8 @@ class StockEntry(StockController): self.reset_default_field_value("to_warehouse", "items", "t_warehouse") self.validate_same_source_target_warehouse_during_material_transfer() - -<<<<<<< HEAD -======= - 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 @@ -272,7 +265,6 @@ class StockEntry(StockController): title=_("Raw Materials Missing"), ) ->>>>>>> f003b3c378 (fix: validation to check at-least one raw material for manufacture entry) def set_serial_batch_for_disassembly(self): if self.purpose != "Disassemble": return From c351d6b1c0b5f782c09968b998dee4abcf613fee Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Sat, 24 Jan 2026 13:51:54 +0530 Subject: [PATCH 3/3] chore: fix conflicts Removed old implementation of make_serialized_item function and updated its definition. --- .../doctype/stock_entry/test_stock_entry.py | 41 +------------------ 1 file changed, 1 insertion(+), 40 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index 076231193fa..2383fabaf89 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -2232,44 +2232,6 @@ class TestStockEntry(FrappeTestCase): se.save() se.submit() - -<<<<<<< HEAD -def make_serialized_item(**args): -======= - warehouse = "_Test Warehouse - _TC" - retain_sample_item = make_item( - "Retain Sample Item", - properties={ - "is_stock_item": 1, - "retain_sample": 1, - "sample_quantity": 2, - "has_batch_no": 1, - "has_serial_no": 1, - "create_new_batch": 1, - "batch_number_series": "SAMPLE-RET-.#####", - "serial_no_series": "SAMPLE-RET-SN-.#####", - }, - ) - material_receipt = make_stock_entry( - item_code=retain_sample_item.item_code, target=warehouse, qty=10, purpose="Material Receipt" - ) - - source_sabb = frappe.get_doc( - "Serial and Batch Bundle", material_receipt.items[0].serial_and_batch_bundle - ) - batch = source_sabb.entries[0].batch_no - serial_nos = [entry.serial_no for entry in source_sabb.entries] - - sample_entry = frappe.get_doc( - move_sample_to_retention_warehouse(material_receipt.company, material_receipt.items) - ) - sample_entry.submit() - target_sabb = frappe.get_doc("Serial and Batch Bundle", sample_entry.items[0].serial_and_batch_bundle) - - self.assertEqual(sample_entry.items[0].transfer_qty, 2) - 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) @@ -2293,8 +2255,7 @@ def make_serialized_item(**args): frappe.db.set_single_value("Manufacturing Settings", "material_consumption", original_value) -def make_serialized_item(self, **args): ->>>>>>> f003b3c378 (fix: validation to check at-least one raw material for manufacture entry) +def make_serialized_item(**args): args = frappe._dict(args) se = frappe.copy_doc(test_records[0])