From 722dc8c3f180be52d640f4d08c20a05e4c291d35 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 23 Apr 2026 17:27:07 +0000 Subject: [PATCH] fix: preserve inventory dimensions when raw materials are reset (backport #54440) (#54492) * fix: preserve inventory dimensions when raw materials are reset (#54440) * fix: preserve inventory dimensions when raw materials are reset * test: add test case (cherry picked from commit 0e20e35842bf090241d7b296c19f2714b6fd2588) # Conflicts: # erpnext/patches.txt # erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js # erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py * chore: resolve conflicts * chore: resolve conflicts * chore: resolve conflicts --------- Co-authored-by: Mihir Kandoi --- erpnext/patches.txt | 1 + erpnext/patches/v16_0/scr_inv_dimension.py | 24 +++++++++++ .../inventory_dimension.py | 15 +++++-- .../test_inventory_dimension.py | 1 - .../subcontracting_receipt.js | 8 +++- .../subcontracting_receipt.py | 32 +++++++++++++++ .../test_subcontracting_receipt.py | 41 +++++++++++++++++++ 7 files changed, 116 insertions(+), 6 deletions(-) create mode 100644 erpnext/patches/v16_0/scr_inv_dimension.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 2dcee807fec..5804b0bd0ee 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -433,3 +433,4 @@ erpnext.patches.v15_0.replace_http_with_https_in_sales_partner erpnext.patches.v16_0.add_portal_redirects erpnext.patches.v16_0.update_order_qty_and_requested_qty_based_on_mr_and_po erpnext.patches.v16_0.depends_on_inv_dimensions +erpnext.patches.v16_0.scr_inv_dimension diff --git a/erpnext/patches/v16_0/scr_inv_dimension.py b/erpnext/patches/v16_0/scr_inv_dimension.py new file mode 100644 index 00000000000..f4b320f674b --- /dev/null +++ b/erpnext/patches/v16_0/scr_inv_dimension.py @@ -0,0 +1,24 @@ +import frappe + +from erpnext.stock.doctype.inventory_dimension.inventory_dimension import get_inventory_dimensions + + +def execute(): + for dimension in get_inventory_dimensions(): + if frappe.db.exists( + "Custom Field", + { + "fieldname": dimension.source_fieldname, + "dt": "Subcontracting Receipt Supplied Item", + "reqd": 1, + }, + ): + frappe.set_value( + "Custom Field", + { + "fieldname": dimension.source_fieldname, + "dt": "Subcontracting Receipt Supplied Item", + "reqd": 1, + }, + {"reqd": 0, "mandatory_depends_on": "eval:doc.reference_name"}, + ) diff --git a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py index b43f2991bad..fa87380ddae 100644 --- a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py +++ b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py @@ -166,6 +166,13 @@ class InventoryDimension(Document): if label_start_with: label = f"{label_start_with} {self.dimension_name}" + mandatory_depends_on = self.mandatory_depends_on + if self.reqd: + if doctype == "Stock Entry Detail": + mandatory_depends_on = "eval:doc.s_warehouse" + elif doctype == "Subcontracting Receipt Supplied Item": + mandatory_depends_on = "eval:doc.reference_name" + dimension_fields = [ dict( fieldname="inventory_dimension", @@ -183,11 +190,11 @@ class InventoryDimension(Document): depends_on="eval:doc.s_warehouse" if doctype == "Stock Entry Detail" else "", search_index=1, reqd=1 - if self.reqd and not self.mandatory_depends_on and doctype != "Stock Entry Detail" + if self.reqd + and not self.mandatory_depends_on + and doctype not in ["Stock Entry Detail", "Subcontracting Receipt Supplied Item"] else 0, - mandatory_depends_on="eval:doc.s_warehouse" - if self.reqd and doctype == "Stock Entry Detail" - else self.mandatory_depends_on, + mandatory_depends_on=mandatory_depends_on, ), ] diff --git a/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py b/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py index 29e811ea4c4..67cf6402488 100644 --- a/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py +++ b/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py @@ -234,7 +234,6 @@ class TestInventoryDimension(FrappeTestCase): ) ) - doc.load_from_db doc.reqd = 0 doc.save() diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js index 4e502793068..9d6e4c05a20 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js @@ -425,7 +425,13 @@ frappe.ui.form.on("Subcontracting Receipt Item", { set_missing_values(frm); }, - items_delete: (frm) => { + before_items_remove(frm, cdt, cdn) { + const filtered_rows = frm.doc.supplied_items.filter((item) => item.reference_name !== cdn); + frm.doc.supplied_items = filtered_rows; + frm.refresh_field("supplied_items"); + }, + + items_delete(frm) { set_missing_values(frm); }, diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py index cd20986dc5a..b193f4fea5b 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py @@ -14,6 +14,7 @@ from erpnext.buying.utils import check_on_hold_or_closed_status from erpnext.controllers.subcontracting_controller import SubcontractingController from erpnext.setup.doctype.brand.brand import get_brand_defaults from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults +from erpnext.stock.doctype.inventory_dimension.inventory_dimension import get_inventory_dimensions from erpnext.stock.doctype.item.item import get_item_defaults from erpnext.stock.get_item_details import get_default_cost_center, get_default_expense_account from erpnext.stock.stock_ledger import get_valuation_rate @@ -118,6 +119,7 @@ class SubcontractingReceipt(SubcontractingController): ) def before_validate(self): + self.save_inventory_dimensions() super().before_validate() self.validate_items_qty() self.set_items_bom() @@ -158,6 +160,7 @@ class SubcontractingReceipt(SubcontractingController): self.set_supplied_items_expense_account() self.set_supplied_items_cost_center() + self.set_supplied_items_inventory_dimensions() def on_submit(self): self.validate_closed_subcontracting_order() @@ -289,6 +292,22 @@ class SubcontractingReceipt(SubcontractingController): self.company, ) + def set_supplied_items_inventory_dimensions(self): + if hasattr(self, "inventory_dimensions") and (inventory_dimensions := get_inventory_dimensions()): + for item in self.supplied_items: + key = ( + item.reference_name, + item.rm_item_code, + item.main_item_code, + item.batch_no, + item.serial_no, + ) + + for dimension in inventory_dimensions: + dimension_values = self.inventory_dimensions.get(dimension.source_fieldname, {}) + if key in dimension_values: + item.set(dimension.source_fieldname, dimension_values[key]) + def set_supplied_items_expense_account(self): for item in self.supplied_items: if not item.expense_account: @@ -305,6 +324,19 @@ class SubcontractingReceipt(SubcontractingController): get_brand_defaults(item.rm_item_code, self.company), ) + def save_inventory_dimensions(self): + if inventory_dimensions := get_inventory_dimensions(): + if not getattr(self, "inventory_dimensions", None): + self.inventory_dimensions = {} + + for dimension in inventory_dimensions: + self.inventory_dimensions[dimension.source_fieldname] = { + (d.reference_name, d.rm_item_code, d.main_item_code, d.batch_no, d.serial_no): d.get( + dimension.source_fieldname + ) + for d in self.supplied_items + } + def reset_supplied_items(self): if ( frappe.db.get_single_value("Buying Settings", "backflush_raw_materials_of_subcontract_based_on") diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py index 26025169979..347154dec68 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py @@ -2002,6 +2002,47 @@ class TestSubcontractingReceipt(FrappeTestCase): self.assertRaises(BOMQuantityError, scr.submit) + def test_inventory_dimensions(self): + """ + The subcontracting controller resets the supplied items table on each save causing the inventory dimensions to be lost. + This test ensures that the inventory dimensions are retained on each save. + """ + from erpnext.stock.doctype.inventory_dimension.test_inventory_dimension import ( + create_inventory_dimension, + ) + + inventory_dimension = create_inventory_dimension( + apply_to_all_doctypes=1, + dimension_name="Inv Site", + reference_document="Inv Site", + document_type="Inv Site", + ) + + inventory_dimension.reqd = 1 + inventory_dimension.save() + + set_backflush_based_on("BOM") + + sco = get_subcontracting_order() + rm_items = get_rm_items(sco.supplied_items) + itemwise_details = make_stock_in_entry(rm_items=rm_items) + make_stock_transfer_entry( + sco_no=sco.name, + rm_items=rm_items, + itemwise_details=copy.deepcopy(itemwise_details), + ) + scr = make_subcontracting_receipt(sco.name) + scr.items[0].inv_site = "Site 1" + scr.save() + + scr.supplied_items[0].inv_site = "Site 1" + scr.save() + + self.assertEqual(scr.supplied_items[0].inv_site, "Site 1") + + inventory_dimension.reqd = 0 + inventory_dimension.save() + def make_return_subcontracting_receipt(**args): args = frappe._dict(args)