[FIX] purchase: correctly calculate average price

cherry-pick of a4904fb4889c16322494981f5a1be586683704bd

Steps to reproduce the bug:
- Create a storable product “P1”
    - costing method: avco
- Create a PO:
    - Add the product “P1”:
        - Line 1: Qty= 10, price= $50
        - Line 2: Qty=1, price= $10
    - Confirm the PO and receive the product

- Go to purchase → Reporting → Purchase Analysis

Problem:
The average price is incorrect, the current calculation is:

(50 + 10) / 2 = 30

The average should take into account the quantities purchased in each
line, And not simply the number of line, so the correct calculation
should be:

((10 * 50) + (10 * 1)) / 11 = 46.36

The SQL query is correct, it is when applying the read_group that the
calculation is incorrect, we should override it to make a personalized
calculation of the average.

opw-3136406

closes odoo/odoo#136097

X-original-commit: cd84549a3e5ea09d230dc1d02e05456a2372fd34
Signed-off-by: William Henrotin (whe) <whe@odoo.com>
Signed-off-by: Djamel Touati (otd) <otd@odoo.com>
This commit is contained in:
Touati Djamel (otd)
2023-09-25 09:38:36 +00:00
committed by Djamel Touati
parent 17b2597482
commit 3f496c3b30
2 changed files with 61 additions and 1 deletions
+27 -1
View File
@@ -5,7 +5,7 @@
# Please note that these reports are not multi-currency !!!
#
from odoo import fields, models
from odoo import fields, models, api
class PurchaseReport(models.Model):
@@ -147,3 +147,29 @@ class PurchaseReport(models.Model):
currency_table.rate
"""
return group_by_str
@api.model
def read_group(self, domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True):
"""
This is a hack to allow us to correctly calculate the average price of product.
"""
if 'price_average:avg' in fields:
fields.extend(['aggregated_qty_ordered:array_agg(qty_ordered)'])
fields.extend(['aggregated_price_average:array_agg(price_average)'])
res = []
if fields:
res = super(PurchaseReport, self).read_group(domain, fields, groupby, offset=offset, limit=limit, orderby=orderby, lazy=lazy)
if 'price_average:avg' in fields:
qties = 'aggregated_qty_ordered'
special_field = 'aggregated_price_average'
for data in res:
if data[special_field] and data[qties]:
total_unit_cost = sum(float(value) * float(qty) for value, qty in zip(data[special_field], data[qties]) if qty and value)
total_qty_ordered = sum(float(qty) for qty in data[qties] if qty)
data['price_average'] = (total_unit_cost / total_qty_ordered) if total_qty_ordered else 0
del data[special_field]
del data[qties]
return res
@@ -184,3 +184,37 @@ class TestPurchaseOrderReport(AccountTestInvoicingCommon):
po_2.flush_model()
report = self.env['purchase.report'].search([('product_id', "=", self.product_a.id)])
self.assertEqual(report.currency_id, self.env.company.currency_id)
def test_avg_price_calculation(self):
"""
Check that the average price is calculated based on the quantity ordered in each line
PO:
- 10 unit of product A -> price $50
- 1 unit of product A -> price $10
Total qty_ordered: 11
avergae price: 46.36 = ((10 * 50) + (10 * 1)) / 11
"""
po = self.env['purchase.order'].create({
'partner_id': self.partner_a.id,
'order_line': [
(0, 0, {
'product_id': self.product_a.id,
'product_qty': 10.0,
'price_unit': 50.0,
}),
(0, 0, {
'product_id': self.product_a.id,
'product_qty': 1.0,
'price_unit': 10.0,
}),
],
})
po.button_confirm()
po.flush_model()
report = self.env['purchase.report'].read_group(
[('product_id', '=', self.product_a.id)],
['qty_ordered', 'price_average:avg'],
['product_id'],
)
self.assertEqual(report[0]['qty_ordered'], 11)
self.assertEqual(round(report[0]['price_average'], 2), 46.36)