[FIX] purchase_stock,stock_account: convert UoM for price diff

When confirming a bill with a price diff, if the UoM of the bill
line is not the default UoM of the product, the values will be incorrect

To reproduce the issue:
(Need account_accountant)
1. In Settings, enable 'Units of Measure'
2. Create a product category PC:
   - Costing Method: FIFO
   - Inventory Valuation: Automated
3. Create a product P
   - Type: Storable
   - Category: PC
4. Create and confirm a PO with
   - 1 Dozen x P at $50
5. Process the receipt R01
6. Create and process the return RT01 of R01
7. Create and process the return RT02 of RT01
8. Create the bill related to PO
9. Set the bill line price to $60
10. Confirm
11. Open the valuation of P

Error: a stock valuation layer has been created because of the price
diff, which is correct, however the value is not the good one: $670
instead of $10 ($60 - $50)

When confirming the bill, we check if there is a price difference.
To do so, we compare the price unit of the bill line with the one of
the incoming layer:
https://github.com/odoo/odoo/blob/d3d41c7679f9068a7b986925b15d0d7670233bfc/addons/stock_account/models/account_move.py#L304
But, currently, the price unit of the bill line is based on the UoM
of the line while the price unit of the layer is based on the
default UoM of the product. We need to convert the PU of the bill line

OPW-3081449
OPW-3062433
OPW-3075917
OPW-3048587
OPW-3078429
OPW-3075627

closes odoo/odoo#107145

X-original-commit: a1906f0b5e22f03937389d47e6ce5a650f9269e6
Signed-off-by: William Henrotin (whe) <whe@odoo.com>
Signed-off-by: Adrien Widart <awt@odoo.com>
This commit is contained in:
Adrien Widart (awt)
2022-12-05 10:07:49 +01:00
parent 6326340265
commit f48056fd32
2 changed files with 49 additions and 28 deletions
@@ -1285,24 +1285,26 @@ class TestStockValuationWithCOA(AccountTestInvoicingCommon):
def test_price_diff_with_return_backorder_and_partial_bill(self):
"""
Fifo + Real time.
Default UoM of the product is Unit.
Company in USD. 1 USD = 2 EUR.
Receive 10 @ $50:
Receive 10 (R1)
Return 10 (RET)
Receive 7 (R2)
Receive 3 (R3)
Deliver 5
Receive 10 Hundred @ $50:
Receive 10 Hundred (R1)
Return 10 Hundred (RET)
Receive 7 Hundred (R2)
Receive 3 Hundred (R3)
Deliver 5 Hundred
Bill
1 @ 120€ -> already out, should not generate any SVL
3 @ 120€ -> already out, should not generate any SVL
2 @ 120€ -> only one out, should generate an SVL for the other one and
1 Hundred @ 120€ -> already out, should not generate any SVL
3 Hundred @ 120€ -> already out, should not generate any SVL
2 Hundred @ 120€ -> only one out, should generate an SVL for the other one and
this SVL should be linked to SVL_R1
4 @ 120€ -> should generate two SVL: one for one product and attached to
4 Hundred @ 120€ -> should generate two SVL: one for one product and attached to
SVL_R1 and another one for the three last products of SVL_R2
Deliver 2
Deliver 2 Hundred
The SVL should include:
- 2 x 50 (the product cost)
- 2 x 10 (the price diff from step "Bill 2@60" and "Bill 4@60")
- 2 x 50 (cost by hundred)
- 2 x 10 (the price diff from step "Bill 2 Hundred @ 60" and "Bill
4 Hundred @ 60")
"""
expected_svl_values = [] # USD
warehouse = self.env['stock.warehouse'].search([('company_id', '=', self.env.company.id)], limit=1)
@@ -1317,6 +1319,21 @@ class TestStockValuationWithCOA(AccountTestInvoicingCommon):
'rate': 2,
})
grp_uom = self.env.ref('uom.group_uom')
self.env.user.write({'groups_id': [(4, grp_uom.id)]})
uom_unit = self.env.ref('uom.product_uom_unit')
uom_hundred = self.env['uom.uom'].create({
'name': '100 x U',
'category_id': uom_unit.category_id.id,
'ratio': 100.0,
'uom_type': 'bigger',
'rounding': uom_unit.rounding,
})
self.product1.write({
'uom_id': uom_unit.id,
'uom_po_id': uom_unit.id,
})
self.product1.categ_id.property_cost_method = 'fifo'
self.product1.categ_id.property_valuation = 'real_time'
@@ -1325,35 +1342,36 @@ class TestStockValuationWithCOA(AccountTestInvoicingCommon):
with po_form.order_line.new() as po_line:
po_line.product_id = self.product1
po_line.product_qty = 10
po_line.product_uom = uom_hundred
po_line.price_unit = 50.0
po = po_form.save()
po.button_confirm()
# Receive 10
# Receive 10 Hundred
receipt01 = po.picking_ids[0]
receipt01.move_ids.quantity_done = 10
receipt01.move_ids._set_quantities_to_reservation()
receipt01.button_validate()
expected_svl_values += [10 * 50]
self.assertEqual(self.product1.stock_valuation_layer_ids.mapped('value'), expected_svl_values)
# Return 10
# Return 10 Hundred
return_form = Form(self.env['stock.return.picking'].with_context(active_id=receipt01.id, active_model='stock.picking'))
return_wizard = return_form.save()
return_picking_id, _pick_type_id = return_wizard._create_returns()
return_picking = self.env['stock.picking'].browse(return_picking_id)
return_picking.move_ids.quantity_done = 10
return_picking.move_ids._set_quantities_to_reservation()
return_picking.button_validate()
expected_svl_values += [-10 * 50]
self.assertEqual(self.product1.stock_valuation_layer_ids.mapped('value'), expected_svl_values)
# Receive 7 with backorder
# Receive 7 Hundred with backorder
return_form = Form(self.env['stock.return.picking'].with_context(active_id=return_picking_id, active_model='stock.picking'))
return_wizard = return_form.save()
receipt_id, _pick_type_id = return_wizard._create_returns()
receipt02 = self.env['stock.picking'].browse(receipt_id)
receipt02.move_ids.quantity_done = 7
receipt02.move_ids.move_line_ids.qty_done = 700
action = receipt02.button_validate()
backorder_wizard = Form(self.env['stock.backorder.confirmation'].with_context(action['context'])).save()
backorder_wizard.process()
@@ -1361,15 +1379,15 @@ class TestStockValuationWithCOA(AccountTestInvoicingCommon):
expected_svl_values += [7 * 50]
self.assertEqual(self.product1.stock_valuation_layer_ids.mapped('value'), expected_svl_values)
# Receive 3
# Receive 3 Hundred
receipt03 = receipt02.backorder_ids
receipt03.move_ids.quantity_done = 3
receipt03.move_ids._set_quantities_to_reservation()
receipt03.button_validate()
expected_svl_values += [3 * 50]
self.assertEqual(self.product1.stock_valuation_layer_ids.mapped('value'), expected_svl_values)
# Delivery 5
# Delivery 5 Hundred
delivery01 = self.env['stock.picking'].create({
'location_id': stock_location.id,
'location_dest_id': customer_location.id,
@@ -1378,33 +1396,35 @@ class TestStockValuationWithCOA(AccountTestInvoicingCommon):
'name': self.product1.name,
'product_id': self.product1.id,
'product_uom_qty': 5,
'product_uom': self.product1.uom_id.id,
'product_uom': uom_hundred.id,
'location_id': stock_location.id,
'location_dest_id': customer_location.id,
})],
})
delivery01.action_confirm()
delivery01.move_ids.quantity_done = 5
delivery01.move_ids._set_quantities_to_reservation()
delivery01.button_validate()
expected_svl_values += [-5 * 50]
self.assertEqual(self.product1.stock_valuation_layer_ids.mapped('value'), expected_svl_values)
# We will create a price diff SVL only for the remaining quantities not yet billed
# On the bill, price unit is 120€, i.e. $60 -> price diff equal to $10
for qty, new_svl_expected in [(1, []), (3, []), (2, [1 * 10.0]), (4, [1 * 10.0, 3 * 10.0])]:
bill_form = Form(self.env['account.move'].with_context(default_move_type='in_invoice'))
bill_form.invoice_date = bill_form.date
bill_form.purchase_vendor_bill_id = self.env['purchase.bill.union'].browse(-po.id)
bill = bill_form.save()
bill.invoice_line_ids.quantity = qty
bill.invoice_line_ids.price_unit = 120.0 # i.e. $60 -> price diff of $10
bill.invoice_line_ids.price_unit = 120.0
bill.invoice_line_ids.product_uom_id = uom_hundred
bill.currency_id = eur_curr
bill.action_post()
expected_svl_values += new_svl_expected
self.assertEqual(self.product1.stock_valuation_layer_ids.mapped('value'), expected_svl_values)
# Delivery 2
# Delivery 2 Hundred
delivery02 = self.env['stock.picking'].create({
'location_id': stock_location.id,
'location_dest_id': customer_location.id,
@@ -1413,13 +1433,13 @@ class TestStockValuationWithCOA(AccountTestInvoicingCommon):
'name': self.product1.name,
'product_id': self.product1.id,
'product_uom_qty': 2,
'product_uom': self.product1.uom_id.id,
'product_uom': uom_hundred.id,
'location_id': stock_location.id,
'location_dest_id': customer_location.id,
})],
})
delivery02.action_confirm()
delivery02.move_ids.quantity_done = 2
delivery02.move_ids._set_quantities_to_reservation()
delivery02.button_validate()
expected_svl_values += [-2 * 50 + -2 * 10]
@@ -301,6 +301,7 @@ class AccountMoveLine(models.Model):
)['total_excluded']
price_unit /= prec
price_unit = line.currency_id._convert(price_unit, line.company_id.currency_id, line.company_id, line.date, round=False)
price_unit = line.product_uom_id._compute_price(price_unit, line.product_id.uom_id)
layers_price_unit = line._get_stock_valuation_layers_price_unit(layers)
layers_to_correct = line._get_stock_layer_price_difference(layers, layers_price_unit, price_unit)
svl_vals_list += line._prepare_in_invoice_svl_vals(layers_to_correct)