[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:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user