From ef24974645cc0c1637a8cc3776720d51171708f8 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Fri, 14 Aug 2026 19:56:25 +0530 Subject: [PATCH] fix(stock): restore subcontracting inward warehouse checks (#58183) --- .../services/material_receipt_issue.py | 6 +- .../stock/doctype/stock_entry/stock_entry.js | 6 +- .../stock/doctype/stock_entry/stock_entry.py | 10 ++- .../doctype/stock_entry/test_stock_entry.py | 65 +++++++++++++++++++ 4 files changed, 83 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/services/material_receipt_issue.py b/erpnext/stock/doctype/stock_entry/services/material_receipt_issue.py index 27dac61c9b2..6aa6859e1a6 100644 --- a/erpnext/stock/doctype/stock_entry/services/material_receipt_issue.py +++ b/erpnext/stock/doctype/stock_entry/services/material_receipt_issue.py @@ -14,6 +14,7 @@ class MaterialReceiptStockEntry(BaseStockEntry): self.validate_warehouse() def set_default_warehouse(self): + self.doc.from_warehouse = None for row in self.doc.items: row.s_warehouse = None if not row.t_warehouse and self.doc.to_warehouse: @@ -27,6 +28,7 @@ class MaterialReceiptStockEntry(BaseStockEntry): class BaseMaterialIssueStockEntry(BaseStockEntry): def set_default_warehouse(self): + self.doc.to_warehouse = None for row in self.doc.items: row.t_warehouse = None if not row.s_warehouse and self.doc.from_warehouse: @@ -38,13 +40,15 @@ class BaseMaterialIssueStockEntry(BaseStockEntry): frappe.throw(_("Source Warehouse is required for item {0}").format(row.item_code)) -class MaterialIssueStockEntry(BaseMaterialIssueStockEntry): +class SourceOnlyStockEntry(BaseMaterialIssueStockEntry): def before_validate(self): self.set_default_warehouse() def validate(self): self.validate_warehouse() + +class MaterialIssueStockEntry(SourceOnlyStockEntry): def add_items(self): self.add_raw_materials_based_on_bom() diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index df3c4ab0d65..8b95c261c7c 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -627,13 +627,15 @@ frappe.ui.form.on("Stock Entry", { frm.fields_dict["items"].grid.update_docfield_property( "s_warehouse", "in_list_view", - !["Material Receipt", "Receive from Customer"].includes(frm.doc.purpose) + !["Material Receipt", "Receive from Customer", "Subcontracting Return"].includes(frm.doc.purpose) ); frm.fields_dict["items"].grid.update_docfield_property( "t_warehouse", "in_list_view", - !["Material Issue"].includes(frm.doc.purpose) + !["Material Issue", "Return Raw Material to Customer", "Subcontracting Delivery"].includes( + frm.doc.purpose + ) ); frm.fields_dict["items"].grid.reset_grid(); diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index b26c004237a..386f49b36ed 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -45,7 +45,11 @@ from .services.manufacturing import ( OperationsNotCompleteError, RepackStockEntry, ) -from .services.material_receipt_issue import MaterialIssueStockEntry, MaterialReceiptStockEntry +from .services.material_receipt_issue import ( + MaterialIssueStockEntry, + MaterialReceiptStockEntry, + SourceOnlyStockEntry, +) from .services.material_transfer import ( MaterialRequestStockEntry, MaterialTransferForManufactureStockEntry, @@ -216,6 +220,10 @@ class StockEntry(StockController, SubcontractingInwardController): "Send to Subcontractor": SendToSubcontractorStockEntry, "Material Issue": MaterialIssueStockEntry, "Material Receipt": MaterialReceiptStockEntry, + "Receive from Customer": MaterialReceiptStockEntry, + "Return Raw Material to Customer": SourceOnlyStockEntry, + "Subcontracting Delivery": SourceOnlyStockEntry, + "Subcontracting Return": MaterialReceiptStockEntry, } self.purpose_cls = purpose_map.get(self.purpose) diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index 2aa9b0505c8..3bddd240507 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -61,6 +61,71 @@ class TestStockEntry(ERPNextTestSuite): self.load_test_records("Stock Entry") frappe.local.flags.dont_execute_stock_reposts = False + def test_subcontracting_inward_warehouse_direction(self): + source_warehouse = "_Test Warehouse - _TC" + target_warehouse = "_Test Warehouse 1 - _TC" + + for purpose in ("Return Raw Material to Customer", "Subcontracting Delivery"): + with self.subTest(purpose=purpose): + stock_entry = frappe.new_doc("Stock Entry") + stock_entry.purpose = purpose + stock_entry.from_warehouse = source_warehouse + stock_entry.to_warehouse = target_warehouse + stock_entry.append( + "items", + { + "item_code": "_Test Item", + "t_warehouse": target_warehouse, + "cost_center": "Main - _TC", + }, + ) + + stock_entry.before_validate() + + self.assertEqual(stock_entry.from_warehouse, source_warehouse) + self.assertIsNone(stock_entry.to_warehouse) + self.assertEqual(stock_entry.items[0].s_warehouse, source_warehouse) + self.assertIsNone(stock_entry.items[0].t_warehouse) + + for purpose in ("Receive from Customer", "Subcontracting Return"): + with self.subTest(purpose=purpose): + stock_entry = frappe.new_doc("Stock Entry") + stock_entry.purpose = purpose + stock_entry.from_warehouse = source_warehouse + stock_entry.to_warehouse = target_warehouse + stock_entry.append( + "items", + { + "item_code": "_Test Item", + "s_warehouse": source_warehouse, + "cost_center": "Main - _TC", + }, + ) + + stock_entry.before_validate() + + self.assertIsNone(stock_entry.from_warehouse) + self.assertEqual(stock_entry.to_warehouse, target_warehouse) + self.assertIsNone(stock_entry.items[0].s_warehouse) + self.assertEqual(stock_entry.items[0].t_warehouse, target_warehouse) + + def test_subcontracting_inward_warehouse_is_mandatory(self): + purposes = { + "Return Raw Material to Customer": "Source Warehouse is required", + "Subcontracting Delivery": "Source Warehouse is required", + "Receive from Customer": "Target Warehouse is required", + "Subcontracting Return": "Target Warehouse is required", + } + + for purpose, message in purposes.items(): + with self.subTest(purpose=purpose): + stock_entry = frappe.new_doc("Stock Entry") + stock_entry.purpose = purpose + stock_entry.append("items", {"item_code": "_Test Item"}) + + with self.assertRaisesRegex(frappe.ValidationError, message): + stock_entry.validate() + def test_stock_entry_qty(self): item_code = "_Test Item 2" warehouse = "_Test Warehouse - _TC"