[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:
co-authored by
Victor Feyens
parent
e4390c224c
commit
9d95e77a61
@@ -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()
|
||||
)
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user