From 35e84e096b2a792c24e6cda2d6cdfc5e7ee85b02 Mon Sep 17 00:00:00 2001 From: Victor Feyens Date: Fri, 27 Jan 2023 12:36:19 +0000 Subject: [PATCH] [IMP] product,sale: harmonize/clarify contextual keys for extra prices Clarify the uses and impacts of current_attributes_price_extra and no_variant_attributes_price_extra context keys. Group the key specification and usage. Provide a clear API shared by templates and variants. Part-of: odoo/odoo#113234 --- addons/product/models/product_product.py | 35 +++++++++++++++++------ addons/product/models/product_template.py | 29 +++++++++++++++---- addons/sale/models/product_template.py | 35 +++++++---------------- addons/sale/models/sale_order_line.py | 18 ++---------- 4 files changed, 63 insertions(+), 54 deletions(-) diff --git a/addons/product/models/product_product.py b/addons/product/models/product_product.py index 6ee0a0a4c54..97fe4b390f7 100644 --- a/addons/product/models/product_product.py +++ b/addons/product/models/product_product.py @@ -620,6 +620,31 @@ class ProductProduct(models.Model): res |= seller return res.sorted('price')[:1] + def _get_product_price_context(self, combination): + self.ensure_one() + res = {} + + # It is possible that a no_variant attribute is still in a variant if + # the type of the attribute has been changed after creation. + no_variant_attributes_price_extra = [ + ptav.price_extra for ptav in combination.filtered( + lambda ptav: + ptav.price_extra + and ptav.product_tmpl_id == self.product_tmpl_id + and ptav not in self.product_template_attribute_value_ids + ) + ] + if no_variant_attributes_price_extra: + res['no_variant_attributes_price_extra'] = tuple(no_variant_attributes_price_extra) + + return res + + def _get_attributes_extra_price(self): + self.ensure_one() + + return self.price_extra + sum( + self.env.context.get('no_variant_attributes_price_extra', [])) + def price_compute(self, price_type, uom=None, currency=None, company=None, date=False): company = company or self.env.company date = date or fields.Date.context_today(self) @@ -637,14 +662,8 @@ class ProductProduct(models.Model): price_currency = product.currency_id if price_type == 'standard_price': price_currency = product.cost_currency_id - - if price_type == 'list_price': - price += product.price_extra - # we need to add the price from the attributes that do not generate variants - # (see field product.attribute create_variant) - if self._context.get('no_variant_attributes_price_extra'): - # we have a list of price_extra that comes from the attribute values, we need to sum all that - price += sum(self._context.get('no_variant_attributes_price_extra')) + elif price_type == 'list_price': + price += product._get_attributes_extra_price() if uom: price = product.uom_id._compute_price(price, uom) diff --git a/addons/product/models/product_template.py b/addons/product/models/product_template.py index 6fd16cd7d9b..b7560de97b5 100644 --- a/addons/product/models/product_template.py +++ b/addons/product/models/product_template.py @@ -562,6 +562,27 @@ class ProductTemplate(models.Model): }, } + def _get_product_price_context(self, combination): + self.ensure_one() + res = {} + + current_attributes_price_extra = [ + ptav.price_extra for ptav in combination.filtered( + lambda ptav: + ptav.price_extra + and ptav.product_tmpl_id == self + ) + ] + if current_attributes_price_extra: + res['current_attributes_price_extra'] = tuple(current_attributes_price_extra) + + return res + + def _get_attributes_extra_price(self): + self.ensure_one() + + return sum(self.env.context.get('current_attributes_price_extra', [])) + def price_compute(self, price_type, uom=None, currency=None, company=None, date=False): company = company or self.env.company date = date or fields.Date.context_today(self) @@ -579,12 +600,8 @@ class ProductTemplate(models.Model): price_currency = template.currency_id if price_type == 'standard_price': price_currency = template.cost_currency_id - - # yes, there can be attribute values for product template if it's not a variant YET - # (see field product.attribute create_variant) - if price_type == 'list_price' and self._context.get('current_attributes_price_extra'): - # we have a list of price_extra that comes from the attribute values, we need to sum all that - price += sum(self._context.get('current_attributes_price_extra')) + elif price_type == 'list_price': + price += template._get_attributes_extra_price() if uom: price = template.uom_id._compute_price(price, uom) diff --git a/addons/sale/models/product_template.py b/addons/sale/models/product_template.py index 03b7b74c684..cc6cc088446 100644 --- a/addons/sale/models/product_template.py +++ b/addons/sale/models/product_template.py @@ -209,8 +209,6 @@ class ProductTemplate(models.Model): discount applied (price < list_price), else False """ self.ensure_one() - # get the name before the change of context to benefit from prefetch - display_name = self.display_name quantity = self.env.context.get('quantity', add_qty) product_template = self @@ -230,38 +228,25 @@ class ProductTemplate(models.Model): product = product_template._get_variant_for_combination(combination) if product: - # We need to add the price_extra for the attributes that are not - # in the variant, typically those of type no_variant, but it is - # possible that a no_variant attribute is still in a variant if - # the type of the attribute has been changed after creation. - no_variant_attributes_price_extra = [ - ptav.price_extra for ptav in combination.filtered( - lambda ptav: - ptav.price_extra and - ptav not in product.product_template_attribute_value_ids - ) - ] - if no_variant_attributes_price_extra: - product = product.with_context( - no_variant_attributes_price_extra=tuple(no_variant_attributes_price_extra) - ) - list_price = product.price_compute('list_price')[product.id] - price = pricelist._get_product_price(product, quantity) display_image = bool(product.image_128) display_name = product.display_name - price_extra = (product.price_extra or 0.0) + (sum(no_variant_attributes_price_extra) or 0.0) else: - current_attributes_price_extra = [v.price_extra or 0.0 for v in combination] - product_template = product_template.with_context(current_attributes_price_extra=current_attributes_price_extra) - price_extra = sum(current_attributes_price_extra) - list_price = product_template.price_compute('list_price')[product_template.id] - price = pricelist._get_product_price(product_template, quantity) display_image = bool(product_template.image_128) + display_name = product_template.display_name combination_name = combination._get_combination_name() if combination_name: display_name = "%s (%s)" % (display_name, combination_name) + product_or_template = product or self + + price_context = product_or_template._get_product_price_context(combination) + product_or_template = product_or_template.with_context(**price_context) + + list_price = product_or_template.price_compute('list_price')[product_or_template.id] + price_extra = product_or_template._get_attributes_extra_price() + price = pricelist._get_product_price(product_or_template, quantity) + currency = pricelist.currency_id or self.env.company.currency_id if currency != product_template.currency_id: list_price = product_template.currency_id._convert( diff --git a/addons/sale/models/sale_order_line.py b/addons/sale/models/sale_order_line.py index 74ccba17bf6..5c3ac59e7e6 100644 --- a/addons/sale/models/sale_order_line.py +++ b/addons/sale/models/sale_order_line.py @@ -489,21 +489,9 @@ class SaleOrderLine(models.Model): :rtype: dict """ self.ensure_one() - res = {} - - # It is possible that a no_variant attribute is still in a variant if - # the type of the attribute has been changed after creation. - no_variant_attributes_price_extra = [ - ptav.price_extra for ptav in self.product_no_variant_attribute_value_ids.filtered( - lambda ptav: - ptav.price_extra and - ptav not in self.product_id.product_template_attribute_value_ids - ) - ] - if no_variant_attributes_price_extra: - res['no_variant_attributes_price_extra'] = tuple(no_variant_attributes_price_extra) - - return res + return self.product_id._get_product_price_context( + self.product_no_variant_attribute_value_ids, + ) def _get_pricelist_price_before_discount(self): """Compute the price used as base for the pricelist price computation.