From 99bdef6bafca36b456eab98334dbcd5da63a2ae3 Mon Sep 17 00:00:00 2001 From: pandiyan Date: Mon, 6 Jul 2026 14:15:29 +0530 Subject: [PATCH] fix: rename variant item_code/item_name when attribute abbreviation changes Item Attribute abbreviations only got baked into a variant's item_code and item_name at creation time (make_variant_item_code returns early once item_code is set). Renaming an abbreviation afterwards left every existing variant stuck with the stale code, silently out of sync with its own attribute. Detect abbreviation renames on Item Attribute save, find every variant using the affected value, and rebuild+rename its item_code via frappe.rename_doc so linked records follow along. item_name is rebuilt in lockstep from the template's item_name, even if it had since been customized, since both fields are meant to be derived from the same abbreviation. (cherry picked from commit c0cfe5f363fa04da18c5efcbe57d7c4e50a5a9a5) --- erpnext/controllers/item_variant.py | 62 +++++++++++++++++++ .../doctype/item_attribute/item_attribute.py | 2 + 2 files changed, 64 insertions(+) diff --git a/erpnext/controllers/item_variant.py b/erpnext/controllers/item_variant.py index c2c620950af..a05ff7f3b7c 100644 --- a/erpnext/controllers/item_variant.py +++ b/erpnext/controllers/item_variant.py @@ -177,6 +177,68 @@ def update_variant_attribute_values(item_attribute): frappe.flags.attribute_values = None +def get_attribute_abbr_renames(item_attribute): + """Return the set of (current) attribute values whose abbreviation was renamed.""" + if item_attribute.numeric_values: + return set() + + db_value = item_attribute.get_doc_before_save() + if not db_value: + return set() + + old_abbrs = {d.name: d.abbr for d in db_value.item_attribute_values} + changed_values = set() + + for row in item_attribute.item_attribute_values: + if row.name in old_abbrs and old_abbrs[row.name] != row.abbr: + changed_values.add(row.attribute_value) + + return changed_values + + +def update_variant_item_codes_for_abbr_renames(item_attribute): + """Rebuild item_code/item_name of variant Items affected by a renamed Item Attribute abbreviation.""" + changed_values = get_attribute_abbr_renames(item_attribute) + if not changed_values: + return + + item_variant_table = frappe.qb.DocType("Item Variant Attribute") + variant_names = ( + frappe.qb.from_(item_variant_table) + .select(item_variant_table.parent) + .where(item_variant_table.attribute == item_attribute.name) + .where(item_variant_table.attribute_value.isin(list(changed_values))) + .distinct() + .run(pluck=True) + ) + + for variant_name in variant_names: + rename_variant_item_code(variant_name) + + +def rename_variant_item_code(variant_name): + """Recompute a variant's item_code/item_name from its template and current attribute abbreviations, + renaming the Item if it has changed.""" + variant = frappe.get_doc("Item", variant_name) + if not variant.variant_of: + return + + template = frappe.get_cached_doc("Item", variant.variant_of) + + new_code = frappe._dict({"item_code": None, "item_name": None, "attributes": variant.attributes}) + make_variant_item_code(template.item_code, template.item_name, new_code) + + if not new_code.item_code or new_code.item_code == variant.item_code: + return + + frappe.rename_doc("Item", variant.item_code, new_code.item_code) + + # Keep item_name in lockstep with item_code: both are derived from the same abbreviation, so + # item_name is always rebuilt here too, even if it had since been customized away from that pattern. + if new_code.item_name and new_code.item_name != variant.item_name: + frappe.db.set_value("Item", new_code.item_code, "item_name", new_code.item_name) + + def validate_item_attribute_value(attributes_list, attribute, attribute_value, item, from_variant=True): allow_rename_attribute_value = frappe.db.get_single_value( "Item Variant Settings", "allow_rename_attribute_value" diff --git a/erpnext/stock/doctype/item_attribute/item_attribute.py b/erpnext/stock/doctype/item_attribute/item_attribute.py index 14d2c6a4f12..09e1d56ffdd 100644 --- a/erpnext/stock/doctype/item_attribute/item_attribute.py +++ b/erpnext/stock/doctype/item_attribute/item_attribute.py @@ -10,6 +10,7 @@ from frappe.utils import flt from erpnext.controllers.item_variant import ( InvalidItemAttributeValueError, update_variant_attribute_values, + update_variant_item_codes_for_abbr_renames, validate_is_incremental, validate_item_attribute_value, ) @@ -49,6 +50,7 @@ class ItemAttribute(Document): def on_update(self): update_variant_attribute_values(self) + update_variant_item_codes_for_abbr_renames(self) self.validate_exising_items() self.set_enabled_disabled_in_items()