diff --git a/erpnext/controllers/item_variant.py b/erpnext/controllers/item_variant.py index 821c81cad31..9817f0f47ae 100644 --- a/erpnext/controllers/item_variant.py +++ b/erpnext/controllers/item_variant.py @@ -169,6 +169,74 @@ def create_variant(item, args): return variant +@frappe.whitelist() +def enqueue_multiple_variant_creation(item, args): + # There can be innumerable attribute combinations, enqueue + frappe.enqueue("erpnext.controllers.item_variant.create_multiple_variants", + item=item, args=args, now=frappe.flags.in_test); + +def create_multiple_variants(item, args): + if isinstance(args, basestring): + args = json.loads(args) + + args_set = generate_keyed_value_combinations(args) + + for attribute_values in args_set: + if not get_variant(item, args=attribute_values): + variant = create_variant(item, attribute_values) + variant.save() + +def generate_keyed_value_combinations(args): + """ + From this: + + args = {"attr1": ["a", "b", "c"], "attr2": ["1", "2"], "attr3": ["A"]} + + To this: + + [ + {u'attr1': u'a', u'attr2': u'1', u'attr3': u'A'}, + {u'attr1': u'b', u'attr2': u'1', u'attr3': u'A'}, + {u'attr1': u'c', u'attr2': u'1', u'attr3': u'A'}, + {u'attr1': u'a', u'attr2': u'2', u'attr3': u'A'}, + {u'attr1': u'b', u'attr2': u'2', u'attr3': u'A'}, + {u'attr1': u'c', u'attr2': u'2', u'attr3': u'A'} + ] + + """ + # Return empty list if empty + if not args: + return [] + + # Turn `args` into a list of lists of key-value tuples: + # [ + # [(u'attr2', u'1'), (u'attr2', u'2')], + # [(u'attr3', u'A')], + # [(u'attr1', u'a'), (u'attr1', u'b'), (u'attr1', u'c')] + # ] + key_value_lists = [[(key, val) for val in args[key]] for key in args.keys()] + + # Store the first, but as objects + # [{u'attr2': u'1'}, {u'attr2': u'2'}] + results = key_value_lists.pop(0) + results = [{d[0]: d[1]} for d in results] + + # Iterate the remaining + # Take the next list to fuse with existing results + for l in key_value_lists: + new_results = [] + for res in results: + for key_val in l: + # create a new clone of object in result + obj = copy.deepcopy(res) + # to be used with every incoming new value + obj[key_val[0]] = key_val[1] + # and pushed into new_results + new_results.append(obj) + results = new_results + + return results + def copy_attributes_to_variant(item, variant): from frappe.model import no_value_fields @@ -208,7 +276,7 @@ def copy_attributes_to_variant(item, variant): attributes_description = "" for d in variant.attributes: attributes_description += "
" + d.attribute + ": " + cstr(d.attribute_value) + "
" - + if attributes_description not in variant.description: variant.description += attributes_description diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js index 656ee690209..a71e1eadbb9 100644 --- a/erpnext/stock/doctype/item/item.js +++ b/erpnext/stock/doctype/item/item.js @@ -57,9 +57,19 @@ frappe.ui.form.on("Item", { frappe.set_route("List", "Item", {"variant_of": frm.doc.name}); }, __("View")); - frm.add_custom_button(__("Variant"), function() { - erpnext.item.make_variant(frm); - }, __("Make")); + if(frm.doc.variant_based_on==="Item Attribute") { + frm.add_custom_button(__("Single Variant"), function() { + erpnext.item.show_single_variant_dialog(frm); + }, __("Make")); + frm.add_custom_button(__("Multiple Variants"), function() { + erpnext.item.show_multiple_variants_dialog(frm); + }, __("Make")); + } else { + frm.add_custom_button(__("Variant"), function() { + erpnext.item.show_modal_for_manufacturers(frm); + }, __("Make")); + } + frm.page.set_inner_btn_group_as_primary(__("Make")); } if (frm.doc.variant_of) { @@ -263,14 +273,6 @@ $.extend(erpnext.item, { } }, - make_variant: function(frm) { - if(frm.doc.variant_based_on==="Item Attribute") { - erpnext.item.show_modal_for_item_attribute_selection(frm); - } else { - erpnext.item.show_modal_for_manufacturers(frm); - } - }, - show_modal_for_manufacturers: function(frm) { var dialog = new frappe.ui.Dialog({ fields: [ @@ -301,7 +303,146 @@ $.extend(erpnext.item, { dialog.show(); }, - show_modal_for_item_attribute_selection: function(frm) { + show_multiple_variants_dialog: function(frm) { + var me = this; + + if(me.multiple_variant_dialog) { + me.multiple_variant_dialog.show(); + return; + } + + let promises = []; + let attr_val_fields = {}; + + function make_fields_from_attribute_values(attr_dict) { + let fields = []; + Object.keys(attr_dict).forEach((name, i) => { + if(i % 3 === 0){ + fields.push({fieldtype: 'Section Break'}); + } + fields.push({fieldtype: 'Column Break', label: name}); + attr_dict[name].forEach(value => { + fields.push({ + fieldtype: 'Check', + label: value, + fieldname: value, + default: 0, + onchange: function() { + let selected_attributes = get_selected_attributes(); + let lengths = []; + Object.keys(selected_attributes).map(key => { + lengths.push(selected_attributes[key].length); + }); + if(lengths.includes(0)) { + me.multiple_variant_dialog.get_primary_btn().html(__("Make Variants")); + me.multiple_variant_dialog.disable_primary_action(); + } else { + let no_of_combinations = lengths.reduce((a, b) => a * b, 1); + me.multiple_variant_dialog.get_primary_btn() + .html(__( + `Make ${no_of_combinations} Variant + ${no_of_combinations === 1 ? '' : 's'}` + )); + me.multiple_variant_dialog.enable_primary_action(); + } + } + }); + }); + }); + return fields; + } + + function make_and_show_dialog(fields) { + me.multiple_variant_dialog = new frappe.ui.Dialog({ + title: __("Select Attribute Values"), + fields: [ + { + fieldtype: "HTML", + fieldname: "help", + options: ``, + } + ].concat(fields) + }); + + me.multiple_variant_dialog.set_primary_action(__("Make Variants"), () => { + let selected_attributes = get_selected_attributes(); + + me.multiple_variant_dialog.hide(); + frappe.call({ + method:"erpnext.controllers.item_variant.enqueue_multiple_variant_creation", + args: { + "item": frm.doc.name, + "args": selected_attributes + }, + callback: function() { + frappe.show_alert({ + message: __("Variant creation has been queued."), + indicator: 'orange' + }); + } + }); + }); + + $($(me.multiple_variant_dialog.$wrapper.find('.form-column')) + .find('.frappe-control')).css('margin-bottom', '0px'); + + me.multiple_variant_dialog.disable_primary_action(); + me.multiple_variant_dialog.clear(); + me.multiple_variant_dialog.show(); + } + + function get_selected_attributes() { + let selected_attributes = {}; + me.multiple_variant_dialog.$wrapper.find('.form-column').each((i, col) => { + if(i===0) return; + let attribute_name = $(col).find('label').html(); + selected_attributes[attribute_name] = []; + let checked_opts = $(col).find('.checkbox input'); + checked_opts.each((i, opt) => { + if($(opt).is(':checked')) { + selected_attributes[attribute_name].push($(opt).attr('data-fieldname')); + } + }); + }); + + return selected_attributes; + } + + let attribute_names = frm.doc.attributes.map(d => d.attribute); + + attribute_names.forEach(function(attribute) { + let p = new Promise(resolve => { + frappe.call({ + method:"frappe.client.get_list", + args:{ + doctype:"Item Attribute Value", + filters: [ + ["parent","=", attribute] + ], + fields: ["attribute_value"] + } + }).then((r) => { + if(r.message) { + attr_val_fields[attribute] = r.message.map(function(d) { return d.attribute_value; }); + resolve(); + } + }); + }); + + promises.push(p); + + }, this); + + Promise.all(promises).then(() => { + let fields = make_fields_from_attribute_values(attr_val_fields); + make_and_show_dialog(fields); + }) + + }, + + show_single_variant_dialog: function(frm) { var fields = [] for(var i=0;i< frm.doc.attributes.length;i++){