# -*- coding: utf-8 -*-
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
from odoo.tests import tagged
from odoo.tests.common import Form
from odoo import fields


@tagged('post_install', '-at_install')
class TestAccountMoveReconcile(AccountTestInvoicingCommon):
    ''' Tests about the account.partial.reconcile model, not the reconciliation itself but mainly the computation of
    the residual amounts on account.move.line.
    '''

    @classmethod
    def setUpClass(cls, chart_template_ref=None):
        super().setUpClass(chart_template_ref=chart_template_ref)

        cls.extra_receivable_account_1 = cls.copy_account(cls.company_data['default_account_receivable'])
        cls.extra_receivable_account_2 = cls.copy_account(cls.company_data['default_account_receivable'])
        cls.extra_payable_account_1 = cls.copy_account(cls.company_data['default_account_payable'])
        cls.extra_payable_account_2 = cls.copy_account(cls.company_data['default_account_payable'])

        # ==== Multi-currency setup ====

        cls.currency_data_2 = cls.setup_multi_currency_data(default_values={
            'name': 'Diamond',
            'symbol': '💎',
            'currency_unit_label': 'Diamond',
            'currency_subunit_label': 'Carbon',
        }, rate2016=6.0, rate2017=4.0)

        # ==== Cash Basis Taxes setup ====

        cls.cash_basis_base_account = cls.env['account.account'].create({
            'code': 'cash_basis_base_account',
            'name': 'cash_basis_base_account',
            'user_type_id': cls.env.ref('account.data_account_type_revenue').id,
            'company_id': cls.company_data['company'].id,
        })
        cls.company_data['company'].account_cash_basis_base_account_id = cls.cash_basis_base_account

        cls.cash_basis_transfer_account = cls.env['account.account'].create({
            'code': 'cash_basis_transfer_account',
            'name': 'cash_basis_transfer_account',
            'user_type_id': cls.env.ref('account.data_account_type_revenue').id,
            'company_id': cls.company_data['company'].id,
        })

        cls.tax_account_1 = cls.env['account.account'].create({
            'code': 'tax_account_1',
            'name': 'tax_account_1',
            'user_type_id': cls.env.ref('account.data_account_type_revenue').id,
            'company_id': cls.company_data['company'].id,
        })

        cls.tax_account_2 = cls.env['account.account'].create({
            'code': 'tax_account_2',
            'name': 'tax_account_2',
            'user_type_id': cls.env.ref('account.data_account_type_revenue').id,
            'company_id': cls.company_data['company'].id,
        })

        cls.fake_country = cls.env['res.country'].create({
            'name': "The Island of the Fly",
            'code': 'YY',
        })

        cls.tax_tags = cls.env['account.account.tag'].create({
            'name': 'tax_tag_%s' % str(i),
            'applicability': 'taxes',
            'country_id': cls.fake_country.id,
        } for i in range(8))

        cls.cash_basis_tax_a_third_amount = cls.env['account.tax'].create({
            'name': 'tax_1',
            'amount': 33.3333,
            'company_id': cls.company_data['company'].id,
            'cash_basis_transition_account_id': cls.cash_basis_transfer_account.id,
            'tax_exigibility': 'on_payment',
            'invoice_repartition_line_ids': [
                (0, 0, {
                    'factor_percent': 100,
                    'repartition_type': 'base',
                    'tag_ids': [(6, 0, cls.tax_tags[0].ids)],
                }),

                (0, 0, {
                    'factor_percent': 100,
                    'repartition_type': 'tax',
                    'account_id': cls.tax_account_1.id,
                    'tag_ids': [(6, 0, cls.tax_tags[1].ids)],
                }),
            ],
            'refund_repartition_line_ids': [
                (0, 0, {
                    'factor_percent': 100,
                    'repartition_type': 'base',
                    'tag_ids': [(6, 0, cls.tax_tags[2].ids)],
                }),

                (0, 0, {
                    'factor_percent': 100,
                    'repartition_type': 'tax',
                    'account_id': cls.tax_account_1.id,
                    'tag_ids': [(6, 0, cls.tax_tags[3].ids)],
                }),
            ],
        })

        cls.cash_basis_tax_tiny_amount = cls.env['account.tax'].create({
            'name': 'cash_basis_tax_tiny_amount',
            'amount': 0.0001,
            'company_id': cls.company_data['company'].id,
            'cash_basis_transition_account_id': cls.cash_basis_transfer_account.id,
            'tax_exigibility': 'on_payment',
            'invoice_repartition_line_ids': [
                (0, 0, {
                    'factor_percent': 100,
                    'repartition_type': 'base',
                    'tag_ids': [(6, 0, cls.tax_tags[4].ids)],
                }),

                (0, 0, {
                    'factor_percent': 100,
                    'repartition_type': 'tax',
                    'account_id': cls.tax_account_2.id,
                    'tag_ids': [(6, 0, cls.tax_tags[5].ids)],
                }),
            ],
            'refund_repartition_line_ids': [
                (0, 0, {
                    'factor_percent': 100,
                    'repartition_type': 'base',
                    'tag_ids': [(6, 0, cls.tax_tags[6].ids)],
                }),

                (0, 0, {
                    'factor_percent': 100,
                    'repartition_type': 'tax',
                    'account_id': cls.tax_account_2.id,
                    'tag_ids': [(6, 0, cls.tax_tags[7].ids)],
                }),
            ],
        })

    # -------------------------------------------------------------------------
    # HELPERS
    # -------------------------------------------------------------------------

    def assertFullReconcile(self, full_reconcile, lines):
        exchange_difference_move = full_reconcile.exchange_move_id
        partials = lines.mapped('matched_debit_ids') + lines.mapped('matched_credit_ids')

        if full_reconcile.exchange_move_id:
            lines += exchange_difference_move.line_ids.filtered(lambda line: line.account_id == lines[0].account_id)

        # Use sets to not depend of the order.
        self.assertEqual(set(full_reconcile.partial_reconcile_ids), set(partials))
        self.assertEqual(set(full_reconcile.reconciled_line_ids), set(lines))

        # Ensure there is no residual amount left.
        self.assertRecordValues(lines, [{
            'amount_residual': 0.0,
            'amount_residual_currency': 0.0,
            'reconciled': bool(line.account_id.reconcile),
        } for line in lines])

    def assertPartialReconcile(self, partials, expected_vals_list):
        partials = partials.sorted(lambda part: (
            part.amount,
            part.debit_amount_currency,
            part.credit_amount_currency,
        ))
        self.assertRecordValues(partials, expected_vals_list)

    def assertAmountsGroupByAccount(self, amount_per_account):
        expected_values = {account.id: (account, balance, amount_currency) for account, balance, amount_currency in amount_per_account}

        if not expected_values:
            return

        self.cr.execute('''
            SELECT
                line.account_id,
                COALESCE(SUM(line.balance), 0.0)            AS total_balance,
                COALESCE(SUM(line.amount_currency), 0.0)    AS total_amount_currency
            FROM account_move_line line
            WHERE line.account_id IN %s
            GROUP BY line.account_id
        ''', [tuple(expected_values.keys())])
        for account_id, total_balance, total_amount_currency in self.cr.fetchall():
            account, expected_balance, expected_amount_currency = expected_values[account_id]
            self.assertEqual(
                total_balance,
                expected_balance,
                "Balance of %s is incorrect" % account.name,
            )
            self.assertEqual(
                total_amount_currency,
                expected_amount_currency,
                "Amount currency of %s is incorrect" % account.name,
            )

    def assertTaxGridAmounts(self, amount_per_tag):
        expected_values = {tag.id: (tag, balance) for tag, balance in amount_per_tag}

        if not expected_values:
            return

        self.cr.execute('''
            SELECT
                rel.account_account_tag_id,
                SUM(line.balance)
            FROM account_account_tag_account_move_line_rel rel
            JOIN account_move_line line ON line.id = rel.account_move_line_id
            WHERE line.tax_exigible IS TRUE
            AND line.company_id IN %(company_ids)s
            GROUP BY rel.account_account_tag_id
        ''', {
            'company_ids': tuple(self.env.companies.ids),
        })

        for tag_id, total_balance in self.cr.fetchall():
            tag, expected_balance = expected_values[tag_id]
            self.assertEqual(
                total_balance,
                expected_balance,
                "Balance of %s is incorrect" % tag.name,
            )

    # -------------------------------------------------------------------------
    # Test creation of account.partial.reconcile/account.full.reconcile
    # during the reconciliation.
    # -------------------------------------------------------------------------

    def test_reconcile_single_currency(self):
        account_id = self.company_data['default_account_receivable'].id

        move = self.env['account.move'].create({
            'move_type': 'entry',
            'date': '2016-01-01',
            'line_ids': [
                (0, 0, {'debit': 1000.0,    'credit': 0.0,      'account_id': account_id}),
                (0, 0, {'debit': 200.0,     'credit': 0.0,      'account_id': account_id}),
                (0, 0, {'debit': 0.0,       'credit': 300.0,    'account_id': account_id}),
                (0, 0, {'debit': 0.0,       'credit': 400.0,    'account_id': account_id}),
                (0, 0, {'debit': 0.0,       'credit': 500.0,    'account_id': account_id}),
            ]
        })
        move.action_post()

        line_1 = move.line_ids.filtered(lambda line: line.debit == 1000.0)
        line_2 = move.line_ids.filtered(lambda line: line.debit == 200.0)
        line_3 = move.line_ids.filtered(lambda line: line.credit == 300.0)
        line_4 = move.line_ids.filtered(lambda line: line.credit == 400.0)
        line_5 = move.line_ids.filtered(lambda line: line.credit == 500.0)

        self.assertRecordValues(line_1 + line_2 + line_3 + line_4 + line_5, [
            {'amount_residual': 1000.0,     'amount_residual_currency': 1000.0, 'reconciled': False},
            {'amount_residual': 200.0,      'amount_residual_currency': 200.0,  'reconciled': False},
            {'amount_residual': -300.0,     'amount_residual_currency': -300.0, 'reconciled': False},
            {'amount_residual': -400.0,     'amount_residual_currency': -400.0, 'reconciled': False},
            {'amount_residual': -500.0,     'amount_residual_currency': -500.0, 'reconciled': False},
        ])

        res = (line_1 + line_3).reconcile()

        self.assertPartialReconcile(res['partials'], [{
            'amount': 300.0,
            'debit_amount_currency': 300.0,
            'credit_amount_currency': 300.0,
            'debit_move_id': line_1.id,
            'credit_move_id': line_3.id,
        }])

        self.assertRecordValues(line_1 + line_3, [
            {'amount_residual': 700.0,      'amount_residual_currency': 700.0,  'reconciled': False},
            {'amount_residual': 0.0,        'amount_residual_currency': 0.0,    'reconciled': True},
        ])

        res = (line_1 + line_4).reconcile()

        self.assertPartialReconcile(res['partials'], [{
            'amount': 400.0,
            'debit_amount_currency': 400.0,
            'credit_amount_currency': 400.0,
            'debit_move_id': line_1.id,
            'credit_move_id': line_4.id,
        }])

        self.assertRecordValues(line_1 + line_4, [
            {'amount_residual': 300.0,      'amount_residual_currency': 300.0,  'reconciled': False},
            {'amount_residual': 0.0,        'amount_residual_currency': 0.0,    'reconciled': True},
        ])

        res = (line_1 + line_5).reconcile()

        self.assertPartialReconcile(res['partials'], [{
            'amount': 300.0,
            'debit_amount_currency': 300.0,
            'credit_amount_currency': 300.0,
            'debit_move_id': line_1.id,
            'credit_move_id': line_5.id,
        }])

        self.assertRecordValues(line_1 + line_5, [
            {'amount_residual': 0.0,        'amount_residual_currency': 0.0,    'reconciled': True},
            {'amount_residual': -200.0,     'amount_residual_currency': -200.0, 'reconciled': False},
        ])

        res = (line_2 + line_5).reconcile()

        self.assertPartialReconcile(res['partials'], [{
            'amount': 200.0,
            'debit_amount_currency': 200.0,
            'credit_amount_currency': 200.0,
            'debit_move_id': line_2.id,
            'credit_move_id': line_5.id,
        }])

        self.assertRecordValues(res['full_reconcile'], [{'exchange_move_id': False}])
        self.assertFullReconcile(res['full_reconcile'], line_1 + line_2 + line_3 + line_4 + line_5)

    def test_reconcile_same_foreign_currency(self):
        account_id = self.company_data['default_account_receivable'].id

        # Rate is 3.0 in 2016, 2.0 in 2017.
        currency_id = self.currency_data['currency'].id

        moves = self.env['account.move'].create([
            {
                'move_type': 'entry',
                'date': '2016-01-01',
                'line_ids': [
                    (0, 0, {'debit': 1200.0,    'credit': 0.0,      'amount_currency': 3600.0,  'account_id': account_id,   'currency_id': currency_id}),
                    (0, 0, {'debit': 120.0,     'credit': 0.0,      'amount_currency': 360.0,   'account_id': account_id,   'currency_id': currency_id}),

                    (0, 0, {'debit': 0.0,       'credit': 1320.0,                               'account_id': account_id}),
                ]
            },
            {
                'move_type': 'entry',
                'date': '2017-01-01',
                'line_ids': [
                    (0, 0, {'debit': 0.0,       'credit': 240.0,    'amount_currency': -480.0,  'account_id': account_id,   'currency_id': currency_id}),
                    (0, 0, {'debit': 0.0,       'credit': 720.0,    'amount_currency': -1440.0, 'account_id': account_id,   'currency_id': currency_id}),
                    (0, 0, {'debit': 0.0,       'credit': 1020.0,   'amount_currency': -2040.0, 'account_id': account_id,   'currency_id': currency_id}),

                    (0, 0, {'debit': 1980.0,    'credit': 0.0,                                  'account_id': account_id}),
                ]
            }
        ])

        moves.action_post()

        line_1 = moves.line_ids.filtered(lambda line: line.debit == 1200.0)
        line_2 = moves.line_ids.filtered(lambda line: line.debit == 120.0)
        line_3 = moves.line_ids.filtered(lambda line: line.credit == 240.0)
        line_4 = moves.line_ids.filtered(lambda line: line.credit == 720.0)
        line_5 = moves.line_ids.filtered(lambda line: line.credit == 1020.0)

        self.assertRecordValues(line_1 + line_2 + line_3 + line_4 + line_5, [
            {'amount_residual': 1200.0,     'amount_residual_currency': 3600.0,     'reconciled': False},
            {'amount_residual': 120.0,      'amount_residual_currency': 360.0,      'reconciled': False},
            {'amount_residual': -240.0,     'amount_residual_currency': -480.0,     'reconciled': False},
            {'amount_residual': -720.0,     'amount_residual_currency': -1440.0,    'reconciled': False},
            {'amount_residual': -1020.0,    'amount_residual_currency': -2040.0,    'reconciled': False},
        ])

        res = (line_1 + line_3 + line_4).reconcile()

        self.assertPartialReconcile(res['partials'], [
            # Partial generated when reconciling line_1 & line_3:
            {
                'amount': 240.0,
                'debit_amount_currency': 480.0,
                'credit_amount_currency': 480.0,
                'debit_move_id': line_1.id,
                'credit_move_id': line_3.id,
            },
            # Partial generated when reconciling line_1 & line_4:
            {
                'amount': 720.0,
                'debit_amount_currency': 1440.0,
                'credit_amount_currency': 1440.0,
                'debit_move_id': line_1.id,
                'credit_move_id': line_4.id,
            },
        ])

        self.assertRecordValues(line_1 + line_3 + line_4, [
            {'amount_residual': 240.0,      'amount_residual_currency': 1680.0,     'reconciled': False},
            {'amount_residual': 0.0,        'amount_residual_currency': 0.0,        'reconciled': True},
            {'amount_residual': 0.0,        'amount_residual_currency': 0.0,        'reconciled': True},
        ])

        res = (line_1 + line_5).reconcile()

        self.assertPartialReconcile(res['partials'], [{
            'amount': 240.0,
            'debit_amount_currency': 1680.0,
            'credit_amount_currency': 1680.0,
            'debit_move_id': line_1.id,
            'credit_move_id': line_5.id,
        }])

        self.assertRecordValues(line_1 + line_5, [
            {'amount_residual': 0.0,        'amount_residual_currency': 0.0,        'reconciled': True},
            {'amount_residual': -780.0,     'amount_residual_currency': -360.0,     'reconciled': False},
        ])

        res = (line_2 + line_5).reconcile()

        exchange_diff = res['full_reconcile'].exchange_move_id
        exchange_diff_lines = exchange_diff.line_ids.sorted(lambda line: (line.currency_id, abs(line.amount_currency), -line.amount_currency))

        self.assertRecordValues(exchange_diff_lines, [
            # Fix line_2:
            {
                'debit': 660.0,
                'credit': 0.0,
                'amount_currency': 0.0,
                'currency_id': currency_id,
                'account_id': account_id,
            },
            {
                'debit': 0.0,
                'credit': 660.0,
                'amount_currency': 0.0,
                'currency_id': currency_id,
                'account_id': exchange_diff.journal_id.company_id.income_currency_exchange_account_id.id,
            },
        ])

        self.assertPartialReconcile(res['partials'], [
            # Partial generated when reconciling line_2 & line_5:
            {
                'amount': 120.0,
                'debit_amount_currency': 360.0,
                'credit_amount_currency': 360.0,
                'debit_move_id': line_2.id,
                'credit_move_id': line_5.id,
            },
            # Partial fixing line_4 (exchange difference):
            {
                'amount': 660.0,
                'debit_amount_currency': 0.0,
                'credit_amount_currency': 0.0,
                'debit_move_id': exchange_diff_lines[0].id,
                'credit_move_id': line_5.id,
            },
        ])

        self.assertFullReconcile(res['full_reconcile'], line_1 + line_2 + line_3 + line_4 + line_5)

    def test_reconcile_multiple_currencies(self):
        account_id = self.company_data['default_account_receivable'].id

        # Rate is 3.0 in 2016, 2.0 in 2017.
        currency1_id = self.currency_data['currency'].id
        # Rate is 6.0 in 2016, 4.0 in 2017.
        currency2_id = self.currency_data_2['currency'].id

        moves = self.env['account.move'].create([
            {
                'move_type': 'entry',
                'date': '2016-01-01',
                'line_ids': [
                    (0, 0, {'debit': 1200.0,    'credit': 0.0,      'amount_currency': 3600.0,  'account_id': account_id,   'currency_id': currency1_id}),
                    (0, 0, {'debit': 780.0,     'credit': 0.0,      'amount_currency': 2340.0,  'account_id': account_id,   'currency_id': currency1_id}),

                    (0, 0, {'debit': 0.0,       'credit': 1980.0,                               'account_id': account_id}),
                ]
            },
            {
                'move_type': 'entry',
                'date': '2017-01-01',
                'line_ids': [
                    (0, 0, {'debit': 0.0,       'credit': 240.0,    'amount_currency': -960.0,  'account_id': account_id,   'currency_id': currency2_id}),
                    (0, 0, {'debit': 0.0,       'credit': 720.0,    'amount_currency': -2880.0, 'account_id': account_id,   'currency_id': currency2_id}),
                    (0, 0, {'debit': 0.0,       'credit': 1020.0,   'amount_currency': -4080.0, 'account_id': account_id,   'currency_id': currency2_id}),

                    (0, 0, {'debit': 1980.0,    'credit': 0.0,                                  'account_id': account_id}),
                ]
            }
        ])

        moves.action_post()

        line_1 = moves.line_ids.filtered(lambda line: line.debit == 1200.0)
        line_2 = moves.line_ids.filtered(lambda line: line.debit == 780.0)
        line_3 = moves.line_ids.filtered(lambda line: line.credit == 240.0)
        line_4 = moves.line_ids.filtered(lambda line: line.credit == 720.0)
        line_5 = moves.line_ids.filtered(lambda line: line.credit == 1020.0)

        self.assertRecordValues(line_1 + line_2 + line_3 + line_4 + line_5, [
            {'amount_residual': 1200.0,     'amount_residual_currency': 3600.0,     'reconciled': False},
            {'amount_residual': 780.0,      'amount_residual_currency': 2340.0,     'reconciled': False},
            {'amount_residual': -240.0,     'amount_residual_currency': -960.0,     'reconciled': False},
            {'amount_residual': -720.0,     'amount_residual_currency': -2880.0,    'reconciled': False},
            {'amount_residual': -1020.0,    'amount_residual_currency': -4080.0,    'reconciled': False},
        ])

        res = (line_1 + line_3 + line_4).reconcile()

        self.assertPartialReconcile(res['partials'], [
            # Partial generated when reconciling line_1 & line_3:
            {
                'amount': 240.0,
                'debit_amount_currency': 480.0,
                'credit_amount_currency': 1440.0,
                'debit_move_id': line_1.id,
                'credit_move_id': line_3.id,
            },
            # Partial generated when reconciling line_1 & line_4:
            {
                'amount': 720.0,
                'debit_amount_currency': 1440.0,
                'credit_amount_currency': 4320.0,
                'debit_move_id': line_1.id,
                'credit_move_id': line_4.id,
            },
        ])

        self.assertRecordValues(line_1 + line_3 + line_4, [
            {'amount_residual': 240.0,      'amount_residual_currency': 1680.0,     'reconciled': False},
            {'amount_residual': 0.0,        'amount_residual_currency': 480.0,      'reconciled': False},
            {'amount_residual': 0.0,        'amount_residual_currency': 1440.0,     'reconciled': False},
        ])

        res = (line_1 + line_5).reconcile()

        self.assertPartialReconcile(res['partials'], [{
            'amount': 240.0,
            'debit_amount_currency': 480.0,
            'credit_amount_currency': 1440.0,
            'debit_move_id': line_1.id,
            'credit_move_id': line_5.id,
        }])

        self.assertRecordValues(line_1 + line_5, [
            {'amount_residual': 0.0,        'amount_residual_currency': 1200.0,     'reconciled': False},
            {'amount_residual': -780.0,     'amount_residual_currency': -2640.0,    'reconciled': False},
        ])

        res = (line_2 + line_5).reconcile()

        exchange_diff = res['full_reconcile'].exchange_move_id
        exchange_diff_lines = exchange_diff.line_ids.sorted(lambda line: (line.currency_id, abs(line.amount_currency), -line.amount_currency))

        self.assertRecordValues(exchange_diff_lines, [
            # Fix line_2:
            {
                'debit': 0.0,
                'credit': 0.0,
                'amount_currency': 780.0,
                'currency_id': currency1_id,
                'account_id': exchange_diff.journal_id.company_id.expense_currency_exchange_account_id.id,
            },
            {
                'debit': 0.0,
                'credit': 0.0,
                'amount_currency': -780.0,
                'currency_id': currency1_id,
                'account_id': account_id,
            },
            # Fix line_3:
            {
                'debit': 0.0,
                'credit': 0.0,
                'amount_currency': 480.0,
                'currency_id': currency2_id,
                'account_id': exchange_diff.journal_id.company_id.expense_currency_exchange_account_id.id,
            },
            {
                'debit': 0.0,
                'credit': 0.0,
                'amount_currency': -480.0,
                'currency_id': currency2_id,
                'account_id': account_id,
            },
            # Fix line_4:
            {
                'debit': 0.0,
                'credit': 0.0,
                'amount_currency': 1440.0,
                'currency_id': currency2_id,
                'account_id': exchange_diff.journal_id.company_id.expense_currency_exchange_account_id.id,
            },
            {
                'debit': 0.0,
                'credit': 0.0,
                'amount_currency': -1440.0,
                'currency_id': currency2_id,
                'account_id': account_id,
            },
            # Fix line_5:
            {
                'debit': 0.0,
                'credit': 0.0,
                'amount_currency': 2040.0,
                'currency_id': currency2_id,
                'account_id': exchange_diff.journal_id.company_id.expense_currency_exchange_account_id.id,
            },
            {
                'debit': 0.0,
                'credit': 0.0,
                'amount_currency': -2040.0,
                'currency_id': currency2_id,
                'account_id': account_id,
            },
            # Fix line_1:
            {
                'debit': 0.0,
                'credit': 0.0,
                'amount_currency': 1200.0,
                'currency_id': currency1_id,
                'account_id': exchange_diff.journal_id.company_id.expense_currency_exchange_account_id.id,
            },
            {
                'debit': 0.0,
                'credit': 0.0,
                'amount_currency': -1200.0,
                'currency_id': currency1_id,
                'account_id': account_id,
            },
        ])

        self.assertPartialReconcile(res['partials'], [
            # Partial fixing line_3 (exchange difference):
            {
                'amount': 0.0,
                'debit_amount_currency': 480.0,
                'credit_amount_currency': 480.0,
                'debit_move_id': line_3.id,
                'credit_move_id': exchange_diff_lines[3].id,
            },
            # Partial fixing line_2 (exchange difference):
            {
                'amount': 0.0,
                'debit_amount_currency': 780.0,
                'credit_amount_currency': 780.0,
                'debit_move_id': line_2.id,
                'credit_move_id': exchange_diff_lines[1].id,
            },
            # Partial fixing line_1 (exchange difference):
            {
                'amount': 0.0,
                'debit_amount_currency': 1200.0,
                'credit_amount_currency': 1200.0,
                'debit_move_id': line_1.id,
                'credit_move_id': exchange_diff_lines[9].id,
            },
            # Partial fixing line_4 (exchange difference):
            {
                'amount': 0.0,
                'debit_amount_currency': 1440.0,
                'credit_amount_currency': 1440.0,
                'debit_move_id': line_4.id,
                'credit_move_id': exchange_diff_lines[5].id,
            },
            # Partial fixing line_5 (exchange difference):
            {
                'amount': 0.0,
                'debit_amount_currency': 2040.0,
                'credit_amount_currency': 2040.0,
                'debit_move_id': line_5.id,
                'credit_move_id': exchange_diff_lines[7].id,
            },
            # Partial generated when reconciling line_2 & line_5:
            {
                'amount': 780.0,
                'debit_amount_currency': 1560.0,
                'credit_amount_currency': 4680.0,
                'debit_move_id': line_2.id,
                'credit_move_id': line_5.id,
            },
        ])

        self.assertFullReconcile(res['full_reconcile'], line_1 + line_2 + line_3 + line_4 + line_5)

    def test_reconcile_asymetric_rate_change(self):
        account_id = self.company_data['default_account_receivable'].id

        # Rate is 3.0 in 2016, 2.0 in 2017.
        currency1_id = self.currency_data['currency'].id
        # Rate is 6.0 in 2016, 4.0 in 2017.
        currency2_id = self.currency_data_2['currency'].id

        # Create rate changes for 2018: currency1 rate increases while currency2 rate decreases.
        self.env['res.currency.rate'].create({
            'name': '2018-01-01',
            'rate': 8.0,
            'currency_id': currency1_id,
            'company_id': self.company_data['company'].id,
        })

        self.env['res.currency.rate'].create({
            'name': '2018-01-01',
            'rate': 2.0,
            'currency_id': currency2_id,
            'company_id': self.company_data['company'].id,
        })

        moves = self.env['account.move'].create([
            {
                'move_type': 'entry',
                'date': '2018-01-01',
                'line_ids': [
                    (0, 0, {
                        'debit': 1200.0,
                        'credit': 0.0,
                        'amount_currency': 9600.0,
                        'account_id': account_id,
                        'currency_id': currency1_id,
                    }),
                    (0, 0, {
                        'debit': 960.0,
                        'credit': 0.0,
                        'amount_currency': 1920.0,
                        'account_id': account_id,
                        'currency_id': currency2_id,
                    }),
                    (0, 0, {
                        'debit': 0.0,
                        'credit': 2160.0,
                        'account_id': account_id,
                    }),
                ]
            },
            {
                'move_type': 'entry',
                'date': '2017-01-01',
                'line_ids': [
                    (0, 0, {
                        'debit': 0.0,
                        'credit': 1200.0,
                        'amount_currency': -4800.0,
                        'account_id': account_id,
                        'currency_id': currency2_id,
                    }),
                    (0, 0, {
                        'debit': 0.0,
                        'credit': 960.0,
                        'amount_currency': -1920.0,
                        'account_id': account_id,
                        'currency_id': currency1_id,
                    }),
                    (0, 0, {
                        'debit': 2160.0,
                        'credit': 0.0,
                        'account_id': account_id,
                    }),
                ]
            }
        ])

        moves.action_post()

        line_1 = moves.line_ids.filtered(lambda line: line.debit == 1200.0)
        line_2 = moves.line_ids.filtered(lambda line: line.debit == 960.0)
        line_3 = moves.line_ids.filtered(lambda line: line.credit == 1200.0)
        line_4 = moves.line_ids.filtered(lambda line: line.credit == 960.0)

        self.assertRecordValues(line_1 + line_2 + line_3 + line_4, [
            {'amount_residual': 1200.0,     'amount_residual_currency': 9600.0,     'reconciled': False},
            {'amount_residual': 960.0,      'amount_residual_currency': 1920.0,     'reconciled': False},
            {'amount_residual': -1200.0,    'amount_residual_currency': -4800.0,    'reconciled': False},
            {'amount_residual': -960.0,     'amount_residual_currency': -1920.0,    'reconciled': False},
        ])

        # Reconcile with debit_line currency rate increased and credit_line currency rate decreased between
        # credit_line.date and debit_line.date.

        res = (line_1 + line_3).reconcile()

        exchange_diff = res['full_reconcile'].exchange_move_id
        exchange_diff_lines = exchange_diff.line_ids.sorted(lambda line: (line.currency_id, abs(line.amount_currency), -line.amount_currency))

        self.assertRecordValues(exchange_diff_lines, [
            {
                'debit': 0.0,
                'credit': 0.0,
                'amount_currency': 2400.0,
                'currency_id': currency2_id,
                'account_id': account_id,
            },
            {
                'debit': 0.0,
                'credit': 0.0,
                'amount_currency': -2400.0,
                'currency_id': currency2_id,
                'account_id': exchange_diff.journal_id.company_id.income_currency_exchange_account_id.id,
            },
            {
                'debit': 0.0,
                'credit': 0.0,
                'amount_currency': 7200.0,
                'currency_id': currency1_id,
                'account_id': exchange_diff.journal_id.company_id.expense_currency_exchange_account_id.id,
            },
            {
                'debit': 0.0,
                'credit': 0.0,
                'amount_currency': -7200.0,
                'currency_id': currency1_id,
                'account_id': account_id,
            }
        ])

        self.assertPartialReconcile(res['partials'], [
            {
                'amount': 0.0,
                'debit_amount_currency': 2400.0,
                'credit_amount_currency': 2400.0,
                'debit_move_id': exchange_diff_lines[0].id,
                'credit_move_id': line_3.id,
            },
            {
                'amount': 0.0,
                'debit_amount_currency': 7200.0,
                'credit_amount_currency': 7200.0,
                'debit_move_id': line_1.id,
                'credit_move_id': exchange_diff_lines[3].id,
            },
            {
                'amount': 1200.0,
                'debit_amount_currency': 2400.0,
                'credit_amount_currency': 2400.0,
                'debit_move_id': line_1.id,
                'credit_move_id': line_3.id,
            },
        ])

        self.assertRecordValues(line_1 + line_3, [
            {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True},
            {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True},
        ])

        # Reconcile with debit_line currency rate decreased and credit_line currency rate increased between
        # credit_line.date and debit_line.date.

        res = (line_2 + line_4).reconcile()

        exchange_diff = res['full_reconcile'].exchange_move_id
        exchange_diff_lines = exchange_diff.line_ids.sorted(lambda line: (line.currency_id, abs(line.amount_currency), -line.amount_currency))

        self.assertRecordValues(exchange_diff_lines, [
            {
                'debit': 0.0,
                'credit': 0.0,
                'amount_currency': 5760.0,
                'currency_id': currency1_id,
                'account_id': exchange_diff.journal_id.company_id.expense_currency_exchange_account_id.id,
            },
            {
                'debit': 0.0,
                'credit': 0.0,
                'amount_currency': -5760.0,
                'currency_id': currency1_id,
                'account_id': account_id,
            },
            {
                'debit': 0.0,
                'credit': 0.0,
                'amount_currency': 1920.0,
                'currency_id': currency2_id,
                'account_id': account_id,
            },
            {
                'debit': 0.0,
                'credit': 0.0,
                'amount_currency': -1920.0,
                'currency_id': currency2_id,
                'account_id': exchange_diff.journal_id.company_id.income_currency_exchange_account_id.id,
            }
        ])

        self.assertPartialReconcile(res['partials'], [
            {
                'amount': 0.0,
                'debit_amount_currency': 1920.0,
                'credit_amount_currency': 1920.0,
                'debit_move_id': exchange_diff_lines[2].id,
                'credit_move_id': line_2.id,
            },
            {
                'amount': 0.0,
                'debit_amount_currency': 5760.0,
                'credit_amount_currency': 5760.0,
                'debit_move_id': line_4.id,
                'credit_move_id': exchange_diff_lines[1].id,
            },
            {
                'amount': 960.0,
                'debit_amount_currency': 3840.0,
                'credit_amount_currency': 7680.0,
                'debit_move_id': line_2.id,
                'credit_move_id': line_4.id,
            },
        ])

        self.assertRecordValues(line_2 + line_4, [
            {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True},
            {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True},
        ])

    def test_reverse_exchange_difference_same_foreign_currency(self):
        move_2016 = self.env['account.move'].create({
            'move_type': 'entry',
            'date': '2016-01-01',
            'line_ids': [
                (0, 0, {
                    'debit': 1200.0,
                    'credit': 0.0,
                    'amount_currency': 3600.0,
                    'account_id': self.company_data['default_account_receivable'].id,
                    'currency_id': self.currency_data['currency'].id,
                }),
                (0, 0, {
                    'debit': 0.0,
                    'credit': 1200.0,
                    'account_id': self.company_data['default_account_revenue'].id,
                }),
            ],
        })
        move_2017 = self.env['account.move'].create({
            'move_type': 'entry',
            'date': '2017-01-01',
            'line_ids': [
                (0, 0, {
                    'debit': 0.0,
                    'credit': 1800.0,
                    'amount_currency': -3600.0,
                    'account_id': self.company_data['default_account_receivable'].id,
                    'currency_id': self.currency_data['currency'].id,
                }),
                (0, 0, {
                    'debit': 1800.0,
                    'credit': 0.0,
                    'account_id': self.company_data['default_account_revenue'].id,
                }),
            ],
        })
        (move_2016 + move_2017).action_post()

        rec_line_2016 = move_2016.line_ids.filtered(lambda line: line.account_id.internal_type == 'receivable')
        rec_line_2017 = move_2017.line_ids.filtered(lambda line: line.account_id.internal_type == 'receivable')

        self.assertRecordValues(rec_line_2016 + rec_line_2017, [
            {'amount_residual': 1200.0,     'amount_residual_currency': 3600.0,     'reconciled': False},
            {'amount_residual': -1800.0,    'amount_residual_currency': -3600.0,    'reconciled': False},
        ])

        # Reconcile.

        res = (rec_line_2016 + rec_line_2017).reconcile()

        self.assertRecordValues(rec_line_2016 + rec_line_2017, [
            {'amount_residual': 0.0,        'amount_residual_currency': 0.0,        'reconciled': True},
            {'amount_residual': 0.0,        'amount_residual_currency': 0.0,        'reconciled': True},
        ])

        exchange_diff = res['full_reconcile'].exchange_move_id
        exchange_diff_lines = exchange_diff.line_ids.sorted('balance')

        self.assertRecordValues(exchange_diff_lines, [
            {
                'debit': 0.0,
                'credit': 600.0,
                'amount_currency': 0.0,
                'currency_id': self.currency_data['currency'].id,
                'account_id': exchange_diff.journal_id.company_id.income_currency_exchange_account_id.id,
            },
            {
                'debit': 600.0,
                'credit': 0.0,
                'amount_currency': 0.0,
                'currency_id': self.currency_data['currency'].id,
                'account_id': self.company_data['default_account_receivable'].id,
            },
        ])

        self.assertRecordValues(exchange_diff_lines, [
            {'amount_residual': 0.0,        'amount_residual_currency': 0.0,        'reconciled': False},
            {'amount_residual': 0.0,        'amount_residual_currency': 0.0,        'reconciled': True},
        ])

        # Unreconcile.
        # A reversal is created to cancel the exchange difference journal entry.

        (rec_line_2016 + rec_line_2017).remove_move_reconcile()

        reverse_exchange_diff = exchange_diff_lines[1].matched_credit_ids.credit_move_id.move_id
        reverse_exchange_diff_lines = reverse_exchange_diff.line_ids.sorted('balance')

        self.assertRecordValues(reverse_exchange_diff_lines, [
            {
                'debit': 0.0,
                'credit': 600.0,
                'amount_currency': 0.0,
                'currency_id': self.currency_data['currency'].id,
                'account_id': self.company_data['default_account_receivable'].id,
            },
            {
                'debit': 600.0,
                'credit': 0.0,
                'amount_currency': 0.0,
                'currency_id': self.currency_data['currency'].id,
                'account_id': exchange_diff.journal_id.company_id.income_currency_exchange_account_id.id,
            },
        ])

        self.assertRecordValues(exchange_diff_lines + reverse_exchange_diff_lines, [
            {'amount_residual': 0.0,        'amount_residual_currency': 0.0,        'reconciled': False},
            {'amount_residual': 0.0,        'amount_residual_currency': 0.0,        'reconciled': True},
            {'amount_residual': 0.0,        'amount_residual_currency': 0.0,        'reconciled': True},
            {'amount_residual': 0.0,        'amount_residual_currency': 0.0,        'reconciled': False},
        ])

        partials = reverse_exchange_diff_lines.matched_debit_ids + reverse_exchange_diff_lines.matched_credit_ids
        self.assertPartialReconcile(partials, [{
            'amount': 600.0,
            'debit_amount_currency': 0.0,
            'credit_amount_currency': 0.0,
            'debit_move_id': exchange_diff_lines[1].id,
            'credit_move_id': reverse_exchange_diff_lines[0].id,
        }])

    def test_reverse_exchange_multiple_foreign_currencies(self):
        move_2016 = self.env['account.move'].create({
            'move_type': 'entry',
            'date': '2016-01-01',
            'line_ids': [
                (0, 0, {
                    'debit': 1200.0,
                    'credit': 0.0,
                    'amount_currency': 7200.0,
                    'account_id': self.company_data['default_account_receivable'].id,
                    'currency_id': self.currency_data_2['currency'].id,
                }),
                (0, 0, {
                    'debit': 0.0,
                    'credit': 1200.0,
                    'account_id': self.company_data['default_account_revenue'].id,
                }),
            ],
        })
        move_2017 = self.env['account.move'].create({
            'move_type': 'entry',
            'date': '2017-01-01',
            'line_ids': [
                (0, 0, {
                    'debit': 0.0,
                    'credit': 1200.0,
                    'amount_currency': -2400.0,
                    'account_id': self.company_data['default_account_receivable'].id,
                    'currency_id': self.currency_data['currency'].id,
                }),
                (0, 0, {
                    'debit': 1200.0,
                    'credit': 0.0,
                    'account_id': self.company_data['default_account_revenue'].id,
                }),
            ],
        })
        (move_2016 + move_2017).action_post()

        rec_line_2016 = move_2016.line_ids.filtered(lambda line: line.account_id.internal_type == 'receivable')
        rec_line_2017 = move_2017.line_ids.filtered(lambda line: line.account_id.internal_type == 'receivable')

        self.assertRecordValues(rec_line_2016 + rec_line_2017, [
            {'amount_residual': 1200.0,     'amount_residual_currency': 7200.0,     'reconciled': False},
            {'amount_residual': -1200.0,    'amount_residual_currency': -2400.0,    'reconciled': False},
        ])

        # Reconcile.

        res = (rec_line_2016 + rec_line_2017).reconcile()

        self.assertRecordValues(rec_line_2016 + rec_line_2017, [
            {'amount_residual': 0.0,        'amount_residual_currency': 0.0,        'reconciled': True},
            {'amount_residual': 0.0,        'amount_residual_currency': 0.0,        'reconciled': True},
        ])

        exchange_diff = res['full_reconcile'].exchange_move_id
        exchange_diff_lines = exchange_diff.line_ids.sorted('amount_currency')

        self.assertRecordValues(exchange_diff_lines, [
            {
                'debit': 0.0,
                'credit': 0.0,
                'amount_currency': -2400.0,
                'currency_id': self.currency_data_2['currency'].id,
                'account_id': self.company_data['default_account_receivable'].id,
            },
            {
                'debit': 0.0,
                'credit': 0.0,
                'amount_currency': -1200.0,
                'currency_id': self.currency_data['currency'].id,
                'account_id': self.company_data['default_account_receivable'].id,
            },
            {
                'debit': 0.0,
                'credit': 0.0,
                'amount_currency': 1200.0,
                'currency_id': self.currency_data['currency'].id,
                'account_id': exchange_diff.journal_id.company_id.expense_currency_exchange_account_id.id,
            },
            {
                'debit': 0.0,
                'credit': 0.0,
                'amount_currency': 2400.0,
                'currency_id': self.currency_data_2['currency'].id,
                'account_id': exchange_diff.journal_id.company_id.expense_currency_exchange_account_id.id,
            },
        ])

        self.assertRecordValues(exchange_diff_lines, [
            {'amount_residual': 0.0,        'amount_residual_currency': 0.0,        'reconciled': True},
            {'amount_residual': 0.0,        'amount_residual_currency': 0.0,        'reconciled': True},
            {'amount_residual': 0.0,        'amount_residual_currency': 0.0,        'reconciled': False},
            {'amount_residual': 0.0,        'amount_residual_currency': 0.0,        'reconciled': False},
        ])

        # Unreconcile.
        # A reversal is created to cancel the exchange difference journal entry.

        (rec_line_2016 + rec_line_2017).remove_move_reconcile()

        reverse_exchange_diff = exchange_diff_lines[1].matched_debit_ids.debit_move_id.move_id
        reverse_exchange_diff_lines = reverse_exchange_diff.line_ids.sorted('amount_currency')

        self.assertRecordValues(reverse_exchange_diff_lines, [
            {
                'debit': 0.0,
                'credit': 0.0,
                'amount_currency': -2400.0,
                'currency_id': self.currency_data_2['currency'].id,
                'account_id': exchange_diff.journal_id.company_id.expense_currency_exchange_account_id.id,
            },
            {
                'debit': 0.0,
                'credit': 0.0,
                'amount_currency': -1200.0,
                'currency_id': self.currency_data['currency'].id,
                'account_id': exchange_diff.journal_id.company_id.expense_currency_exchange_account_id.id,
            },
            {
                'debit': 0.0,
                'credit': 0.0,
                'amount_currency': 1200.0,
                'currency_id': self.currency_data['currency'].id,
                'account_id': self.company_data['default_account_receivable'].id,
            },
            {
                'debit': 0.0,
                'credit': 0.0,
                'amount_currency': 2400.0,
                'currency_id': self.currency_data_2['currency'].id,
                'account_id': self.company_data['default_account_receivable'].id,
            },
        ])

        self.assertRecordValues(exchange_diff_lines + reverse_exchange_diff_lines, [
            {'amount_residual': 0.0,        'amount_residual_currency': 0.0,        'reconciled': True},
            {'amount_residual': 0.0,        'amount_residual_currency': 0.0,        'reconciled': True},
            {'amount_residual': 0.0,        'amount_residual_currency': 0.0,        'reconciled': False},
            {'amount_residual': 0.0,        'amount_residual_currency': 0.0,        'reconciled': False},
            {'amount_residual': 0.0,        'amount_residual_currency': 0.0,        'reconciled': False},
            {'amount_residual': 0.0,        'amount_residual_currency': 0.0,        'reconciled': False},
            {'amount_residual': 0.0,        'amount_residual_currency': 0.0,        'reconciled': True},
            {'amount_residual': 0.0,        'amount_residual_currency': 0.0,        'reconciled': True},
        ])

        partials = reverse_exchange_diff_lines.matched_debit_ids + reverse_exchange_diff_lines.matched_credit_ids
        self.assertPartialReconcile(partials, [
            {
                'amount': 0.0,
                'debit_amount_currency': 1200.0,
                'credit_amount_currency': 1200.0,
                'debit_move_id': reverse_exchange_diff_lines[2].id,
                'credit_move_id': exchange_diff_lines[1].id,
            },
            {
                'amount': 0.0,
                'debit_amount_currency': 2400.0,
                'credit_amount_currency': 2400.0,
                'debit_move_id': reverse_exchange_diff_lines[3].id,
                'credit_move_id': exchange_diff_lines[0].id,
            },
        ])

    # -------------------------------------------------------------------------
    # Test creation of extra journal entries during the reconciliation to
    # deal with taxes that are exigible on payment (cash basis).
    # -------------------------------------------------------------------------

    def test_reconcile_cash_basis_workflow_single_currency(self):
        ''' Test the generated journal entries during the reconciliation to manage the cash basis taxes.
        Also,
        - Test the case when there is multiple receivable/payable accounts.
        - Test the reconciliation with tiny amounts.
        - Check there is no rounding issue when making the percentage.
        - Check there is no lost cents when the journal entry is fully reconciled.
        '''
        cash_basis_move = self.env['account.move'].create({
            'move_type': 'entry',
            'date': '2016-01-01',
            'line_ids': [
                # Base Tax line
                (0, 0, {
                    'debit': 0.0,
                    'credit': 100.0,
                    'account_id': self.company_data['default_account_revenue'].id,
                    'tax_ids': [(6, 0, (self.cash_basis_tax_a_third_amount + self.cash_basis_tax_tiny_amount).ids)],
                    'tax_exigible': False,
                }),

                # Tax lines
                (0, 0, {
                    'debit': 0.0,
                    'credit': 33.33,
                    'account_id': self.cash_basis_transfer_account.id,
                    'tax_repartition_line_id': self.cash_basis_tax_a_third_amount.invoice_repartition_line_ids.filtered(lambda line: line.repartition_type == 'tax').id,
                    'tax_exigible': False,
                }),
                (0, 0, {
                    'debit': 0.0,
                    'credit': 0.01,
                    'account_id': self.cash_basis_transfer_account.id,
                    'tax_repartition_line_id': self.cash_basis_tax_tiny_amount.invoice_repartition_line_ids.filtered(lambda line: line.repartition_type == 'tax').id,
                    'tax_exigible': False,
                }),

                # Receivable lines
                (0, 0, {
                    'debit': 44.45,
                    'credit': 0.0,
                    'account_id': self.extra_receivable_account_1.id,
                }),
                (0, 0, {
                    'debit': 44.45,
                    'credit': 0.0,
                    'account_id': self.extra_receivable_account_2.id,
                }),
                (0, 0, {
                    'debit': 44.45,
                    'credit': 0.0,
                    'account_id': self.extra_receivable_account_2.id,
                }),
                (0, 0, {
                    'debit': 0.0,
                    'credit': 0.01,
                    'account_id': self.extra_payable_account_1.id,
                }),
            ]
        })

        payment_move = self.env['account.move'].create({
            'move_type': 'entry',
            'date': '2017-01-01',
            'line_ids': [
                (0, 0, {'debit': 0.0,       'credit': 33.34,    'account_id': self.extra_receivable_account_1.id}),
                (0, 0, {'debit': 0.0,       'credit': 11.11,    'account_id': self.extra_receivable_account_1.id}),
                (0, 0, {'debit': 0.0,       'credit': 88.89,    'account_id': self.extra_receivable_account_2.id}),
                (0, 0, {'debit': 0.0,       'credit': 0.01,     'account_id': self.extra_receivable_account_2.id}),
                (0, 0, {'debit': 0.01,      'credit': 0.0,      'account_id': self.extra_payable_account_1.id}),
                (0, 0, {'debit': 133.34,    'credit': 0.0,      'account_id': self.company_data['default_account_revenue'].id}),
            ]
        })

        (cash_basis_move + payment_move).action_post()

        # Initial amounts by accounts:

        self.assertAmountsGroupByAccount([
            # Account                               Balance     Amount Currency
            (self.cash_basis_transfer_account,      -33.34,     -33.34),
            (self.tax_account_1,                    0.0,        0.0),
            (self.tax_account_2,                    0.0,        0.0),
            (self.cash_basis_base_account,          0.0,        0.0),
        ])

        # There is 44.45 + 44.45 + 44.45 + 0.01 = 133.36 to reconcile on 'cash_basis_move'.
        # Reconciling all the amount in extra_receivable_account_1 should compute 2 percentages:
        # 33.34 / 133.36 = 0.25
        # 11.11 / 133.36 = 0.083308338

        receivable_lines_1 = (cash_basis_move + payment_move).line_ids\
            .filtered(lambda line: line.account_id == self.extra_receivable_account_1)
        res = receivable_lines_1.reconcile()

        self.assertFullReconcile(res['full_reconcile'], receivable_lines_1)
        self.assertEqual(len(res.get('tax_cash_basis_moves', [])), 2)
        self.assertRecordValues(res['tax_cash_basis_moves'][0].line_ids, [
            # Base amount of tax_1 & tax_2:
            {'debit': 25.0,     'credit': 0.0,      'account_id': self.cash_basis_base_account.id},
            {'debit': 0.0,      'credit': 25.0,     'account_id': self.cash_basis_base_account.id},
            # tax_1:
            {'debit': 8.33,     'credit': 0.0,      'account_id': self.cash_basis_transfer_account.id},
            {'debit': 0.0,      'credit': 8.33,     'account_id': self.tax_account_1.id},
            # tax_2:
            {'debit': 0.0,      'credit': 0.0,      'account_id': self.cash_basis_transfer_account.id},
            {'debit': 0.0,      'credit': 0.0,      'account_id': self.tax_account_2.id},
        ])
        self.assertRecordValues(res['tax_cash_basis_moves'][1].line_ids, [
            # Base amount of tax_1 & tax_2:
            {'debit': 8.33,     'credit': 0.0,      'account_id': self.cash_basis_base_account.id},
            {'debit': 0.0,      'credit': 8.33,     'account_id': self.cash_basis_base_account.id},
            # tax_1:
            {'debit': 2.78,     'credit': 0.0,      'account_id': self.cash_basis_transfer_account.id},
            {'debit': 0.0,      'credit': 2.78,     'account_id': self.tax_account_1.id},
            # tax_2:
            {'debit': 0.0,      'credit': 0.0,      'account_id': self.cash_basis_transfer_account.id},
            {'debit': 0.0,      'credit': 0.0,      'account_id': self.tax_account_2.id},
        ])

        self.assertAmountsGroupByAccount([
            # Account                               Balance     Amount Currency
            (self.cash_basis_transfer_account,      -22.23,     -22.23),
            (self.tax_account_1,                    -11.11,     -11.11),
            (self.tax_account_2,                    0.0,        0.0),
        ])

        # Reconciling all the amount in extra_receivable_account_2 should compute 3 percentages:
        # 44.45 / 133.36 = 0.333308338
        # 44.44 / 133.36 = 0.333233353
        # 0.01 / 133.36 = 0.000074985

        receivable_lines_2 = (cash_basis_move + payment_move).line_ids\
            .filtered(lambda line: line.account_id == self.extra_receivable_account_2)
        res = receivable_lines_2.reconcile()

        self.assertFullReconcile(res['full_reconcile'], receivable_lines_2)
        self.assertEqual(len(res.get('tax_cash_basis_moves', [])), 3)
        self.assertRecordValues(res['tax_cash_basis_moves'][0].line_ids, [
            # Base amount of tax_1 & tax_2:
            {'debit': 33.33,    'credit': 0.0,      'account_id': self.cash_basis_base_account.id},
            {'debit': 0.0,      'credit': 33.33,    'account_id': self.cash_basis_base_account.id},
            # tax_1:
            {'debit': 11.11,    'credit': 0.0,      'account_id': self.cash_basis_transfer_account.id},
            {'debit': 0.0,      'credit': 11.11,    'account_id': self.tax_account_1.id},
            # tax_2:
            {'debit': 0.0,      'credit': 0.0,      'account_id': self.cash_basis_transfer_account.id},
            {'debit': 0.0,      'credit': 0.0,      'account_id': self.tax_account_2.id},
        ])
        self.assertRecordValues(res['tax_cash_basis_moves'][1].line_ids, [
            # Base amount of tax_1 & tax_2:
            {'debit': 33.32,    'credit': 0.0,      'account_id': self.cash_basis_base_account.id},
            {'debit': 0.0,      'credit': 33.32,    'account_id': self.cash_basis_base_account.id},
            # tax_1:
            {'debit': 11.11,    'credit': 0.0,      'account_id': self.cash_basis_transfer_account.id},
            {'debit': 0.0,      'credit': 11.11,    'account_id': self.tax_account_1.id},
            # tax_2:
            {'debit': 0.0,      'credit': 0.0,      'account_id': self.cash_basis_transfer_account.id},
            {'debit': 0.0,      'credit': 0.0,      'account_id': self.tax_account_2.id},
        ])
        self.assertRecordValues(res['tax_cash_basis_moves'][2].line_ids, [
            # Base amount of tax_1 & tax_2:
            {'debit': 0.01,     'credit': 0.0,      'account_id': self.cash_basis_base_account.id},
            {'debit': 0.0,      'credit': 0.01,     'account_id': self.cash_basis_base_account.id},
            # tax_1:
            {'debit': 0.0,      'credit': 0.0,      'account_id': self.cash_basis_transfer_account.id},
            {'debit': 0.0,      'credit': 0.0,      'account_id': self.tax_account_1.id},
            # tax_2:
            {'debit': 0.0,      'credit': 0.0,      'account_id': self.cash_basis_transfer_account.id},
            {'debit': 0.0,      'credit': 0.0,      'account_id': self.tax_account_2.id},
        ])

        self.assertAmountsGroupByAccount([
            # Account                               Balance     Amount Currency
            (self.cash_basis_transfer_account,      -0.01,      -0.01),
            (self.tax_account_1,                    -33.33,     -33.33),
            (self.tax_account_2,                    0.0,        0.0),
        ])

        # Reconciling all the amount in extra_payable_account_1 should trigger the matching number and ensure all
        # the base amount has been covered without any rounding issue.

        payable_lines_1 = (cash_basis_move + payment_move).line_ids\
            .filtered(lambda line: line.account_id == self.extra_payable_account_1)
        res = payable_lines_1.reconcile()

        self.assertFullReconcile(res['full_reconcile'], payable_lines_1)
        self.assertEqual(len(res.get('tax_cash_basis_moves', [])), 1)
        self.assertRecordValues(res['tax_cash_basis_moves'].line_ids, [
            # Base amount of tax_1 & tax_2:
            {'debit': 0.01,     'credit': 0.0,      'account_id': self.cash_basis_base_account.id},
            {'debit': 0.0,      'credit': 0.01,     'account_id': self.cash_basis_base_account.id},
            # tax_1:
            {'debit': 0.0,      'credit': 0.0,      'account_id': self.cash_basis_transfer_account.id},
            {'debit': 0.0,      'credit': 0.0,      'account_id': self.tax_account_1.id},
            # tax_2:
            {'debit': 0.0,      'credit': 0.0,      'account_id': self.cash_basis_transfer_account.id},
            {'debit': 0.0,      'credit': 0.0,      'account_id': self.tax_account_2.id},
        ])

        self.assertRecordValues(res['full_reconcile'].exchange_move_id.line_ids, [
            {'account_id': self.tax_account_2.id,               'debit': 0.0,   'credit': 0.01, 'tax_ids': [],          'tax_line_id': self.cash_basis_tax_tiny_amount.id},
            {'account_id': self.cash_basis_transfer_account.id, 'debit': 0.01,  'credit': 0.0,  'tax_ids': [],          'tax_line_id': False},
        ])

        self.assertAmountsGroupByAccount([
            # Account                               Balance     Amount Currency
            (self.cash_basis_transfer_account,      0.0,        0.0),
            (self.tax_account_1,                    -33.33,     -33.33),
            (self.tax_account_2,                    -0.01,      -0.01),
        ])

    def test_reconcile_cash_basis_workflow_multi_currency(self):
        ''' Same as before with a foreign currency. '''

        currency_id = self.currency_data['currency'].id
        taxes = self.cash_basis_tax_a_third_amount + self.cash_basis_tax_tiny_amount

        cash_basis_move = self.env['account.move'].create({
            'move_type': 'entry',
            'date': '2016-01-01',
            'line_ids': [
                # Base Tax line
                (0, 0, {
                    'debit': 0.0,
                    'credit': 33.34,
                    'amount_currency': -100.0,
                    'currency_id': currency_id,
                    'account_id': self.company_data['default_account_revenue'].id,
                    'tax_ids': [(6, 0, taxes.ids)],
                    'tax_exigible': False,
                }),

                # Tax lines
                (0, 0, {
                    'debit': 0.0,
                    'credit': 11.10,
                    'amount_currency': -33.33,
                    'currency_id': currency_id,
                    'account_id': self.cash_basis_transfer_account.id,
                    'tax_repartition_line_id': self.cash_basis_tax_a_third_amount.invoice_repartition_line_ids.filtered(lambda line: line.repartition_type == 'tax').id,
                    'tax_exigible': False,
                }),
                (0, 0, {
                    'debit': 0.0,
                    'credit': 0.01,
                    'amount_currency': -0.01,
                    'currency_id': currency_id,
                    'account_id': self.cash_basis_transfer_account.id,
                    'tax_repartition_line_id': self.cash_basis_tax_tiny_amount.invoice_repartition_line_ids.filtered(lambda line: line.repartition_type == 'tax').id,
                    'tax_exigible': False,
                }),

                # Receivable lines
                (0, 0, {
                    'debit': 14.82,
                    'credit': 0.0,
                    'amount_currency': 44.45,
                    'currency_id': currency_id,
                    'account_id': self.extra_receivable_account_1.id,
                }),
                (0, 0, {
                    'debit': 14.82,
                    'credit': 0.0,
                    'amount_currency': 44.45,
                    'currency_id': currency_id,
                    'account_id': self.extra_receivable_account_2.id,
                }),
                (0, 0, {
                    'debit': 14.82,
                    'credit': 0.0,
                    'amount_currency': 44.45,
                    'currency_id': currency_id,
                    'account_id': self.extra_receivable_account_2.id,
                }),
                (0, 0, {
                    'debit': 0.0,
                    'credit': 0.01,
                    'amount_currency': -0.01,
                    'currency_id': currency_id,
                    'account_id': self.extra_payable_account_1.id,
                }),
            ]
        })

        payment_move = self.env['account.move'].create({
            'move_type': 'entry',
            'date': '2017-01-01',
            'line_ids': [
                (0, 0, {'debit': 0.0,       'credit': 16.67,    'amount_currency': -33.34,  'currency_id': currency_id,     'account_id': self.extra_receivable_account_1.id}),
                (0, 0, {'debit': 0.0,       'credit': 5.6,      'amount_currency': -11.11,  'currency_id': currency_id,     'account_id': self.extra_receivable_account_1.id}),
                (0, 0, {'debit': 0.0,       'credit': 44.45,    'amount_currency': -88.89,  'currency_id': currency_id,     'account_id': self.extra_receivable_account_2.id}),
                (0, 0, {'debit': 0.0,       'credit': 0.01,     'amount_currency': -0.01,   'currency_id': currency_id,     'account_id': self.extra_receivable_account_2.id}),
                (0, 0, {'debit': 0.01,      'credit': 0.0,      'amount_currency': 0.01,    'currency_id': currency_id,     'account_id': self.extra_payable_account_1.id}),
                (0, 0, {'debit': 66.72,     'credit': 0.0,                                                                  'account_id': self.company_data['default_account_revenue'].id}),
            ]
        })

        (cash_basis_move + payment_move).action_post()

        # Initial amounts by accounts:

        self.assertAmountsGroupByAccount([
            # Account                               Balance     Amount Currency
            (self.cash_basis_transfer_account,      -11.11,     -33.34),
            (self.tax_account_1,                    0.0,        0.0),
            (self.tax_account_2,                    0.0,        0.0),
        ])

        # There is 44.45 + 44.45 + 44.45 + 0.01 = 133.36 to reconcile on 'cash_basis_move'.
        # Reconciling all the amount in extra_receivable_account_1 should compute 2 percentages:
        # 33.34 / 133.36 = 0.25
        # 11.11 / 133.36 = 0.083308338

        receivable_lines_1 = (cash_basis_move + payment_move).line_ids\
            .filtered(lambda line: line.account_id == self.extra_receivable_account_1)
        res = receivable_lines_1.reconcile()

        self.assertFullReconcile(res['full_reconcile'], receivable_lines_1)
        self.assertEqual(len(res.get('tax_cash_basis_moves', [])), 2)
        self.assertRecordValues(res['tax_cash_basis_moves'][0].line_ids, [
            # Base amount of tax_1 & tax_2:
            {'debit': 12.5,     'credit': 0.0,      'amount_currency': 25.0,    'currency_id': currency_id,     'account_id': self.cash_basis_base_account.id},
            {'debit': 0.0,      'credit': 12.5,     'amount_currency': -25.0,   'currency_id': currency_id,     'account_id': self.cash_basis_base_account.id},
            # tax_1:
            {'debit': 4.17,     'credit': 0.0,      'amount_currency': 8.333,   'currency_id': currency_id,     'account_id': self.cash_basis_transfer_account.id},
            {'debit': 0.0,      'credit': 4.17,     'amount_currency': -8.333,  'currency_id': currency_id,     'account_id': self.tax_account_1.id},
            # tax_2:
            {'debit': 0.0,      'credit': 0.0,      'amount_currency': 0.003,   'currency_id': currency_id,     'account_id': self.cash_basis_transfer_account.id},
            {'debit': 0.0,      'credit': 0.0,      'amount_currency': -0.003,  'currency_id': currency_id,     'account_id': self.tax_account_2.id},
        ])
        self.assertRecordValues(res['tax_cash_basis_moves'][1].line_ids, [
            # Base amount of tax_1 & tax_2:
            {'debit': 4.2,      'credit': 0.0,      'amount_currency': 8.331,   'currency_id': currency_id,     'account_id': self.cash_basis_base_account.id},
            {'debit': 0.0,      'credit': 4.2,      'amount_currency': -8.331,  'currency_id': currency_id,     'account_id': self.cash_basis_base_account.id},
            # tax_1:
            {'debit': 1.4,      'credit': 0.0,      'amount_currency': 2.777,   'currency_id': currency_id,     'account_id': self.cash_basis_transfer_account.id},
            {'debit': 0.0,      'credit': 1.4,      'amount_currency': -2.777,  'currency_id': currency_id,     'account_id': self.tax_account_1.id},
            # tax_2:
            {'debit': 0.0,      'credit': 0.0,      'amount_currency': 0.001,   'currency_id': currency_id,     'account_id': self.cash_basis_transfer_account.id},
            {'debit': 0.0,      'credit': 0.0,      'amount_currency': -0.001,  'currency_id': currency_id,     'account_id': self.tax_account_2.id},
        ])

        self.assertAmountsGroupByAccount([
            # Account                               Balance     Amount Currency
            (self.cash_basis_transfer_account,      -5.54,      -22.226),
            (self.tax_account_1,                    -5.57,      -11.11),
            (self.tax_account_2,                    0.0,        -0.004),
        ])

        # Reconciling all the amount in extra_receivable_account_2 should compute 3 percentages:
        # 44.45 / 133.36 = 0.333308338
        # 44.44 / 133.36 = 0.333233353
        # 0.01 / 133.36 = 0.000074985

        receivable_lines_2 = (cash_basis_move + payment_move).line_ids\
            .filtered(lambda line: line.account_id == self.extra_receivable_account_2)
        res = receivable_lines_2.reconcile()

        self.assertFullReconcile(res['full_reconcile'], receivable_lines_2)
        self.assertEqual(len(res.get('tax_cash_basis_moves', [])), 3)
        self.assertRecordValues(res['tax_cash_basis_moves'][0].line_ids, [
            # Base amount of tax_1 & tax_2:
            {'debit': 16.67,    'credit': 0.0,      'amount_currency': 33.331,  'currency_id': currency_id,     'account_id': self.cash_basis_base_account.id},
            {'debit': 0.0,      'credit': 16.67,    'amount_currency': -33.331, 'currency_id': currency_id,     'account_id': self.cash_basis_base_account.id},
            # tax_1:
            {'debit': 5.56,     'credit': 0.0,      'amount_currency': 11.109,  'currency_id': currency_id,     'account_id': self.cash_basis_transfer_account.id},
            {'debit': 0.0,      'credit': 5.56,     'amount_currency': -11.109, 'currency_id': currency_id,     'account_id': self.tax_account_1.id},
            # tax_2:
            {'debit': 0.0,      'credit': 0.0,      'amount_currency': 0.003,   'currency_id': currency_id,     'account_id': self.cash_basis_transfer_account.id},
            {'debit': 0.0,      'credit': 0.0,      'amount_currency': -0.003,  'currency_id': currency_id,     'account_id': self.tax_account_2.id},
        ])
        self.assertRecordValues(res['tax_cash_basis_moves'][1].line_ids, [
            # Base amount of tax_1 & tax_2:
            {'debit': 16.66,    'credit': 0.0,      'amount_currency': 33.323,  'currency_id': currency_id,     'account_id': self.cash_basis_base_account.id},
            {'debit': 0.0,      'credit': 16.66,    'amount_currency': -33.323, 'currency_id': currency_id,     'account_id': self.cash_basis_base_account.id},
            # tax_1:
            {'debit': 5.55,     'credit': 0.0,      'amount_currency': 11.107,  'currency_id': currency_id,     'account_id': self.cash_basis_transfer_account.id},
            {'debit': 0.0,      'credit': 5.55,     'amount_currency': -11.107, 'currency_id': currency_id,     'account_id': self.tax_account_1.id},
            # tax_2:
            {'debit': 0.0,      'credit': 0.0,      'amount_currency': 0.003,   'currency_id': currency_id,     'account_id': self.cash_basis_transfer_account.id},
            {'debit': 0.0,      'credit': 0.0,      'amount_currency': -0.003,  'currency_id': currency_id,     'account_id': self.tax_account_2.id},
        ])
        self.assertRecordValues(res['tax_cash_basis_moves'][2].line_ids, [
            # Base amount of tax_1 & tax_2:
            {'debit': 0.01,     'credit': 0.0,      'amount_currency': 0.007,   'currency_id': currency_id,     'account_id': self.cash_basis_base_account.id},
            {'debit': 0.0,      'credit': 0.01,     'amount_currency': -0.007,  'currency_id': currency_id,     'account_id': self.cash_basis_base_account.id},
            # tax_1:
            {'debit': 0.0,      'credit': 0.0,      'amount_currency': 0.002,   'currency_id': currency_id,     'account_id': self.cash_basis_transfer_account.id},
            {'debit': 0.0,      'credit': 0.0,      'amount_currency': -0.002,  'currency_id': currency_id,     'account_id': self.tax_account_1.id},
            # tax_2:
            {'debit': 0.0,      'credit': 0.0,      'amount_currency': 0.0,     'currency_id': currency_id,     'account_id': self.cash_basis_transfer_account.id},
            {'debit': 0.0,      'credit': 0.0,      'amount_currency': 0.0,     'currency_id': currency_id,     'account_id': self.tax_account_2.id},
        ])

        self.assertAmountsGroupByAccount([
            # Account                               Balance     Amount Currency
            (self.cash_basis_transfer_account,      5.57,       -0.002),
            (self.tax_account_1,                    -16.68,     -33.328),
            (self.tax_account_2,                    0.0,        -0.01),
        ])

        # Reconciling all the amount in extra_payable_account_1 should trigger the matching number and ensure all
        # the base amount has been covered without any rounding issue.

        payable_lines_1 = (cash_basis_move + payment_move).line_ids\
            .filtered(lambda line: line.account_id == self.extra_payable_account_1)
        res = payable_lines_1.reconcile()

        self.assertFullReconcile(res['full_reconcile'], payable_lines_1)
        self.assertEqual(len(res.get('tax_cash_basis_moves', [])), 1)
        self.assertRecordValues(res['tax_cash_basis_moves'].line_ids, [
            # Base amount of tax_1 & tax_2:
            {'debit': 0.01,     'credit': 0.0,      'amount_currency': 0.007,   'currency_id': currency_id,     'account_id': self.cash_basis_base_account.id},
            {'debit': 0.0,      'credit': 0.01,     'amount_currency': -0.007,  'currency_id': currency_id,     'account_id': self.cash_basis_base_account.id},
            # tax_1:
            {'debit': 0.0,      'credit': 0.0,      'amount_currency': 0.002,   'currency_id': currency_id,     'account_id': self.cash_basis_transfer_account.id},
            {'debit': 0.0,      'credit': 0.0,      'amount_currency': -0.002,  'currency_id': currency_id,     'account_id': self.tax_account_1.id},
            # tax_2:
            {'debit': 0.0,      'credit': 0.0,      'amount_currency': 0.0,     'currency_id': currency_id,     'account_id': self.cash_basis_transfer_account.id},
            {'debit': 0.0,      'credit': 0.0,      'amount_currency': 0.0,     'currency_id': currency_id,     'account_id': self.tax_account_2.id},
        ])

        self.assertRecordValues(res['full_reconcile'].exchange_move_id.line_ids, [
            {'account_id': self.cash_basis_base_account.id,     'debit': 16.71, 'credit': 0.0,      'tax_ids': taxes.ids,   'tax_line_id': False},
            {'account_id': self.cash_basis_base_account.id,     'debit': 0.0,   'credit': 16.71,    'tax_ids': [],          'tax_line_id': False},
            {'account_id': self.tax_account_1.id,               'debit': 5.58,  'credit': 0.0,      'tax_ids': [],          'tax_line_id': self.cash_basis_tax_a_third_amount.id},
            {'account_id': self.cash_basis_transfer_account.id, 'debit': 0.0,   'credit': 5.58,     'tax_ids': [],          'tax_line_id': False},
            {'account_id': self.tax_account_2.id,               'debit': 0.0,   'credit': 0.01,     'tax_ids': [],          'tax_line_id': self.cash_basis_tax_tiny_amount.id},
            {'account_id': self.cash_basis_transfer_account.id, 'debit': 0.01,  'credit': 0.0,      'tax_ids': [],          'tax_line_id': False},
        ])

        self.assertAmountsGroupByAccount([
            # Account                               Balance     Amount Currency
            (self.cash_basis_transfer_account,      0.0,        0.0),
            (self.tax_account_1,                    -11.1,      -33.33),
            (self.tax_account_2,                    -0.01,      -0.01),
        ])

    def test_reconcile_cash_basis_exchange_difference_transfer_account_check_entries_1(self):
        ''' Test the generation of the exchange difference for a tax cash basis journal entry when the transfer
        account is not a reconcile one.
        '''
        currency_id = self.currency_data['currency'].id

        # Rate 1/3 in 2016.
        cash_basis_move = self.env['account.move'].create({
            'move_type': 'entry',
            'date': '2016-01-01',
            'line_ids': [
                # Base Tax line
                (0, 0, {
                    'debit': 0.0,
                    'credit': 100.0,
                    'amount_currency': -300.0,
                    'currency_id': currency_id,
                    'account_id': self.company_data['default_account_revenue'].id,
                    'tax_ids': [(6, 0, self.cash_basis_tax_a_third_amount.ids)],
                    'tax_exigible': False,
                }),

                # Tax line
                (0, 0, {
                    'debit': 0.0,
                    'credit': 33.33,
                    'amount_currency': -100.0,
                    'currency_id': currency_id,
                    'account_id': self.cash_basis_transfer_account.id,
                    'tax_repartition_line_id': self.cash_basis_tax_a_third_amount.invoice_repartition_line_ids.filtered(lambda line: line.repartition_type == 'tax').id,
                    'tax_exigible': False,
                }),

                # Receivable lines
                (0, 0, {
                    'debit': 133.33,
                    'credit': 0.0,
                    'amount_currency': 400.0,
                    'currency_id': currency_id,
                    'account_id': self.extra_receivable_account_1.id,
                }),
            ]
        })

        # Rate 1/2 in 2017.
        payment_move = self.env['account.move'].create({
            'move_type': 'entry',
            'date': '2017-01-01',
            'line_ids': [
                (0, 0, {
                    'debit': 0.0,
                    'credit': 201.0,
                    'amount_currency': -402.0, # Don't create the full reconcile directly.
                    'currency_id': currency_id,
                    'account_id': self.extra_receivable_account_1.id,
                }),
                (0, 0, {
                    'debit': 201.0,
                    'credit': 0.0,
                    'account_id': self.company_data['default_account_revenue'].id,
                }),
            ]
        })

        # Move making the payment fully paid.
        end_move = self.env['account.move'].create({
            'move_type': 'entry',
            'date': '2017-01-01',
            'line_ids': [
                (0, 0, {
                    'debit': 1.0,
                    'credit': 0.0,
                    'amount_currency': 2.0,
                    'currency_id': currency_id,
                    'account_id': self.extra_receivable_account_1.id,
                }),
                (0, 0, {
                    'debit': 0.0,
                    'credit': 1.0,
                    'account_id': self.company_data['default_account_revenue'].id,
                }),
            ]
        })

        (cash_basis_move + payment_move + end_move).action_post()

        self.assertAmountsGroupByAccount([
            # Account                               Balance     Amount Currency
            (self.cash_basis_transfer_account,      -33.33,     -100.0),
            (self.tax_account_1,                    0.0,        0.0),
        ])

        receivable_lines = (cash_basis_move + payment_move).line_ids\
            .filtered(lambda line: line.account_id == self.extra_receivable_account_1)
        res = receivable_lines.reconcile()

        self.assertEqual(len(res.get('tax_cash_basis_moves', [])), 1)
        self.assertRecordValues(res['tax_cash_basis_moves'].line_ids, [
            # Base amount:
            {'debit': 150.0,    'credit': 0.0,      'amount_currency': 300.0,   'currency_id': currency_id,     'account_id': self.cash_basis_base_account.id},
            {'debit': 0.0,      'credit': 150.0,    'amount_currency': -300.0,  'currency_id': currency_id,     'account_id': self.cash_basis_base_account.id},
            # tax:
            {'debit': 50.0,     'credit': 0.0,      'amount_currency': 100.0,   'currency_id': currency_id,     'account_id': self.cash_basis_transfer_account.id},
            {'debit': 0.0,      'credit': 50.0,     'amount_currency': -100.0,  'currency_id': currency_id,     'account_id': self.tax_account_1.id},
        ])

        receivable_lines2 = (payment_move + end_move).line_ids\
            .filtered(lambda line: line.account_id == self.extra_receivable_account_1)
        res = receivable_lines2.reconcile()

        self.assertFullReconcile(res['full_reconcile'], receivable_lines + receivable_lines2)

        exchange_diff = res['full_reconcile'].exchange_move_id
        exchange_diff_lines = exchange_diff.line_ids\
            .filtered(lambda line: line.account_id == self.cash_basis_transfer_account)\
            .sorted(lambda line: (line.account_id, line.debit, line.credit))

        self.assertRecordValues(exchange_diff_lines, [
            {
                'debit': 0.0,
                'credit': 16.67,
                'amount_currency': 0.0,
                'currency_id': currency_id,
                'account_id': self.cash_basis_transfer_account.id,
            },
        ])

        self.assertAmountsGroupByAccount([
            # Account                               Balance     Amount Currency
            (self.cash_basis_transfer_account,      0.0,        0.0),
            (self.tax_account_1,                    -33.33,     -100.0),
        ])

    def test_reconcile_cash_basis_exchange_difference_transfer_account_check_entries_2(self):
        ''' Test the generation of the exchange difference for a tax cash basis journal entry when the transfer
        account is not a reconcile one.
        '''
        currency_id = self.setup_multi_currency_data(default_values={
            'name': 'bitcoin',
            'symbol': 'bc',
            'currency_unit_label': 'Bitcoin',
            'currency_subunit_label': 'Tiny bitcoin',
        }, rate2016=0.5, rate2017=0.66666666666666)['currency'].id

        # Rate 2/1 in 2016.
        caba_inv = self.env['account.move'].create({
            'move_type': 'entry',
            'date': '2016-01-01',
            'line_ids': [
                # Base Tax line
                (0, 0, {
                    'debit': 0.0,
                    'credit': 200.0,
                    'amount_currency': -100.0,
                    'currency_id': currency_id,
                    'account_id': self.company_data['default_account_revenue'].id,
                    'tax_ids': [(6, 0, self.cash_basis_tax_a_third_amount.ids)],
                    'tax_exigible': False,
                }),

                # Tax line
                (0, 0, {
                    'debit': 0.0,
                    'credit': 20.0,
                    'amount_currency': -10.0,
                    'currency_id': currency_id,
                    'account_id': self.cash_basis_transfer_account.id,
                    'tax_repartition_line_id': self.cash_basis_tax_a_third_amount.invoice_repartition_line_ids.filtered(lambda line: line.repartition_type == 'tax').id,
                    'tax_exigible': False,
                }),

                # Receivable lines
                (0, 0, {
                    'debit': 220.0,
                    'credit': 0.0,
                    'amount_currency': 110.0,
                    'currency_id': currency_id,
                    'account_id': self.extra_receivable_account_1.id,
                }),
            ]
        })
        caba_inv.action_post()

        # Rate 3/2 in 2017. Full payment of 110 in foreign currency
        pmt_wizard = self.env['account.payment.register'].with_context(active_model='account.move', active_ids=caba_inv.ids).create({
            'payment_date': '2017-01-01',
            'journal_id': self.company_data['default_journal_bank'].id,
            'payment_method_id': self.env.ref('account.account_payment_method_manual_in').id,
        })
        pmt_wizard._create_payments()
        partial_rec = caba_inv.mapped('line_ids.matched_credit_ids')
        caba_move = self.env['account.move'].search([('tax_cash_basis_rec_id', 'in', partial_rec.ids)])

        self.assertRecordValues(caba_move.line_ids, [
            {'account_id': self.cash_basis_base_account.id,     'debit': 150.0, 'credit': 0.0,      'amount_currency': 100.0,   'tax_ids': [],                                      'tax_line_id': False},
            {'account_id': self.cash_basis_base_account.id,     'debit': 0.0,   'credit': 150.0,    'amount_currency': -100.0,  'tax_ids': self.cash_basis_tax_a_third_amount.ids,  'tax_line_id': False},
            {'account_id': self.cash_basis_transfer_account.id, 'debit': 15.0,  'credit': 0.0,      'amount_currency': 10.0,    'tax_ids': [],                                      'tax_line_id': False},
            {'account_id': self.tax_account_1.id,               'debit': 0.0,   'credit': 15.0,     'amount_currency': -10.0,   'tax_ids': [],                                      'tax_line_id': self.cash_basis_tax_a_third_amount.id},
        ])

        receivable_line = caba_inv.line_ids.filtered(lambda x: x.account_id.internal_type == 'receivable')
        self.assertTrue(receivable_line.full_reconcile_id, "Invoice should be fully paid")

        exchange_move = receivable_line.full_reconcile_id.exchange_move_id
        self.assertTrue(exchange_move, "There should be an exchange difference move created")
        self.assertRecordValues(exchange_move.line_ids, [
            {'account_id': receivable_line.account_id.id,                                   'debit': 0.0,   'credit': 55.0, 'amount_currency': 0.0, 'tax_ids': [],                                      'tax_line_id': False},
            {'account_id': caba_move.company_id.expense_currency_exchange_account_id.id,    'debit': 55.0,  'credit': 0.0,  'amount_currency': 0.0, 'tax_ids': [],                                      'tax_line_id': False},
            {'account_id': self.cash_basis_base_account.id,                                 'debit': 0.0,   'credit': 50.0, 'amount_currency': 0.0, 'tax_ids': self.cash_basis_tax_a_third_amount.ids,  'tax_line_id': False},
            {'account_id': self.cash_basis_base_account.id,                                 'debit': 50.0,  'credit': 0.0,  'amount_currency': 0.0, 'tax_ids': [],                                      'tax_line_id': False},
            {'account_id': self.tax_account_1.id,                                           'debit': 0.0,   'credit': 5.0,  'amount_currency': 0.0, 'tax_ids': [],                                      'tax_line_id': self.cash_basis_tax_a_third_amount.id},
            {'account_id': self.cash_basis_transfer_account.id,                             'debit': 5.0,   'credit': 0.0,  'amount_currency': 0.0, 'tax_ids': [],                                      'tax_line_id': False},
        ])

        self.assertAmountsGroupByAccount([
            # Account                               Balance     Amount Currency
            (self.cash_basis_transfer_account,      0.0,        0.0),
            (self.tax_account_1,                    -20.0,      -10.0),
        ])

    def test_reconcile_cash_basis_exchange_difference_transfer_account_check_entries_3(self):
        ''' Test the generation of the exchange difference for a tax cash basis journal entry when the transfer
        account is not a reconcile one.
        '''
        currency_id = self.setup_multi_currency_data(default_values={
            'name': 'bitcoin',
            'symbol': 'bc',
            'currency_unit_label': 'Bitcoin',
            'currency_subunit_label': 'Tiny bitcoin',
            'rounding': 0.01,
        }, rate2016=0.5, rate2017=0.66666666666666)['currency'].id

        # Rate 2/1 in 2016.
        caba_inv = self.env['account.move'].create({
            'move_type': 'entry',
            'date': '2016-01-01',
            'line_ids': [
                # Base Tax line
                (0, 0, {
                    'debit': 0.0,
                    'credit': 200.0,
                    'amount_currency': -100.0,
                    'currency_id': currency_id,
                    'account_id': self.company_data['default_account_revenue'].id,
                    'tax_ids': [(6, 0, self.cash_basis_tax_a_third_amount.ids)],
                    'tax_exigible': False,
                }),

                # Tax line
                (0, 0, {
                    'debit': 0.0,
                    'credit': 20.0,
                    'amount_currency': -10.0,
                    'currency_id': currency_id,
                    'account_id': self.cash_basis_transfer_account.id,
                    'tax_repartition_line_id': self.cash_basis_tax_a_third_amount.invoice_repartition_line_ids.filtered(lambda line: line.repartition_type == 'tax').id,
                    'tax_exigible': False,
                }),

                # Receivable lines
                (0, 0, {
                    'debit': 220.0,
                    'credit': 0.0,
                    'amount_currency': 110.0,
                    'currency_id': currency_id,
                    'account_id': self.extra_receivable_account_1.id,
                }),
            ]
        })
        caba_inv.action_post()

        # Rate 3/2 in 2017. Full payment of 220 in company currency
        pmt_wizard = self.env['account.payment.register'].with_context(active_model='account.move', active_ids=caba_inv.ids).create({
            'payment_date': '2017-01-01',
            'journal_id': self.company_data['default_journal_bank'].id,
            'payment_method_id': self.env.ref('account.account_payment_method_manual_in').id,
            'currency_id': self.company_data['currency'].id,
            'amount': 220.0,
        })
        pmt_wizard._create_payments()

        caba_move = self.env['account.move'].search([('tax_cash_basis_rec_id', 'in', caba_inv.line_ids.matched_credit_ids.ids)])
        self.assertRecordValues(caba_move.line_ids, [
            {'account_id': self.cash_basis_base_account.id,     'debit': 200.01,    'credit': 0.0,      'amount_currency': 133.34,  'tax_ids': [],                                      'tax_line_id': False},
            {'account_id': self.cash_basis_base_account.id,     'debit': 0.0,       'credit': 200.01,   'amount_currency': -133.34, 'tax_ids': self.cash_basis_tax_a_third_amount.ids,  'tax_line_id': False},
            {'account_id': self.cash_basis_transfer_account.id, 'debit': 20.0,      'credit': 0.0,      'amount_currency': 13.33,   'tax_ids': [],                                      'tax_line_id': False},
            {'account_id': self.tax_account_1.id,               'debit': 0.0,       'credit': 20.0,     'amount_currency': -13.33,  'tax_ids': [],                                      'tax_line_id': self.cash_basis_tax_a_third_amount.id},
        ])

        receivable_line = caba_inv.line_ids.filtered(lambda x: x.account_id.internal_type == 'receivable')
        self.assertTrue(receivable_line.full_reconcile_id, "Invoice should be fully paid")

        exchange_move = receivable_line.full_reconcile_id.exchange_move_id
        self.assertRecordValues(exchange_move.line_ids, [
            {'account_id': self.extra_receivable_account_1.id,                          'debit': 0.0,   'credit': 0.0,  'amount_currency': 36.67,   'tax_ids': [], 'tax_line_id': False},
            {'account_id': caba_move.company_id.income_currency_exchange_account_id.id, 'debit': 0.0,   'credit': 0.0,  'amount_currency': -36.67,  'tax_ids': [], 'tax_line_id': False},
            {'account_id': self.cash_basis_base_account.id,                             'debit': 0.01,  'credit': 0.0,  'amount_currency': 0.0,     'tax_ids': self.cash_basis_tax_a_third_amount.ids,  'tax_line_id': False},
            {'account_id': self.cash_basis_base_account.id,                             'debit': 0.0,   'credit': 0.01, 'amount_currency': 0.0,     'tax_ids': [],                                      'tax_line_id': False},
        ])

        self.assertAmountsGroupByAccount([
            # Account                               Balance     Amount Currency
            (self.cash_basis_transfer_account,      0.0,        3.33),
            (self.tax_account_1,                    -20.0,      -13.33),
        ])

    def test_reconcile_cash_basis_revert(self):
        ''' Ensure the cash basis journal entry can be reverted. '''
        self.cash_basis_transfer_account.reconcile = True

        invoice_move = self.env['account.move'].create({
            'move_type': 'entry',
            'date': '2016-01-01',
            'line_ids': [
                # Base Tax line
                (0, 0, {
                    'debit': 0.0,
                    'credit': 100.0,
                    'account_id': self.company_data['default_account_revenue'].id,
                    'tax_ids': [(6, 0, self.cash_basis_tax_a_third_amount.ids)],
                    'tax_exigible': False,
                }),

                # Tax line
                (0, 0, {
                    'debit': 0.0,
                    'credit': 33.33,
                    'account_id': self.cash_basis_transfer_account.id,
                    'tax_repartition_line_id': self.cash_basis_tax_a_third_amount.invoice_repartition_line_ids.filtered(lambda line: line.repartition_type == 'tax').id,
                    'tax_exigible': False,
                }),

                # Receivable line
                (0, 0, {
                    'debit': 133.33,
                    'credit': 0.0,
                    'account_id': self.extra_receivable_account_1.id,
                }),
            ]
        })

        payment_move = self.env['account.move'].create({
            'move_type': 'entry',
            'date': '2016-01-01',
            'line_ids': [
                (0, 0, {'debit': 0.0,       'credit': 133.33,   'account_id': self.extra_receivable_account_1.id}),
                (0, 0, {'debit': 133.33,    'credit': 0.0,      'account_id': self.company_data['default_account_revenue'].id}),
            ]
        })

        (invoice_move + payment_move).action_post()

        receivable_lines = (invoice_move + payment_move).line_ids\
            .filtered(lambda line: line.account_id == self.extra_receivable_account_1)
        res = receivable_lines.reconcile()

        # == Check reconciliation of invoice with payment ==

        self.assertFullReconcile(res['full_reconcile'], receivable_lines)
        self.assertEqual(len(res.get('tax_cash_basis_moves', [])), 1)

        # == Check the reconciliation of invoice with tax cash basis journal entry.
        # /!\ We make the assumption the tax cash basis journal entry is well created.

        tax_cash_basis_move = res['tax_cash_basis_moves']

        taxes_lines = (invoice_move.line_ids + tax_cash_basis_move.line_ids.filtered('debit'))\
            .filtered(lambda line: line.account_id == self.cash_basis_transfer_account)
        taxes_full_reconcile = taxes_lines.matched_debit_ids.full_reconcile_id

        self.assertTrue(taxes_full_reconcile)
        self.assertFullReconcile(taxes_full_reconcile, taxes_lines)

        # == Check the reconciliation after the reverse ==

        tax_cash_basis_move_reverse = tax_cash_basis_move._reverse_moves(cancel=True)

        self.assertFullReconcile(res['full_reconcile'], receivable_lines)

        # == Check the reconciliation of the tax cash basis journal entry with its reverse ==

        reversed_taxes_lines = (tax_cash_basis_move + tax_cash_basis_move_reverse).line_ids\
            .filtered(lambda line: line.account_id == self.cash_basis_transfer_account)

        reversed_taxes_full_reconcile = reversed_taxes_lines.matched_debit_ids.full_reconcile_id

        self.assertTrue(reversed_taxes_full_reconcile)
        self.assertFullReconcile(reversed_taxes_full_reconcile, reversed_taxes_lines)

    def test_reconcile_cash_basis_tax_grid_refund(self):
        invoice_move = self.env['account.move'].create({
            'move_type': 'entry',
            'date': '2016-01-01',
            'line_ids': [
                # Base Tax line
                (0, 0, {
                    'debit': 0.0,
                    'credit': 100.0,
                    'account_id': self.company_data['default_account_revenue'].id,
                    'tax_ids': [(6, 0, self.cash_basis_tax_a_third_amount.ids)],
                    'tax_tag_ids': [(6, 0, self.tax_tags[0].ids)],
                    'tax_exigible': False,
                }),

                # Tax line
                (0, 0, {
                    'debit': 0.0,
                    'credit': 33.33,
                    'account_id': self.cash_basis_transfer_account.id,
                    'tax_repartition_line_id': self.cash_basis_tax_a_third_amount.invoice_repartition_line_ids.filtered(lambda line: line.repartition_type == 'tax').id,
                    'tax_tag_ids': [(6, 0, self.tax_tags[1].ids)],
                    'tax_exigible': False,
                }),

                # Receivable line
                (0, 0, {
                    'debit': 133.33,
                    'credit': 0.0,
                    'account_id': self.extra_receivable_account_1.id,
                }),
            ]
        })

        refund_move = self.env['account.move'].create({
            'move_type': 'out_refund',
            'partner_id': self.partner_a.id,
            'invoice_date': '2016-01-01',
            'date': '2016-01-01',
            'line_ids': [
                # Base Tax line
                (0, 0, {
                    'debit': 100.0,
                    'credit': 0.0,
                    'account_id': self.company_data['default_account_revenue'].id,
                    'tax_ids': [(6, 0, self.cash_basis_tax_a_third_amount.ids)],
                    'tax_tag_ids': [(6, 0, self.tax_tags[2].ids)],
                    'tax_exigible': False,
                }),

                # Tax line
                (0, 0, {
                    'debit': 33.33,
                    'credit': 0.0,
                    'account_id': self.cash_basis_transfer_account.id,
                    'tax_repartition_line_id': self.cash_basis_tax_a_third_amount.invoice_repartition_line_ids.filtered(lambda line: line.repartition_type == 'tax').id,
                    'tax_tag_ids': [(6, 0, self.tax_tags[3].ids)],
                    'tax_exigible': False,
                }),

                # Receivable line
                (0, 0, {
                    'debit': 0.0,
                    'credit': 133.33,
                    'account_id': self.extra_receivable_account_1.id,
                }),
            ]
        })

        (invoice_move + refund_move).action_post()

        receivable_lines = (invoice_move + refund_move).line_ids\
            .filtered(lambda line: line.account_id == self.extra_receivable_account_1)
        res = receivable_lines.reconcile()

        self.assertFullReconcile(res['full_reconcile'], receivable_lines)
        self.assertEqual(len(res.get('tax_cash_basis_moves', [])), 2)

        tax_cash_basis_moves = res['tax_cash_basis_moves'].sorted(lambda move: move.tax_cash_basis_move_id.id)

        # Invoice:
        cb_lines = tax_cash_basis_moves[0].line_ids.sorted(lambda line: (-abs(line.balance), -line.debit, line.account_id))
        self.assertRecordValues(cb_lines, [
            # Base amount:
            {'debit': 100.0,    'credit': 0.0,      'tax_tag_ids': [],                      'account_id': self.cash_basis_base_account.id},
            {'debit': 0.0,      'credit': 100.0,    'tax_tag_ids': self.tax_tags[0].ids,    'account_id': self.cash_basis_base_account.id},
            # tax:
            {'debit': 33.33,    'credit': 0.0,      'tax_tag_ids': [],                      'account_id': self.cash_basis_transfer_account.id},
            {'debit': 0.0,      'credit': 33.33,    'tax_tag_ids': self.tax_tags[1].ids,    'account_id': self.tax_account_1.id},
        ])

        # Refund:
        cb_lines = tax_cash_basis_moves[1].line_ids.sorted(lambda line: (-abs(line.balance), -line.debit, line.account_id))
        self.assertRecordValues(cb_lines, [
            # Base amount:
            {'debit': 100.0,    'credit': 0.0,      'tax_tag_ids': self.tax_tags[2].ids,    'account_id': self.cash_basis_base_account.id},
            {'debit': 0.0,      'credit': 100.0,    'tax_tag_ids': [],                      'account_id': self.cash_basis_base_account.id},
            # tax:
            {'debit': 33.33,    'credit': 0.0,      'tax_tag_ids': self.tax_tags[3].ids,    'account_id': self.tax_account_1.id},
            {'debit': 0.0,      'credit': 33.33,    'tax_tag_ids': [],                      'account_id': self.cash_basis_transfer_account.id},
        ])

        self.assertTaxGridAmounts([
            # Tag               Balance
            (self.tax_tags[0],  -100.0),
            (self.tax_tags[1],  -33.33),
            (self.tax_tags[2],  100.0),
            (self.tax_tags[3],  33.33),
        ])

    def test_reconcile_cash_basis_tax_grid_multi_taxes(self):
        ''' Test the tax grid when reconciling an invoice with multiple taxes/tax repartition. '''
        base_taxes = self.cash_basis_tax_a_third_amount + self.cash_basis_tax_tiny_amount
        base_tags = self.tax_tags[0] + self.tax_tags[4]

        # An invoice with 2 taxes:
        invoice_move = self.env['account.move'].create({
            'move_type': 'entry',
            'date': '2016-01-01',
            'line_ids': [
                # Base Tax line
                (0, 0, {
                    'debit': 0.0,
                    'credit': 100.0,
                    'account_id': self.company_data['default_account_revenue'].id,
                    'tax_ids': [(6, 0, base_taxes.ids)],
                    'tax_tag_ids': [(6, 0, base_tags.ids)],
                    'tax_exigible': False,
                }),

                # Tax lines
                (0, 0, {
                    'debit': 0.0,
                    'credit': 33.33,
                    'account_id': self.cash_basis_transfer_account.id,
                    'tax_repartition_line_id': self.cash_basis_tax_a_third_amount.invoice_repartition_line_ids.filtered(lambda line: line.repartition_type == 'tax').id,
                    'tax_tag_ids': [(6, 0, self.tax_tags[1].ids)],
                    'tax_exigible': False,
                }),
                (0, 0, {
                    'debit': 0.0,
                    'credit': 0.01,
                    'account_id': self.cash_basis_transfer_account.id,
                    'tax_repartition_line_id': self.cash_basis_tax_tiny_amount.invoice_repartition_line_ids.filtered(lambda line: line.repartition_type == 'tax').id,
                    'tax_tag_ids': [(6, 0, self.tax_tags[5].ids)],
                    'tax_exigible': False,
                }),

                # Receivable lines
                (0, 0, {
                    'debit': 133.34,
                    'credit': 0.0,
                    'account_id': self.extra_receivable_account_1.id,
                }),
            ]
        })

        # A payment paying the full invoice amount.
        payment_move = self.env['account.move'].create({
            'move_type': 'entry',
            'date': '2017-01-01',
            'line_ids': [
                (0, 0, {'debit': 0.0,       'credit': 133.34,   'account_id': self.extra_receivable_account_1.id}),
                (0, 0, {'debit': 133.34,    'credit': 0.0,      'account_id': self.company_data['default_account_revenue'].id}),
            ]
        })

        (invoice_move + payment_move).action_post()

        receivable_lines = (invoice_move + payment_move).line_ids\
            .filtered(lambda line: line.account_id == self.extra_receivable_account_1)
        res = receivable_lines.reconcile()

        self.assertFullReconcile(res['full_reconcile'], receivable_lines)
        self.assertEqual(len(res.get('tax_cash_basis_moves', [])), 1)

        self.assertRecordValues(res['tax_cash_basis_moves'].line_ids, [
            # Base amount x 2 because there is two taxes:
            {'debit': 100.0,    'credit': 0.0,      'tax_ids': [],              'tax_tag_ids': [],                      'account_id': self.cash_basis_base_account.id},
            {'debit': 0.0,      'credit': 100.0,    'tax_ids': base_taxes.ids,  'tax_tag_ids': base_tags.ids,           'account_id': self.cash_basis_base_account.id},
            # tax_1:
            {'debit': 33.33,    'credit': 0.0,      'tax_ids': [],              'tax_tag_ids': [],                      'account_id': self.cash_basis_transfer_account.id},
            {'debit': 0.0,      'credit': 33.33,    'tax_ids': [],              'tax_tag_ids': self.tax_tags[1].ids,    'account_id': self.tax_account_1.id},
            # tax_2:
            {'debit': 0.01,     'credit': 0.0,      'tax_ids': [],              'tax_tag_ids': [],                      'account_id': self.cash_basis_transfer_account.id},
            {'debit': 0.0,      'credit': 0.01,     'tax_ids': [],              'tax_tag_ids': self.tax_tags[5].ids,    'account_id': self.tax_account_2.id},
        ])

        self.assertTaxGridAmounts([
            # Tag               Balance
            (self.tax_tags[0],  -100.0),
            (self.tax_tags[1],  -33.33),
            (self.tax_tags[4],  -100.0),
            (self.tax_tags[5],  -0.01),
        ])

    def test_caba_mix_reconciliation(self):
        """ Test the reconciliation of tax lines (when using a reconcilable tax account)
        for cases mixing taxes exigible on payment and on invoices.
        """

        # Make the tax account reconcilable
        self.tax_account_1.reconcile = True

        # Create a tax using the same accounts as the CABA one
        non_caba_tax = self.env['account.tax'].create({
            'name': 'tax 20%',
            'type_tax_use': 'purchase',
            'company_id': self.company_data['company'].id,
            'amount': 20,
            'tax_exigibility': 'on_invoice',
            'invoice_repartition_line_ids': [
                (0,0, {
                    'factor_percent': 100,
                    'repartition_type': 'base',
                }),

                (0,0, {
                    'factor_percent': 100,
                    'repartition_type': 'tax',
                    'account_id': self.tax_account_1.id,
                }),
            ],
            'refund_repartition_line_ids': [
                (0,0, {
                    'factor_percent': 100,
                    'repartition_type': 'base',
                }),

                (0,0, {
                    'factor_percent': 100,
                    'repartition_type': 'tax',
                    'account_id': self.tax_account_1.id,
                }),
            ],
        })

        # Create an invoice with a non-CABA tax
        non_caba_inv = self.init_invoice('in_invoice', amounts=[1000], post=True, taxes=non_caba_tax)

        # Create an invoice with a CABA tax using the same tax account and pay it
        caba_inv = self.init_invoice('in_invoice', amounts=[300], post=True, taxes=self.cash_basis_tax_a_third_amount)

        pmt_wizard = self.env['account.payment.register'].with_context(active_model='account.move', active_ids=caba_inv.ids).create({
            'payment_date': caba_inv.date,
            'journal_id': self.company_data['default_journal_bank'].id,
            'payment_method_id': self.env.ref('account.account_payment_method_manual_in').id,
        })
        pmt_wizard._create_payments()

        partial_rec = caba_inv.mapped('line_ids.matched_debit_ids')
        caba_move = self.env['account.move'].search([('tax_cash_basis_rec_id', '=', partial_rec.id)])

        # Create a misc operation with a line on the tax account, for full reconcile of those tax lines
        misc_move = self.env['account.move'].create({
            'name': "Misc move",
            'journal_id': self.company_data['default_journal_misc'].id,
            'line_ids': [
                (0, 0, {
                    'name': 'line 1',
                    'account_id': self.tax_account_1.id,
                    'credit': 300,
                }),
                (0, 0, {
                    'name': 'line 2',
                    'account_id': self.company_data['default_account_expense'].id, # Whatever the account here
                    'debit': 300,
                })
            ],
        })

        misc_move.action_post()

        lines_to_reconcile = (misc_move + caba_move + non_caba_inv).mapped('line_ids').filtered(lambda x: x.account_id == self.tax_account_1)
        lines_to_reconcile.reconcile()

        # Check full reconciliation
        self.assertTrue(all(line.full_reconcile_id for line in lines_to_reconcile), "All tax lines should be fully reconciled")

    def test_caba_double_tax(self):
        """ Test the CABA entries generated from an invoice with almost
        equal lines, different only on analytic accounting
        """
        # Make the tax account reconcilable
        self.tax_account_1.reconcile = True

        # Create an invoice with a CABA tax using 'Include in analytic cost'
        move_form = Form(self.env['account.move'].with_context(default_move_type='in_invoice', account_predictive_bills_disable_prediction=True))
        move_form.invoice_date = fields.Date.from_string('2019-01-01')
        move_form.partner_id = self.partner_a
        self.cash_basis_tax_a_third_amount.analytic = True
        test_analytic_account = self.env['account.analytic.account'].create({'name': 'test_analytic_account'})

        tax = self.cash_basis_tax_a_third_amount

        # line with analytic account, will generate 2 lines in CABA move
        with move_form.invoice_line_ids.new() as line_form:
            line_form.name = "test line with analytic account"
            line_form.product_id = self.product_a
            line_form.tax_ids.clear()
            line_form.tax_ids.add(tax)
            line_form.analytic_account_id = test_analytic_account
            line_form.price_unit = 100

        # line with analytic account, will generate other 2 lines in CABA move
        # even if the tax is the same
        with move_form.invoice_line_ids.new() as line_form:
            line_form.name = "test line"
            line_form.product_id = self.product_a
            line_form.tax_ids.clear()
            line_form.tax_ids.add(tax)
            line_form.price_unit = 100

        rslt = move_form.save()
        rslt.action_post()

        pmt_wizard = self.env['account.payment.register'].with_context(active_model='account.move', active_ids=rslt.ids).create({
            'amount': rslt.amount_total,
            'payment_date': rslt.date,
            'journal_id': self.company_data['default_journal_bank'].id,
            'payment_method_id': self.env.ref('account.account_payment_method_manual_in').id,
        })
        pmt_wizard._create_payments()

        partial_rec = rslt.mapped('line_ids.matched_debit_ids')
        caba_move = self.env['account.move'].search([('tax_cash_basis_rec_id', '=', partial_rec.id)])
        self.assertEqual(len(caba_move.line_ids), 4, "All lines should be there")
        self.assertEqual(caba_move.line_ids.filtered(lambda x: x.tax_line_id).balance, 66.66, "Tax amount should take into account both lines")

    def test_caba_double_tax_negative_line(self):
        """ Tests making a cash basis invoice with 2 lines using the same tax: a positive and a negative one.
        """
        invoice = self.init_invoice('in_invoice', amounts=[300, -60], post=True, taxes=self.cash_basis_tax_a_third_amount)

        pmt_wizard = self.env['account.payment.register'].with_context(active_model='account.move', active_ids=invoice.ids).create({
            'amount': 320,
            'payment_date': invoice.date,
            'journal_id': self.company_data['default_journal_bank'].id,
            'payment_method_id': self.env.ref('account.account_payment_method_manual_in').id,
        })

        pmt_wizard._create_payments()

        partial_rec = invoice.mapped('line_ids.matched_debit_ids')
        caba_move = self.env['account.move'].search([('tax_cash_basis_rec_id', '=', partial_rec.id)])

        self.assertRecordValues(caba_move.line_ids.sorted(lambda line: (-abs(line.balance), -line.debit, line.account_id)), [
            # Base amount:
            {'debit': 240.0,    'credit': 0.0,      'tax_tag_ids': self.tax_tags[0].ids,    'account_id': self.cash_basis_base_account.id},
            {'debit': 0.0,      'credit': 240.0,    'tax_tag_ids': [],                      'account_id': self.cash_basis_base_account.id},
            # tax:
            {'debit': 80.0,     'credit': 0.0,      'tax_tag_ids': self.tax_tags[1].ids,    'account_id': self.tax_account_1.id},
            {'debit': 0.0,      'credit': 80.0,     'tax_tag_ids': [],                      'account_id': self.cash_basis_transfer_account.id},
        ])

    def test_caba_dest_acc_reconciliation_partial_pmt(self):
        """ Test the reconciliation of tax lines (when using a reconcilable tax account)
        for partially paid invoices with cash basis taxes.
        This test is especially useful to check the implementation of the use case tested by
        test_reconciliation_cash_basis_foreign_currency_low_values does not have unwanted side effects.
        """

        # Make the tax account reconcilable
        self.tax_account_1.reconcile = True

        # Create an invoice with a CABA tax using the same tax account and pay half of it
        caba_inv = self.init_invoice('in_invoice', amounts=[900], post=True, taxes=self.cash_basis_tax_a_third_amount)

        pmt_wizard = self.env['account.payment.register'].with_context(active_model='account.move', active_ids=caba_inv.ids).create({
            'amount': 600,
            'payment_date': caba_inv.date,
            'journal_id': self.company_data['default_journal_bank'].id,
            'payment_method_id': self.env.ref('account.account_payment_method_manual_in').id,
        })
        pmt_wizard._create_payments()

        partial_rec = caba_inv.mapped('line_ids.matched_debit_ids')
        caba_move = self.env['account.move'].search([('tax_cash_basis_rec_id', '=', partial_rec.id)])

        # Create a misc operation with a line on the tax account, for full reconcile with the tax line
        misc_move = self.env['account.move'].create({
            'name': "Misc move",
            'journal_id': self.company_data['default_journal_misc'].id,
            'line_ids': [
                (0, 0, {
                    'name': 'line 1',
                    'account_id': self.tax_account_1.id,
                    'credit': 150,
                }),
                (0, 0, {
                    'name': 'line 2',
                    'account_id': self.company_data['default_account_expense'].id, # Whatever the account here
                    'debit': 150,
                })
            ],
        })

        misc_move.action_post()

        lines_to_reconcile = (misc_move + caba_move).mapped('line_ids').filtered(lambda x: x.account_id == self.tax_account_1)
        lines_to_reconcile.reconcile()

        # Check full reconciliation
        self.assertTrue(all(line.full_reconcile_id for line in lines_to_reconcile), "All tax lines should be fully reconciled")

    def test_caba_undo_reconciliation(self):
        ''' Make sure there is no traceback like "Record has already been deleted" during the deletion of partials. '''
        self.cash_basis_transfer_account.reconcile = True

        bill = self.env['account.move'].create({
            'move_type': 'in_invoice',
            'partner_id': self.partner_a.id,
            'invoice_date': '2019-01-01',
            'date': '2019-01-01',
            'invoice_line_ids': [(0, 0, {
                'name': 'line',
                'account_id': self.company_data['default_account_expense'].id,
                'price_unit': 1000.0,
                'tax_ids': [(6, 0, self.cash_basis_tax_a_third_amount.ids)],
            })],
        })
        bill.action_post()

        # Register a payment creating the CABA journal entry on the fly and reconcile it with the tax line.
        self.env['account.payment.register']\
            .with_context(active_ids=bill.ids, active_model='account.move')\
            .create({})\
            ._create_payments()

        bill.button_draft()
