[FIX] product: prevent archived product variants from overshadowing active ones

This commit addresses an issue where archived product variants were
taking precedence over active variants with the same combination,
rendering the combination unusable in the sales app.

Steps to reproduce:
1. Install Sale & enable product variants
2. Create a product with two attributes, each having two values
3. Make a sale for each variant
4. Remove one of the attributes and save
5. Add back the same attribute with only one of the values
6. Make a new quote with the product; Option is not available.

After this commit:
Archived variants will no longer exclude a combination if an active
variant with the same combination exists.

opw-3538366

closes odoo/odoo#144431

X-original-commit: ceb540da0898cc27999c8ee30329051102e0f8f3
Signed-off-by: Victor Feyens (vfe) <vfe@odoo.com>
Signed-off-by: Walravens Mathieu (wama) <wama@odoo.com>
Co-authored-by: Victor Feyens <vfe@odoo.com>
This commit is contained in:
Mathieu Walravens
2023-12-01 14:23:40 +00:00
co-authored by Victor Feyens
parent e4390c224c
commit 9d95e77a61
2 changed files with 102 additions and 4 deletions
+4 -3
View File
@@ -848,16 +848,17 @@ class ProductTemplate(models.Model):
self.ensure_one()
parent_combination = parent_combination or self.env['product.template.attribute.value']
archived_products = self.with_context(active_test=False).product_variant_ids.filtered(lambda l: not l.active)
active_combinations = set(tuple(product.product_template_attribute_value_ids.ids) for product in self.product_variant_ids)
return {
'exclusions': self._complete_inverse_exclusions(self._get_own_attribute_exclusions()),
'archived_combinations': [
product.product_template_attribute_value_ids.ids
'archived_combinations': list(set(
tuple(product.product_template_attribute_value_ids.ids)
for product in archived_products
if product.product_template_attribute_value_ids and all(
ptav.ptav_active
for ptav in product.product_template_attribute_value_ids
)
],
) - active_combinations),
'parent_exclusions': self._get_parent_attribute_exclusions(parent_combination),
'parent_combination': parent_combination.ids,
'parent_product_name': parent_name,
@@ -2,7 +2,7 @@
from odoo.fields import Command
from odoo.tests import HttpCase, tagged
from odoo.addons.product.tests.common import ProductVariantsCommon
from odoo.addons.product.tests.common import ProductAttributesCommon, ProductVariantsCommon
from odoo.addons.sale.tests.common import SaleCommon
@@ -153,3 +153,100 @@ class TestProductConfiguratorData(HttpCase, ProductVariantsCommon, SaleCommon):
# The inactive PTAVs should not be in the product exclusions dict
self.assertFalse(str(ptav_with_exclusion.id) in result['products'][0]['exclusions'])
self.assertFalse(str(ptav_excluded.id) in result['products'][0]['exclusions'])
@tagged('post_install', '-at_install')
class TestSaleProductVariants(ProductAttributesCommon, SaleCommon):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.product_template_2lines_2attributes = cls.env['product.template'].create({
'name': '2 lines 2 attributes',
'uom_id': cls.uom_unit.id,
'uom_po_id': cls.uom_unit.id,
'categ_id': cls.product_category.id,
'attribute_line_ids': [
Command.create({
'attribute_id': cls.color_attribute.id,
'value_ids': [Command.set([
cls.color_attribute_red.id,
cls.color_attribute_blue.id,
])],
}),
Command.create({
'attribute_id': cls.size_attribute.id,
'value_ids': [Command.set([
cls.size_attribute_s.id,
cls.size_attribute_m.id,
])]
})
]
})
# Sell all variants
cls.empty_order.order_line = [
Command.create({
'product_id': product.id,
})
for product in cls.product_template_2lines_2attributes.product_variant_ids
]
def test_attribute_removal(self):
def _get_ptavs():
return self.product_template_2lines_2attributes.with_context(
active_test=False
).attribute_line_ids.product_template_value_ids
def _get_archived_variants():
return self.product_template_2lines_2attributes.with_context(
active_test=False
).product_variant_ids.filtered(lambda p: not p.active)
def _get_active_variants():
return self.product_template_2lines_2attributes.product_variant_ids
self.assertEqual(len(_get_ptavs()), 4)
self.product_template_2lines_2attributes.attribute_line_ids = [
Command.unlink(self.product_template_2lines_2attributes.attribute_line_ids.filtered(
lambda ptal: ptal.attribute_id.id == self.size_attribute.id
).id)
]
self.assertEqual(len(_get_ptavs()), 4)
# Use products s.t. they are archived and not deleted
self.empty_order.order_line = [
Command.create({
'product_id': product.id,
})
for product in self.product_template_2lines_2attributes.product_variant_ids
]
self.assertEqual(len(_get_archived_variants()), 4)
self.assertEqual(len(_get_active_variants()), 2)
self.product_template_2lines_2attributes.attribute_line_ids = [
Command.create({
'attribute_id': self.size_attribute.id,
'value_ids': [Command.set([
self.size_attribute_s.id,
])]
})
]
self.assertEqual(len(_get_ptavs()), 4)
self.assertEqual(len(_get_active_variants()), 2)
self.assertEqual(len(_get_archived_variants()), 4)
# When adding a single attribute line, the attribute will be added to all existing variants
# Instead of unarchiving existing archived variants with the same combination
# Leading to a state where the database holds two variants with the same combination
# We don't want this combination to be excluded from the product configurator as it is valid
# as long as there is one active variant with this configuration.
exclusions_data = self.product_template_2lines_2attributes._get_attribute_exclusions()
self.assertTrue(
all(
tuple(product.product_template_attribute_value_ids.ids) not in exclusions_data['archived_combinations']
for product in _get_active_variants()
)
)