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