From a2df68f39566fdb5ea5dfb0cfdb83487bcc46048 Mon Sep 17 00:00:00 2001
From: Josse Colpaert <jco@odoo.com>
Date: Wed, 1 Sep 2021 09:23:36 +0000
Subject: [PATCH] [IMP] account_edi: Backport of the method to retrieve tax
 details per line

Task: 2492978
Part-of: odoo/odoo#70302
---
 addons/account_edi/models/account_move.py | 170 ++++++++++++++++++++++
 1 file changed, 170 insertions(+)

diff --git a/addons/account_edi/models/account_move.py b/addons/account_edi/models/account_move.py
index 3faa9e144a24..9cf62463d285 100644
--- a/addons/account_edi/models/account_move.py
+++ b/addons/account_edi/models/account_move.py
@@ -1,6 +1,8 @@
 # -*- coding: utf-8 -*-
 # Part of Odoo. See LICENSE file for full copyright and licensing details.
 
+from collections import defaultdict
+
 from odoo import api, fields, models, _
 from odoo.exceptions import UserError
 
@@ -93,6 +95,174 @@ class AccountMove(models.Model):
     # Export Electronic Document
     ####################################################
 
+    @api.model
+    def _add_edi_tax_values(self, results, grouping_key, serialized_grouping_key, tax_values):
+        # Add to global results.
+        results['tax_amount'] += tax_values['tax_amount']
+        results['tax_amount_currency'] += tax_values['tax_amount_currency']
+
+        # Add to tax details.
+        tax_details = results['tax_details'][serialized_grouping_key]
+        tax_details.update(grouping_key)
+        if tax_values['base_line_id'] not in set(x['base_line_id'] for x in tax_details['group_tax_details']):
+            tax_details['base_amount'] += tax_values['base_amount']
+            tax_details['base_amount_currency'] += tax_values['base_amount_currency']
+
+        tax_details['tax_amount'] += tax_values['tax_amount']
+        tax_details['tax_amount_currency'] += tax_values['tax_amount_currency']
+        tax_details['group_tax_details'].append(tax_values)
+
+    def _prepare_edi_tax_details(self, filter_to_apply=None, filter_invl_to_apply=None, grouping_key_generator=None):
+        ''' Compute amounts related to taxes for the current invoice.
+        :param filter_to_apply:         Optional filter to exclude some tax values from the final results.
+                                        The filter is defined as a method getting a dictionary as parameter
+                                        representing the tax values for a single repartition line.
+                                        This dictionary contains:
+            'base_line_id':             An account.move.line record.
+            'tax_id':                   An account.tax record.
+            'tax_repartition_line_id':  An account.tax.repartition.line record.
+            'base_amount':              The tax base amount expressed in company currency.
+            'tax_amount':               The tax amount expressed in company currency.
+            'base_amount_currency':     The tax base amount expressed in foreign currency.
+            'tax_amount_currency':      The tax amount expressed in foreign currency.
+                                        If the filter is returning False, it means the current tax values will be
+                                        ignored when computing the final results.
+        :param grouping_key_generator:  Optional method used to group tax values together. By default, the tax values
+                                        are grouped by tax. This parameter is a method getting a dictionary as parameter
+                                        (same signature as 'filter_to_apply').
+                                        This method must returns a dictionary where values will be used to create the
+                                        grouping_key to aggregate tax values together. The returned dictionary is added
+                                        to each tax details in order to retrieve the full grouping_key later.
+        :return:                        The full tax details for the current invoice and for each invoice line
+                                        separately. The returned dictionary is the following:
+            'base_amount':              The total tax base amount in company currency for the whole invoice.
+            'tax_amount':               The total tax amount in company currency for the whole invoice.
+            'base_amount_currency':     The total tax base amount in foreign currency for the whole invoice.
+            'tax_amount_currency':      The total tax amount in foreign currency for the whole invoice.
+            'tax_details':              A mapping of each grouping key (see 'grouping_key_generator') to a dictionary
+                                        containing:
+                'base_amount':              The tax base amount in company currency for the current group.
+                'tax_amount':               The tax amount in company currency for the current group.
+                'base_amount_currency':     The tax base amount in foreign currency for the current group.
+                'tax_amount_currency':      The tax amount in foreign currency for the current group.
+                'group_tax_details':        The list of all tax values aggregated into this group.
+            'invoice_line_tax_details': A mapping of each invoice line to a dictionary containing:
+                'base_amount':          The total tax base amount in company currency for the whole invoice line.
+                'tax_amount':           The total tax amount in company currency for the whole invoice line.
+                'base_amount_currency': The total tax base amount in foreign currency for the whole invoice line.
+                'tax_amount_currency':  The total tax amount in foreign currency for the whole invoice line.
+                'tax_details':          A mapping of each grouping key (see 'grouping_key_generator') to a dictionary
+                                        containing:
+                    'base_amount':          The tax base amount in company currency for the current group.
+                    'tax_amount':           The tax amount in company currency for the current group.
+                    'base_amount_currency': The tax base amount in foreign currency for the current group.
+                    'tax_amount_currency':  The tax amount in foreign currency for the current group.
+                    'group_tax_details':    The list of all tax values aggregated into this group.
+        '''
+        self.ensure_one()
+
+        def _serialize_python_dictionary(vals):
+            return '-'.join(str(vals[k]) for k in sorted(vals.keys()))
+
+        def default_grouping_key_generator(tax_values):
+            return {'tax': tax_values['tax_id']}
+
+        # Compute the taxes values for each invoice line.
+
+        invoice_lines = self.invoice_line_ids.filtered(lambda line: not line.display_type)
+        if filter_invl_to_apply:
+            invoice_lines = invoice_lines.filtered(filter_invl_to_apply)
+
+        invoice_lines_tax_values_dict = {}
+        sign = -1 if self.is_inbound() else 1
+        for invoice_line in invoice_lines:
+            taxes_res = invoice_line.tax_ids.compute_all(
+                invoice_line.price_unit * (1 - (invoice_line.discount / 100.0)),
+                currency=invoice_line.currency_id,
+                quantity=invoice_line.quantity,
+                product=invoice_line.product_id,
+                partner=invoice_line.partner_id,
+                is_refund=invoice_line.move_id.move_type in ('in_refund', 'out_refund'),
+            )
+            tax_values_list = invoice_lines_tax_values_dict[invoice_line] = []
+            rate = abs(invoice_line.balance) / abs(invoice_line.amount_currency) if invoice_line.amount_currency else 0.0
+            for tax_res in taxes_res['taxes']:
+                tax_values_list.append({
+                    'base_line_id': invoice_line,
+                    'tax_id': self.env['account.tax'].browse(tax_res['id']),
+                    'tax_repartition_line_id': self.env['account.tax.repartition.line'].browse(tax_res['tax_repartition_line_id']),
+                    'base_amount': sign * invoice_line.company_currency_id.round(tax_res['base'] * rate),
+                    'tax_amount': sign * invoice_line.company_currency_id.round(tax_res['amount'] * rate),
+                    'base_amount_currency': sign * tax_res['base'],
+                    'tax_amount_currency': sign * tax_res['amount'],
+                })
+        grouping_key_generator = grouping_key_generator or default_grouping_key_generator
+
+        # Apply 'filter_to_apply'.
+
+        if filter_to_apply:
+            invoice_lines_tax_values_dict = {
+                invoice_line: [x for x in tax_values_list if filter_to_apply(x)]
+                for invoice_line, tax_values_list in invoice_lines_tax_values_dict.items()
+            }
+
+        # Initialize the results dict.
+
+        invoice_global_tax_details = {
+            'base_amount': 0.0,
+            'tax_amount': 0.0,
+            'base_amount_currency': 0.0,
+            'tax_amount_currency': 0.0,
+            'tax_details': defaultdict(lambda: {
+                'base_amount': 0.0,
+                'tax_amount': 0.0,
+                'base_amount_currency': 0.0,
+                'tax_amount_currency': 0.0,
+                'group_tax_details': [],
+            }),
+            'invoice_line_tax_details': defaultdict(lambda: {
+                'base_amount': 0.0,
+                'tax_amount': 0.0,
+                'base_amount_currency': 0.0,
+                'tax_amount_currency': 0.0,
+                'tax_details': defaultdict(lambda: {
+                    'base_amount': 0.0,
+                    'tax_amount': 0.0,
+                    'base_amount_currency': 0.0,
+                    'tax_amount_currency': 0.0,
+                    'group_tax_details': [],
+                }),
+            }),
+        }
+
+        # Apply 'grouping_key_generator' to 'invoice_lines_tax_values_list' and add all values to the final results.
+
+        for invoice_line in invoice_lines:
+            tax_values_list = invoice_lines_tax_values_dict[invoice_line]
+
+            # Add to invoice global tax amounts.
+            invoice_global_tax_details['base_amount'] += invoice_line.balance
+            invoice_global_tax_details['base_amount_currency'] += invoice_line.amount_currency
+
+            for tax_values in tax_values_list:
+                grouping_key = grouping_key_generator(tax_values)
+                serialized_grouping_key = _serialize_python_dictionary(grouping_key)
+
+                # Add to invoice line global tax amounts.
+                if serialized_grouping_key not in invoice_global_tax_details['invoice_line_tax_details'][invoice_line]:
+                    invoice_line_global_tax_details = invoice_global_tax_details['invoice_line_tax_details'][invoice_line]
+                    invoice_line_global_tax_details.update({
+                        'base_amount': invoice_line.balance,
+                        'base_amount_currency': invoice_line.amount_currency,
+                    })
+                else:
+                    invoice_line_global_tax_details = invoice_global_tax_details['invoice_line_tax_details'][invoice_line]
+
+                self._add_edi_tax_values(invoice_global_tax_details, grouping_key, serialized_grouping_key, tax_values)
+                self._add_edi_tax_values(invoice_line_global_tax_details, grouping_key, serialized_grouping_key, tax_values)
+
+        return invoice_global_tax_details
+
     def _update_payments_edi_documents(self):
         ''' Update the edi documents linked to the current journal entries. These journal entries must be linked to an
         account.payment of an account.bank.statement.line. This additional method is needed because the payment flow is
-- 
GitLab