From d5878829cde586cf3fde776f7b520bfa680d8e43 Mon Sep 17 00:00:00 2001 From: MerlinGuillaume Date: Wed, 9 Aug 2023 10:26:35 +0000 Subject: [PATCH] [FIX] product: use configurator with deleted attribute The product configurator can have irrelevant disabled options if the product selected has some archived variants Steps to reproduce: 1. Install Sales and Inventory 2. Create a product with two attributes, each with two values (e.g. Color: C1, C2 and Weight: W1, W2) 3. Open one of the variant, add some stock then archive it 4. Delete the attribute Weight from the product 5. Create a SO for any customer and add the product 6. The product configurator dialog opens but the C1 color is unavailable Solution: Only return the archived combinations for which all of the attribute are still used in the product opw-3427753 closes odoo/odoo#132420 X-original-commit: d5cd215dcf1b7e633c86ad734274a65827368ade Signed-off-by: Victor Feyens (vfe) Co-authored-by: Victor Feyens --- addons/product/models/product_template.py | 5 +- .../tests/test_product_configurator_data.py | 69 ++++++++++++++++++- 2 files changed, 72 insertions(+), 2 deletions(-) diff --git a/addons/product/models/product_template.py b/addons/product/models/product_template.py index 755e182b7da..5a2d9f1ad60 100644 --- a/addons/product/models/product_template.py +++ b/addons/product/models/product_template.py @@ -798,7 +798,10 @@ class ProductTemplate(models.Model): 'archived_combinations': [ product.product_template_attribute_value_ids.ids for product in archived_products - if product.product_template_attribute_value_ids + if product.product_template_attribute_value_ids and all( + ptav.ptav_active + for ptav in product.product_template_attribute_value_ids + ) ], 'parent_exclusions': self._get_parent_attribute_exclusions(parent_combination), 'parent_combination': parent_combination.ids, diff --git a/addons/sale_product_configurator/tests/test_product_configurator_data.py b/addons/sale_product_configurator/tests/test_product_configurator_data.py index 0ba4ff6af1d..de370a4b7e2 100644 --- a/addons/sale_product_configurator/tests/test_product_configurator_data.py +++ b/addons/sale_product_configurator/tests/test_product_configurator_data.py @@ -9,8 +9,10 @@ from odoo.addons.sale.tests.common import SaleCommon @tagged('post_install', '-at_install') class TestProductConfiguratorData(HttpCase, ProductVariantsCommon, SaleCommon): - def test_dropped_attribute_isnt_shown(self): + def test_dropped_value_isnt_shown(self): self.assertEqual(len(self.product_template_sofa.product_variant_ids), 3) + + # 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 @@ -19,6 +21,7 @@ class TestProductConfiguratorData(HttpCase, ProductVariantsCommon, SaleCommon): ] self.empty_order.action_confirm() + # Remove attribute value self.product_template_sofa.attribute_line_ids.value_ids -= self.color_attribute_red self.assertEqual(len(self.product_template_sofa.product_variant_ids.filtered('active')), 2) @@ -44,3 +47,67 @@ class TestProductConfiguratorData(HttpCase, ProductVariantsCommon, SaleCommon): # Make sure the inactive ptav was removed from the loaded attributes self.assertEqual(len(result['products'][0]['attribute_lines'][0]['attribute_values']), 2) + + def test_dropped_attribute(self): + self.product_template_2_attribute_lines = self.env['product.template'].create({ + 'name': 'Shirt', + 'categ_id': self.product_category.id, + 'attribute_line_ids': [ + Command.create({ + 'attribute_id': self.size_attribute.id, + 'value_ids': [ + Command.set([ + self.size_attribute_l.id, + self.size_attribute_m.id, + ]), + ], + }), + Command.create({ + 'attribute_id': self.color_attribute.id, + 'value_ids': [ + Command.set([ + self.color_attribute_red.id, + self.color_attribute_blue.id, + ]) + ], + }), + ], + }) + self.assertEqual(len(self.product_template_2_attribute_lines.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 self.product_template_2_attribute_lines.product_variant_ids + ] + self.empty_order.action_confirm() + + # Remove attribute + self.product_template_2_attribute_lines.attribute_line_ids[0].unlink() + self.assertEqual(len(self.product_template_2_attribute_lines.product_variant_ids), 2) + + self.authenticate('demo', 'demo') + base_url = self.product_template_2_attribute_lines.get_base_url() + response = self.opener.post( + url=base_url + '/sale_product_configurator/get_values', + json={ + 'params': { + 'product_template_id': self.product_template_2_attribute_lines.id, + 'quantity': 1.0, + 'currency_id': 1, + 'so_date': str(self.env.cr.now()), + 'product_uom_id': None, + 'company_id': None, + 'pricelist_id': None, + 'ptav_ids': None, + 'only_main_product': False, + }, + } + ) + result = response.json()['result'] + + # Make sure archived combinations with inactive ptav are not loaded as it's useless to + # exclude combinations that are not even available + self.assertFalse(result['products'][0]['archived_combinations'])