Skip to content

Commit

Permalink
[FIX] sale_product_configurator,product: show archived combination
Browse files Browse the repository at this point in the history
Steps to reproduce:
1) Create SO with customizable product (example “Customizable Desk”)
2) Let default values in product configurator
3) Save SO
4) Go to product template, and in the Attribute remove all value
options, only leaving the custom value
5) Try to open the product in configurator in the saved SO

Reason:
archived combination is not loaded

After this commit:
When requested combination is archived, load it

opw-3513685

closes odoo#150653

X-original-commit: fc8fb42
Signed-off-by: Victor Feyens (vfe) <vfe@odoo.com>
Signed-off-by: Valeriya Chuprina (vchu) <vchu@odoo.com>
  • Loading branch information
vchu-odoo committed Jan 26, 2024
1 parent 23a79ee commit c3b7671
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 10 deletions.
20 changes: 15 additions & 5 deletions addons/product/models/product_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -849,7 +849,9 @@ def _get_possible_variants(self, parent_combination=None):
self.ensure_one()
return self.product_variant_ids.filtered(lambda p: p._is_variant_possible(parent_combination))

def _get_attribute_exclusions(self, parent_combination=None, parent_name=None):
def _get_attribute_exclusions(
self, parent_combination=None, parent_name=None, combination_ids=None
):
"""Return the list of attribute exclusions of a product.
:param parent_combination: the combination from which
Expand All @@ -858,6 +860,8 @@ def _get_attribute_exclusions(self, parent_combination=None, parent_name=None):
:type parent_combination: recordset `product.template.attribute.value`
:param parent_name: the name of the parent product combination.
:type parent_name: str
:param list combination: The combination of the product, as a
list of `product.template.attribute.value` ids.
:return: dict of exclusions
- exclusions: from this product itself
Expand All @@ -876,12 +880,14 @@ def _get_attribute_exclusions(self, parent_combination=None, parent_name=None):
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()),
'exclusions': self._complete_inverse_exclusions(
self._get_own_attribute_exclusions(combination_ids=combination_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
ptav.ptav_active or combination_ids and ptav.id in combination_ids
for ptav in product.product_template_attribute_value_ids
)
) - active_combinations),
Expand All @@ -907,9 +913,11 @@ def _complete_inverse_exclusions(self, exclusions):

return result

def _get_own_attribute_exclusions(self):
def _get_own_attribute_exclusions(self, combination_ids=None):
"""Get exclusions coming from the current template.
:param list combination: The combination of the product, as a
list of `product.template.attribute.value` ids.
Dictionnary, each product template attribute value is a key, and for each of them
the value is an array with the other ptav that they exclude (empty if no exclusion).
"""
Expand All @@ -922,7 +930,9 @@ def _get_own_attribute_exclusions(self):
lambda filter_line: filter_line.product_tmpl_id == self
) for value in filter_line.value_ids if value.ptav_active
]
for ptav in product_template_attribute_values if ptav.ptav_active
for ptav in product_template_attribute_values if (
ptav.ptav_active or combination_ids and ptav.id in combination_ids
)
}

def _get_parent_attribute_exclusions(self, parent_combination):
Expand Down
5 changes: 2 additions & 3 deletions addons/product/tests/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,8 @@ def setUpClass(cls):
cls.color_attribute_green,
) = cls.color_attribute.value_ids

cls.no_variant_attribute = cls.env['product.attribute'].create(
{
'name': 'No variant',
cls.no_variant_attribute = cls.env['product.attribute'].create({
'name': 'No variant',
'create_variant': 'no_variant',
'value_ids': [
Command.create({'name': 'extra'}),
Expand Down
5 changes: 3 additions & 2 deletions addons/sale_product_configurator/controllers/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,8 @@ def _get_product_information(
currency = request.env['res.currency'].browse(currency_id)
product = product_template._get_variant_for_combination(combination)
attribute_exclusions = product_template._get_attribute_exclusions(
parent_combination=parent_combination
parent_combination=parent_combination,
combination_ids=combination.ids,
)

return dict(
Expand Down Expand Up @@ -289,7 +290,7 @@ def _get_product_information(
datetime.fromisoformat(so_date).date(),
),
) for ptav in ptal.product_template_value_ids
if ptav.ptav_active
if ptav.ptav_active or combination and ptav.id in combination.ids
],
selected_attribute_value_ids=combination.filtered(
lambda c: ptal in c.attribute_line_id
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,59 @@ def test_dropped_attribute(self):
# exclude combinations that are not even available
self.assertFalse(result['products'][0]['archived_combinations'])

def test_dropped_attribute_value(self):
product_template = self.create_product_template_with_2_attributes()
self.assertEqual(len(product_template.product_variant_ids), 4)

# Use variants s.t. they are archived and not deleted when value is removed
self.empty_order.order_line = [
Command.create(
{
'product_id': product.id
}
)
for product in product_template.product_variant_ids
]
self.empty_order.action_confirm()

# Remove attribute value red
product_template.attribute_line_ids.filtered(
lambda ptal: ptal.attribute_id == self.color_attribute
).value_ids = [Command.unlink(self.color_attribute_red.id)]
self.assertEqual(len(product_template.product_variant_ids), 2)
archived_variants = product_template.with_context(
active_test=False
).product_variant_ids - product_template.product_variant_ids
self.assertEqual(len(archived_variants), 2)

archived_ptav = product_template.attribute_line_ids.product_template_value_ids.filtered(
lambda ptav: ptav.product_attribute_value_id == self.color_attribute_red
)
# Choose the variant (red, L)
variant_ptav_ids = [
archived_ptav.id,
product_template.attribute_line_ids.product_template_value_ids.filtered(
lambda ptav: ptav.product_attribute_value_id == self.size_attribute_l
).id,
]
self.authenticate('demo', 'demo')
result = self.request_get_values(product_template, variant_ptav_ids)
archived_ptav = archived_variants.product_template_attribute_value_ids.filtered(
lambda ptav: ptav.product_attribute_value_id == self.color_attribute_red
)

# When requested combination contains inactive ptav
# check that archived combinations are loaded
self.assertEqual(
len(result['products'][0]['archived_combinations']),
2
)
for combination in result['products'][0]['archived_combinations']:
self.assertIn(archived_ptav.id, combination)

# When requested combination contains inactive ptav check that exclusions contains it
self.assertIn(str(archived_ptav.id), result['products'][0]['exclusions'])

def test_excluded_inactive_ptav(self):
product_template = self.create_product_template_with_2_attributes()
self.assertEqual(len(product_template.product_variant_ids), 4)
Expand Down

0 comments on commit c3b7671

Please sign in to comment.