From 141bed936033b1c01e00319a4dc4e2000f3d9ab0 Mon Sep 17 00:00:00 2001 From: aliya Date: Wed, 15 Feb 2023 10:19:27 +0000 Subject: [PATCH] [FIX] sale: fix currency conversion in sale report Sale Analysis report shows prices in company currency, converting foreign currency into the company currency according to the currency exchange rate. Currently, the currency conversion is done incorrectly. For example let's say 1 EUR = 25 CZK OR 1 CZK = 0.04 EUR. Sales order: 39 EUR = 975 CZK. Sales Analysis report shows 1.56. This is because we are trying to multiply the order amount by 0.04, while it should be divided. It used to be divided up until 16.0. This commit changes the operator to convert the currency correctly. opw-3167522 closes odoo/odoo#113525 X-original-commit: ef57d53866dc1d404138809132176c79d60a3b51 Signed-off-by: Aliya Tastemirova (alta) --- addons/pos_sale/report/sale_report.py | 20 +++--- addons/sale/report/sale_report.py | 20 +++--- addons/sale/tests/__init__.py | 1 + addons/sale/tests/test_sale_report.py | 82 ++++++++++++++++++++++++ addons/sale_margin/report/sale_report.py | 4 +- 5 files changed, 105 insertions(+), 22 deletions(-) create mode 100644 addons/sale/tests/test_sale_report.py diff --git a/addons/pos_sale/report/sale_report.py b/addons/pos_sale/report/sale_report.py index ad1c91da8f5..46d6de5ad11 100644 --- a/addons/pos_sale/report/sale_report.py +++ b/addons/pos_sale/report/sale_report.py @@ -33,20 +33,20 @@ class SaleReport(models.Model): CASE WHEN pos.state = 'invoiced' THEN SUM(l.qty) ELSE 0 END AS qty_invoiced, CASE WHEN pos.state != 'invoiced' THEN SUM(l.qty) ELSE 0 END AS qty_to_invoice, SUM(l.price_subtotal_incl) - * MIN({self._case_value_or_one('pos.currency_rate')}) - * {self._case_value_or_one('currency_table.rate')} + / MIN({self._case_value_or_one('pos.currency_rate')}) + / {self._case_value_or_one('currency_table.rate')} AS price_total, SUM(l.price_subtotal) - * MIN({self._case_value_or_one('pos.currency_rate')}) - * {self._case_value_or_one('currency_table.rate')} + / MIN({self._case_value_or_one('pos.currency_rate')}) + / {self._case_value_or_one('currency_table.rate')} AS price_subtotal, (CASE WHEN pos.state != 'invoiced' THEN SUM(l.price_subtotal) ELSE 0 END) - * MIN({self._case_value_or_one('pos.currency_rate')}) - * {self._case_value_or_one('currency_table.rate')} + / MIN({self._case_value_or_one('pos.currency_rate')}) + / {self._case_value_or_one('currency_table.rate')} AS amount_to_invoice, (CASE WHEN pos.state = 'invoiced' THEN SUM(l.price_subtotal) ELSE 0 END) - * MIN({self._case_value_or_one('pos.currency_rate')}) - * {self._case_value_or_one('currency_table.rate')} + / MIN({self._case_value_or_one('pos.currency_rate')}) + / {self._case_value_or_one('currency_table.rate')} AS amount_invoiced, count(*) AS nbr, pos.name AS name, @@ -71,8 +71,8 @@ class SaleReport(models.Model): (SUM(p.volume) * l.qty / u.factor) AS volume, l.discount AS discount, SUM((l.price_unit * l.discount * l.qty / 100.0 - * {self._case_value_or_one('pos.currency_rate')} - * {self._case_value_or_one('currency_table.rate')})) + / {self._case_value_or_one('pos.currency_rate')} + / {self._case_value_or_one('currency_table.rate')})) AS discount_amount, NULL AS order_id""" diff --git a/addons/sale/report/sale_report.py b/addons/sale/report/sale_report.py index a7a179ffb52..0ad5b4df3a9 100644 --- a/addons/sale/report/sale_report.py +++ b/addons/sale/report/sale_report.py @@ -79,23 +79,23 @@ class SaleReport(models.Model): CASE WHEN l.product_id IS NOT NULL THEN SUM(l.qty_invoiced / u.factor * u2.factor) ELSE 0 END AS qty_invoiced, CASE WHEN l.product_id IS NOT NULL THEN SUM(l.qty_to_invoice / u.factor * u2.factor) ELSE 0 END AS qty_to_invoice, CASE WHEN l.product_id IS NOT NULL THEN SUM(l.price_total - * {self._case_value_or_one('s.currency_rate')} - * {self._case_value_or_one('currency_table.rate')} + / {self._case_value_or_one('s.currency_rate')} + / {self._case_value_or_one('currency_table.rate')} ) ELSE 0 END AS price_total, CASE WHEN l.product_id IS NOT NULL THEN SUM(l.price_subtotal - * {self._case_value_or_one('s.currency_rate')} - * {self._case_value_or_one('currency_table.rate')} + / {self._case_value_or_one('s.currency_rate')} + / {self._case_value_or_one('currency_table.rate')} ) ELSE 0 END AS price_subtotal, CASE WHEN l.product_id IS NOT NULL THEN SUM(l.untaxed_amount_to_invoice - * {self._case_value_or_one('s.currency_rate')} - * {self._case_value_or_one('currency_table.rate')} + / {self._case_value_or_one('s.currency_rate')} + / {self._case_value_or_one('currency_table.rate')} ) ELSE 0 END AS untaxed_amount_to_invoice, CASE WHEN l.product_id IS NOT NULL THEN SUM(l.untaxed_amount_invoiced - * {self._case_value_or_one('s.currency_rate')} - * {self._case_value_or_one('currency_table.rate')} + / {self._case_value_or_one('s.currency_rate')} + / {self._case_value_or_one('currency_table.rate')} ) ELSE 0 END AS untaxed_amount_invoiced, COUNT(*) AS nbr, @@ -121,8 +121,8 @@ class SaleReport(models.Model): CASE WHEN l.product_id IS NOT NULL THEN SUM(p.volume * l.product_uom_qty / u.factor * u2.factor) ELSE 0 END AS volume, l.discount AS discount, CASE WHEN l.product_id IS NOT NULL THEN SUM(l.price_unit * l.product_uom_qty * l.discount / 100.0 - * {self._case_value_or_one('s.currency_rate')} - * {self._case_value_or_one('currency_table.rate')} + / {self._case_value_or_one('s.currency_rate')} + / {self._case_value_or_one('currency_table.rate')} ) ELSE 0 END AS discount_amount, s.id AS order_id""" diff --git a/addons/sale/tests/__init__.py b/addons/sale/tests/__init__.py index 9348f7d6e16..2a21d3536a3 100644 --- a/addons/sale/tests/__init__.py +++ b/addons/sale/tests/__init__.py @@ -17,3 +17,4 @@ from . import test_sale_refund from . import test_sale_tax_totals from . import test_sale_to_invoice from . import test_sale_order_down_payment +from . import test_sale_report diff --git a/addons/sale/tests/test_sale_report.py b/addons/sale/tests/test_sale_report.py new file mode 100644 index 00000000000..74c4a5553e7 --- /dev/null +++ b/addons/sale/tests/test_sale_report.py @@ -0,0 +1,82 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from freezegun import freeze_time + +from odoo import fields +from odoo.fields import Command +from odoo.tests import tagged + +from odoo.addons.sale.tests.common import SaleCommon + + +@tagged('-at_install', 'post_install') +class TestSaleReportCurrencyRate(SaleCommon): + + @classmethod + def setUpClass(cls): + super().setUpClass() + + cls.company = cls.env['res.company'].create({ + 'name': 'Test Company', + 'currency_id': cls.env.ref('base.USD').id, + }) + + def test_sale_report_foreign_currency(self): + # Test the amounts shown in the sales report for orders in foreign currency + currency_eur = self._enable_currency('EUR') + + eur_pricelist = self.env['product.pricelist'].create({ + 'name': 'Pricelist (EUR)', + 'currency_id': currency_eur.id, + }) + + with freeze_time('2022-02-22'): + self.env['res.currency.rate'].create({ + 'name': fields.Date.today(), + 'company_rate': 3.0, + 'currency_id': currency_eur.id, + 'company_id': self.company.id, + }) + eur_order = self.env['sale.order'].with_company(self.company).create({ + 'partner_id': self.partner.id, + 'pricelist_id': eur_pricelist.id, + 'date_order': fields.Date.today(), + 'order_line': [ + Command.create({ + 'product_id': self.product.id, + 'product_uom_qty': 1.0, + }), + ] + }) + # the sales order's amount is in foreign currency + self.assertEqual(eur_order.amount_total, 60.0) + + # the report should show the amount in company currency + report_line = self.env['sale.report'].sudo().search( + [('order_id', '=', eur_order.id)]) + self.assertEqual(report_line.price_total, 20.0) + + def test_sale_report_company_currency(self): + usd_pricelist = self.env['product.pricelist'].create({ + 'name': 'Pricelist (USD)', + 'currency_id': self.company.currency_id.id, + }) + + usd_order = self.env['sale.order'].with_company(self.company).create({ + 'partner_id': self.partner.id, + 'pricelist_id': usd_pricelist.id, + 'order_line': [ + Command.create({ + 'product_id': self.product.id, + 'product_uom_qty': 1.0, + }), + ] + }) + # the sales order's amount is in company currency + self.assertEqual(usd_order.amount_total, 20.0) + + # the report should match the amount on the SO + report_line = self.env['sale.report'].sudo().search( + [('order_id', '=', usd_order.id)]) + self.assertEqual(report_line.price_total, 20.0) diff --git a/addons/sale_margin/report/sale_report.py b/addons/sale_margin/report/sale_report.py index 7a9d7b7b59e..75d5b41aac1 100644 --- a/addons/sale_margin/report/sale_report.py +++ b/addons/sale_margin/report/sale_report.py @@ -12,7 +12,7 @@ class SaleReport(models.Model): def _select_additional_fields(self): res = super()._select_additional_fields() res['margin'] = f"""SUM(l.margin - * {self._case_value_or_one('s.currency_rate')} - * {self._case_value_or_one('currency_table.rate')}) + / {self._case_value_or_one('s.currency_rate')} + / {self._case_value_or_one('currency_table.rate')}) """ return res