diff --git a/addons/account/report/account_invoice_report.py b/addons/account/report/account_invoice_report.py index a4f5ee9ba3a..44656840d4b 100644 --- a/addons/account/report/account_invoice_report.py +++ b/addons/account/report/account_invoice_report.py @@ -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()) ) diff --git a/addons/account/tests/test_account_invoice_report.py b/addons/account/tests/test_account_invoice_report.py index d1458036c9d..62d3048a74d 100644 --- a/addons/account/tests/test_account_invoice_report.py +++ b/addons/account/tests/test_account_invoice_report.py @@ -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], ])