From 7130939bcdc5c5428a947c52e28f06c4dcaec55b Mon Sep 17 00:00:00 2001 From: Jinjiu Liu Date: Wed, 6 Apr 2022 08:11:56 +0000 Subject: [PATCH] [FIX] product: a temporal variant copy for future convenience MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reproduction: 1. Create a dynamic attribute "dyn_att" with a couple of values 2. Create a product template "dyn_prod" with those attribute values 3. Create an order for "dyn_prod", this will trigger creating a variant 4. Make sure to check Variant Grid Entry in Sales Settings 5. Open variant form view of dyn_prod and edit it to allow duplication 6. Duplicating it leads to an error Reason: copying the variant is not possible and disabled here: https://github.com/odoo/odoo/pull/38303 For future convenience, maybe it’s better to give a temporal working solution. The function _create_first_product_variant is used in the product module but only defined in its child module website_sale Fix: copy the product template, create and return its first possible variant. Added test for dynamic variant copy. change the definition place of _create_first_product_variant to module product opw-2790543 closes odoo/odoo#98809 X-original-commit: 34a2948d3d6e0f597c1b5d65f9f118e891c07cc4 Signed-off-by: Nicolas Lempereur (nle) Signed-off-by: Liu Jinjiu (jili) --- addons/product/models/product_product.py | 7 +++++- addons/product/models/product_template.py | 12 +++++++++ addons/product/tests/test_variants.py | 25 +++++++++++++++++++ .../website_sale/models/product_template.py | 12 --------- 4 files changed, 43 insertions(+), 13 deletions(-) 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,