diff --git a/addons/product/models/product.py b/addons/product/models/product.py index 9bccf02fabc..8bd8600f7a0 100644 --- a/addons/product/models/product.py +++ b/addons/product/models/product.py @@ -346,6 +346,16 @@ class ProductProduct(models.Model): product._set_standard_price(vals.get('standard_price') or 0.0) # `_get_variant_id_for_combination` depends on existing variants self.clear_caches() + self.env['product.template'].invalidate_cache( + fnames=[ + 'valid_archived_variant_ids', + 'valid_existing_variant_ids', + 'product_variant_ids', + 'product_variant_id', + 'product_variant_count' + ], + ids=products.mapped('product_tmpl_id').ids + ) return products @api.multi diff --git a/addons/product/models/product_template.py b/addons/product/models/product_template.py index a54ddbce909..5fbe41d8b9b 100644 --- a/addons/product/models/product_template.py +++ b/addons/product/models/product_template.py @@ -830,9 +830,16 @@ class ProductTemplate(models.Model): # combination has different values than the ones configured on the template return False - if not self.has_dynamic_attributes() and not self._get_variant_for_combination(combination): - # the variant has been deleted - return False + variant = self._get_variant_for_combination(combination) + + if self.has_dynamic_attributes(): + if variant and not variant.active: + # dynamic and the variant has been archived + return False + else: + if not variant or not variant.active: + # not dynamic, the variant has been archived or deleted + return False exclusions = self._get_own_attribute_exclusions() if exclusions: @@ -849,11 +856,6 @@ class ProductTemplate(models.Model): if exclusion in combination.ids: return False - filtered_combination = combination._without_no_variant_attributes() - archived_combinations = self._get_archived_combinations() - if archived_combinations and filtered_combination.ids in archived_combinations: - return False - return True @api.multi @@ -887,7 +889,7 @@ class ProductTemplate(models.Model): for pav in attribute_values: domain = expression.AND([[('attribute_value_ids', 'in', pav.id)], domain]) - res = self.env['product.product'].with_context(active_test=False).search(domain) + res = self.env['product.product'].with_context(active_test=False).search(domain, order='active DESC') # The domain above is checking for the `product.attribute.value`, but we # need to make sure it's the same `product.template.attribute.value`. diff --git a/addons/product/tests/test_product_attribute_value_config.py b/addons/product/tests/test_product_attribute_value_config.py index e966adbdbdc..497f6fbeba3 100644 --- a/addons/product/tests/test_product_attribute_value_config.py +++ b/addons/product/tests/test_product_attribute_value_config.py @@ -312,6 +312,21 @@ class TestProductAttributeValueConfig(TestProductAttributeValueSetup): variant.unlink() self.assertFalse(self.computer._is_combination_possible(computer_ssd_256 + computer_ram_8 + computer_hdd_1)) + # CASE: if multiple variants exist for the same combination and at least + # one of them is not archived, the combination is possible + values = self.ssd_256 + self.ram_8 + self.hdd_1 + self.env['product.product'].create({ + 'product_tmpl_id': self.computer.id, + 'attribute_value_ids': [(6, 0, values.ids)], + 'active': False, + }) + self.env['product.product'].create({ + 'product_tmpl_id': self.computer.id, + 'attribute_value_ids': [(6, 0, values.ids)], + 'active': True, + }) + self.assertTrue(self.computer._is_combination_possible(computer_ssd_256 + computer_ram_8 + computer_hdd_1)) + def test_get_first_possible_combination(self): computer_ssd_256 = self._get_product_template_attribute_value(self.ssd_256) computer_ssd_512 = self._get_product_template_attribute_value(self.ssd_512)