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)