From 00139081f6a9aa4af29a2846b0db5e51e6ea6587 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Mon, 3 Aug 2026 15:57:35 +0530 Subject: [PATCH] fix(stock): validate only the variant attributes that changed Disabling an Item Attribute writes `disabled = 1` into every Item Variant Attribute row, including the rows on the template. `validate_variant` runs on every save and walks the whole attribute table, so any later save of an existing variant re-checked its untouched rows against the now-disabled template row and threw. `update_variants` hit the same wall, which made a single template save fail once an attribute was disabled. The flag exists to keep an attribute out of new variants, not to freeze the variants that already use it. item.js only reads it to drop the attribute from the variant creation dialog. Skip rows that are unchanged since the last save. New and edited rows are still checked, so a disabled attribute cannot be added to an existing variant, and the same guard covers the sibling checks for attributes and values that the template no longer offers. (cherry picked from commit 25cd7936176cd946ad4d5899294524125d181f40) --- erpnext/stock/doctype/item/item.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index bb40f6cb810..13842784f9a 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -853,7 +853,17 @@ class Item(Document): frappe.throw(_("Item {0} is not a template item.").format(frappe.bold(self.variant_of))) if based_on == "Item Attribute": + previous_doc = self.get_doc_before_save() + saved_attributes = ( + {(row.attribute, row.attribute_value) for row in previous_doc.attributes} + if previous_doc + else set() + ) + for d in self.attributes: + if (d.attribute, d.attribute_value) in saved_attributes: + continue + if not frappe.db.exists( "Item Variant Attribute", {"attribute": d.attribute, "parent": self.variant_of} ):