[FIX] sale_stock_margin: use data of SOL's company

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 <svs@odoo.com>
Signed-off-by: Adrien Widart <awt@odoo.com>
This commit is contained in:
Adrien Widart
2021-11-08 07:59:42 +00:00
parent eaae9ea8ec
commit db95421c7d
2 changed files with 71 additions and 5 deletions
@@ -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,
@@ -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)