diff --git a/addons/product/models/product_template.py b/addons/product/models/product_template.py index f3f57d86bbd..bfe6d522803 100644 --- a/addons/product/models/product_template.py +++ b/addons/product/models/product_template.py @@ -953,12 +953,17 @@ class ProductTemplate(models.Model): if ignore_no_variant: attribute_lines = attribute_lines._without_no_variant_attributes() - if len(combination) != len(attribute_lines): + attribute_lines_without_multi = attribute_lines.filtered( + lambda l: l.attribute_id.display_type != 'multi') + combination_without_multi = combination.filtered( + lambda l: l.attribute_line_id.attribute_id.display_type != 'multi') + + if len(combination_without_multi) != len(attribute_lines_without_multi): # number of attribute values passed is different than the # configuration of attributes on the template return False - if attribute_lines != combination.attribute_line_id: + if attribute_lines_without_multi != combination_without_multi.attribute_line_id: # combination has different attributes than the ones configured on the template return False @@ -1189,6 +1194,16 @@ class ProductTemplate(models.Model): while True: current_line_values = product_template_attribute_values_per_line[line_index] current_ptav_index = value_index_per_line[line_index] + + # For multi-checkbox attribute, the list is empty as we want to start without any selected value + if not current_line_values: + if line_index == len(product_template_attribute_values_per_line) - 1: + # submit combination if we're on the last line + yield partial_combination + else: + line_index += 1 + continue + current_ptav = current_line_values[current_ptav_index] # removing exclusions from current_ptav as we're removing it from partial_combination @@ -1258,16 +1273,18 @@ class ProductTemplate(models.Model): necessary_values = necessary_values or self.env['product.template.attribute.value'] necessary_attribute_lines = necessary_values.mapped('attribute_line_id') attribute_lines = self.valid_product_template_attribute_line_ids.filtered( - lambda ptal: ptal not in necessary_attribute_lines and ( - ptal.attribute_id.display_type != 'multi')) + lambda ptal: ptal not in necessary_attribute_lines) if not attribute_lines and self._is_combination_possible(necessary_values, parent_combination): yield necessary_values - product_template_attribute_values_per_line = [ - ptal.product_template_value_ids._only_active() - for ptal in attribute_lines - ] + product_template_attribute_values_per_line = [] + for ptal in attribute_lines: + if ptal.attribute_id.display_type != 'multi': + values_to_add = ptal.product_template_value_ids._only_active() + else: + values_to_add = self.env['product.template.attribute.value'] + product_template_attribute_values_per_line.append(values_to_add) for partial_combination in self._cartesian_product(product_template_attribute_values_per_line, parent_combination): combination = partial_combination + necessary_values diff --git a/addons/website_sale/static/tests/tours/website_sale_shop_multi_checkbox.js b/addons/website_sale/static/tests/tours/website_sale_shop_multi_checkbox.js new file mode 100644 index 00000000000..72de67579d1 --- /dev/null +++ b/addons/website_sale/static/tests/tours/website_sale_shop_multi_checkbox.js @@ -0,0 +1,60 @@ +/** @odoo-module **/ + +import { registry } from "@web/core/registry"; +import tourUtils from "@website_sale/js/tours/tour_utils"; + +// This tour relies on a data created from the python test. +registry.category("web_tour.tours").add('tour_shop_multi_checkbox', { + test: true, + url: '/shop?search=Product Multi', + steps: () => [ + { + content: "select Product", + trigger: '.oe_product_cart a:containsExact("Product Multi")', + }, + { + content: "check price", + trigger: '.oe_currency_value:contains("750")', + run: function () {}, + }, + { + content: 'click on the first option to select it', + trigger: 'input[data-attribute_name="Options"][data-value_name="Option 1"]', + }, + { + content: 'click on the third option to select it', + trigger: 'input[data-attribute_name="Options"][data-value_name="Option 3"]', + }, + { + content: 'check combination is not possible', + trigger: '.js_main_product.css_not_available .css_not_available_msg:contains("This combination does not exist.")' + }, + { + content: "check add to cart not possible", + trigger: '#add_to_cart.disabled', + run: function () {}, + }, + { + content: 'click on the third option to unselect it', + trigger: 'input[data-attribute_name="Options"][data-value_name="Option 3"]', + }, + { + content: 'click on the second option to select it', + trigger: 'input[data-attribute_name="Options"][data-value_name="Option 2"]', + }, + { + content: "check price of options is correct", + trigger: '.oe_currency_value:contains("753")', + run: function () {}, + }, + { + content: "add to cart", + trigger: 'a:contains(ADD TO CART)', + }, + tourUtils.goToCart(), + { + content: "check price is correct", + trigger: '.td-product_name:contains("Options: Option 1, Option 2")', + run: function () {}, + }, +]}); diff --git a/addons/website_sale/tests/test_customize.py b/addons/website_sale/tests/test_customize.py index 7b0e8fbbf20..652f688fa47 100644 --- a/addons/website_sale/tests/test_customize.py +++ b/addons/website_sale/tests/test_customize.py @@ -445,3 +445,60 @@ class TestUi(HttpCaseWithUserDemo, HttpCaseWithUserPortal): ]) self.start_tour("/", 'test_09_pills_variant', login="portal") + + def test_10_multi_checkbox_attribute(self): + product_template = self.env['product.template'].create({ + 'name': 'Product Multi', + 'is_published': True, + 'list_price': 750, + }) + attribute = self.env['product.attribute'].create([ + { + 'name': 'Options', + 'create_variant': 'no_variant', + 'display_type': 'multi', + }, + ]) + attribute_values = self.env['product.attribute.value'].create([ + { + 'name': 'Option 1', + 'attribute_id': attribute.id, + 'default_extra_price': 1, + 'sequence': 1, + }, + { + 'name': 'Option 2', + 'attribute_id': attribute.id, + 'sequence': 2, + }, + { + 'name': 'Option 3', + 'attribute_id': attribute.id, + 'default_extra_price': 3, + 'sequence': 3, + }, + { + 'name': 'Option 4', + 'attribute_id': attribute.id, + 'sequence': 4, + }, + ]) + self.env['product.template.attribute.line'].create([{ + 'attribute_id': attribute.id, + 'product_tmpl_id': product_template.id, + 'value_ids': [(6, 0, attribute_values.ids)], + }]) + # set an extra price for free attribute values on the product (nothing is free) + self.env['product.template.attribute.value'].search( + [('product_tmpl_id', '=', product_template.id), ('price_extra', '=', 0)] + ).price_extra = 2 + # set an exclusion between option 1 and option 3 + self.env['product.template.attribute.value'].search( + [('product_tmpl_id', '=', product_template.id), ('price_extra', '=', 1)] + ).exclude_for = [(0, 0, { + 'product_tmpl_id': product_template.id, + 'value_ids': [(6, 0, [self.env['product.template.attribute.value'].search( + [('product_tmpl_id', '=', product_template.id), ('price_extra', '=', 3)]).id])] + })] + + self.start_tour("/", 'tour_shop_multi_checkbox', login="portal") diff --git a/addons/website_sale/views/templates.xml b/addons/website_sale/views/templates.xml index 67fb00c24e6..1b891f5644f 100644 --- a/addons/website_sale/views/templates.xml +++ b/addons/website_sale/views/templates.xml @@ -859,7 +859,7 @@ - +
@@ -1033,13 +1033,10 @@ - + - -
    -

    This combination does not exist.

    diff --git a/addons/website_sale/views/variant_templates.xml b/addons/website_sale/views/variant_templates.xml index 6c72cc2b635..e639ec20cc9 100644 --- a/addons/website_sale/views/variant_templates.xml +++ b/addons/website_sale/views/variant_templates.xml @@ -36,13 +36,13 @@ - +
    - +
      @@ -92,7 +92,7 @@
    - +