Skip to content
Browse files

[FIX] product,sale,website_sale: compute combination possible in rpc

This has several advantages:
- make the code easier to maintain by having the logic only on the python model
- prevent price flickering when changing from/to an impossible combination
- fix the UI for the issue mentioned in the previous commit/the opw
- make the code much faster when using many combinations, by not requiring to
	browse and to return every existing and every archived variants

The opportunity is also taken to properly debounce the rpc and/or its result:
- fix all the other remaining flickering when changing quantity or combination
- fix the server being spammed of rpc when using the configurator modal, but
	also in a lot of other cases such as incrementing the quantity, ...
- fix UI consistency by ensuring the result of the last rpc is always the one
	displayed, obsolete rpc returns are ignored

By computing the possibility of the combination asynchronously, the two
drawbacks are:
- the UI is a bit less reactive than in the previously the best case scenario,
	but this almost completely mitigated by debouncing with a leading call
- it is possible for a quick user to click on "add to cart" on an impossible
	combination before the return of the rpc, but this is not really an issue
	because when the product will be added to cart there will a check and the
	closest possible combination will be selected automatically instead

PR: #32691
  • Loading branch information...
seb-odoo committed Apr 18, 2019
1 parent 81d0b83 commit c6a4a3b92038ac7342f0bf5294a68cb873b5dff2
@@ -132,8 +132,10 @@ def _get_default_uom_id(self):
compute="_compute_valid_attributes", string='Valid Product Attribute Values Without No Variant Attributes', help="Technical compute")
valid_product_attribute_wnva_ids = fields.Many2many('product.attribute',
compute="_compute_valid_attributes", string='Valid Product Attributes Without No Variant Attributes', help="Technical compute")
# valid_archived_variant_ids deprecated
valid_archived_variant_ids = fields.Many2many('product.product',
compute="_compute_valid_attributes", string='Valid Archived Variants', help="Technical compute")
compute="_compute_valid_archived_variant_ids", string='Valid Archived Variants', help="Technical compute")
# valid_existing_variant_ids deprecated
valid_existing_variant_ids = fields.Many2many('product.product',
compute="_compute_valid_existing_variant_ids", string='Valid Existing Variants', help="Technical compute")

@@ -583,8 +585,6 @@ def _compute_valid_attributes(self):
For what is considered an archived variant, see `_has_valid_attributes`.
archived_variants = self.env['product.product'].search([('product_tmpl_id', 'in', self.ids), ('active', '=', False)])

# prefetch
@@ -599,6 +599,12 @@ def _compute_valid_attributes(self):
record.valid_product_attribute_ids = record.valid_product_template_attribute_line_ids.mapped('attribute_id')
record.valid_product_attribute_wnva_ids = record.valid_product_template_attribute_line_wnva_ids.mapped('attribute_id')

def _compute_valid_archived_variant_ids(self):
"""This compute is done outside of `_compute_valid_attributes` because
it is often not needed, and it can be very bad on performance."""
archived_variants = self.env['product.product'].search([('product_tmpl_id', 'in', self.ids), ('active', '=', False)])
for record in self:
valid_value_ids = record.valid_product_attribute_value_wnva_ids
valid_attribute_ids = record.valid_product_attribute_wnva_ids

@@ -690,23 +696,23 @@ def _get_attribute_exclusions(self, parent_combination=None):
:return: dict of exclusions
- exclusions: from this product itself
- parent_combination: ids of the given parent_combination
- parent_exclusions: from the parent_combination
- archived_combinations: variants that are archived
- existing_combinations: variants that are existing (as opposed to
deleted, because deleted need to be considered impossible if
there are no dynamic attributes).
- has_dynamic_attributes: whether there is a dynamic attribute
- no_variant_product_template_attribute_value_ids: values that are
- archived_combinations: deprecated
- existing_combinations: deprecated
- has_dynamic_attributes: deprecated
- no_variant_product_template_attribute_value_ids: deprecated
parent_combination = parent_combination or self.env['product.template.attribute.value']
return {
'exclusions': self._get_own_attribute_exclusions(),
'parent_exclusions': self._get_parent_attribute_exclusions(parent_combination),
'archived_combinations': self._get_archived_combinations(),
'parent_combination': parent_combination.ids,
'archived_combinations': [],
'has_dynamic_attributes': self.has_dynamic_attributes(),
'existing_combinations': self._get_existing_combinations(),
'no_variant_product_template_attribute_value_ids': self._get_no_variant_product_template_attribute_values(),
'existing_combinations': [],
'no_variant_product_template_attribute_value_ids': [],

@@ -759,29 +765,21 @@ def _get_parent_attribute_exclusions(self, parent_combination):

def _get_archived_combinations(self):
"""Get archived combinations.
Array, each element is an array with ids of an archived combination.
return [archived_variant.product_template_attribute_value_ids.ids
for archived_variant in self.valid_archived_variant_ids]

def _get_existing_combinations(self):
"""Get existing combinations.
Needed because when not using dynamic attributes, the combination is
not ok if it doesn't exist (= if the variant has been deleted).
Array, each element is an array with ids of an existing combination.
return [variant.product_template_attribute_value_ids.ids
for variant in self.valid_existing_variant_ids]

def _get_no_variant_product_template_attribute_values(self):
product_template_attribute_values = self._get_valid_product_template_attribute_lines().mapped('product_template_value_ids')
return product_template_attribute_values.filtered(
@@ -44,7 +44,14 @@ def get_combination_info(self, product_template_id, product_id, combination, add
ProductTemplate = request.env['product.template']
if 'context' in kw:
ProductTemplate = ProductTemplate.with_context(**kw.get('context'))
return ProductTemplate.browse(int(product_template_id))._get_combination_info(combination, int(product_id or 0), int(add_qty or 1), pricelist)
product_template = ProductTemplate.browse(int(product_template_id))
res = product_template._get_combination_info(combination, int(product_id or 0), int(add_qty or 1), pricelist)
if 'parent_combination' in kw:
parent_combination = request.env['product.template.attribute.value'].browse(kw.get('parent_combination'))
'is_combination_possible': product_template._is_combination_possible(combination=combination, parent_combination=parent_combination),
return res

@http.route(['/product_configurator/create_product_variant'], type='json', auth="user", methods=['POST'])
def create_product_variant(self, product_template_id, product_template_attribute_value_ids, **kwargs):
Oops, something went wrong.

0 comments on commit c6a4a3b

Please sign in to comment.
You can’t perform that action at this time.