diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py index 3f8d40dc5a1..a817b0b3f44 100644 --- a/addons/account/models/account_move.py +++ b/addons/account/models/account_move.py @@ -1250,6 +1250,7 @@ class AccountMove(models.Model): is_refund=move.move_type in ('out_refund', 'in_refund'), handle_price_include=False, )) + kwargs['is_company_currency_requested'] = move.currency_id != move.company_id.currency_id move.tax_totals = self.env['account.tax']._prepare_tax_totals(**kwargs) rounding_line = move.line_ids.filtered(lambda l: l.display_type == 'rounding') if rounding_line: diff --git a/addons/account/models/account_tax.py b/addons/account/models/account_tax.py index b6db50691a0..ca4e317be12 100644 --- a/addons/account/models/account_tax.py +++ b/addons/account/models/account_tax.py @@ -1154,13 +1154,17 @@ class AccountTax(models.Model): return res @api.model - def _prepare_tax_totals(self, base_lines, currency, tax_lines=None): + def _prepare_tax_totals(self, base_lines, currency, tax_lines=None, is_company_currency_requested=False): """ Compute the tax totals details for the business documents. - :param base_lines: A list of python dictionaries created using the '_convert_to_tax_base_line_dict' method. - :param currency: The currency set on the business document. - :param tax_lines: Optional list of python dictionaries created using the '_convert_to_tax_line_dict' method. - If specified, the taxes will be recomputed using them instead of recomputing the taxes on - the provided base lines. + :param base_lines: A list of python dictionaries created using the '_convert_to_tax_base_line_dict' method. + :param currency: The currency set on the business document. + :param tax_lines: Optional list of python dictionaries created using the '_convert_to_tax_line_dict' + method. If specified, the taxes will be recomputed using them instead of + recomputing the taxes on the provided base lines. + :param is_company_currency_requested : Optional boolean which indicates whether or not the company currency is + requested from the function. This can typically be used when using an + invoice in foreign currency and the company currency is required. + :return: A dictionary in the following form: { 'amount_total': The total amount to be displayed on the document, including every total @@ -1175,14 +1179,20 @@ class AccountTax(models.Model): default one: 'Untaxed Amount'. And groups_data is a list of dict in the following form: { - 'tax_group_name': The name of the tax groups this total is made for. - 'tax_group_amount': The total tax amount in this tax group. - 'tax_group_base_amount': The base amount for this tax group. - 'formatted_tax_group_amount': Same as tax_group_amount, but as a string formatted accordingly - with partner's locale. - 'formatted_tax_group_base_amount': Same as tax_group_base_amount, but as a string formatted - accordingly with partner's locale. - 'tax_group_id': The id of the tax group corresponding to this dict. + 'tax_group_name': The name of the tax groups this total is made for. + 'tax_group_amount': The total tax amount in this tax group. + 'tax_group_base_amount': The base amount for this tax group. + 'formatted_tax_group_amount': Same as tax_group_amount, but as a string formatted accordingly + with partner's locale. + 'formatted_tax_group_base_amount': Same as tax_group_base_amount, but as a string formatted + accordingly with partner's locale. + 'tax_group_id': The id of the tax group corresponding to this dict. + 'tax_group_base_amount_company_currency': OPTIONAL: the base amount of the tax group expressed in + the company currency when the parameter + is_company_currency_requested is True + 'tax_group_amount_company_currency': OPTIONAL: the tax amount of the tax group expressed in + the company currency when the parameter + is_company_currency_requested is True } 'subtotals': A list of dictionaries in the following form, one for each subtotal in 'groups_by_subtotals' keys. @@ -1192,6 +1202,8 @@ class AccountTax(models.Model): belonging to preceding subtotals and the base amount 'formatted_amount': Same as amount, but as a string formatted accordingly with partner's locale. + 'amount_company_currency': OPTIONAL: The total amount in company currency when the + parameter is_company_currency_requested is True } 'subtotals_order': A list of keys of `groups_by_subtotals` defining the order in which it needs to be displayed @@ -1218,6 +1230,9 @@ class AccountTax(models.Model): 'base_amount': tax_detail['base_amount_currency'], 'tax_amount': tax_detail['tax_amount_currency'], } + if is_company_currency_requested: + tax_group_vals['base_amount_company_currency'] = tax_detail['base_amount'] + tax_group_vals['tax_amount_company_currency'] = tax_detail['tax_amount'] # Handle a manual edition of tax lines. if tax_lines is not None: @@ -1238,6 +1253,9 @@ class AccountTax(models.Model): amount_untaxed = global_tax_details['base_amount_currency'] amount_tax = 0.0 + amount_untaxed_company_currency = global_tax_details['base_amount'] + amount_tax_company_currency = 0.0 + subtotal_order = {} groups_by_subtotal = defaultdict(list) for tax_group_vals in tax_group_vals_list: @@ -1256,6 +1274,9 @@ class AccountTax(models.Model): 'formatted_tax_group_amount': formatLang(self.env, tax_group_vals['tax_amount'], currency_obj=currency), 'formatted_tax_group_base_amount': formatLang(self.env, tax_group_vals['base_amount'], currency_obj=currency), }) + if is_company_currency_requested: + groups_by_subtotal[subtotal_title][-1]['tax_group_amount_company_currency'] = tax_group_vals['tax_amount_company_currency'] + groups_by_subtotal[subtotal_title][-1]['tax_group_base_amount_company_currency'] = tax_group_vals['base_amount_company_currency'] # ==== Build the final result ==== @@ -1267,6 +1288,10 @@ class AccountTax(models.Model): 'amount': amount_total, 'formatted_amount': formatLang(self.env, amount_total, currency_obj=currency), }) + if is_company_currency_requested: + subtotals[-1]['amount_company_currency'] = amount_untaxed_company_currency + amount_tax_company_currency + amount_tax_company_currency += sum(x['tax_group_amount_company_currency'] for x in groups_by_subtotal[subtotal_title]) + amount_tax += sum(x['tax_group_amount'] for x in groups_by_subtotal[subtotal_title]) amount_total = amount_untaxed + amount_tax diff --git a/addons/account/static/src/css/report_invoice.css b/addons/account/static/src/css/report_invoice.css index 7abb185c986..e4b15776f2a 100644 --- a/addons/account/static/src/css/report_invoice.css +++ b/addons/account/static/src/css/report_invoice.css @@ -16,3 +16,7 @@ height:18%; width:18%; } + +.tax_computation_company_currency { + margin-bottom: 5px; +} diff --git a/addons/account/tests/test_invoice_tax_totals.py b/addons/account/tests/test_invoice_tax_totals.py index 5c6253997a9..8584151abff 100644 --- a/addons/account/tests/test_invoice_tax_totals.py +++ b/addons/account/tests/test_invoice_tax_totals.py @@ -732,3 +732,80 @@ class TestTaxTotals(AccountTestInvoicingCommon): ] run_case('round_per_line', lines, [16.60]) run_case('round_globally', lines, [16.59]) + + def test_invoice_foreign_currency_tax_totals(self): + self.env['res.currency.rate'].create({ + 'name': '2018-01-01', + 'rate': 0.2, + 'currency_id': self.currency_data['currency'].id, + 'company_id': self.env.company.id, + }) + + tax_10 = self.env['account.tax'].create({ + 'name': "tax_10", + 'amount_type': 'percent', + 'amount': 10.0, + 'tax_group_id': self.tax_group1.id, + }) + + tax_20 = self.env['account.tax'].create({ + 'name': "tax_20", + 'amount_type': 'percent', + 'amount': 20.0, + 'tax_group_id': self.tax_group2.id, + }) + + invoice = self.env['account.move'].create({ + 'move_type': 'out_invoice', + 'partner_id': self.partner_a.id, + 'invoice_date': '2019-01-01', + 'currency_id': self.currency_data['currency'].id, + }) + + lines_data = [(100, tax_10), (300, tax_20)] + invoice_lines_vals = [ + Command.create({ + 'name': 'line', + 'display_type': 'product', + 'account_id': self.company_data['default_account_revenue'].id, + 'price_unit': amount, + 'tax_ids': [Command.set(taxes.ids)], + }) + for amount, taxes in lines_data + ] + + invoice['invoice_line_ids'] = invoice_lines_vals + + self.assertTaxTotals(invoice, { + 'amount_total': 470, + 'amount_untaxed': 400, + 'display_tax_base': True, + 'groups_by_subtotal': { + 'Untaxed Amount': [ + { + 'tax_group_name': self.tax_group1.name, + 'tax_group_amount': 10, + 'tax_group_base_amount': 100, + 'tax_group_id': self.tax_group1.id, + 'tax_group_amount_company_currency': 50, + 'tax_group_base_amount_company_currency': 500, + }, + { + 'tax_group_name': self.tax_group2.name, + 'tax_group_amount': 60, + 'tax_group_base_amount': 300, + 'tax_group_id': self.tax_group2.id, + 'tax_group_amount_company_currency': 300, + 'tax_group_base_amount_company_currency': 1500, + } + ] + }, + 'subtotals': [ + { + 'name': "Untaxed Amount", + 'amount': 400, + 'amount_company_currency': 2000, + } + ], + 'subtotals_order': ["Untaxed Amount"], + }) diff --git a/addons/account/views/report_invoice.xml b/addons/account/views/report_invoice.xml index 7cb2980a4b9..d9ea609a5e6 100644 --- a/addons/account/views/report_invoice.xml +++ b/addons/account/views/report_invoice.xml @@ -48,7 +48,7 @@ -
+

Invoice @@ -210,6 +210,7 @@

+
@@ -349,6 +350,67 @@ + + - -
- -
- Exchange Rate -

-

-
- Subtotal (AED) -

-

-
- VAT Amount (AED) -

-

-
- Total (AED) -

-

-
+ diff --git a/addons/web/static/src/webclient/actions/reports/layout_assets/layout_background.scss b/addons/web/static/src/webclient/actions/reports/layout_assets/layout_background.scss index f47d39e7f7d..ff5f5d4d70c 100644 --- a/addons/web/static/src/webclient/actions/reports/layout_assets/layout_background.scss +++ b/addons/web/static/src/webclient/actions/reports/layout_assets/layout_background.scss @@ -69,7 +69,7 @@ /*Total table*/ /* row div rule compat 12.0 */ .row > div > table, - div#total table { + div#total table, .totals_taxes_company_currency table { tr { &:nth-child(odd) { background-color: transparent !important; diff --git a/addons/web/static/src/webclient/actions/reports/layout_assets/layout_boxed.scss b/addons/web/static/src/webclient/actions/reports/layout_assets/layout_boxed.scss index 568f6c5e243..ea674142edf 100644 --- a/addons/web/static/src/webclient/actions/reports/layout_assets/layout_boxed.scss +++ b/addons/web/static/src/webclient/actions/reports/layout_assets/layout_boxed.scss @@ -82,7 +82,7 @@ /*Total table*/ /* row div rule compat 12.0 */ .row > div > table, - div#total table { + div#total table, .totals_taxes_company_currency table { thead tr:first-child, tr.o_subtotal { border-bottom: 1px solid map-get($grays, '700'); diff --git a/addons/web/static/src/webclient/actions/reports/layout_assets/layout_clean.scss b/addons/web/static/src/webclient/actions/reports/layout_assets/layout_clean.scss index 4933042efff..054f14448e8 100644 --- a/addons/web/static/src/webclient/actions/reports/layout_assets/layout_clean.scss +++ b/addons/web/static/src/webclient/actions/reports/layout_assets/layout_clean.scss @@ -70,7 +70,7 @@ } /* row col rule compat 12.0 */ .row > div > table, - div#total table { + div#total table, .totals_taxes_company_currency table { tr { &:first-child td, &.o_subtotal { diff --git a/addons/web/views/report_templates.xml b/addons/web/views/report_templates.xml index e93243d82f1..828c5938238 100644 --- a/addons/web/views/report_templates.xml +++ b/addons/web/views/report_templates.xml @@ -573,7 +573,7 @@ } } .row > div > table, - div#total table { + div#total table, .totals_taxes_company_currency table { &:first-child, &:last-child, &.o_subtotal, @@ -589,6 +589,9 @@ #total strong { color: ; } + .totals_taxes_company_currency strong{ + color: ; + } #informations strong { color: ; } @@ -613,7 +616,7 @@ } } .row > div > table, - div#total table { + div#total table, .totals_taxes_company_currency table { tr { &.o_total td { background-color: rgba(, 0.9); @@ -652,7 +655,7 @@ } } } - #total { + #total, .totals_taxes_company_currency { strong { color: ; } @@ -670,6 +673,9 @@ #total strong{ color: ; } + .totals_taxes_company_currency strong{ + color: ; + } table { thead { color: ;