[FIX] account: "Total" in company currency on invoice analysis

Currently for the "Total" measure on the invoice analysis report
the values in the currency of the invoice are used.
This is a problem in multi-currency environments: Different
currencies are added together leading to nonsensical sums.

This commit converts the "Total" measure to company currency (at the
time of the invoice).
In multi-company case the "Total" measure is further converted to the currency
of the active company (using the day of the report creation for the
conversion rate).

The "Total" will also be displayed in company currency when viewing
the invoices in the list view of an entry in the table (by clicking on the entry).

task-3537868

closes odoo/odoo#141277

X-original-commit: 28004b7e92f22cf0f0078d4512f8a5f59c82122c
Signed-off-by: William André (wan) <wan@odoo.com>
This commit is contained in:
Sven Fuehr
2023-11-06 18:11:39 +00:00
parent 9890fb7df7
commit ea6285b42d
2 changed files with 56 additions and 9 deletions
@@ -67,7 +67,21 @@ class AccountInvoiceReport(models.Model):
@property
def _table_query(self):
return '%s %s %s' % (self._select(), self._from(), self._where())
return '%s %s %s %s' % (self._with(), self._select(), self._from(), self._where())
@api.model
def _with(self):
return f'''
WITH company_currencies
AS MATERIALIZED
-- The table res_currency_rate only contains the individual dates where the currency rates changes (start date).
-- The following table is similar but i.e. also contains an end date for each rate (the start date of the next rate
-- or NULL if it is the last rate change).
-- It is used to convert the value line.price_total to currency line.company_currency_id with conversion date line.date
(SELECT *
FROM ({self.env['res.currency']._select_companies_rates()}) AS all_company_currencies
WHERE currency_id IN (SELECT id FROM res_currency currency WHERE currency.active))
'''
@api.model
def _select(self):
@@ -95,7 +109,14 @@ class AccountInvoiceReport(models.Model):
line.quantity / NULLIF(COALESCE(uom_line.factor, 1) / COALESCE(uom_template.factor, 1), 0.0) * (CASE WHEN move.move_type IN ('in_invoice','out_refund','in_receipt') THEN -1 ELSE 1 END)
AS quantity,
-line.balance * currency_table.rate AS price_subtotal,
line.price_total * (CASE WHEN move.move_type IN ('in_invoice','out_refund','in_receipt') THEN -1 ELSE 1 END)
-- line.price_total needs to be converted to line.company_currency_id (using the rate at the time of the invoice),
-- get the correct sign and be converted to the currency of the active company (using the rate from today)
ROUND(line.price_total
* (CASE WHEN move.move_type IN ('in_invoice','out_refund','in_receipt') THEN -1 ELSE 1 END)
* COALESCE(company_currency_conversion.rate, 1.0)
/ COALESCE(line_currency_conversion.rate, 1.0)
, company_currency.decimal_places)
* currency_table.rate
AS price_total,
-COALESCE(
-- Average line price
@@ -119,6 +140,17 @@ class AccountInvoiceReport(models.Model):
INNER JOIN account_move move ON move.id = line.move_id
LEFT JOIN res_partner commercial_partner ON commercial_partner.id = move.commercial_partner_id
JOIN {currency_table} ON currency_table.company_id = line.company_id
LEFT JOIN company_currencies line_currency_conversion ON
line.currency_id = line_currency_conversion.currency_id
AND line.company_id = line_currency_conversion.company_id
AND COALESCE(line.date, NOW()) >= line_currency_conversion.date_start
AND (line_currency_conversion.date_end IS NULL OR line_currency_conversion.date_end > COALESCE(line.date, NOW()))
LEFT JOIN company_currencies company_currency_conversion ON
line.company_currency_id = company_currency_conversion.currency_id
AND line.company_id = company_currency_conversion.company_id
AND COALESCE(line.date, NOW()) >= company_currency_conversion.date_start
AND (company_currency_conversion.date_end IS NULL OR company_currency_conversion.date_end > COALESCE(line.date, NOW()))
LEFT JOIN res_currency company_currency ON company_currency.id = line.company_currency_id
'''.format(
currency_table=self.env['res.currency']._get_query_currency_table(self.env.companies.ids, fields.Date.today())
)
@@ -94,6 +94,19 @@ class TestAccountInvoiceReport(AccountTestInvoicingCommon):
}),
]
},
{
'move_type': 'out_invoice',
'partner_id': cls.partner_a.id,
'invoice_date': fields.Date.from_string('1900-01-01'), # before the first currency rate is set
'currency_id': cls.currency_data['currency'].id,
'invoice_line_ids': [
(0, None, {
'product_id': cls.product_a.id,
'quantity': 1,
'price_unit': 1200,
}),
]
},
])
def assertInvoiceReportValues(self, expected_values_list):
@@ -102,6 +115,7 @@ class TestAccountInvoiceReport(AccountTestInvoicingCommon):
'price_average': vals[0],
'price_subtotal': vals[1],
'quantity': vals[2],
'price_total': vals[3],
} for vals in expected_values_list]
self.assertRecordValues(reports, expected_values_dict)
@@ -109,11 +123,12 @@ class TestAccountInvoiceReport(AccountTestInvoicingCommon):
def test_invoice_report_multiple_types(self):
self.assertInvoiceReportValues([
#price_average price_subtotal quantity
[2000, 2000, 1],
[1000, 1000, 1],
[250, 750, 3],
[6, 6, 1],
[20, -20, -1],
[20, -20, -1],
[600, -600, -1],
[2000, 2000, 1, 2300],
[1200, 1200, 1, 1380],
[1000, 1000, 1, 1150],
[250, 750, 3, 862.5],
[6, 6, 1, 6.9],
[20, -20, -1, -23],
[20, -20, -1, -23],
[600, -600, -1, -690],
])