diff --git a/addons/product/models/product_product.py b/addons/product/models/product_product.py index aee74f3387d..9a3d24562ec 100644 --- a/addons/product/models/product_product.py +++ b/addons/product/models/product_product.py @@ -386,7 +386,12 @@ class ProductProduct(models.Model): For convenience the template is copied instead and its first variant is returned. """ - return self.product_tmpl_id.copy(default=default).product_variant_id + # copy variant is disabled in https://github.com/odoo/odoo/pull/38303 + # this returns the first possible combination of variant to make it + # works for now, need to be fixed to return product_variant_id if it's + # possible in the future + template = self.product_tmpl_id.copy(default=default) + return template.product_variant_id or template._create_first_product_variant() @api.model def _search(self, args, offset=0, limit=None, order=None, count=False, access_rights_uid=None): diff --git a/addons/product/models/product_template.py b/addons/product/models/product_template.py index 572952c303e..547011f56ef 100644 --- a/addons/product/models/product_template.py +++ b/addons/product/models/product_template.py @@ -1006,6 +1006,18 @@ class ProductTemplate(models.Model): 'product_template_attribute_value_ids': [(6, 0, combination._without_no_variant_attributes().ids)] }) + def _create_first_product_variant(self, log_warning=False): + """Create if necessary and possible and return the first product + variant for this template. + + :param log_warning: whether a warning should be logged on fail + :type log_warning: bool + + :return: the first product variant or none + :rtype: recordset of `product.product` + """ + return self._create_product_variant(self._get_first_possible_combination(), log_warning) + @tools.ormcache('self.id', 'frozenset(filtered_combination.ids)') def _get_variant_id_for_combination(self, filtered_combination): """See `_get_variant_for_combination`. This method returns an ID diff --git a/addons/product/tests/test_variants.py b/addons/product/tests/test_variants.py index e0b4dae309e..bfbc8c265b8 100644 --- a/addons/product/tests/test_variants.py +++ b/addons/product/tests/test_variants.py @@ -211,6 +211,31 @@ class TestVariants(ProductVariantsCommon): self.assertEqual(variant_copy.name, 'Test Copy (copy) (copy)') self.assertEqual(len(variant_copy.product_variant_ids), 2) + def test_dynamic_variants_copy(self): + self.color_attr = self.env['product.attribute'].create({'name': 'Color', 'create_variant': 'dynamic'}) + self.color_attr_value_r = self.env['product.attribute.value'].create({'name': 'Red', 'attribute_id': self.color_attr.id}) + self.color_attr_value_b = self.env['product.attribute.value'].create({'name': 'Blue', 'attribute_id': self.color_attr.id}) + + # test copy of variant with dynamic attribute + template_dyn = self.env['product.template'].create({ + 'name': 'Test Dynamical', + 'attribute_line_ids': [(0, 0, { + 'attribute_id': self.color_attr.id, + 'value_ids': [(4, self.color_attr_value_r.id), (4, self.color_attr_value_b.id)], + })] + }) + + self.assertEqual(len(template_dyn.product_variant_ids), 0) + self.assertEqual(template_dyn.name, 'Test Dynamical') + + variant_dyn = template_dyn._create_product_variant(template_dyn._get_first_possible_combination()) + self.assertEqual(len(template_dyn.product_variant_ids), 1) + + variant_dyn_copy = variant_dyn.copy() + template_dyn_copy = variant_dyn_copy.product_tmpl_id + self.assertEqual(len(template_dyn_copy.product_variant_ids), 1) + self.assertEqual(template_dyn_copy.name, 'Test Dynamical (copy)') + def test_standard_price(self): """ Ensure template values are correctly (re)computed depending on the context """ one_variant_product = self.product diff --git a/addons/website_sale/models/product_template.py b/addons/website_sale/models/product_template.py index 81be788f462..19ce7d39a3d 100644 --- a/addons/website_sale/models/product_template.py +++ b/addons/website_sale/models/product_template.py @@ -282,18 +282,6 @@ class ProductTemplate(models.Model): # The list_price is always the price of one. return taxes.compute_all(price, pricelist.currency_id, 1, product, partner)[tax_display] - def _create_first_product_variant(self, log_warning=False): - """Create if necessary and possible and return the first product - variant for this template. - - :param log_warning: whether a warning should be logged on fail - :type log_warning: bool - - :return: the first product variant or none - :rtype: recordset of `product.product` - """ - return self._create_product_variant(self._get_first_possible_combination(), log_warning) - def _get_image_holder(self): """Returns the holder of the image to use as default representation. If the product template has an image it is the product template,