From b7ec0a2e3fb86c18cb156a49eaebcf08f3781ba5 Mon Sep 17 00:00:00 2001 From: "Laurent Desausoi (lade)" <lade@odoo.com> Date: Tue, 22 Mar 2022 12:56:29 +0000 Subject: [PATCH] [FIX] product: pricelist surcharges and margin incorrectly converted MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When using a pricelist with a currency different than the one of the product, the prices used in the pricelist (surcharge, min_margin, & max_margin) are using the default currency of the company which is invalid. To reproduce the issue: 1. Enable multiple currency and enable a second one with an exagerated rate for the sake of clarity. 2. Create a formula pricelist with the newly activated currency using either surcharge or the margin. 3. Create a SO with a standard product. By switching between pricelist, you will see an invalid price. E.G.: EUR_rates = 2 compared to USD PRICELIST = surcharge of 10€ on any product PRODUCT_price = 300$ PRODUCT_price_euros = 150€ CURRENT_SO_price = (300 + 10)/rate_EUR = (300 + 10)/2 = 155€ EXPECTED_SO_price = (300 + 10*rate_EUR)/rate_EUR = (300 + 10*2)/2 = 160€ Solution: Convert the pricelist item margin & surcharges to the base price currency. To do so, the needed currency conversion date is given through the context. opw-2760720 closes odoo/odoo#87018 Signed-off-by: Victor Feyens (vfe) <vfe@odoo.com> --- addons/product/models/product_pricelist.py | 23 ++++++-- .../product/tests/test_product_pricelist.py | 59 +++++++++++++++++++ 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/addons/product/models/product_pricelist.py b/addons/product/models/product_pricelist.py index 50d4961ef687..413888f717e8 100644 --- a/addons/product/models/product_pricelist.py +++ b/addons/product/models/product_pricelist.py @@ -221,7 +221,9 @@ class Pricelist(models.Model): price = product.price_compute(rule.base)[product.id] if price is not False: - price = rule._compute_price(price, price_uom, product, quantity=qty, partner=partner) + # pass the date through the context for further currency conversions + rule_with_date_context = rule.with_context(date=date) + price = rule_with_date_context._compute_price(price, price_uom, product, quantity=qty, partner=partner) suitable_rule = rule break # Final price conversion into pricelist currency @@ -583,6 +585,7 @@ class PricelistItem(models.Model): The unused parameters are there to make the full context available for overrides. """ self.ensure_one() + date = self.env.context.get('date') or fields.Date.today() convert_to_price_uom = (lambda price: product.uom_id._compute_price(price, price_uom)) if self.compute_price == 'fixed': price = convert_to_price_uom(self.fixed_price) @@ -592,18 +595,30 @@ class PricelistItem(models.Model): # complete formula price_limit = price price = (price - (price * (self.price_discount / 100))) or 0.0 + if self.base == 'standard_price': + price_currency = product.cost_currency_id + elif self.base == 'pricelist': + price_currency = self.currency_id # Already converted before to the pricelist currency + else: + price_currency = product.currency_id if self.price_round: price = tools.float_round(price, precision_rounding=self.price_round) + def convert_to_base_price_currency(amount): + return self.currency_id._convert(amount, price_currency, self.env.company, date, round=False) + if self.price_surcharge: - price_surcharge = convert_to_price_uom(self.price_surcharge) + price_surcharge = convert_to_base_price_currency(self.price_surcharge) + price_surcharge = convert_to_price_uom(price_surcharge) price += price_surcharge if self.price_min_margin: - price_min_margin = convert_to_price_uom(self.price_min_margin) + price_min_margin = convert_to_base_price_currency(self.price_min_margin) + price_min_margin = convert_to_price_uom(price_min_margin) price = max(price, price_limit + price_min_margin) if self.price_max_margin: - price_max_margin = convert_to_price_uom(self.price_max_margin) + price_max_margin = convert_to_base_price_currency(self.price_max_margin) + price_max_margin = convert_to_price_uom(price_max_margin) price = min(price, price_limit + price_max_margin) return price diff --git a/addons/product/tests/test_product_pricelist.py b/addons/product/tests/test_product_pricelist.py index 0269a570037a..b3ec0955b611 100644 --- a/addons/product/tests/test_product_pricelist.py +++ b/addons/product/tests/test_product_pricelist.py @@ -1,11 +1,14 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. +import time + from odoo.tests.common import TransactionCase from odoo.tools import float_compare, test_reports class TestProductPricelist(TransactionCase): + # create context with pricelist and get price of it -> def setUp(self): super(TestProductPricelist, self).setUp() @@ -22,6 +25,12 @@ class TestProductPricelist(TransactionCase): self.uom_unit_id = self.ref('uom.product_uom_unit') self.list0 = self.ref('product.list0') + self.new_currency = self.env['res.currency'].create({ + 'name': 'Wonderful Currency', + 'symbol': ':)', + 'rate_ids': [(0, 0, {'rate': 10, 'name': time.strftime('%Y-%m-%d')})], + }) + self.ipad_retina_display.write({'uom_id': self.uom_unit_id, 'categ_id': self.category_5_id}) self.customer_pricelist = self.ProductPricelist.create({ 'name': 'Customer Pricelist', @@ -127,3 +136,53 @@ class TestProductPricelist(TransactionCase): self.env.company.external_report_layout_id = self.env.ref('web.external_layout_standard').id test_reports.try_report_action(self.cr, self.uid, 'action_product_price_list', wiz_data=data_dict, context=ctx, our_module='product') + + def test_20_price_different_currency_pricelist(self): + pricelist = self.ProductPricelist.create({ + 'name': 'Currency Pricelist', + 'currency_id': self.new_currency.id, + 'item_ids': [(0, 0, { + 'compute_price': 'formula', + 'base': 'list_price', + 'price_surcharge': 100 + })] + }) + product = self.computer_SC234.with_context({ + 'pricelist': pricelist.id, 'quantity': 1 + }) + # product price use the currency of the pricelist + self.assertEqual(product.price, 4600) + + def test_21_price_diff_cur_min_margin_pricelist(self): + pricelist = self.ProductPricelist.create({ + 'name': 'Currency with Margin Pricelist', + 'currency_id': self.new_currency.id, + 'item_ids': [(0, 0, { + 'compute_price': 'formula', + 'base': 'list_price', + 'price_min_margin': 10, + 'price_max_margin': 100, + })] + }) + product = self.computer_SC234.with_context({ + 'pricelist': pricelist.id, 'quantity': 1 + }) + # product price use the currency of the pricelist + self.assertEqual(product.price, 4510) + + def test_22_price_diff_cur_max_margin_pricelist(self): + pricelist = self.ProductPricelist.create({ + 'name': 'Currency with Margin Pricelist', + 'currency_id': self.new_currency.id, + 'item_ids': [(0, 0, { + 'compute_price': 'formula', + 'base': 'list_price', + 'price_surcharge': 100, + 'price_max_margin': 90 + })] + }) + product = self.computer_SC234.with_context({ + 'pricelist': pricelist.id, 'quantity': 1 + }) + # product price use the currency of the pricelist + self.assertEqual(product.price, 4590) -- GitLab