From 090c25d848a57c10a3b1ee078832f4b6f43a2743 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Tue, 26 May 2026 13:09:14 +0530 Subject: [PATCH] feat: allow creation of any number of variants in multiple item variant creation dialog --- erpnext/controllers/item_variant.py | 14 +++++++++++++- .../controllers/tests/test_item_variant.py | 19 ++++++++++++++++++- erpnext/stock/doctype/item/item.js | 12 +++++++----- 3 files changed, 38 insertions(+), 7 deletions(-) diff --git a/erpnext/controllers/item_variant.py b/erpnext/controllers/item_variant.py index 689f0c6a3f6..7c5db01c835 100644 --- a/erpnext/controllers/item_variant.py +++ b/erpnext/controllers/item_variant.py @@ -215,7 +215,9 @@ def create_variant(item: str, args: dict | str, use_template_image: bool = False variant_attributes = [] for d in template.attributes: - variant_attributes.append({"attribute": d.attribute, "attribute_value": args.get(_(d.attribute))}) + attribute_value = args.get(_(d.attribute)) or args.get(d.attribute) + if attribute_value: + variant_attributes.append({"attribute": d.attribute, "attribute_value": attribute_value}) variant.set("attributes", variant_attributes) copy_attributes_to_variant(template, variant) @@ -234,6 +236,12 @@ def enqueue_multiple_variant_creation(item: str, args: dict | str, use_template_ # There can be innumerable attribute combinations, enqueue if isinstance(args, str): variants = json.loads(args) + else: + variants = args + variants = {key: values for key, values in variants.items() if values} + if not variants: + frappe.throw(_("Please select at least one attribute value")) + total_variants = 1 for key in variants: total_variants *= len(variants[key]) @@ -257,6 +265,7 @@ def create_multiple_variants(item, args, use_template_image=False): count = 0 if isinstance(args, str): args = json.loads(args) + args = {key: values for key, values in args.items() if values} template_item = frappe.get_doc("Item", item) args_set = generate_keyed_value_combinations(args) @@ -291,6 +300,9 @@ def generate_keyed_value_combinations(args): """ # Return empty list if empty + if not args: + return [] + args = {key: values for key, values in args.items() if values} if not args: return [] diff --git a/erpnext/controllers/tests/test_item_variant.py b/erpnext/controllers/tests/test_item_variant.py index 04634cf911a..77c8e708d14 100644 --- a/erpnext/controllers/tests/test_item_variant.py +++ b/erpnext/controllers/tests/test_item_variant.py @@ -2,7 +2,11 @@ import json import frappe -from erpnext.controllers.item_variant import copy_attributes_to_variant, make_variant_item_code +from erpnext.controllers.item_variant import ( + copy_attributes_to_variant, + generate_keyed_value_combinations, + make_variant_item_code, +) from erpnext.stock.doctype.item.test_item import set_item_variant_settings from erpnext.stock.doctype.quality_inspection.test_quality_inspection import ( create_quality_inspection_parameter, @@ -17,6 +21,19 @@ class TestItemVariant(ERPNextTestSuite): variant = make_item_variant() self.assertEqual(variant.get("quality_inspection_template"), "_Test QC Template") + def test_generate_keyed_value_combinations_ignores_empty_attributes(self): + combinations = generate_keyed_value_combinations( + {"Test Colour": ["Red", "Blue"], "Test Size": ["Small", "Large"], "Test Fit": []} + ) + + self.assertEqual(len(combinations), 4) + self.assertNotIn("Test Fit", combinations[0]) + + single_attribute_combinations = generate_keyed_value_combinations( + {"Test Colour": ["Red", "Blue"], "Test Size": []} + ) + self.assertEqual(single_attribute_combinations, [{"Test Colour": "Red"}, {"Test Colour": "Blue"}]) + def create_variant_with_tables(item, args): if isinstance(args, str): diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js index 51fa652d310..125c6b86c46 100644 --- a/erpnext/stock/doctype/item/item.js +++ b/erpnext/stock/doctype/item/item.js @@ -777,11 +777,10 @@ $.extend(erpnext.item, { default: 0, onchange: function () { let selected_attributes = get_selected_attributes(); - let lengths = []; - Object.keys(selected_attributes).map((key) => { - lengths.push(selected_attributes[key].length); + let lengths = Object.keys(selected_attributes).map((key) => { + return selected_attributes[key].length; }); - if (lengths.includes(0)) { + if (!lengths.length) { me.multiple_variant_dialog.get_primary_btn().html(__("Create Variants")); me.multiple_variant_dialog.disable_primary_action(); } else { @@ -818,7 +817,7 @@ $.extend(erpnext.item, { fieldtype: "HTML", fieldname: "help", options: ``, }, ] @@ -880,6 +879,9 @@ $.extend(erpnext.item, { selected_attributes[attribute_name].push($(opt).attr("data-fieldname")); } }); + if (!selected_attributes[attribute_name].length) { + delete selected_attributes[attribute_name]; + } }); return selected_attributes;