# -*- coding: utf-8 -*-
#pylint: disable=too-many-lines
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
from odoo.tests import tagged
from odoo import Command


@tagged('post_install', '-at_install')
class TestAccountTaxDetailsReport(AccountTestInvoicingCommon):

    def _dispatch_move_lines(self, moves):
        base_lines = moves.line_ids\
            .filtered(lambda x: x.tax_ids and not x.tax_line_id)\
            .sorted(lambda x: (x.move_id.id, x.id, -abs(x.amount_currency)))
        tax_lines = moves.line_ids\
            .filtered(lambda x: x.tax_line_id)\
            .sorted(lambda x: (x.move_id.id, x.tax_line_id.id, x.tax_ids.ids, x.tax_repartition_line_id.id))
        return base_lines, tax_lines

    def _get_tax_details(self, fallback=False, extra_domain=None):
        domain = [('company_id', '=', self.env.company.id)] + (extra_domain or [])
        tax_details_query, tax_details_params = self.env['account.move.line']._get_query_tax_details_from_domain(domain, fallback=fallback)
        self.env['account.move.line'].flush_model()
        self.cr.execute(tax_details_query, tax_details_params)
        tax_details_res = self.cr.dictfetchall()
        return sorted(tax_details_res, key=lambda x: (x['base_line_id'], abs(x['base_amount']), abs(x['tax_amount'])))

    def assertTaxDetailsValues(self, tax_details, expected_values_list):
        self.assertEqual(len(tax_details), len(expected_values_list))

        for i, expected_values in enumerate(expected_values_list):
            keys = set(expected_values.keys())
            tax_detail = tax_details[i]
            self.assertDictEqual({k: v for k, v in tax_detail.items() if k in keys}, expected_values)

    def assertTotalAmounts(self, moves, tax_details):
        tax_lines = moves.line_ids.filtered('tax_line_id')
        taxes = tax_lines.mapped(lambda x: x.group_tax_id or x.tax_line_id)
        for tax in taxes:
            lines = tax_lines.filtered(lambda x: (x.group_tax_id or x.tax_line_id) == tax)
            tax_amount = sum(lines.mapped('balance'))
            tax_details_amount = sum(x['tax_amount']
                                     for x in tax_details
                                     if (x['group_tax_id'] or x['tax_id']) == tax.id)
            self.assertAlmostEqual(tax_amount, tax_details_amount)

    def test_affect_base_amount_1(self):
        tax_20_affect = self.env['account.tax'].create({
            'name': "tax_20_affect",
            'amount_type': 'percent',
            'amount': 20.0,
            'include_base_amount': True,
        })
        tax_10 = self.env['account.tax'].create({
            'name': "tax_10",
            'amount_type': 'percent',
            'amount': 10.0,
        })
        tax_5 = self.env['account.tax'].create({
            'name': "tax_5",
            'amount_type': 'percent',
            'amount': 5.0,
        })

        invoice_create_values = {
            'move_type': 'out_invoice',
            'partner_id': self.partner_a.id,
            'invoice_date': '2019-01-01',
            'invoice_line_ids': [
                Command.create({
                    'name': 'line1',
                    'account_id': self.company_data['default_account_revenue'].id,
                    'price_unit': 1000.0,
                    'tax_ids': [Command.set((tax_20_affect + tax_10 + tax_5).ids)],
                }),
                Command.create({
                    'name': 'line2',
                    'account_id': self.company_data['default_account_revenue'].id,
                    'price_unit': 1000.0,
                    'tax_ids': [Command.set(tax_10.ids)],
                }),
                Command.create({
                    'name': 'line3',
                    'account_id': self.company_data['default_account_revenue'].id,
                    'price_unit': 1000.0,
                    'tax_ids': [Command.set(tax_10.ids)],
                }),
                Command.create({
                    'name': 'line4',
                    'account_id': self.company_data['default_account_revenue'].id,
                    'price_unit': 2000.0,
                    'tax_ids': [Command.set((tax_20_affect + tax_10).ids)],
                }),
            ]
        }

        invoice = self.env['account.move'].create(invoice_create_values)
        base_lines, tax_lines = self._dispatch_move_lines(invoice)

        tax_details = self._get_tax_details()
        self.assertTaxDetailsValues(tax_details, [
            {
                'base_line_id': base_lines[0].id,
                'tax_line_id': tax_lines[3].id,
                'base_amount': -200.0,
                'tax_amount': -10.0,
            },
            {
                'base_line_id': base_lines[0].id,
                'tax_line_id': tax_lines[2].id,
                'base_amount': -200.0,
                'tax_amount': -20.0,
            },
            {
                'base_line_id': base_lines[0].id,
                'tax_line_id': tax_lines[3].id,
                'base_amount': -1000.0,
                'tax_amount': -50.0,
            },
            {
                'base_line_id': base_lines[0].id,
                'tax_line_id': tax_lines[2].id,
                'base_amount': -1000.0,
                'tax_amount': -100.0,
            },
            {
                'base_line_id': base_lines[0].id,
                'tax_line_id': tax_lines[1].id,
                'base_amount': -1000.0,
                'tax_amount': -200.0,
            },
            {
                'base_line_id': base_lines[1].id,
                'tax_line_id': tax_lines[2].id,
                'base_amount': -1000.0,
                'tax_amount': -100.0,
            },
            {
                'base_line_id': base_lines[2].id,
                'tax_line_id': tax_lines[2].id,
                'base_amount': -1000.0,
                'tax_amount': -100.0,
            },
            {
                'base_line_id': base_lines[3].id,
                'tax_line_id': tax_lines[2].id,
                'base_amount': -400.0,
                'tax_amount': -40.0,
            },
            {
                'base_line_id': base_lines[3].id,
                'tax_line_id': tax_lines[2].id,
                'base_amount': -2000.0,
                'tax_amount': -200.0,
            },
            {
                'base_line_id': base_lines[3].id,
                'tax_line_id': tax_lines[0].id,
                'base_amount': -2000.0,
                'tax_amount': -400.0,
            },
        ])
        self.assertTotalAmounts(invoice, tax_details)

        # Same with a group of taxes

        tax_group = self.env['account.tax'].create({
            'name': "tax_group",
            'amount_type': 'group',
            'children_tax_ids': [Command.set((tax_20_affect + tax_10 + tax_5).ids)],
        })

        invoice_create_values['invoice_line_ids'][0][2]['tax_ids'] = [Command.set(tax_group.ids)]
        invoice = self.env['account.move'].create(invoice_create_values)

        base_lines, tax_lines = self._dispatch_move_lines(invoice)

        tax_details = self._get_tax_details(extra_domain=[('move_id', '=', invoice.id)])
        self.assertTaxDetailsValues(tax_details, [
            {
                'base_line_id': base_lines[0].id,
                'tax_line_id': tax_lines[4].id,
                'base_amount': -200.0,
                'tax_amount': -10.0,
            },
            {
                'base_line_id': base_lines[0].id,
                'tax_line_id': tax_lines[2].id,
                'base_amount': -200.0,
                'tax_amount': -20.0,
            },
            {
                'base_line_id': base_lines[0].id,
                'tax_line_id': tax_lines[4].id,
                'base_amount': -1000.0,
                'tax_amount': -50.0,
            },
            {
                'base_line_id': base_lines[0].id,
                'tax_line_id': tax_lines[2].id,
                'base_amount': -1000.0,
                'tax_amount': -100.0,
            },
            {
                'base_line_id': base_lines[0].id,
                'tax_line_id': tax_lines[1].id,
                'base_amount': -1000.0,
                'tax_amount': -200.0,
            },
            {
                'base_line_id': base_lines[1].id,
                'tax_line_id': tax_lines[3].id,
                'base_amount': -1000.0,
                'tax_amount': -100.0,
            },
            {
                'base_line_id': base_lines[2].id,
                'tax_line_id': tax_lines[3].id,
                'base_amount': -1000.0,
                'tax_amount': -100.0,
            },
            {
                'base_line_id': base_lines[3].id,
                'tax_line_id': tax_lines[3].id,
                'base_amount': -400.0,
                'tax_amount': -40.0,
            },
            {
                'base_line_id': base_lines[3].id,
                'tax_line_id': tax_lines[3].id,
                'base_amount': -2000.0,
                'tax_amount': -200.0,
            },
            {
                'base_line_id': base_lines[3].id,
                'tax_line_id': tax_lines[0].id,
                'base_amount': -2000.0,
                'tax_amount': -400.0,
            },
        ])
        self.assertTotalAmounts(invoice, tax_details)

    def test_affect_base_amount_2(self):
        taxes_10_affect = self.env['account.tax'].create([{
            'name': "tax_10_affect_%s" % i,
            'amount_type': 'percent',
            'amount': 10.0,
            'include_base_amount': True,
        } for i in range(3)])

        invoice = self.env['account.move'].create({
            'move_type': 'out_invoice',
            'partner_id': self.partner_a.id,
            'invoice_date': '2019-01-01',
            'invoice_line_ids': [
                Command.create({
                    'name': 'line1',
                    'account_id': self.company_data['default_account_revenue'].id,
                    'price_unit': 1000.0,
                    'tax_ids': [Command.set(taxes_10_affect.ids)],
                }),
                Command.create({
                    'name': 'line2',
                    'account_id': self.company_data['default_account_revenue'].id,
                    'price_unit': 1000.0,
                    'tax_ids': [Command.set((taxes_10_affect[0] + taxes_10_affect[2]).ids)],
                }),
            ]
        })
        base_lines, tax_lines = self._dispatch_move_lines(invoice)

        tax_details = self._get_tax_details()
        self.assertTaxDetailsValues(
            tax_details,
            [
                {
                    'base_line_id': base_lines[0].id,
                    'tax_line_id': tax_lines[2].id,
                    'base_amount': -100.0,
                    'tax_amount': -10.0,
                },
                {
                    'base_line_id': base_lines[0].id,
                    'tax_line_id': tax_lines[3].id,
                    'base_amount': -100.0,
                    'tax_amount': -10.0,
                },
                {
                    'base_line_id': base_lines[0].id,
                    'tax_line_id': tax_lines[3].id,
                    'base_amount': -110.0,
                    'tax_amount': -11.0,
                },
                {
                    'base_line_id': base_lines[0].id,
                    'tax_line_id': tax_lines[0].id,
                    'base_amount': -1000.0,
                    'tax_amount': -100.0,
                },
                {
                    'base_line_id': base_lines[0].id,
                    'tax_line_id': tax_lines[2].id,
                    'base_amount': -1000.0,
                    'tax_amount': -100.0,
                },
                {
                    'base_line_id': base_lines[0].id,
                    'tax_line_id': tax_lines[3].id,
                    'base_amount': -1000.0,
                    'tax_amount': -100.0,
                },
                {
                    'base_line_id': base_lines[1].id,
                    'tax_line_id': tax_lines[3].id,
                    'base_amount': -100.0,
                    'tax_amount': -10.0,
                },
                {
                    'base_line_id': base_lines[1].id,
                    'tax_line_id': tax_lines[3].id,
                    'base_amount': -1000.0,
                    'tax_amount': -100.0,
                },
                {
                    'base_line_id': base_lines[1].id,
                    'tax_line_id': tax_lines[1].id,
                    'base_amount': -1000.0,
                    'tax_amount': -100.0,
                },
            ],
        )
        self.assertTotalAmounts(invoice, tax_details)

    def test_affect_base_amount_3(self):
        eco_tax = self.env['account.tax'].create({
            'name': "eco_tax",
            'amount_type': 'fixed',
            'amount': 5.0,
            'include_base_amount': True,
        })
        tax_20 = self.env['account.tax'].create({
            'name': "tax_20",
            'amount_type': 'percent',
            'amount': 20.0,
        })

        invoice = self.env['account.move'].create({
            'move_type': 'out_invoice',
            'partner_id': self.partner_a.id,
            'invoice_date': '2019-01-01',
            'invoice_line_ids': [
                Command.create({
                    'name': 'line1',
                    'account_id': self.company_data['default_account_revenue'].id,
                    'price_unit': 95.0,
                    'tax_ids': [Command.set((eco_tax + tax_20).ids)],
                }),
            ]
        })
        base_lines, tax_lines = self._dispatch_move_lines(invoice)

        tax_details = self._get_tax_details()
        self.assertTaxDetailsValues(
            tax_details,
            [
                {
                    'base_line_id': base_lines[0].id,
                    'tax_line_id': tax_lines[1].id,
                    'base_amount': -5.0,
                    'tax_amount': -1.0,
                },
                {
                    'base_line_id': base_lines[0].id,
                    'tax_line_id': tax_lines[0].id,
                    'base_amount': -95.0,
                    'tax_amount': -5.0,
                },
                {
                    'base_line_id': base_lines[0].id,
                    'tax_line_id': tax_lines[1].id,
                    'base_amount': -95.0,
                    'tax_amount': -19.0,
                },
            ],
        )
        self.assertTotalAmounts(invoice, tax_details)

    def test_affect_base_amount_4(self):
        tax_10 = self.env['account.tax'].create({
            'name': "eco_tax",
            'amount_type': 'percent',
            'amount': 10.0,
            'include_base_amount': True,
        })
        tax_20 = self.env['account.tax'].create({
            'name': "tax_20",
            'amount_type': 'percent',
            'amount': 20.0,
        })

        invoice = self.env['account.move'].create({
            'move_type': 'out_invoice',
            'partner_id': self.partner_a.id,
            'invoice_date': '2019-01-01',
            'invoice_line_ids': [
                Command.create({
                    'name': 'line1',
                    'account_id': self.company_data['default_account_revenue'].id,
                    'price_unit': 100.0,
                    'tax_ids': [Command.set((tax_10 + tax_20).ids)],
                }),
                Command.create({
                    'name': 'line1',
                    'account_id': self.company_data['default_account_revenue'].id,
                    'price_unit': 100.0,
                    'tax_ids': [Command.set(tax_10.ids)],
                }),
            ]
        })
        base_lines, tax_lines = self._dispatch_move_lines(invoice)

        tax_details = self._get_tax_details()
        self.assertTaxDetailsValues(
            tax_details,
            [
                {
                    'base_line_id': base_lines[0].id,
                    'tax_line_id': tax_lines[2].id,
                    'base_amount': -10.0,
                    'tax_amount': -2.0,
                },
                {
                    'base_line_id': base_lines[0].id,
                    'tax_line_id': tax_lines[1].id,
                    'base_amount': -100.0,
                    'tax_amount': -10.0,
                },
                {
                    'base_line_id': base_lines[0].id,
                    'tax_line_id': tax_lines[2].id,
                    'base_amount': -100.0,
                    'tax_amount': -20.0,
                },
                {
                    'base_line_id': base_lines[1].id,
                    'tax_line_id': tax_lines[0].id,
                    'base_amount': -100.0,
                    'tax_amount': -10.0,
                },
            ],
        )
        self.assertTotalAmounts(invoice, tax_details)

    def test_affect_base_amount_5(self):
        affecting_tax = self.env['account.tax'].create({
            'name': 'Affecting',
            'amount': 42,
            'amount_type': 'percent',
            'type_tax_use': 'sale',
            'include_base_amount': True,
            'sequence': 0,
        })

        affected_tax = self.env['account.tax'].create({
            'name': 'Affected',
            'amount': 10,
            'amount_type': 'percent',
            'type_tax_use': 'sale',
            'sequence': 1
        })

        invoice = self.env['account.move'].create({
            'move_type': 'out_invoice',
            'partner_id': self.partner_a.id,
            'invoice_date': '2021-08-01',
            'invoice_line_ids': [
                Command.create({
                    'name': "affecting",
                    'account_id': self.company_data['default_account_revenue'].id,
                    'quantity': 1.0,
                    'price_unit': 100.0,
                    'tax_ids': affecting_tax.ids,
                }),

                Command.create({
                    'name': "affected",
                    'account_id': self.company_data['default_account_revenue'].id,
                    'quantity': 1.0,
                    'price_unit': 100.0,
                    'tax_ids': affected_tax.ids,
                }),

                Command.create({
                    'name': "affecting + affected",
                    'account_id': self.company_data['default_account_revenue'].id,
                    'quantity': 1.0,
                    'price_unit': 100.0,
                    'tax_ids': (affecting_tax + affected_tax).ids,
                }),
            ]
        })

        base_lines, tax_lines = self._dispatch_move_lines(invoice)
        tax_details = self._get_tax_details()

        self.assertTaxDetailsValues(
            tax_details,
            [
                {
                    'base_line_id': base_lines[0].id,
                    'tax_line_id': tax_lines[0].id,
                    'base_amount': -100.0,
                    'tax_amount': -42.0,
                },
                {
                    'base_line_id': base_lines[1].id,
                    'tax_line_id': tax_lines[2].id,
                    'base_amount': -100.0,
                    'tax_amount': -10.0,
                },
                {
                    'base_line_id': base_lines[2].id,
                    'tax_line_id': tax_lines[2].id,
                    'base_amount': -42.0,
                    'tax_amount': -4.2,
                },
                {
                    'base_line_id': base_lines[2].id,
                    'tax_line_id': tax_lines[2].id,
                    'base_amount': -100.0,
                    'tax_amount': -10.0,
                },
                {
                    'base_line_id': base_lines[2].id,
                    'tax_line_id': tax_lines[1].id,
                    'base_amount': -100.0,
                    'tax_amount': -42.0,
                },
            ],
        )
        self.assertTotalAmounts(invoice, tax_details)

    def test_affect_base_amount_6(self):
        affecting_tax = self.env['account.tax'].create({
            'name': 'Affecting',
            'amount': 42,
            'amount_type': 'percent',
            'type_tax_use': 'sale',
            'include_base_amount': True,
            'sequence': 0,
        })

        affected_tax = self.env['account.tax'].create({
            'name': 'Affected',
            'amount': 10,
            'amount_type': 'percent',
            'type_tax_use': 'sale',
            'sequence': 1
        })

        invoice = self.env['account.move'].create({
            'move_type': 'out_invoice',
            'partner_id': self.partner_a.id,
            'invoice_date': '2021-08-01',
            'invoice_line_ids': [
                Command.create({
                    'name': "affecting + affected",
                    'account_id': self.company_data['default_account_revenue'].id,
                    'quantity': 1.0,
                    'price_unit': 100.0,
                    'tax_ids': (affecting_tax + affected_tax).ids,
                }),
            ]
        })

        invoice.write({'invoice_line_ids': [Command.delete(invoice.invoice_line_ids.id)]})
        base_lines, tax_lines = self._dispatch_move_lines(invoice)
        self.assertFalse(base_lines)
        self.assertFalse(tax_lines)
        tax_details = self._get_tax_details()
        self.assertFalse(tax_details)

    def test_round_globally_rounding(self):
        self.env.company.tax_calculation_rounding_method = 'round_globally'

        tax_50 = self.env['account.tax'].create({
            'name': "tax_50",
            'amount_type': 'percent',
            'amount': 50.0,
        })

        invoice = self.env['account.move'].create({
            'move_type': 'out_invoice',
            'partner_id': self.partner_a.id,
            'invoice_date': '2019-01-01',
            'invoice_line_ids': [
                Command.create({
                    'name': 'line%s' % i,
                    'account_id': self.company_data['default_account_revenue'].id,
                    'price_unit': 0.01,
                    'tax_ids': [Command.set(tax_50.ids)],
                })
            for i in range(7)]
        })
        base_lines, tax_lines = self._dispatch_move_lines(invoice)

        tax_details = self._get_tax_details()
        self.assertTaxDetailsValues(
            tax_details,
            [
                {
                    'base_line_id': base_lines[0].id,
                    'tax_line_id': tax_lines.id,
                    'base_amount': -0.01,
                    'tax_amount': -0.01,
                },
                {
                    'base_line_id': base_lines[1].id,
                    'tax_line_id': tax_lines.id,
                    'base_amount': -0.01,
                    'tax_amount': 0.0,
                },
                {
                    'base_line_id': base_lines[2].id,
                    'tax_line_id': tax_lines.id,
                    'base_amount': -0.01,
                    'tax_amount': -0.01,
                },
                {
                    'base_line_id': base_lines[3].id,
                    'tax_line_id': tax_lines.id,
                    'base_amount': -0.01,
                    'tax_amount': 0.00,
                },
                {
                    'base_line_id': base_lines[4].id,
                    'tax_line_id': tax_lines.id,
                    'base_amount': -0.01,
                    'tax_amount': -0.01,
                },
                {
                    'base_line_id': base_lines[5].id,
                    'tax_line_id': tax_lines.id,
                    'base_amount': -0.01,
                    'tax_amount': 0.0,
                },
                {
                    'base_line_id': base_lines[6].id,
                    'tax_line_id': tax_lines.id,
                    'base_amount': -0.01,
                    'tax_amount': -0.01,
                },
            ],
        )
        self.assertTotalAmounts(invoice, tax_details)

    def test_round_per_line_update(self):
        self.env.company.tax_calculation_rounding_method = 'round_per_line'

        tax_8 = self.env['account.tax'].create({
            'name': "tax_8",
            'amount_type': 'percent',
            'amount': 8.0,
        })

        invoice = self.env['account.move'].create({
            'move_type': 'out_invoice',
            'partner_id': self.partner_a.id,
            'invoice_date': '2019-01-01',
            'invoice_cash_rounding_id': self.cash_rounding_b.id,
            'invoice_line_ids': [
                Command.create({
                    'name': 'line1',
                    'account_id': self.company_data['default_account_revenue'].id,
                    'price_unit': 45.45,
                    'tax_ids': [Command.set(tax_8.ids)],
                })
            ]
        })
        invoice.invoice_line_ids.write({"price_unit": 4545})

        base_lines, tax_lines = self._dispatch_move_lines(invoice)

        tax_details = self._get_tax_details()
        self.assertTaxDetailsValues(
            tax_details,
            [
                {
                    'base_line_id': base_lines[0].id,
                    'tax_line_id': tax_lines[0].id,
                    'base_amount': -4545.0,
                    'tax_amount': -363.6,
                },
            ],
        )
        self.assertTotalAmounts(invoice, tax_details)

    def test_partitioning_lines_by_moves(self):
        tax_20_affect = self.env['account.tax'].create({
            'name': "tax_20_affect",
            'amount_type': 'percent',
            'amount': 20.0,
            'include_base_amount': True,
        })
        tax_10 = self.env['account.tax'].create({
            'name': "tax_10",
            'amount_type': 'percent',
            'amount': 10.0,
        })

        invoices = self.env['account.move']
        expected_values_list = []
        for i in range(1, 6):
            invoice = invoices.create({
                'move_type': 'out_invoice',
                'partner_id': self.partner_a.id,
                'invoice_date': '2019-01-01',
                'invoice_line_ids': [
                    Command.create({
                        'name': 'line1',
                        'account_id': self.company_data['default_account_revenue'].id,
                        'price_unit': i * 1000.0,
                        'tax_ids': [Command.set((tax_20_affect + tax_10).ids)],
                    }),
                ]
            })
            invoices |= invoice
            base_lines, tax_lines = self._dispatch_move_lines(invoice)
            expected_values_list += [
                {
                    'base_line_id': base_lines[0].id,
                    'tax_line_id': tax_lines[1].id,
                    'base_amount': -200.0 * i,
                    'tax_amount': -20.0 * i,
                },
                {
                    'base_line_id': base_lines[0].id,
                    'tax_line_id': tax_lines[1].id,
                    'base_amount': -1000.0 * i,
                    'tax_amount': -100.0 * i,
                },
                {
                    'base_line_id': base_lines[0].id,
                    'tax_line_id': tax_lines[0].id,
                    'base_amount': -1000.0 * i,
                    'tax_amount': -200.0 * i,
                },
            ]

        tax_details = self._get_tax_details()
        self.assertTaxDetailsValues(tax_details, expected_values_list)
        self.assertTotalAmounts(invoices, tax_details)

    def test_fixed_tax_with_negative_quantity(self):
        fixed_tax = self.env['account.tax'].create({
            'name': "fixed_tax",
            'amount_type': 'fixed',
            'amount': 10.0,
        })

        invoice = self.env['account.move'].create({
            'move_type': 'out_invoice',
            'partner_id': self.partner_a.id,
            'invoice_date': '2019-01-01',
            'invoice_line_ids': [
                Command.create({
                    'name': 'line1',
                    'account_id': self.company_data['default_account_revenue'].id,
                    'price_unit': 100.0,
                    'quantity': 5,
                    'tax_ids': [Command.set(fixed_tax.ids)],
                }),
                Command.create({
                    'name': 'line2',
                    'account_id': self.company_data['default_account_revenue'].id,
                    'price_unit': 100.0,
                    'quantity': 9,
                    'tax_ids': [Command.set(fixed_tax.ids)],
                }),
                Command.create({
                    'name': 'line3',
                    'account_id': self.company_data['default_account_revenue'].id,
                    'price_unit': 100.0,
                    'quantity': -4,
                    'tax_ids': [Command.set(fixed_tax.ids)],
                }),
            ]
        })
        base_lines, tax_lines = self._dispatch_move_lines(invoice)

        tax_details = self._get_tax_details()
        self.assertTaxDetailsValues(
            tax_details,
            [
                {
                    'base_line_id': base_lines[0].id,
                    'tax_line_id': tax_lines[0].id,
                    'base_amount': -500.0,
                    'tax_amount': -50.0,
                },
                {
                    'base_line_id': base_lines[1].id,
                    'tax_line_id': tax_lines[0].id,
                    'base_amount': -900.0,
                    'tax_amount': -90.0,
                },
                {
                    'base_line_id': base_lines[2].id,
                    'tax_line_id': tax_lines[0].id,
                    'base_amount': 400.0,
                    'tax_amount': 40.0,
                },
            ],
        )
        self.assertTotalAmounts(invoice, tax_details)

    def test_percent_tax_with_negative_balance(self):
        percent_tax = self.env['account.tax'].create({
            'name': "percent_tax",
            'amount_type': 'percent',
            'amount': 10.0,
        })

        invoice = self.env['account.move'].create({
            'move_type': 'out_invoice',
            'partner_id': self.partner_a.id,
            'invoice_date': '2019-01-01',
            'invoice_line_ids': [
                Command.create({
                    'name': 'line1',
                    'account_id': self.company_data['default_account_revenue'].id,
                    'price_unit': 500.0,
                    'tax_ids': [Command.set(percent_tax.ids)],
                }),
                Command.create({
                    'name': 'line2',
                    'account_id': self.company_data['default_account_revenue'].id,
                    'price_unit': 900.0,
                    'tax_ids': [Command.set(percent_tax.ids)],
                }),
                Command.create({
                    'name': 'line3',
                    'account_id': self.company_data['default_account_revenue'].id,
                    'price_unit': -400.0,
                    'tax_ids': [Command.set(percent_tax.ids)],
                }),
            ]
        })
        base_lines, tax_lines = self._dispatch_move_lines(invoice)

        tax_details = self._get_tax_details()
        self.assertTaxDetailsValues(
            tax_details,
            [
                {
                    'base_line_id': base_lines[0].id,
                    'tax_line_id': tax_lines[0].id,
                    'base_amount': -500.0,
                    'tax_amount': -50.0,
                },
                {
                    'base_line_id': base_lines[1].id,
                    'tax_line_id': tax_lines[0].id,
                    'base_amount': -900.0,
                    'tax_amount': -90.0,
                },
                {
                    'base_line_id': base_lines[2].id,
                    'tax_line_id': tax_lines[0].id,
                    'base_amount': 400.0,
                    'tax_amount': 40.0,
                },
            ],
        )
        self.assertTotalAmounts(invoice, tax_details)

    def test_fixed_tax_with_negative_balance(self):
        fixed_tax = self.env['account.tax'].create({
            'name': "fixed_tax",
            'amount_type': 'fixed',
            'amount': 10.0,
        })

        invoice = self.env['account.move'].create({
            'move_type': 'out_invoice',
            'partner_id': self.partner_a.id,
            'invoice_date': '2019-01-01',
            'invoice_line_ids': [
                Command.create({
                    'name': 'line1',
                    'account_id': self.company_data['default_account_revenue'].id,
                    'price_unit': 500.0,
                    'tax_ids': [Command.set(fixed_tax.ids)],
                }),
                Command.create({
                    'name': 'line2',
                    'account_id': self.company_data['default_account_revenue'].id,
                    'price_unit': 900.0,
                    'tax_ids': [Command.set(fixed_tax.ids)],
                }),
                Command.create({
                    'name': 'line3',
                    'account_id': self.company_data['default_account_revenue'].id,
                    'price_unit': -400.0,
                    'tax_ids': [Command.set(fixed_tax.ids)],
                }),
            ]
        })
        base_lines, tax_lines = self._dispatch_move_lines(invoice)

        tax_details = self._get_tax_details()
        self.assertTaxDetailsValues(
            tax_details,
            [
                {
                    'base_line_id': base_lines[0].id,
                    'tax_line_id': tax_lines[0].id,
                    'base_amount': -500.0,
                    'tax_amount': -10.0,
                },
                {
                    'base_line_id': base_lines[1].id,
                    'tax_line_id': tax_lines[0].id,
                    'base_amount': -900.0,
                    'tax_amount': -10.0,
                },
                {
                    'base_line_id': base_lines[2].id,
                    'tax_line_id': tax_lines[0].id,
                    'base_amount': 400.0,
                    'tax_amount': 10.0,
                },
            ],
        )
        self.assertTotalAmounts(invoice, tax_details)

    def test_multiple_same_tax_lines(self):
        """ In expense, the same tax line could be generated multiple times. """
        percent_tax = self.env['account.tax'].create({
            'name': "percent_tax",
            'amount_type': 'percent',
            'amount': 10.0,
        })
        tax_rep = percent_tax.refund_repartition_line_ids.filtered(lambda x: x.repartition_type == 'tax')

        move = self.env['account.move'].with_context(skip_invoice_sync=True).create({
            'date': '2019-01-01',
            'line_ids': [
                # Base lines
                Command.create({
                    'name': 'base1',
                    'debit': 1000.0,
                    'credit': 0.0,
                    'account_id': self.company_data['default_account_revenue'].id,
                    'tax_ids': [Command.set(percent_tax.ids)],
                }),
                Command.create({
                    'name': 'base2',
                    'debit': 10000.0,
                    'credit': 0.0,
                    'account_id': self.company_data['default_account_revenue'].id,
                    'tax_ids': [Command.set(percent_tax.ids)],
                }),
                # Tax lines
                Command.create({
                    'name': 'tax1',
                    'debit': 100.0,
                    'credit': 0.0,
                    'account_id': self.company_data['default_account_revenue'].id,
                    'tax_repartition_line_id': tax_rep.id,
                }),
                Command.create({
                    'name': 'tax1',
                    'debit': 1000.0,
                    'credit': 0.0,
                    'account_id': self.company_data['default_account_revenue'].id,
                    'tax_repartition_line_id': tax_rep.id,
                }),
                # Balance
                Command.create({
                    'name': 'balance',
                    'debit': 0.0,
                    'credit': 12100.0,
                    'account_id': self.company_data['default_account_receivable'].id,
                }),
            ],
        })
        base_lines, tax_lines = self._dispatch_move_lines(move)

        tax_details = self._get_tax_details()
        self.assertTaxDetailsValues(
            tax_details,
            [
                {
                    'base_line_id': base_lines[0].id,
                    'tax_line_id': tax_lines[0].id,
                    'base_amount': 1000.0,
                    'tax_amount': 9.09,
                },
                {
                    'base_line_id': base_lines[0].id,
                    'tax_line_id': tax_lines[1].id,
                    'base_amount': 1000.0,
                    'tax_amount': 90.91,
                },
                {
                    'base_line_id': base_lines[1].id,
                    'tax_line_id': tax_lines[0].id,
                    'base_amount': 10000.0,
                    'tax_amount': 90.91,
                },
                {
                    'base_line_id': base_lines[1].id,
                    'tax_line_id': tax_lines[1].id,
                    'base_amount': 10000.0,
                    'tax_amount': 909.09,
                },
            ],
        )
        self.assertTotalAmounts(move, tax_details)

    def test_multiple_same_tax_lines_multi_currencies_manual_edition(self):
        percent_tax = self.env['account.tax'].create({
            'name': "percent_tax",
            'amount_type': 'percent',
            'amount': 10.0,
        })
        tax_rep = percent_tax.refund_repartition_line_ids.filtered(lambda x: x.repartition_type == 'tax')

        move = self.env['account.move'].with_context(skip_invoice_sync=True).create({
            'date': '2019-01-01',
            'line_ids': [
                # Base lines
                Command.create({
                    'name': 'base1',
                    'debit': 1200.0,
                    'credit': 0.0,
                    'amount_currency': 2400.0,
                    'account_id': self.company_data['default_account_revenue'].id,
                    'currency_id': self.currency_data['currency'].id,
                    'tax_ids': [Command.set(percent_tax.ids)],
                }),
                Command.create({
                    'name': 'base2',
                    'debit': 12000.0,
                    'credit': 0.0,
                    'amount_currency': 6000.0,
                    'account_id': self.company_data['default_account_revenue'].id,
                    'currency_id': self.currency_data['currency'].id,
                    'tax_ids': [Command.set(percent_tax.ids)],
                }),
                # Tax lines
                Command.create({
                    'name': 'tax1',
                    'debit': 120.0,
                    'credit': 0.0,
                    'amount_currency': 360.0,
                    'account_id': self.company_data['default_account_revenue'].id,
                    'currency_id': self.currency_data['currency'].id,
                    'tax_repartition_line_id': tax_rep.id,
                }),
                Command.create({
                    'name': 'tax1',
                    'debit': 1200.0,
                    'credit': 0.0,
                    'amount_currency': 200.0,
                    'account_id': self.company_data['default_account_revenue'].id,
                    'currency_id': self.currency_data['currency'].id,
                    'tax_repartition_line_id': tax_rep.id,
                }),
                # Balance
                Command.create({
                    'name': 'balance',
                    'debit': 0.0,
                    'credit': 14520.0,
                    'account_id': self.company_data['default_account_receivable'].id,
                }),
            ],
        })
        base_lines, tax_lines = self._dispatch_move_lines(move)

        tax_details = self._get_tax_details()
        self.assertTaxDetailsValues(
            tax_details,
            [
                {
                    'base_line_id': base_lines[0].id,
                    'tax_line_id': tax_lines[0].id,
                    'base_amount': 1200.0,
                    'tax_amount': 10.91,
                    'base_amount_currency': 2400.0,
                    'tax_amount_currency': 102.857, # (2400.0 / 8400.0) * (360.0 / 560.0) * 560.0
                },
                {
                    'base_line_id': base_lines[0].id,
                    'tax_line_id': tax_lines[1].id,
                    'base_amount': 1200.0,
                    'tax_amount': 109.09,
                    'base_amount_currency': 2400.0,
                    'tax_amount_currency': 57.143, # (2400.0 / 8400.0) * (200.0 / 560.0) * 560.0
                },
                {
                    'base_line_id': base_lines[1].id,
                    'tax_line_id': tax_lines[0].id,
                    'base_amount': 12000.0,
                    'tax_amount': 109.09,
                    'base_amount_currency': 6000.0,
                    'tax_amount_currency': 257.143, # (6000.0 / 8400.0) * (360.0 / 560.0) * 560.0
                },
                {
                    'base_line_id': base_lines[1].id,
                    'tax_line_id': tax_lines[1].id,
                    'base_amount': 12000.0,
                    'tax_amount': 1090.91,
                    'base_amount_currency': 6000.0,
                    'tax_amount_currency': 142.857, # (6000.0 / 8400.0) * (200.0 / 560.0) * 560.0
                },
            ],
        )
        self.assertTotalAmounts(move, tax_details)

    def test_mixing_tax_inside_and_outside_a_group_of_taxes(self):
        percent_tax = self.env['account.tax'].create({
            'name': "percent_tax",
            'amount_type': 'percent',
            'amount': 10.0,
        })
        tax_group = self.env['account.tax'].create({
            'name': "tax_group",
            'amount_type': 'group',
            'children_tax_ids': [Command.set(percent_tax.ids)],
        })

        invoice = self.env['account.move'].create({
            'move_type': 'out_invoice',
            'partner_id': self.partner_a.id,
            'invoice_date': '2019-01-01',
            'invoice_line_ids': [
                Command.create({
                    'name': 'line1',
                    'account_id': self.company_data['default_account_revenue'].id,
                    'price_unit': 1000.0,
                    'tax_ids': [Command.set(percent_tax.ids)],
                }),
                Command.create({
                    'name': 'line2',
                    'account_id': self.company_data['default_account_revenue'].id,
                    'price_unit': 1000.0,
                    'tax_ids': [Command.set(tax_group.ids)],
                }),
            ]
        })
        base_lines, tax_lines = self._dispatch_move_lines(invoice)

        tax_details = self._get_tax_details()
        self.assertTaxDetailsValues(
            tax_details,
            [
                {
                    'base_line_id': base_lines[0].id,
                    'tax_line_id': tax_lines[0].id,
                    'base_amount': -1000.0,
                    'tax_amount': -100.0,
                },
                {
                    'base_line_id': base_lines[1].id,
                    'tax_line_id': tax_lines[1].id,
                    'base_amount': -1000.0,
                    'tax_amount': -100.0,
                },
            ],
        )
        self.assertTotalAmounts(invoice, tax_details)

    def test_broken_configuration(self):
        percent_tax = self.env['account.tax'].create({
            'name': "percent_tax",
            'amount_type': 'percent',
            'amount': 10.0,
        })

        invoice = self.env['account.move'].create({
            'move_type': 'out_invoice',
            'partner_id': self.partner_a.id,
            'invoice_date': '2019-01-01',
            'invoice_line_ids': [
                Command.create({
                    'name': 'line1',
                    'account_id': self.company_data['default_account_revenue'].id,
                    'price_unit': 1000.0,
                    'tax_ids': [Command.set(percent_tax.ids)],
                }),
            ]
        })
        base_lines, tax_lines = self._dispatch_move_lines(invoice)

        # Break the configuration
        tax_lines.account_id = self.company_data['default_account_assets']

        tax_details = self._get_tax_details(fallback=True)
        self.assertTaxDetailsValues(
            tax_details,
            [
                {
                    'base_line_id': base_lines[0].id,
                    'tax_line_id': tax_lines[0].id,
                    'base_amount': -1000.0,
                    'tax_amount': -100.0,
                },
            ],
        )
        self.assertTotalAmounts(invoice, tax_details)

    def test_tax_on_payment(self):
        percent_tax = self.env['account.tax'].create({
            'name': "percent_tax",
            'amount_type': 'percent',
            'amount': 10.0,
            'tax_exigibility': 'on_payment',
            'cash_basis_transition_account_id': self.company_data['default_account_assets'].id,
        })

        invoice = self.env['account.move'].create({
            'move_type': 'out_invoice',
            'partner_id': self.partner_a.id,
            'invoice_date': '2019-01-01',
            'invoice_line_ids': [
                Command.create({
                    'name': 'line1',
                    'account_id': self.company_data['default_account_revenue'].id,
                    'price_unit': 1000.0,
                    'tax_ids': [Command.set(percent_tax.ids)],
                }),
            ]
        })
        base_lines, tax_lines = self._dispatch_move_lines(invoice)

        tax_details = self._get_tax_details()
        self.assertTaxDetailsValues(
            tax_details,
            [
                {
                    'base_line_id': base_lines[0].id,
                    'tax_line_id': tax_lines[0].id,
                    'base_amount': -1000.0,
                    'tax_amount': -100.0,
                },
            ],
        )
        self.assertTotalAmounts(invoice, tax_details)

    def test_amounts_sign(self):
        for tax_sign in (1, -1):
            tax = self.env['account.tax'].create({
                'name': f"tax {tax_sign}",
                'amount_type': 'percent',
                'amount': tax_sign * 10.0,
            })

            amounts_list = [
                (-1000.0, 7000.0, -2000.0),
                (1000.0, -7000.0, 2000.0),
                (-1000.0, -7000.0, 2000.0),
                (1000.0, 7000.0, -2000.0),
            ]
            for amounts in amounts_list:
                with self.subTest(tax_sign=tax_sign, amounts=amounts):
                    invoice = self.env['account.move'].create({
                        'move_type': 'in_invoice',
                        'partner_id': self.partner_a.id,
                        'invoice_date': '2019-01-01',
                        'invoice_line_ids': [
                            Command.create({
                                'name': 'line2',
                                'account_id': self.company_data['default_account_revenue'].id,
                                'price_unit': amount,
                                'tax_ids': [Command.set(tax.ids)],
                            })
                        for amount in amounts],
                    })
                    _base_lines, tax_lines = self._dispatch_move_lines(invoice)

                    tax_details = self._get_tax_details(extra_domain=[('move_id', '=', invoice.id)])
                    self.assertTaxDetailsValues(
                        tax_details,
                        [
                            {
                                'tax_line_id': tax_lines[0].id,
                                'base_amount': amount,
                                'tax_amount': tax_sign * amount * 0.1,
                            }
                        for amount in amounts],
                    )
                    self.assertTotalAmounts(invoice, tax_details)
