From db95421c7dcc9d9af2606cf3f2bce8027c54b6ce Mon Sep 17 00:00:00 2001 From: Adrien Widart Date: Wed, 20 Oct 2021 15:54:29 +0000 Subject: [PATCH] [FIX] sale_stock_margin: use data of SOL's company MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In a multi-company and multi-currency environment, the computation of the purchase price may be based on the incorrect company (and thus lead to incorrect results) To reproduce the issue: (Use demo data) 1. In Settings: - Enable "Multi-Currencies" 2. Update "My Company (Chicago)": - Currency: EUR 3. Edit currencies' rate for all companies(!): - USD: 1.0 - EUR: 3.0 4. Create a product category PC: - Costing Method: FIFO 5. Create a product P: - Type: Storable - Category: PC 6. Switch to "My Company (Chicago)" - Note that in this company, the costing method of PC is "Standard Price" 7. Edit P: - Cost: 10 8. Create a sale order SO: - Pricelist: EUR - Lines (add Cost columns): - 1 x P (Note that cost is 10€) 9. Confirm SO Error: The sale order line is incorrect, the cost is now 30€. A rate conversion has been applied on the amount In `_compute_purchase_price`, several fields/methods depend on the SOL's company: `property_cost_method`, `_compute_average_price`, `cost_currency_id`. Therefore, we should include this specific company in the environment of the product OPW-2659265 closes odoo/odoo#79430 X-original-commit: 868142c3885f7bf65a7974066234feb0eb6241ae Signed-off-by: Steve Van Essche Signed-off-by: Adrien Widart --- .../models/sale_order_line.py | 11 ++-- .../tests/test_sale_stock_margin.py | 65 +++++++++++++++++++ 2 files changed, 71 insertions(+), 5 deletions(-) diff --git a/addons/sale_stock_margin/models/sale_order_line.py b/addons/sale_stock_margin/models/sale_order_line.py index cc0234271e3..c9cc59c55ef 100644 --- a/addons/sale_stock_margin/models/sale_order_line.py +++ b/addons/sale_stock_margin/models/sale_order_line.py @@ -11,14 +11,15 @@ class SaleOrderLine(models.Model): def _compute_purchase_price(self): lines_without_moves = self.browse() for line in self: + product = line.product_id.with_company(line.company_id) if not line.move_ids: lines_without_moves |= line - elif line.product_id.categ_id.property_cost_method != 'standard': - 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: - purch_price = line.product_id.uom_id._compute_price(purch_price, line.product_uom) + elif product.categ_id.property_cost_method != 'standard': + purch_price = product._compute_average_price(0, line.product_uom_qty, line.move_ids) + if line.product_uom and line.product_uom != product.uom_id: + purch_price = product.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( + line.purchase_price = product.cost_currency_id._convert( from_amount=purch_price, to_currency=to_cur, company=line.company_id or self.env.company, 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 96d8ea7c95c..7fa377337aa 100644 --- a/addons/sale_stock_margin/tests/test_sale_stock_margin.py +++ b/addons/sale_stock_margin/tests/test_sale_stock_margin.py @@ -200,3 +200,68 @@ class TestSaleStockMargin(TestStockValuationCommon): so.action_confirm() self.assertEqual(so_line.purchase_price, 200) self.assertEqual(so_line.price_unit, 400) + + def test_so_and_multicompany(self): + """ In a multicompany environnement, when the user is on company C01 and confirms a SO that + belongs to a second company C02, this test ensures that the computations will be based on + C02's data""" + main_company = self.env['res.company']._get_main_company() + main_company_currency = main_company.currency_id + new_company_currency = self.env.ref('base.EUR') if main_company_currency == self.env.ref('base.USD') else self.env.ref('base.USD') + + date = fields.Date.today() + self.env['res.currency.rate'].search([]).unlink() + self.env['res.currency.rate'].create([ + {'currency_id': main_company_currency.id, 'rate': 1, 'name': date, 'company_id': False}, + {'currency_id': new_company_currency.id, 'rate': 3, 'name': date, 'company_id': False}, + ]) + + new_company = self.env['res.company'].create({ + 'name': 'Super Company', + 'currency_id': new_company_currency.id, + }) + self.env.user.company_id = new_company.id + + self.pricelist.currency_id = new_company_currency.id + + product = self._create_product() + + incoming_picking_type = self.env['stock.picking.type'].search([('company_id', '=', new_company.id), ('code', '=', 'incoming')], limit=1) + production_location = self.env['stock.location'].search([('company_id', '=', new_company.id), ('usage', '=', 'production')]) + + picking = self.env['stock.picking'].create({ + 'picking_type_id': incoming_picking_type.id, + 'location_id': production_location.id, + 'location_dest_id': incoming_picking_type.default_location_dest_id.id, + }) + self.env['stock.move'].create({ + 'name': 'Incoming Product', + 'product_id': product.id, + 'location_id': production_location.id, + 'location_dest_id': incoming_picking_type.default_location_dest_id.id, + 'product_uom': product.uom_id.id, + 'product_uom_qty': 1, + 'price_unit': 100, + 'picking_type_id': incoming_picking_type.id, + 'picking_id': picking.id, + }) + picking.action_confirm() + res_dict = picking.button_validate() + wizard = Form(self.env[(res_dict.get('res_model'))].with_context(res_dict['context'])).save() + wizard.process() + + self.pricelist.currency_id = new_company_currency.id + partner = self.env['res.partner'].create({'name': 'Super Partner'}) + so = self.env['sale.order'].create({ + 'name': 'Sale order', + 'partner_id': partner.id, + 'partner_invoice_id': partner.id, + 'pricelist_id': self.pricelist.id, + }) + sol = self._create_sale_order_line(so, product, 1, price_unit=200) + + self.env.user.company_id = main_company.id + so.action_confirm() + + self.assertEqual(sol.purchase_price, 100) + self.assertEqual(sol.margin, 100)