From 04e4cd91637c836b4d856b8638d88c5cf1770421 Mon Sep 17 00:00:00 2001
From: Adrien Widart <awt@odoo.com>
Date: Mon, 12 Jul 2021 09:30:19 +0000
Subject: [PATCH] [FIX] sale_stock_margin: convert purchase_price
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

When confirming a SO, if the costing method of a product's category is
not `Standard' and if the SO's currency is different from the company's
currency, the cost of the product won't be converted to the SO's
currency

To reproduce the error:
1. In Settings, enable:
    - Margins
    - Multi-Currencies
2. Create a product category PC:
    - Costing Method: FIFO
3. Create a product P:
    - Product Type: Storable
    - Product Category: PC
    - Sales Price: 200
    - Cost: 100
4. Update P's quantity to 1
5. Create a pricelist PL:
    - Currency: EUR
6. Create a SO:
    - Pricelist: PL
    - Order Lines:
        - 1 x P
7. Add field "Cost" to the tree view of the order lines
    - The value is correctly converted
8. Confirm the SO

Error: The cost of the order line is now 100€. This is actually the USD
cost, it should be converted

This code applies the same conversion as when the cost method is
standard:
https://github.com/odoo/odoo/blob/8a8ff03f6111f377bcd9c2b0f584c450b82a4182/addons/sale_margin/models/sale_order.py#L42-L48

OPW-2563442

closes odoo/odoo#73545

Signed-off-by: Rémy Voet <ryv-odoo@users.noreply.github.com>
---
 .../models/sale_order_line.py                 | 14 ++++--
 .../tests/test_sale_stock_margin.py           | 43 +++++++++++++++++++
 2 files changed, 54 insertions(+), 3 deletions(-)

diff --git a/addons/sale_stock_margin/models/sale_order_line.py b/addons/sale_stock_margin/models/sale_order_line.py
index 9ed4be628490f..cc0234271e3fe 100644
--- a/addons/sale_stock_margin/models/sale_order_line.py
+++ b/addons/sale_stock_margin/models/sale_order_line.py
@@ -1,7 +1,7 @@
 # -*- coding: utf-8 -*-
 # Part of Odoo. See LICENSE file for full copyright and licensing details.
 
-from odoo import api, models
+from odoo import api, fields, models
 
 
 class SaleOrderLine(models.Model):
@@ -14,7 +14,15 @@ def _compute_purchase_price(self):
             if not line.move_ids:
                 lines_without_moves |= line
             elif line.product_id.categ_id.property_cost_method != 'standard':
-                line.purchase_price = line.product_id.with_company(line.company_id)._compute_average_price(0, line.product_uom_qty, line.move_ids)
+                purch_price = line.product_id.with_company(line.company_id)._compute_average_price(0, line.product_uom_qty, line.move_ids)
                 if line.product_uom and line.product_uom != line.product_id.uom_id:
-                    line.purchase_price = line.product_id.uom_id._compute_price(line.purchase_price, line.product_uom)
+                    purch_price = line.product_id.uom_id._compute_price(purch_price, line.product_uom)
+                to_cur = line.currency_id or line.order_id.currency_id
+                line.purchase_price = line.product_id.cost_currency_id._convert(
+                    from_amount=purch_price,
+                    to_currency=to_cur,
+                    company=line.company_id or self.env.company,
+                    date=line.order_id.date_order or fields.Date.today(),
+                    round=False,
+                ) if to_cur and purch_price else purch_price
         return super(SaleOrderLine, lines_without_moves)._compute_purchase_price()
diff --git a/addons/sale_stock_margin/tests/test_sale_stock_margin.py b/addons/sale_stock_margin/tests/test_sale_stock_margin.py
index 0964726902002..f96816e53834f 100644
--- a/addons/sale_stock_margin/tests/test_sale_stock_margin.py
+++ b/addons/sale_stock_margin/tests/test_sale_stock_margin.py
@@ -1,11 +1,19 @@
 # -*- coding: utf-8 -*-
 # Part of Odoo. See LICENSE file for full copyright and licensing details.
 
+from odoo import fields
+
 from odoo.tests.common import Form
 from odoo.addons.stock_account.tests.test_stockvaluationlayer import TestStockValuationCommon
 
 
 class TestSaleStockMargin(TestStockValuationCommon):
+
+    @classmethod
+    def setUpClass(cls):
+        super(TestSaleStockMargin, cls).setUpClass()
+        cls.pricelist = cls.env['product.pricelist'].create({'name': 'Simple Pricelist'})
+
     #########
     # UTILS #
     #########
@@ -15,6 +23,7 @@ def _create_sale_order(self):
             'name': 'Sale order',
             'partner_id': self.env.ref('base.partner_admin').id,
             'partner_invoice_id': self.env.ref('base.partner_admin').id,
+            'pricelist_id': self.pricelist.id,
         })
 
     def _create_sale_order_line(self, sale_order, product, quantity, price_unit=0):
@@ -156,3 +165,37 @@ def test_sale_stock_margin_5(self):
         self.assertAlmostEqual(order_line_1.margin, 34)               # (60 - 43) * 2
         self.assertAlmostEqual(order_line_2.margin, 30)               # (20 - 12.5) * 4
         self.assertAlmostEqual(sale_order.margin, 64)
+
+    def test_so_and_multicurrency(self):
+        ResCurrencyRate = self.env['res.currency.rate']
+        company_currency = self.env.company.currency_id
+        other_currency = self.env.ref('base.EUR') if company_currency == self.env.ref('base.USD') else self.env.ref('base.USD')
+
+        ResCurrencyRate.create({'currency_id': company_currency.id, 'rate': 1})
+        other_currency_rate = ResCurrencyRate.search([('name', '=', fields.Date.today()), ('currency_id', '=', other_currency.id)])
+        if other_currency_rate:
+            other_currency_rate.rate = 2
+        else:
+            ResCurrencyRate.create({'currency_id': other_currency.id, 'rate': 2})
+
+        so = self._create_sale_order()
+        so.pricelist_id = self.env['product.pricelist'].create({
+            'name': 'Super Pricelist',
+            'currency_id': other_currency.id,
+        })
+
+        product = self._create_product()
+        product.standard_price = 100
+        product.list_price = 200
+
+        so_form = Form(so)
+        with so_form.order_line.new() as line:
+            line.product_id = product
+        so = so_form.save()
+        so_line = so.order_line
+
+        self.assertEqual(so_line.purchase_price, 200)
+        self.assertEqual(so_line.price_unit, 400)
+        so.action_confirm()
+        self.assertEqual(so_line.purchase_price, 200)
+        self.assertEqual(so_line.price_unit, 400)