# -*- coding: utf-8 -*-
from odoo.addons.fusion_accounting.tests.test_bank_rec_widget_common import TestBankRecWidgetCommon
from odoo.tests import tagged
from odoo.tools import html2plaintext
from odoo import fields, Command

from freezegun import freeze_time
from unittest.mock import patch
import re


@tagged('post_install', '-at_install')
class TestBankRecWidget(TestBankRecWidgetCommon):

    @classmethod
    def setUpClass(cls):
        super().setUpClass()
        cls.company_data_2 = cls.setup_other_company()

        cls.early_payment_term = cls.env['account.payment.term'].create({
            'name': "early_payment_term",
            'company_id': cls.company_data['company'].id,
            'discount_percentage': 10,
            'discount_days': 10,
            'early_discount': True,
            'line_ids': [
                Command.create({
                    'value': 'percent',
                    'value_amount': 100,
                    'nb_days': 20,
                }),
            ],
        })

        cls.account_revenue1 = cls.company_data['default_account_revenue']
        cls.account_revenue2 = cls.copy_account(cls.account_revenue1)

        cls.reco_model_bill = cls.env['account.reconcile.model'].create({
            'name': "test create bill",
            'rule_type': 'writeoff_button',
            'counterpart_type': 'purchase',
            'line_ids': [
                Command.create({'amount_string': '50'}),
                Command.create({'amount_string': '50'}),
            ],
        })

    def assert_form_extra_text_value(self, wizard, regex):
        line = wizard.line_ids.filtered(lambda x: x.index == wizard.form_index)
        value = line.suggestion_html
        if regex:
            cleaned_value = html2plaintext(value).replace('\n', '')
            if not re.match(regex, cleaned_value):
                self.fail(f"The following 'form_extra_text':\n\n'{cleaned_value}'\n\n...doesn't match the provided regex:\n\n'{regex}'")
        else:
            self.assertFalse(value)

    def test_retrieve_partner_from_account_number(self):
        st_line = self._create_st_line(1000.0, partner_id=None, account_number="014 474 8555")
        bank_account = self.env['res.partner.bank'].create({
            'acc_number': '0144748555',
            'partner_id': self.partner_a.id,
        })
        self.assertEqual(st_line._retrieve_partner(), bank_account.partner_id)

        # Can't retrieve the partner since the bank account is used by multiple partners.
        self.env['res.partner.bank'].create({
            'acc_number': '0144748555',
            'partner_id': self.partner_b.id,
        })
        self.assertEqual(st_line._retrieve_partner(), self.env['res.partner'])

        # Archive partner_a and see if partner_b is then chosen
        self.partner_a.active = False
        self.assertEqual(st_line._retrieve_partner(), self.partner_b)

    def test_retrieve_partner_from_account_number_in_other_company(self):
        st_line = self._create_st_line(1000.0, partner_id=None, account_number="014 474 8555")
        self.env['res.partner.bank'].create({
            'acc_number': '0144748555',
            'partner_id': self.partner_a.id,
        })

        # Bank account is owned by another company.
        new_company = self.env['res.company'].create({'name': "test_retrieve_partner_from_account_number_in_other_company"})
        self.partner_a.company_id = new_company
        self.assertEqual(st_line._retrieve_partner(), self.env['res.partner'])

    def test_retrieve_partner_from_partner_name(self):
        """ Ensure the partner having a name fitting exactly the 'partner_name' is retrieved first.
        This test create two partners that will be ordered in the lexicographic order when performing
        a search. So:
        row1: "Turlututu tsoin tsoin"
        row2: "turlututu"

        Since "turlututu" matches exactly (case insensitive) the partner_name of the statement line,
        it should be suggested first.

        However if we have two partners called turlututu, we should not suggest any or we risk selecting
        the wrong one.
        """
        _partner_a, partner_b = self.env['res.partner'].create([
            {'name': "Turlututu tsoin tsoin"},
            {'name': "turlututu"},
        ])

        st_line = self._create_st_line(1000.0, partner_id=None, partner_name="Turlututu")
        self.assertEqual(st_line._retrieve_partner(), partner_b)

        self.env['res.partner'].create({'name': "turlututu"})
        self.assertFalse(st_line._retrieve_partner())

    def test_retrieve_partner_suggested_account_from_rank(self):
        """ Ensure a retrieved partner is proposing his receivable/payable according his customer/supplier rank. """
        partner = self.env['res.partner'].create({'name': "turlututu"})
        rec_account_id = partner.property_account_receivable_id.id
        pay_account_id = partner.property_account_payable_id.id

        st_line = self._create_st_line(1000.0, partner_id=None, partner_name="turlututu")
        liq_account_id = st_line.journal_id.default_account_id.id
        wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',     'account_id': liq_account_id, 'balance': 1000.0},
            {'flag': 'auto_balance',  'account_id': rec_account_id, 'balance': -1000.0},
        ])

        partner._increase_rank('supplier_rank', 1)
        wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',     'account_id': liq_account_id, 'balance': 1000.0},
            {'flag': 'auto_balance',  'account_id': pay_account_id, 'balance': -1000.0},
        ])

    def test_res_partner_bank_find_create_when_archived(self):
        """ Test we don't get the "The combination Account Number/Partner must be unique." error with archived
        bank account.
        """
        partner = self.env['res.partner'].create({
            'name': "Zitycard",
            'bank_ids': [Command.create({
                'acc_number': "123456789",
                'active': False,
            })],
        })

        st_line = self._create_st_line(
            100.0,
            partner_name="Zeumat Zitycard",
            account_number="123456789",
        )
        inv_line = self._create_invoice_line(
            'out_invoice',
            partner_id=partner.id,
            invoice_line_ids=[{'price_unit': 100.0, 'tax_ids': []}],
        )
        wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
        wizard._action_add_new_amls(inv_line)
        wizard._action_validate()

        # Should not trigger the error.
        self.env['res.partner.bank'].flush_model()

    def test_res_partner_bank_find_create_multi_company(self):
        """ Test we don't get the "The combination Account Number/Partner must be unique." error when the bank account
        already exists on another company.
        """
        partner = self.env['res.partner'].create({
            'name': "Zitycard",
            'bank_ids': [Command.create({'acc_number': "123456789"})],
        })
        partner.bank_ids.company_id = self.company_data_2['company']
        self.env.user.company_ids = self.env.company

        st_line = self._create_st_line(
            100.0,
            partner_name="Zeumat Zitycard",
            account_number="123456789",
        )
        inv_line = self._create_invoice_line(
            'out_invoice',
            partner_id=partner.id,
            invoice_line_ids=[{'price_unit': 100.0, 'tax_ids': []}],
        )
        wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
        wizard._action_add_new_amls(inv_line)
        wizard._action_validate()

        # Should not trigger the error.
        self.env['res.partner.bank'].flush_model()

    def test_validation_base_case(self):
        st_line = self._create_st_line(
            1000.0,
            date='2017-01-01',
        )

        wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
        line = wizard.line_ids.filtered(lambda x: x.flag == 'auto_balance')
        wizard._js_action_mount_line_in_edit(line.index)
        line.account_id = self.account_revenue1
        wizard._line_value_changed_account_id(line)
        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',     'amount_currency': 1000.0,  'currency_id': self.company_data['currency'].id,    'balance': 1000.0},
            {'flag': 'manual',        'amount_currency': -1000.0, 'currency_id': self.company_data['currency'].id,    'balance': -1000.0},
        ])
        self.assertRecordValues(wizard, [{'state': 'valid'}])

        # The amount is the same, no message under the 'amount' field.
        self.assert_form_extra_text_value(wizard, False)

        wizard._action_validate()
        self.assertRecordValues(st_line.line_ids, [
            # pylint: disable=C0326
            {'account_id': st_line.journal_id.default_account_id.id,    'amount_currency': 1000.0,      'currency_id': self.company_data['currency'].id,    'balance': 1000.0,  'reconciled': False},
            {'account_id': self.account_revenue1.id,                    'amount_currency': -1000.0,     'currency_id': self.company_data['currency'].id,    'balance': -1000.0, 'reconciled': False},
        ])

        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity', 'account_id': st_line.journal_id.default_account_id.id,  'amount_currency': 1000.0,  'currency_id': self.company_data['currency'].id,    'balance': 1000.0},
            {'flag': 'aml',       'account_id': self.account_revenue1.id,                  'amount_currency': -1000.0, 'currency_id': self.company_data['currency'].id,    'balance': -1000.0},
        ])

    def test_validation_exchange_difference(self):
        # 240.0 curr2 == 120.0 comp_curr
        st_line = self._create_st_line(
            120.0,
            date='2017-01-01',
            foreign_currency_id=self.other_currency.id,
            amount_currency=240.0,
        )
        # 240.0 curr2 == 80.0 comp_curr
        inv_line = self._create_invoice_line(
            'out_invoice',
            currency_id=self.other_currency.id,
            invoice_date='2016-01-01',
            invoice_line_ids=[{'price_unit': 240.0}],
        )

        wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
        wizard._action_add_new_amls(inv_line)
        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',       'amount_currency': 120.0,       'currency_id': self.company_data['currency'].id,    'balance': 120.0},
            {'flag': 'new_aml',         'amount_currency': -240.0,      'currency_id': self.other_currency.id,              'balance': -80.0},
            {'flag': 'exchange_diff',   'amount_currency': 0.0,         'currency_id': self.other_currency.id,              'balance': -40.0},
        ])
        self.assertRecordValues(wizard, [{'state': 'valid'}])

        wizard._action_validate()

        # Check the statement line.
        self.assertRecordValues(st_line.line_ids.sorted(), [
            # pylint: disable=C0326
            {'account_id': st_line.journal_id.default_account_id.id,    'amount_currency': 120.0,   'currency_id': self.company_data['currency'].id,    'balance': 120.0,  'reconciled': False},
            {'account_id': inv_line.account_id.id,                      'amount_currency': -240.0,  'currency_id': self.other_currency.id,              'balance': -120.0, 'reconciled': True},
        ])

        # Check the partials.
        partials = st_line.line_ids.matched_debit_ids
        exchange_move = partials.exchange_move_id
        _liquidity_line, _suspense_line, other_line = st_line._seek_for_lines()
        self.assertRecordValues(partials.sorted(), [
            # pylint: disable=C0326
            {
                'amount': 40.0,
                'debit_amount_currency': 0.0,
                'credit_amount_currency': 0.0,
                'debit_move_id': exchange_move.line_ids.sorted()[0].id,
                'credit_move_id': other_line.id,
                'exchange_move_id': False,
            },
            {
                'amount': 80.0,
                'debit_amount_currency': 240.0,
                'credit_amount_currency': 240.0,
                'debit_move_id': inv_line.id,
                'credit_move_id': other_line.id,
                'exchange_move_id': exchange_move.id,
            },
        ])

        # Check the exchange diff journal entry.
        self.assertRecordValues(exchange_move.line_ids.sorted(), [
            # pylint: disable=C0326
            {'account_id': inv_line.account_id.id,                                  'amount_currency': 0.0,     'currency_id': self.other_currency.id,  'balance': 40.0,  'reconciled': True},
            {'account_id': self.env.company.income_currency_exchange_account_id.id, 'amount_currency': 0.0,     'currency_id': self.other_currency.id,  'balance': -40.0, 'reconciled': False},
        ])

    def test_validation_new_aml_same_foreign_currency(self):
        income_exchange_account = self.env.company.income_currency_exchange_account_id

        # 6000.0 curr2 == 1200.0 comp_curr (bank rate 5:1 instead of the odoo rate 4:1)
        st_line = self._create_st_line(
            1200.0,
            date='2017-01-01',
            foreign_currency_id=self.other_currency_2.id,
            amount_currency=6000.0,
        )
        # 6000.0 curr2 == 1000.0 comp_curr (rate 6:1)
        inv_line = self._create_invoice_line(
            'out_invoice',
            currency_id=self.other_currency_2.id,
            invoice_date='2016-01-01',
            invoice_line_ids=[{'price_unit': 6000.0}],
        )

        wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
        wizard._action_add_new_amls(inv_line)
        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',       'amount_currency': 1200.0,      'currency_id': self.company_data['currency'].id,    'balance': 1200.0},
            {'flag': 'new_aml',         'amount_currency': -6000.0,     'currency_id': self.other_currency_2.id,            'balance': -1000.0},
            {'flag': 'exchange_diff',   'amount_currency': 0.0,         'currency_id': self.other_currency_2.id,            'balance': -200.0},
        ])
        self.assertRecordValues(wizard, [{'state': 'valid'}])

        # The amount is the same, no message under the 'amount' field.
        self.assert_form_extra_text_value(wizard, False)

        wizard._action_validate()
        self.assertRecordValues(st_line.line_ids, [
            # pylint: disable=C0326
            {'account_id': st_line.journal_id.default_account_id.id,    'amount_currency': 1200.0,      'currency_id': self.company_data['currency'].id,    'balance': 1200.0,  'reconciled': False},
            {'account_id': inv_line.account_id.id,                      'amount_currency': -6000.0,     'currency_id': self.other_currency_2.id,            'balance': -1200.0, 'reconciled': True},
        ])
        self.assertRecordValues(st_line, [{'is_reconciled': True}])
        self.assertRecordValues(inv_line.move_id, [{'payment_state': 'paid'}])
        self.assertRecordValues(inv_line.matched_credit_ids.exchange_move_id.line_ids, [
            # pylint: disable=C0326
            {'account_id': inv_line.account_id.id,                      'amount_currency': 0.0,         'currency_id': self.other_currency_2.id, 'balance': 200.0,   'reconciled': True,  'date': fields.Date.from_string('2017-01-31')},
            {'account_id': income_exchange_account.id,                  'amount_currency': 0.0,         'currency_id': self.other_currency_2.id, 'balance': -200.0,  'reconciled': False, 'date': fields.Date.from_string('2017-01-31')},
        ])

        # Reset the wizard.
        wizard._js_action_reset()
        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',       'amount_currency': 1200.0,      'currency_id': self.company_data['currency'].id,    'balance': 1200.0},
            {'flag': 'auto_balance',    'amount_currency': -6000.0,     'currency_id': self.other_currency_2.id,            'balance': -1200.0},
        ])

        # Create the same invoice with a higher amount to check the partial flow.
        # 9000.0 curr2 == 1500.0 comp_curr (rate 6:1)
        inv_line = self._create_invoice_line(
            'out_invoice',
            currency_id=self.other_currency_2.id,
            invoice_date='2016-01-01',
            invoice_line_ids=[{'price_unit': 9000.0}],
        )
        wizard._action_add_new_amls(inv_line)
        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',       'amount_currency': 1200.0,      'currency_id': self.company_data['currency'].id,    'balance': 1200.0,  'name': "turlututu"},
            {'flag': 'new_aml',         'amount_currency': -6000.0,     'currency_id': self.other_currency_2.id,            'balance': -1000.0, 'name': "INV/2016/00002"},
            {'flag': 'exchange_diff',   'amount_currency': 0.0,         'currency_id': self.other_currency_2.id,            'balance': -200.0,  'name': "Exchange Difference: INV/2016/00002"},
        ])

        # Check the message under the 'amount' field.
        line = wizard.line_ids.filtered(lambda x: x.flag == 'new_aml')
        wizard._js_action_mount_line_in_edit(line.index)
        self.assert_form_extra_text_value(
            wizard,
            r".+open amount of 9,000.000.+ reduced by 6,000.000.+ set the invoice as fully paid .",
        )
        self.assertRecordValues(line, [{
            'suggestion_amount_currency': -9000.0,
            'suggestion_balance': -1500.0,
        }])

        # Switch to a full reconciliation.
        wizard._js_action_apply_line_suggestion(line.index)
        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',       'amount_currency': 1200.0,      'currency_id': self.company_data['currency'].id,    'balance': 1200.0,  'name': "turlututu"},
            {'flag': 'new_aml',         'amount_currency': -9000.0,     'currency_id': self.other_currency_2.id,            'balance': -1500.0, 'name': "INV/2016/00002"},
            {'flag': 'exchange_diff',   'amount_currency': 0.0,         'currency_id': self.other_currency_2.id,            'balance': -300.0,  'name': "Exchange Difference: INV/2016/00002"},
            {'flag': 'auto_balance',    'amount_currency': 3000.0,      'currency_id': self.other_currency_2.id,            'balance': 600.0,   'name': "Open balance of 6,000.000 $"},
        ])

        # Check the message under the 'amount' field.
        line = wizard.line_ids.filtered(lambda x: x.flag == 'new_aml')
        wizard._js_action_mount_line_in_edit(line.index)
        self.assert_form_extra_text_value(
            wizard,
            r".+open amount of 9,000.000.+ paid .+ record a partial payment .",
        )
        self.assertRecordValues(line, [{
            'suggestion_amount_currency': -6000.0,
            'suggestion_balance': -1000.0,
        }])

        # Switch back to a partial reconciliation.
        wizard._js_action_apply_line_suggestion(line.index)
        self.assertRecordValues(wizard, [{'state': 'valid'}])

        # Reconcile
        wizard._action_validate()
        self.assertRecordValues(st_line.line_ids, [
            # pylint: disable=C0326
            {'account_id': st_line.journal_id.default_account_id.id,    'amount_currency': 1200.0,      'currency_id': self.company_data['currency'].id,    'balance': 1200.0,  'reconciled': False},
            {'account_id': inv_line.account_id.id,                      'amount_currency': -6000.0,     'currency_id': self.other_currency_2.id,            'balance': -1200.0, 'reconciled': True},
        ])
        self.assertRecordValues(st_line, [{'is_reconciled': True}])
        self.assertRecordValues(inv_line.move_id, [{
            'payment_state': 'partial',
            'amount_residual': 3000.0,
        }])
        self.assertRecordValues(inv_line.matched_credit_ids.exchange_move_id.line_ids, [
            # pylint: disable=C0326
            {'account_id': inv_line.account_id.id,                      'amount_currency': 0.0,         'currency_id': self.other_currency_2.id, 'balance': 200.0,   'reconciled': True,  'date': fields.Date.from_string('2017-01-31')},
            {'account_id': income_exchange_account.id,                  'amount_currency': 0.0,         'currency_id': self.other_currency_2.id, 'balance': -200.0,  'reconciled': False, 'date': fields.Date.from_string('2017-01-31')},
        ])

    def test_validation_expense_exchange_difference(self):
        expense_exchange_account = self.env.company.expense_currency_exchange_account_id

        # 1200.0 comp_curr = 3600.0 foreign_curr in 2016 (rate 1:3)
        st_line = self._create_st_line(
            1200.0,
            date='2016-01-01',
        )
        # 1800.0 comp_curr = 3600.0 foreign_curr in 2017 (rate 1:2)
        inv_line = self._create_invoice_line(
            'out_invoice',
            currency_id=self.other_currency.id,
            invoice_date='2017-01-01',
            invoice_line_ids=[{'price_unit': 3600.0}],
        )

        wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
        wizard._action_add_new_amls(inv_line)
        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',       'amount_currency': 1200.0,      'currency_id': self.company_data['currency'].id,    'balance': 1200.0},
            {'flag': 'new_aml',         'amount_currency': -3600.0,     'currency_id': self.other_currency.id,              'balance': -1800.0},
            {'flag': 'exchange_diff',   'amount_currency': 0.0,         'currency_id': self.other_currency.id,              'balance': 600.0},
        ])
        self.assertRecordValues(wizard, [{'state': 'valid'}])

        wizard._action_validate()
        self.assertRecordValues(st_line.line_ids, [
            # pylint: disable=C0326
            {'account_id': st_line.journal_id.default_account_id.id,    'amount_currency': 1200.0,      'currency_id': self.company_data['currency'].id,    'balance': 1200.0,   'reconciled': False},
            {'account_id': inv_line.account_id.id,                      'amount_currency': -3600.0,     'currency_id': self.other_currency.id,              'balance': -1200.0,  'reconciled': True},
        ])
        self.assertRecordValues(st_line, [{'is_reconciled': True}])
        self.assertRecordValues(inv_line.move_id, [{'payment_state': 'paid'}])
        self.assertRecordValues(inv_line.matched_credit_ids.exchange_move_id.line_ids, [
            # pylint: disable=C0326
            {'account_id': inv_line.account_id.id,          'amount_currency': 0.0,     'currency_id': self.other_currency.id,   'balance': -600.0,  'reconciled': True,     'date': fields.Date.from_string('2017-01-31')},
            {'account_id': expense_exchange_account.id,     'amount_currency': 0.0,     'currency_id': self.other_currency.id,   'balance': 600.0,   'reconciled': False,    'date': fields.Date.from_string('2017-01-31')},
        ])
        # Checks that the wizard still display the 3 initial lines
        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',       'amount_currency': 1200.0,      'currency_id': self.company_data['currency'].id,    'balance': 1200.0},
            {'flag': 'aml',             'amount_currency': -3600.0,     'currency_id': self.other_currency.id,              'balance': -1800.0},
            {'flag': 'aml',             'amount_currency': 0.0,         'currency_id': self.other_currency.id,              'balance': 600.0},
        ])

    def test_validation_income_exchange_difference(self):
        income_exchange_account = self.env.company.income_currency_exchange_account_id

        # 1800.0 comp_curr = 3600.0 foreign_curr in 2017 (rate 1:2)
        st_line = self._create_st_line(
            1800.0,
            date='2017-01-01',
        )
        # 1200.0 comp_curr = 3600.0 foreign_curr in 2016 (rate 1:3)
        inv_line = self._create_invoice_line(
            'out_invoice',
            currency_id=self.other_currency.id,
            invoice_date='2016-01-01',
            invoice_line_ids=[{'price_unit': 3600.0}],
        )

        wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
        wizard._action_add_new_amls(inv_line)
        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',       'amount_currency': 1800.0,      'currency_id': self.company_data['currency'].id,    'balance': 1800.0},
            {'flag': 'new_aml',         'amount_currency': -3600.0,     'currency_id': self.other_currency.id,              'balance': -1200.0},
            {'flag': 'exchange_diff',   'amount_currency': 0.0,         'currency_id': self.other_currency.id,              'balance': -600.0},
        ])
        self.assertRecordValues(wizard, [{'state': 'valid'}])

        wizard._action_validate()
        self.assertRecordValues(st_line.line_ids, [
            # pylint: disable=C0326
            {'account_id': st_line.journal_id.default_account_id.id,    'amount_currency': 1800.0,      'currency_id': self.company_data['currency'].id,    'balance': 1800.0,   'reconciled': False},
            {'account_id': inv_line.account_id.id,                      'amount_currency': -3600.0,     'currency_id': self.other_currency.id,              'balance': -1800.0,  'reconciled': True},
        ])
        self.assertRecordValues(st_line, [{'is_reconciled': True}])
        self.assertRecordValues(inv_line.move_id, [{'payment_state': 'paid'}])
        self.assertRecordValues(inv_line.matched_credit_ids.exchange_move_id.line_ids, [
            # pylint: disable=C0326
            {'account_id': inv_line.account_id.id,          'amount_currency': 0.0,     'currency_id': self.other_currency.id,   'balance': 600.0,   'reconciled': True,     'date': fields.Date.from_string('2017-01-31')},
            {'account_id': income_exchange_account.id,      'amount_currency': 0.0,     'currency_id': self.other_currency.id,   'balance': -600.0,  'reconciled': False,    'date': fields.Date.from_string('2017-01-31')},
        ])
        # Checks that the wizard still display the 3 initial lines
        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',       'amount_currency': 1800.0,      'currency_id': self.company_data['currency'].id,    'balance': 1800.0},
            {'flag': 'aml',             'amount_currency': -3600.0,     'currency_id': self.other_currency.id,              'balance': -1200.0},
            {'flag': 'aml',             'amount_currency': 0.0,         'currency_id': self.other_currency.id,              'balance': -600.0},
        ])

    def test_validation_income_exchange_difference_with_rounding(self):
        # 1000.0 comp_curr = 3000.0 foreign_curr in 2016 (rate 1:3)
        # However divided in 3 invoices + rounding we have 333.33333 ≃ 333.33 comp_curr = 1000.0 foreign_curr
        # this implies that the full amount has been used in foreign_curr but there is 0.01 in comp_curr
        st_line = self._create_st_line(
            1000.0,
            date='2016-01-01',
        )

        # 1500 comp_curr = 3000.0 foreign_curr in 2017 (rate 1:2)
        inv_line_1 = self._create_invoice_line(
            'out_invoice',
            currency_id=self.other_currency.id,
            invoice_date='2017-01-01',
            invoice_line_ids=[{'price_unit': 1000.0}],
        )

        inv_line_2 = self._create_invoice_line(
            'out_invoice',
            currency_id=self.other_currency.id,
            invoice_date='2017-01-01',
            invoice_line_ids=[{'price_unit': 1000.0}],
        )

        inv_line_3 = self._create_invoice_line(
            'out_invoice',
            currency_id=self.other_currency.id,
            invoice_date='2017-01-01',
            invoice_line_ids=[{'price_unit': 1000.0}],
        )

        wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
        wizard._action_add_new_amls(inv_line_1 + inv_line_2 + inv_line_3)

        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',       'amount_currency': 1000.0,      'currency_id': self.company_data['currency'].id,    'balance': 1000.0},
            {'flag': 'new_aml',         'amount_currency': -1000.0,     'currency_id': self.other_currency.id,   'balance': -500.0},
            {'flag': 'exchange_diff',   'amount_currency': 0.0,         'currency_id': self.other_currency.id,   'balance': 166.67},
            {'flag': 'new_aml',         'amount_currency': -1000.0,     'currency_id': self.other_currency.id,   'balance': -500.0},
            {'flag': 'exchange_diff',   'amount_currency': 0.0,         'currency_id': self.other_currency.id,   'balance': 166.67},
            {'flag': 'new_aml',         'amount_currency': -1000.0,     'currency_id': self.other_currency.id,   'balance': -500.0},
            {'flag': 'exchange_diff',   'amount_currency': 0.0,         'currency_id': self.other_currency.id,   'balance': 166.67},
            {'flag': 'auto_balance',    'amount_currency': -0.01,       'currency_id': self.company_data['currency'].id,    'balance': -0.01},
        ])

        # Remove 0.01 cent in the balance of first exchange line
        first_exchange_line = wizard.line_ids.filtered(lambda x: x.flag == 'exchange_diff')[:1]
        wizard._js_action_mount_line_in_edit(first_exchange_line.index)
        first_exchange_line.balance = 166.66
        wizard._line_value_changed_balance(first_exchange_line)

        # Every line balance so no 'auto_balance' is generated
        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',       'amount_currency': 1000.0,      'currency_id': self.company_data['currency'].id,    'balance': 1000.0},
            {'flag': 'new_aml',         'amount_currency': -1000.0,     'currency_id': self.other_currency.id,   'balance': -500.0},
            {'flag': 'exchange_diff',   'amount_currency': 0.0,         'currency_id': self.other_currency.id,   'balance': 166.66},
            {'flag': 'new_aml',         'amount_currency': -1000.0,     'currency_id': self.other_currency.id,   'balance': -500.0},
            {'flag': 'exchange_diff',   'amount_currency': 0.0,         'currency_id': self.other_currency.id,   'balance': 166.67},
            {'flag': 'new_aml',         'amount_currency': -1000.0,     'currency_id': self.other_currency.id,   'balance': -500.0},
            {'flag': 'exchange_diff',   'amount_currency': 0.0,         'currency_id': self.other_currency.id,   'balance': 166.67},
        ])

        self.assertRecordValues(wizard, [{'state': 'valid'}])

        wizard._action_validate()

        # Check that the first line with exchange has -0.01 compared to others
        self.assertRecordValues(st_line.line_ids, [
            # pylint: disable=C0326
            {'account_id': st_line.journal_id.default_account_id.id,    'amount_currency': 1000.0,      'currency_id': self.company_data['currency'].id,    'balance': 1000.0,   'reconciled': False},
            {'account_id': inv_line_1.account_id.id,                    'amount_currency': -1000.0,     'currency_id': self.other_currency.id,   'balance': -333.34,  'reconciled': True},
            {'account_id': inv_line_2.account_id.id,                    'amount_currency': -1000.0,     'currency_id': self.other_currency.id,   'balance': -333.33,  'reconciled': True},
            {'account_id': inv_line_3.account_id.id,                    'amount_currency': -1000.0,     'currency_id': self.other_currency.id,   'balance': -333.33,  'reconciled': True},
        ])

        self.assertRecordValues(st_line, [{'is_reconciled': True}])
        self.assertRecordValues(inv_line_1.move_id, [{'payment_state': 'paid'}])
        self.assertRecordValues(inv_line_2.move_id, [{'payment_state': 'paid'}])
        self.assertRecordValues(inv_line_3.move_id, [{'payment_state': 'paid'}])

    def test_validation_exchange_diff_multiple(self):
        income_exchange_account = self.env.company.income_currency_exchange_account_id
        foreign_currency = self.setup_other_currency('AED', rates=[('2016-01-01', 6.0), ('2017-01-01', 5.0)])

        # 6000.0 curr2 == 1200.0 comp_curr (bank rate 5:1 instead of the odoo rate 6:1)
        st_line = self._create_st_line(
            1200.0,
            date='2016-01-01',
            foreign_currency_id=foreign_currency.id,
            amount_currency=6000.0,
        )
        # 1000.0 foreign_curr == 166.67 comp_curr (rate 6:1)
        inv_line_1 = self._create_invoice_line(
            'out_invoice',
            currency_id=foreign_currency.id,
            invoice_date='2016-01-01',
            invoice_line_ids=[{'price_unit': 1000.0}],
        )
        # 2000.00 foreign_curr == 400.0 comp_curr (rate 5:1)
        inv_line_2 = self._create_invoice_line(
            'out_invoice',
            currency_id=foreign_currency.id,
            invoice_date='2017-01-01',
            invoice_line_ids=[{'price_unit': 2000.0}],
        )
        # 3000.0 foreign_curr == 500.0 comp_curr (rate 6:1)
        inv_line_3 = self._create_invoice_line(
            'out_invoice',
            currency_id=foreign_currency.id,
            invoice_date='2016-01-01',
            invoice_line_ids=[{'price_unit': 3000.0}],
        )

        wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
        wizard._action_add_new_amls(inv_line_1 + inv_line_2 + inv_line_3)
        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',       'amount_currency': 1200.0,      'currency_id': self.company_data['currency'].id,  'balance': 1200.0},
            {'flag': 'new_aml',         'amount_currency': -1000.0,     'currency_id': foreign_currency.id,               'balance': -166.67},
            {'flag': 'exchange_diff',   'amount_currency': 0.0,         'currency_id': foreign_currency.id,               'balance': -33.33},
            {'flag': 'new_aml',         'amount_currency': -2000.0,     'currency_id': foreign_currency.id,               'balance': -400.0},
            {'flag': 'new_aml',         'amount_currency': -3000.0,     'currency_id': foreign_currency.id,               'balance': -500.0},
            {'flag': 'exchange_diff',   'amount_currency': 0.0,         'currency_id': foreign_currency.id,               'balance': -100.0},
        ])
        self.assertRecordValues(wizard, [{'state': 'valid'}])

        # The amount is the same, no message under the 'amount' field.
        self.assert_form_extra_text_value(wizard, False)

        wizard._action_validate()
        self.assertRecordValues(st_line.line_ids, [
            # pylint: disable=C0326
            {'account_id': st_line.journal_id.default_account_id.id,      'amount_currency': 1200.0,      'currency_id': self.company_data['currency'].id,  'balance': 1200.0,  'reconciled': False},
            {'account_id': inv_line_1.account_id.id,                      'amount_currency': -1000.0,     'currency_id': foreign_currency.id,               'balance': -200.0,  'reconciled': True},
            {'account_id': inv_line_2.account_id.id,                      'amount_currency': -2000.0,     'currency_id': foreign_currency.id,               'balance': -400.0,  'reconciled': True},
            {'account_id': inv_line_3.account_id.id,                      'amount_currency': -3000.0,     'currency_id': foreign_currency.id,               'balance': -600.0,  'reconciled': True},
        ])
        self.assertRecordValues(st_line, [{'is_reconciled': True}])
        self.assertRecordValues(inv_line_1.move_id, [{'payment_state': 'paid'}])
        self.assertRecordValues(inv_line_2.move_id, [{'payment_state': 'paid'}])
        self.assertRecordValues(inv_line_3.move_id, [{'payment_state': 'paid'}])
        self.assertRecordValues((inv_line_1 + inv_line_2 + inv_line_3).matched_credit_ids.exchange_move_id.line_ids, [
            # pylint: disable=C0326
            {'account_id': inv_line_1.account_id.id,    'amount_currency': 0.0, 'currency_id': foreign_currency.id, 'balance': 33.33,   'reconciled': True},
            {'account_id': income_exchange_account.id,  'amount_currency': 0.0, 'currency_id': foreign_currency.id, 'balance': -33.33,  'reconciled': False},
            {'account_id': inv_line_3.account_id.id,    'amount_currency': 0.0, 'currency_id': foreign_currency.id, 'balance': 100.0,   'reconciled': True},
            {'account_id': income_exchange_account.id,  'amount_currency': 0.0, 'currency_id': foreign_currency.id, 'balance': -100.0,  'reconciled': False},
        ])

    def test_validation_foreign_curr_st_line_comp_curr_payment_partial_exchange_difference(self):
        comp_curr = self.env.company.currency_id
        foreign_curr = self.other_currency

        st_line = self._create_st_line(
            650.0,
            date='2017-01-01',
            foreign_currency_id=foreign_curr.id,
            amount_currency=800,
        )

        payment = self.env['account.payment'].create({
            'partner_id': self.partner_a.id,
            'payment_type': 'inbound',
            'partner_type': 'customer',
            'date': '2017-01-01',
            'amount': 725.0,
        })
        payment.action_post()
        pay_line, _counterpart_lines, _writeoff_lines = payment._seek_for_lines()

        wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
        wizard._action_add_new_amls(pay_line)

        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',       'amount_currency': 650.0,       'currency_id': comp_curr.id,        'balance': 650.0},
            {'flag': 'new_aml',         'amount_currency': -650.0,      'currency_id': comp_curr.id,        'balance': -650.0},
        ])

        # Switch to a full reconciliation.
        line = wizard.line_ids.filtered(lambda x: x.flag == 'new_aml')
        wizard._js_action_mount_line_in_edit(line.index)
        wizard._js_action_apply_line_suggestion(line.index)

        # 725 * 800 / 650 = 892.308
        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',       'amount_currency': 650.0,       'currency_id': comp_curr.id,        'balance': 650.0},
            {'flag': 'new_aml',         'amount_currency': -725.0,      'currency_id': comp_curr.id,        'balance': -725.0},
            {'flag': 'auto_balance',    'amount_currency': 92.308,      'currency_id': foreign_curr.id,     'balance': 75.0},
        ])

        # Switch to a partial reconciliation.
        wizard._js_action_apply_line_suggestion(line.index)

        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',       'amount_currency': 650.0,       'currency_id': comp_curr.id,        'balance': 650.0},
            {'flag': 'new_aml',         'amount_currency': -650.0,      'currency_id': comp_curr.id,        'balance': -650.0},
        ])

        wizard._action_validate()
        self.assertRecordValues(pay_line, [{'amount_residual': 75.0}])

    def test_validation_remove_exchange_difference(self):
        """ Test the case when the foreign currency is missing on the statement line.
        In that case, the user can remove the exchange difference in order to fully reconcile both items without additional
        write-off/exchange difference.
        """
        # 1200.0 comp_curr = 2400.0 foreign_curr in 2017 (rate 1:2)
        st_line = self._create_st_line(
            1200.0,
            date='2017-01-01',
        )
        # 1200.0 comp_curr = 3600.0 foreign_curr in 2016 (rate 1:3)
        inv_line = self._create_invoice_line(
            'out_invoice',
            currency_id=self.other_currency.id,
            invoice_date='2016-01-01',
            invoice_line_ids=[{'price_unit': 3600.0}],
        )

        wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
        wizard._action_add_new_amls(inv_line)
        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',       'amount_currency': 1200.0,      'currency_id': self.company_data['currency'].id,    'balance': 1200.0},
            {'flag': 'new_aml',         'amount_currency': -2400.0,     'currency_id': self.other_currency.id,              'balance': -800.0},
            {'flag': 'exchange_diff',   'amount_currency': 0.0,         'currency_id': self.other_currency.id,              'balance': -400.0},
        ])
        self.assertRecordValues(wizard, [{'state': 'valid'}])

        # Remove the partial.
        line_index = wizard.line_ids.filtered(lambda x: x.flag == 'new_aml').index
        wizard._js_action_mount_line_in_edit(line_index)
        wizard._js_action_apply_line_suggestion(line_index)
        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',       'amount_currency': 1200.0,      'currency_id': self.company_data['currency'].id,    'balance': 1200.0},
            {'flag': 'new_aml',         'amount_currency': -3600.0,     'currency_id': self.other_currency.id,              'balance': -1200.0},
            {'flag': 'exchange_diff',   'amount_currency': 0.0,         'currency_id': self.other_currency.id,              'balance': -600.0},
            {'flag': 'auto_balance',    'amount_currency': 600.0,       'currency_id': self.company_data['currency'].id,    'balance': 600.0},
        ])

        exchange_diff_index = wizard.line_ids.filtered(lambda x: x.flag == 'exchange_diff').index
        wizard._js_action_remove_line(exchange_diff_index)
        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',       'amount_currency': 1200.0,      'currency_id': self.company_data['currency'].id,    'balance': 1200.0},
            {'flag': 'new_aml',         'amount_currency': -3600.0,     'currency_id': self.other_currency.id,              'balance': -1200.0},
        ])

        wizard._action_validate()
        self.assertRecordValues(st_line.line_ids, [
            # pylint: disable=C0326
            {'account_id': st_line.journal_id.default_account_id.id,    'amount_currency': 1200.0,      'currency_id': self.company_data['currency'].id,    'balance': 1200.0,   'reconciled': False},
            {'account_id': inv_line.account_id.id,                      'amount_currency': -3600.0,     'currency_id': self.other_currency.id,              'balance': -1200.0,  'reconciled': True},
        ])
        self.assertRecordValues(st_line, [{'is_reconciled': True}])
        self.assertRecordValues(inv_line.move_id, [{'payment_state': 'paid'}])

    def test_validation_new_aml_one_foreign_currency_on_st_line(self):
        income_exchange_account = self.env.company.income_currency_exchange_account_id

        # 4800.0 curr2 == 1200.0 comp_curr (rate 4:1)
        st_line = self._create_st_line(
            1200.0,
            date='2017-01-01',
        )
        # 4800.0 curr2 in 2016 (rate 6:1)
        inv_line = self._create_invoice_line(
            'out_invoice',
            invoice_date='2016-01-01',
            currency_id=self.other_currency_2.id,
            invoice_line_ids=[{'price_unit': 4800.0}],
        )

        wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
        wizard._action_add_new_amls(inv_line)
        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',       'amount_currency': 1200.0,      'currency_id': self.company_data['currency'].id,    'balance': 1200.0},
            {'flag': 'new_aml',         'amount_currency': -4800.0,     'currency_id': self.other_currency_2.id,            'balance': -800.0},
            {'flag': 'exchange_diff',   'amount_currency': 0.0,         'currency_id': self.other_currency_2.id,            'balance': -400.0},
        ])
        self.assertRecordValues(wizard, [{'state': 'valid'}])

        # The amount is the same, no message under the 'amount' field.
        self.assert_form_extra_text_value(wizard, False)

        wizard._action_validate()
        self.assertRecordValues(st_line.line_ids, [
            # pylint: disable=C0326
            {'account_id': st_line.journal_id.default_account_id.id,    'amount_currency': 1200.0,       'currency_id': self.company_data['currency'].id,    'balance': 1200.0,   'reconciled': False},
            {'account_id': inv_line.account_id.id,                      'amount_currency': -4800.0,      'currency_id': self.other_currency_2.id,            'balance': -1200.0,  'reconciled': True},
        ])
        self.assertRecordValues(st_line, [{'is_reconciled': True}])
        self.assertRecordValues(inv_line.move_id, [{'payment_state': 'paid'}])
        self.assertRecordValues(inv_line.matched_credit_ids.exchange_move_id.line_ids, [
            # pylint: disable=C0326
            {'account_id': inv_line.account_id.id,                      'amount_currency': 0.0,     'currency_id': self.other_currency_2.id,  'balance': 400.0,   'reconciled': True,    'date': fields.Date.from_string('2017-01-31')},
            {'account_id': income_exchange_account.id,                  'amount_currency': 0.0,     'currency_id': self.other_currency_2.id,  'balance': -400.0,  'reconciled': False,   'date': fields.Date.from_string('2017-01-31')},
        ])

        # Checks that the wizard still display the 3 initial lines
        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',       'amount_currency': 1200.0,      'currency_id': self.company_data['currency'].id,    'balance': 1200.0},
            {'flag': 'aml',             'amount_currency': -4800.0,     'currency_id': self.other_currency_2.id,            'balance': -800.0},
            {'flag': 'aml',             'amount_currency': 0.0,         'currency_id': self.other_currency_2.id,            'balance': -400.0},  # represents the exchange diff
        ])

        # Reset the wizard.
        wizard._js_action_reset()
        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',       'amount_currency': 1200.0,      'currency_id': self.company_data['currency'].id,    'balance': 1200.0},
            {'flag': 'auto_balance',    'amount_currency': -1200.0,     'currency_id': self.company_data['currency'].id,    'balance': -1200.0},
        ])

        # Create the same invoice with a higher amount to check the partial flow.
        # 4800.0 curr2 in 2016 (rate 6:1)
        inv_line = self._create_invoice_line(
            'out_invoice',
            invoice_date='2016-01-01',
            currency_id=self.other_currency_2.id,
            invoice_line_ids=[{'price_unit': 9600.0}],
        )
        wizard._action_add_new_amls(inv_line)
        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',       'amount_currency': 1200.0,  'currency_id': self.company_data['currency'].id,        'balance': 1200.0},
            {'flag': 'new_aml',         'amount_currency': -4800.0, 'currency_id': self.other_currency_2.id,                'balance': -800.0},
            {'flag': 'exchange_diff',   'amount_currency': 0.0,     'currency_id': self.other_currency_2.id,                'balance': -400.0},
        ])

        # Check the message under the 'amount' field.
        line = wizard.line_ids.filtered(lambda x: x.flag == 'new_aml')
        wizard._js_action_mount_line_in_edit(line.index)
        self.assert_form_extra_text_value(
            wizard,
            r".+open amount of 9,600.000.+ reduced by 4,800.000.+ set the invoice as fully paid .",
        )
        self.assertRecordValues(line, [{
            'suggestion_amount_currency': -9600.0,
            'suggestion_balance': -1600.0,
        }])

        # Switch to a full reconciliation.
        wizard._js_action_apply_line_suggestion(line.index)
        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',       'amount_currency': 1200.0,      'currency_id': self.company_data['currency'].id,    'balance': 1200.0},
            {'flag': 'new_aml',         'amount_currency': -9600.0,     'currency_id': self.other_currency_2.id,            'balance': -1600.0},
            {'flag': 'exchange_diff',   'amount_currency': 0.0,         'currency_id': self.other_currency_2.id,            'balance': -800.0},
            {'flag': 'auto_balance',    'amount_currency': 1200.0,      'currency_id': self.company_data['currency'].id,    'balance': 1200.0},
        ])

        # Check the message under the 'amount' field.
        line = wizard.line_ids.filtered(lambda x: x.flag == 'new_aml')
        wizard._js_action_mount_line_in_edit(line.index)
        self.assert_form_extra_text_value(
            wizard,
            r".+open amount of 9,600.000.+ paid .+ record a partial payment .",
        )
        self.assertRecordValues(line, [{
            'suggestion_amount_currency': -4800.0,
            'suggestion_balance': -800.0,
        }])

        # Switch back to a partial reconciliation.
        wizard._js_action_apply_line_suggestion(line.index)
        self.assertRecordValues(wizard, [{'state': 'valid'}])

        # Reconcile
        wizard._action_validate()
        self.assertRecordValues(st_line.line_ids, [
            # pylint: disable=C0326
            {'account_id': st_line.journal_id.default_account_id.id,    'amount_currency': 1200.0,       'currency_id': self.company_data['currency'].id,    'balance': 1200.0,   'reconciled': False},
            {'account_id': inv_line.account_id.id,                      'amount_currency': -4800.0,      'currency_id': self.other_currency_2.id,            'balance': -1200.0,  'reconciled': True},
        ])
        self.assertRecordValues(st_line, [{'is_reconciled': True}])
        self.assertRecordValues(inv_line.move_id, [{
            'payment_state': 'partial',
            'amount_residual': 4800.0,
        }])
        self.assertRecordValues(inv_line, [{
            'amount_residual_currency': 4800.0,
            'amount_residual': 800.0,
            'reconciled': False,
        }])
        self.assertRecordValues(inv_line.matched_credit_ids.exchange_move_id.line_ids, [
            # pylint: disable=C0326
            {'account_id': inv_line.account_id.id,      'amount_currency': 0.0, 'currency_id': self.other_currency_2.id, 'balance': 400.0,   'reconciled': True,     'date': fields.Date.from_string('2017-01-31')},
            {'account_id': income_exchange_account.id,  'amount_currency': 0.0, 'currency_id': self.other_currency_2.id, 'balance': -400.0,  'reconciled': False,    'date': fields.Date.from_string('2017-01-31')},
        ])

    def test_validation_new_aml_one_foreign_currency_on_inv_line(self):
        income_exchange_account = self.env.company.income_currency_exchange_account_id

        # 1200.0 comp_curr is equals to 4800.0 curr2 in 2017 (rate 4:1)
        st_line = self._create_st_line(
            1200.0,
            date='2017-01-01',
        )
        # 4800.0 curr2 == 800.0 comp_curr (rate 6:1)
        inv_line = self._create_invoice_line(
            'out_invoice',
            currency_id=self.other_currency_2.id,
            invoice_date='2016-01-01',
            invoice_line_ids=[{'price_unit': 4800.0}],
        )

        wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
        wizard._action_add_new_amls(inv_line)
        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',       'amount_currency': 1200.0,      'currency_id': self.company_data['currency'].id,    'balance': 1200.0},
            {'flag': 'new_aml',         'amount_currency': -4800.0,     'currency_id': self.other_currency_2.id,            'balance': -800.0},
            {'flag': 'exchange_diff',   'amount_currency': 0.0,         'currency_id': self.other_currency_2.id,            'balance': -400.0},
        ])
        self.assertRecordValues(wizard, [{'state': 'valid'}])

        # The amount is the same, no message under the 'amount' field.
        self.assert_form_extra_text_value(wizard, False)

        # Remove the line to see if the exchange difference is well removed.
        wizard._action_remove_new_amls(inv_line)
        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',       'amount_currency': 1200.0,      'currency_id': self.company_data['currency'].id,    'balance': 1200.0},
            {'flag': 'auto_balance',    'amount_currency': -1200.0,     'currency_id': self.company_data['currency'].id,    'balance': -1200.0},
        ])
        self.assertRecordValues(wizard, [{'state': 'invalid'}])

        # Mount the line again and validate.
        wizard._action_add_new_amls(inv_line)
        wizard._action_validate()
        self.assertRecordValues(st_line.line_ids, [
            # pylint: disable=C0326
            {'account_id': st_line.journal_id.default_account_id.id,    'amount_currency': 1200.0,      'currency_id': self.company_data['currency'].id,    'balance': 1200.0,   'reconciled': False},
            {'account_id': inv_line.account_id.id,                      'amount_currency': -4800.0,     'currency_id': self.other_currency_2.id,            'balance': -1200.0,  'reconciled': True},
        ])
        self.assertRecordValues(st_line, [{'is_reconciled': True}])
        self.assertRecordValues(inv_line.move_id, [{'payment_state': 'paid'}])
        self.assertRecordValues(inv_line.matched_credit_ids.exchange_move_id.line_ids, [
            # pylint: disable=C0326
            {'account_id': inv_line.account_id.id,                      'amount_currency': 0.0,         'currency_id': self.other_currency_2.id, 'balance': 400.0,   'reconciled': True,  'date': fields.Date.from_string('2017-01-31')},
            {'account_id': income_exchange_account.id,                  'amount_currency': 0.0,         'currency_id': self.other_currency_2.id, 'balance': -400.0,  'reconciled': False, 'date': fields.Date.from_string('2017-01-31')},
        ])

        # Reset the wizard.
        wizard._js_action_reset()
        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',       'amount_currency': 1200.0,      'currency_id': self.company_data['currency'].id,    'balance': 1200.0},
            {'flag': 'auto_balance',    'amount_currency': -1200.0,     'currency_id': self.company_data['currency'].id,    'balance': -1200.0},
        ])

        # Create the same invoice with a higher amount to check the partial flow.
        # 7200.0 curr2 == 1200.0 comp_curr (rate 6:1)
        inv_line = self._create_invoice_line(
            'out_invoice',
            currency_id=self.other_currency_2.id,
            invoice_date='2016-01-01',
            invoice_line_ids=[{'price_unit': 7200.0}],
        )
        wizard._action_add_new_amls(inv_line)
        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',       'amount_currency': 1200.0,      'currency_id': self.company_data['currency'].id,    'balance': 1200.0},
            {'flag': 'new_aml',         'amount_currency': -4800.0,     'currency_id': self.other_currency_2.id,            'balance': -800.0},
            {'flag': 'exchange_diff',   'amount_currency': 0.0,         'currency_id': self.other_currency_2.id,            'balance': -400.0},
        ])

        # Check the message under the 'amount' field.
        line = wizard.line_ids.filtered(lambda x: x.flag == 'new_aml')
        wizard._js_action_mount_line_in_edit(line.index)
        self.assert_form_extra_text_value(
            wizard,
            r".+open amount of 7,200.000.+ reduced by 4,800.000.+ set the invoice as fully paid .",
        )
        self.assertRecordValues(line, [{
            'suggestion_amount_currency': -7200.0,
            'suggestion_balance': -1200.0,
        }])

        # Switch to a full reconciliation.
        wizard._js_action_apply_line_suggestion(line.index)
        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',       'amount_currency': 1200.0,      'currency_id': self.company_data['currency'].id,    'balance': 1200.0},
            {'flag': 'new_aml',         'amount_currency': -7200.0,     'currency_id': self.other_currency_2.id,            'balance': -1200.0},
            {'flag': 'exchange_diff',   'amount_currency': 0.0,         'currency_id': self.other_currency_2.id,            'balance': -600.0},
            {'flag': 'auto_balance',    'amount_currency': 600.0,       'currency_id': self.company_data['currency'].id,    'balance': 600.0},
        ])

        # Check the message under the 'amount' field.
        line = wizard.line_ids.filtered(lambda x: x.flag == 'new_aml')
        wizard._js_action_mount_line_in_edit(line.index)
        self.assert_form_extra_text_value(
            wizard,
            r".+open amount of 7,200.000.+ paid .+ record a partial payment .",
        )
        self.assertRecordValues(line, [{
            'suggestion_amount_currency': -4800.0,
            'suggestion_balance': -800.0,
        }])

        # Switch back to a partial reconciliation.
        wizard._js_action_apply_line_suggestion(line.index)
        self.assertRecordValues(wizard, [{'state': 'valid'}])

        # Reconcile
        wizard._action_validate()
        self.assertRecordValues(st_line.line_ids, [
            # pylint: disable=C0326
            {'account_id': st_line.journal_id.default_account_id.id,    'amount_currency': 1200.0,      'currency_id': self.company_data['currency'].id,    'balance': 1200.0,  'reconciled': False},
            {'account_id': inv_line.account_id.id,                      'amount_currency': -4800.0,     'currency_id': self.other_currency_2.id,            'balance': -1200.0,  'reconciled': True},
        ])
        self.assertRecordValues(st_line, [{'is_reconciled': True}])
        self.assertRecordValues(inv_line.move_id, [{
            'payment_state': 'partial',
            'amount_residual': 2400.0,
        }])
        self.assertRecordValues(inv_line.matched_credit_ids.exchange_move_id.line_ids, [
            # pylint: disable=C0326
            {'account_id': inv_line.account_id.id,                      'amount_currency': 0.0,         'currency_id': self.other_currency_2.id, 'balance': 400.0,   'reconciled': True,  'date': fields.Date.from_string('2017-01-31')},
            {'account_id': income_exchange_account.id,                  'amount_currency': 0.0,         'currency_id': self.other_currency_2.id, 'balance': -400.0,  'reconciled': False, 'date': fields.Date.from_string('2017-01-31')},
        ])

    def test_validation_new_aml_multi_currencies(self):
        # 6300.0 curr2 == 1800.0 comp_curr (bank rate 3.5:1 instead of the odoo rate 4:1)
        st_line = self._create_st_line(
            1800.0,
            date='2017-01-01',
            foreign_currency_id=self.other_currency_2.id,
            amount_currency=6300.0,
        )
        # 21600.0 curr3 == 1800.0 comp_curr (rate 12:1)
        inv_line = self._create_invoice_line(
            'out_invoice',
            currency_id=self.other_currency_3.id,
            invoice_date='2016-01-01',
            invoice_line_ids=[{'price_unit': 21600.0}],
        )

        wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
        wizard._action_add_new_amls(inv_line)
        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',   'amount_currency': 1800.0,      'currency_id': self.company_data['currency'].id,    'balance': 1800.0},
            {'flag': 'new_aml',     'amount_currency': -21600.0,    'currency_id': self.other_currency_3.id,            'balance': -1800.0},
        ])
        self.assertRecordValues(wizard, [{'state': 'valid'}])

        # The amount is the same, no message under the 'amount' field.
        self.assert_form_extra_text_value(wizard, False)

        wizard._action_validate()
        self.assertRecordValues(st_line.line_ids, [
            # pylint: disable=C0326
            {'account_id': st_line.journal_id.default_account_id.id,    'amount_currency': 1800.0,      'currency_id': self.company_data['currency'].id,    'balance': 1800.0,  'reconciled': False},
            {'account_id': inv_line.account_id.id,                      'amount_currency': -21600.0,    'currency_id': self.other_currency_3.id,            'balance': -1800.0, 'reconciled': True},
        ])
        self.assertRecordValues(st_line, [{'is_reconciled': True}])
        self.assertRecordValues(inv_line.move_id, [{'payment_state': 'paid'}])

        # Reset the wizard.
        wizard._js_action_reset()
        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',       'amount_currency': 1800.0,      'currency_id': self.company_data['currency'].id,    'balance': 1800.0},
            {'flag': 'auto_balance',    'amount_currency': -6300.0,     'currency_id': self.other_currency_2.id,            'balance': -1800.0},
        ])

        # Create the same invoice with a higher amount to check the partial flow.
        # 32400.0 curr3 == 2700.0 comp_curr (rate 12:1)
        inv_line = self._create_invoice_line(
            'out_invoice',
            currency_id=self.other_currency_3.id,
            invoice_date='2016-01-01',
            invoice_line_ids=[{'price_unit': 32400.0}],
        )
        wizard._action_add_new_amls(inv_line)
        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',   'amount_currency': 1800.0,      'currency_id': self.company_data['currency'].id,    'balance': 1800.0},
            {'flag': 'new_aml',     'amount_currency': -21600.0,    'currency_id': self.other_currency_3.id,            'balance': -1800.0},
        ])

        # Check the message under the 'amount' field.
        line = wizard.line_ids.filtered(lambda x: x.flag == 'new_aml')
        wizard._js_action_mount_line_in_edit(line.index)
        self.assert_form_extra_text_value(
            wizard,
            r".+open amount of 32,400.000.+ reduced by 21,600.000.+ set the invoice as fully paid .",
        )
        self.assertRecordValues(line, [{
            'suggestion_amount_currency': -32400.0,
            'suggestion_balance': -2700.0,
        }])

        # Switch to a full reconciliation.
        wizard._js_action_apply_line_suggestion(line.index)
        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',       'amount_currency': 1800.0,      'currency_id': self.company_data['currency'].id,    'balance': 1800.0},
            {'flag': 'new_aml',         'amount_currency': -32400.0,    'currency_id': self.other_currency_3.id,            'balance': -2700.0},
            {'flag': 'auto_balance',    'amount_currency': 3150.0,      'currency_id': self.other_currency_2.id,            'balance': 900.0},
        ])

        # Check the message under the 'amount' field.
        line = wizard.line_ids.filtered(lambda x: x.flag == 'new_aml')
        wizard._js_action_mount_line_in_edit(line.index)
        self.assert_form_extra_text_value(
            wizard,
            r".+open amount of 32,400.000.+ paid .+ record a partial payment .",
        )
        self.assertRecordValues(line, [{
            'suggestion_amount_currency': -21600.0,
            'suggestion_balance': -1800.0,
        }])

        # Switch back to a partial reconciliation.
        wizard._js_action_apply_line_suggestion(line.index)
        self.assertRecordValues(wizard, [{'state': 'valid'}])

        # Reconcile
        wizard._action_validate()
        self.assertRecordValues(st_line.line_ids, [
            # pylint: disable=C0326
            {'account_id': st_line.journal_id.default_account_id.id,    'amount_currency': 1800.0,      'currency_id': self.company_data['currency'].id,    'balance': 1800.0,  'reconciled': False},
            {'account_id': inv_line.account_id.id,                      'amount_currency': -21600.0,    'currency_id': self.other_currency_3.id,            'balance': -1800.0, 'reconciled': True},
        ])
        self.assertRecordValues(st_line, [{'is_reconciled': True}])
        self.assertRecordValues(inv_line.move_id, [{
            'payment_state': 'partial',
            'amount_residual': 10800.0,
        }])

    def test_validation_new_aml_multi_currencies_exchange_diff_custom_rates(self):
        self.company_data['default_journal_bank'].currency_id = self.other_currency

        self.env['res.currency.rate'].create([
            {
                'name': '2017-02-01',
                'rate': 1.0683,
                'currency_id': self.other_currency.id,
                'company_id': self.env.company.id,
            },
            {
                'name': '2017-03-01',
                'rate': 1.0812,
                'currency_id': self.other_currency.id,
                'company_id': self.env.company.id,
            },
        ])

        # 960.14 curr1 = 888.03 comp_curr
        st_line = self._create_st_line(
            -960.14,
            date='2017-03-01',
        )
        # 112.7 curr1 == 105.49 comp_curr
        inv_line1 = self._create_invoice_line(
            'in_invoice',
            currency_id=self.other_currency.id,
            invoice_date='2017-02-01',
            invoice_line_ids=[{'price_unit': 112.7}],
        )
        # 847.44 curr1 == 793.26 comp_curr
        inv_line2 = self._create_invoice_line(
            'in_invoice',
            currency_id=self.other_currency.id,
            invoice_date='2017-02-01',
            invoice_line_ids=[{'price_unit': 847.44}],
        )

        wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
        wizard._action_add_new_amls(inv_line1)
        wizard._action_add_new_amls(inv_line2)

        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',       'amount_currency': -960.14,     'balance': -888.03},
            {'flag': 'new_aml',         'amount_currency': 112.7,       'balance': 105.49},
            {'flag': 'exchange_diff',   'amount_currency': 0.0,         'balance': -1.25},
            {'flag': 'new_aml',         'amount_currency': 847.44,      'balance': 793.26},
            {'flag': 'exchange_diff',   'amount_currency': 0.0,         'balance': -9.47},
        ])
        wizard._action_remove_new_amls(inv_line1 + inv_line2)
        wizard._action_add_new_amls(inv_line2)
        wizard._action_add_new_amls(inv_line1)

        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',       'amount_currency': -960.14,     'balance': -888.03},
            {'flag': 'new_aml',         'amount_currency': 847.44,      'balance': 793.26},
            {'flag': 'exchange_diff',   'amount_currency': 0.0,         'balance': -9.47},
            {'flag': 'new_aml',         'amount_currency': 112.7,       'balance': 105.49},
            {'flag': 'exchange_diff',   'amount_currency': 0.0,         'balance': -1.25},
        ])

    def test_validation_with_partner(self):
        partner = self.partner_a.copy()

        st_line = self._create_st_line(1000.0, partner_id=self.partner_a.id)

        # The wizard can be validated directly thanks to the receivable account set on the partner.
        wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
        self.assertRecordValues(wizard, [{'state': 'valid'}])

        # Validate and check the statement line.
        wizard._action_validate()
        self.assertRecordValues(st_line, [{'partner_id': self.partner_a.id}])
        liquidity_line, _suspense_line, other_line = st_line._seek_for_lines()
        account = self.partner_a.property_account_receivable_id
        self.assertRecordValues(liquidity_line + other_line, [
            # pylint: disable=C0326
            {'account_id': liquidity_line.account_id.id,    'balance': 1000.0},
            {'account_id': account.id,                      'balance': -1000.0},
        ])
        self.assertRecordValues(wizard, [{'state': 'reconciled'}])

        # Match an invoice with a different partner.
        wizard._js_action_reset()
        inv_line = self._create_invoice_line(
            'out_invoice',
            partner_id=partner.id,
            invoice_line_ids=[{'price_unit': 1000.0}],
        )
        wizard._action_add_new_amls(inv_line)
        wizard._action_validate()
        liquidity_line, suspense_line, other_line = st_line._seek_for_lines()
        self.assertRecordValues(st_line, [{'partner_id': partner.id}])
        self.assertRecordValues(st_line.move_id, [{'partner_id': partner.id}])
        self.assertRecordValues(liquidity_line + other_line, [
            # pylint: disable=C0326
            {'account_id': liquidity_line.account_id.id,    'partner_id': partner.id,   'balance': 1000.0},
            {'account_id': inv_line.account_id.id,          'partner_id': partner.id,   'balance': -1000.0},
        ])
        self.assertRecordValues(wizard, [{'state': 'reconciled'}])

        # Reset the wizard and match invoices with different partners.
        wizard._js_action_reset()
        partner1 = self.partner_a.copy()
        inv_line1 = self._create_invoice_line(
            'out_invoice',
            partner_id=partner1.id,
            invoice_line_ids=[{'price_unit': 300.0}],
        )
        partner2 = self.partner_a.copy()
        inv_line2 = self._create_invoice_line(
            'out_invoice',
            partner_id=partner2.id,
            invoice_line_ids=[{'price_unit': 300.0}],
        )
        wizard._action_add_new_amls(inv_line1 + inv_line2)
        wizard._action_validate()
        liquidity_line, _suspense_line, other_line = st_line._seek_for_lines()
        self.assertRecordValues(st_line, [{'partner_id': False}])
        self.assertRecordValues(st_line.move_id, [{'partner_id': False}])
        self.assertRecordValues(liquidity_line + other_line, [
            # pylint: disable=C0326
            {'account_id': liquidity_line.account_id.id,    'partner_id': False,        'balance': 1000.0},
            {'account_id': inv_line1.account_id.id,         'partner_id': partner1.id,  'balance': -300.0},
            {'account_id': inv_line2.account_id.id,         'partner_id': partner2.id,  'balance': -300.0},
            {'account_id': account.id,                      'partner_id': False,        'balance': -400.0},
        ])
        self.assertRecordValues(wizard, [{'state': 'reconciled'}])

        # Clear the accounts set on the partner and reset the widget.
        # The wizard should be invalid since we are not able to set an open balance.
        partner.property_account_receivable_id = None
        wizard._js_action_reset()
        liquidity_line, suspense_line, other_line = st_line._seek_for_lines()
        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',       'account_id': liquidity_line.account_id.id},
            {'flag': 'auto_balance',    'account_id': suspense_line.account_id.id},
        ])
        self.assertRecordValues(wizard, [{'state': 'invalid'}])

    def test_partner_receivable_payable_account(self):
        self.partner_a.write({'customer_rank': 1, 'supplier_rank': 0})  # always receivable
        self.partner_b.write({'customer_rank': 0, 'supplier_rank': 1})  # always payable
        partner_c = self.partner_b.copy({'customer_rank': 3, 'supplier_rank': 2})  # no preference

        positive_st_line = self._create_st_line(1000)
        journal_account = positive_st_line.journal_id.default_account_id

        wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=positive_st_line.id).new({})
        suspense_line = wizard.line_ids.filtered(lambda l: l.flag != "liquidity")
        wizard._js_action_mount_line_in_edit(suspense_line.index)

        suspense_line.partner_id = self.partner_a
        wizard._line_value_changed_partner_id(suspense_line)
        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'partner_id': False,             'account_id': journal_account.id},
            {'partner_id': self.partner_a.id, 'account_id': self.partner_a.property_account_receivable_id.id},
        ])

        suspense_line.partner_id = self.partner_b
        wizard._line_value_changed_partner_id(suspense_line)
        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'partner_id': False,             'account_id': journal_account.id},
            {'partner_id': self.partner_b.id, 'account_id': self.partner_b.property_account_payable_id.id},
        ])

        suspense_line.partner_id = partner_c
        wizard._line_value_changed_partner_id(suspense_line)
        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'partner_id': False,             'account_id': journal_account.id},
            {'partner_id': partner_c.id,      'account_id': partner_c.property_account_receivable_id.id},
        ])

        negative_st_line = self._create_st_line(-1000)
        wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=negative_st_line.id).new({})
        suspense_line = wizard.line_ids.filtered(lambda l: l.flag != "liquidity")
        wizard._js_action_mount_line_in_edit(suspense_line.index)

        suspense_line.partner_id = self.partner_a
        wizard._line_value_changed_partner_id(suspense_line)
        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'partner_id': False,             'account_id': journal_account.id},
            {'partner_id': self.partner_a.id, 'account_id': self.partner_a.property_account_receivable_id.id},
        ])

        suspense_line.partner_id = self.partner_b
        wizard._line_value_changed_partner_id(suspense_line)
        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'partner_id': False,             'account_id': journal_account.id},
            {'partner_id': self.partner_b.id, 'account_id': self.partner_b.property_account_payable_id.id},
        ])

        suspense_line.partner_id = partner_c
        wizard._line_value_changed_partner_id(suspense_line)
        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'partner_id': False,             'account_id': journal_account.id},
            {'partner_id': partner_c.id,      'account_id': partner_c.property_account_payable_id.id},
        ])

    def test_validation_using_custom_account(self):
        st_line = self._create_st_line(1000.0)

        # By default, the wizard can't be validated directly due to the suspense account.
        wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
        self.assertRecordValues(wizard, [{'state': 'invalid'}])

        # Mount the auto-balance line in edit mode.
        line = wizard.line_ids.filtered(lambda x: x.flag == 'auto_balance')
        wizard._js_action_mount_line_in_edit(line.index)
        liquidity_line, suspense_line, _other_lines = st_line._seek_for_lines()
        self.assertRecordValues(line, [{
            'account_id': suspense_line.account_id.id,
            'balance': -1000.0,
        }])

        # Switch to a custom account.
        account = self.env['account.account'].create({
            'name': "test_validation_using_custom_account",
            'code': "424242",
            'account_type': "asset_current",
        })
        line.account_id = account
        wizard._line_value_changed_account_id(line)
        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',   'account_id': liquidity_line.account_id.id, 'balance': 1000.0},
            {'flag': 'manual',      'account_id': account.id,                   'balance': -1000.0},
        ])

        # The wizard can be validated.
        self.assertRecordValues(wizard, [{'state': 'valid'}])

        # Validate and check the statement line.
        wizard._action_validate()
        liquidity_line, _suspense_line, other_line = st_line._seek_for_lines()
        self.assertRecordValues(liquidity_line + other_line, [
            # pylint: disable=C0326
            {'account_id': liquidity_line.account_id.id,    'balance': 1000.0},
            {'account_id': account.id,                      'balance': -1000.0},
        ])
        self.assertRecordValues(wizard, [{'state': 'reconciled'}])

    def test_validation_with_taxes(self):
        st_line = self._create_st_line(1000.0)

        tax_tags = self.env['account.account.tag'].create({
            'name': f'tax_tag_{i}',
            'applicability': 'taxes',
            'country_id': self.env.company.account_fiscal_country_id.id,
        } for i in range(4))

        tax_21 = self.env['account.tax'].create({
            'name': "tax_21",
            'amount': 21,
            'invoice_repartition_line_ids': [
                Command.create({
                    'factor_percent': 100,
                    'repartition_type': 'base',
                    'tag_ids': [Command.set(tax_tags[0].ids)],
                }),
                Command.create({
                    'factor_percent': 100,
                    'repartition_type': 'tax',
                    'tag_ids': [Command.set(tax_tags[1].ids)],
                }),
            ],
            'refund_repartition_line_ids': [
                Command.create({
                    'factor_percent': 100,
                    'repartition_type': 'base',
                    'tag_ids': [Command.set(tax_tags[2].ids)],
                }),
                Command.create({
                    'factor_percent': 100,
                    'repartition_type': 'tax',
                    'tag_ids': [Command.set(tax_tags[3].ids)],
                }),
            ],
        })

        wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
        line = wizard.line_ids.filtered(lambda x: x.flag == 'auto_balance')
        wizard._js_action_mount_line_in_edit(line.index)
        line.tax_ids = [Command.link(tax_21.id)]
        wizard._line_value_changed_tax_ids(line)

        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',   'balance': 1000.0,  'tax_tag_ids': []},
            {'flag': 'manual',      'balance': -826.45, 'tax_tag_ids': tax_tags[0].ids},
            {'flag': 'tax_line',    'balance': -173.55, 'tax_tag_ids': tax_tags[1].ids},
        ])

        # Remove the tax directly.
        line.tax_ids = [Command.clear()]
        wizard._line_value_changed_tax_ids(line)

        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',   'balance': 1000.0,  'tax_tag_ids': []},
            {'flag': 'manual',      'balance': -1000.0, 'tax_tag_ids': []},
        ])

        # Edit the base line. The tax tags should be the refund ones.
        line = wizard.line_ids.filtered(lambda x: x.flag == 'manual')
        wizard._js_action_mount_line_in_edit(line.index)
        line.tax_ids = [Command.link(tax_21.id)]
        wizard._line_value_changed_tax_ids(line)
        line.balance = 500.0
        wizard._line_value_changed_balance(line)

        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',       'balance': 1000.0,      'tax_tag_ids': []},
            {'flag': 'manual',          'balance': 500.0,       'tax_tag_ids': tax_tags[2].ids},
            {'flag': 'tax_line',        'balance': 105.0,       'tax_tag_ids': tax_tags[3].ids},
            {'flag': 'auto_balance',    'balance': -1605.0,     'tax_tag_ids': []},
        ])

        # Edit the base line.
        line = wizard.line_ids.filtered(lambda x: x.flag == 'manual')
        wizard._js_action_mount_line_in_edit(line.index)
        line.balance = -500.0
        wizard._line_value_changed_balance(line)

        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',       'balance': 1000.0,  'tax_tag_ids': []},
            {'flag': 'manual',          'balance': -500.0,  'tax_tag_ids': tax_tags[0].ids},
            {'flag': 'tax_line',        'balance': -105.0,  'tax_tag_ids': tax_tags[1].ids},
            {'flag': 'auto_balance',    'balance': -395.0,  'tax_tag_ids': []},
        ])

        # Edit the tax line.
        line = wizard.line_ids.filtered(lambda x: x.flag == 'tax_line')
        wizard._js_action_mount_line_in_edit(line.index)
        line.balance = -100.0
        wizard._line_value_changed_balance(line)

        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',       'balance': 1000.0,  'tax_tag_ids': []},
            {'flag': 'manual',          'balance': -500.0,  'tax_tag_ids': tax_tags[0].ids},
            {'flag': 'tax_line',        'balance': -100.0,  'tax_tag_ids': tax_tags[1].ids},
            {'flag': 'auto_balance',    'balance': -400.0,  'tax_tag_ids': []},
        ])

        # Add a new tax.
        tax_10 = self.env['account.tax'].create({
            'name': "tax_10",
            'amount': 10,
        })

        line = wizard.line_ids.filtered(lambda x: x.flag == 'manual')
        wizard._js_action_mount_line_in_edit(line.index)
        line.tax_ids = [Command.link(tax_10.id)]
        wizard._line_value_changed_tax_ids(line)

        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',       'balance': 1000.0},
            {'flag': 'manual',          'balance': -500.0},
            {'flag': 'tax_line',        'balance': -105.0},
            {'flag': 'tax_line',        'balance': -50.0},
            {'flag': 'auto_balance',    'balance': -345.0},
        ])

        # Remove the taxes.
        line = wizard.line_ids.filtered(lambda x: x.flag == 'manual')
        wizard._js_action_mount_line_in_edit(line.index)
        line.tax_ids = [Command.clear()]
        wizard._line_value_changed_tax_ids(line)

        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',       'balance': 1000.0},
            {'flag': 'manual',          'balance': -500.0},
            {'flag': 'auto_balance',    'balance': -500.0},
        ])

        # Reset the amount.
        line = wizard.line_ids.filtered(lambda x: x.flag == 'manual')
        wizard._js_action_mount_line_in_edit(line.index)
        line.balance = -1000.0
        wizard._line_value_changed_balance(line)

        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',       'balance': 1000.0},
            {'flag': 'manual',          'balance': -1000.0},
        ])

        # Add taxes. We should be back into the "price included taxes" mode.
        line = wizard.line_ids.filtered(lambda x: x.flag == 'manual')
        wizard._js_action_mount_line_in_edit(line.index)
        line.tax_ids = [Command.link(tax_21.id)]
        wizard._line_value_changed_tax_ids(line)

        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',   'balance': 1000.0},
            {'flag': 'manual',      'balance': -826.45},
            {'flag': 'tax_line',    'balance': -173.55},
        ])

        line = wizard.line_ids.filtered(lambda x: x.flag == 'manual')
        wizard._js_action_mount_line_in_edit(line.index)
        line.tax_ids = [Command.link(tax_10.id)]
        wizard._line_value_changed_tax_ids(line)

        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',   'balance': 1000.0},
            {'flag': 'manual',      'balance': -763.35},
            {'flag': 'tax_line',    'balance': -160.31},
            {'flag': 'tax_line',    'balance': -76.34},
        ])

        # Changing the account should recompute the taxes but preserve the "price included taxes" mode.
        line = wizard.line_ids.filtered(lambda x: x.flag == 'manual')
        wizard._js_action_mount_line_in_edit(line.index)
        line.account_id = self.account_revenue1
        wizard._line_value_changed_account_id(line)

        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',   'balance': 1000.0},
            {'flag': 'manual',      'balance': -763.35},
            {'flag': 'tax_line',    'balance': -160.31},
            {'flag': 'tax_line',    'balance': -76.34},
        ])

        # The wizard can be validated.
        self.assertRecordValues(wizard, [{'state': 'valid'}])

        # Validate and check the statement line.
        wizard._action_validate()
        self.assertRecordValues(st_line.line_ids, [
            # pylint: disable=C0326
            {'balance': 1000.0},
            {'balance': -763.35},
            {'balance': -160.31},
            {'balance': -76.34},
        ])
        self.assertRecordValues(wizard, [{'state': 'reconciled'}])

    def test_validation_caba_tax_account(self):
        """ Cash basis taxes usually put their tax lines on a transition account, and the cash basis entries then move those amounts
        to the regular tax accounts. When using a cash basis tax in the bank reconciliation widget, their won't be any cash basis
        entry and the lines will directly be exigible, so we want to use the final tax account directly.
        """
        tax_account = self.company_data['default_account_tax_sale']

        caba_tax = self.env['account.tax'].create({
            'name': "CABA",
            'amount_type': 'percent',
            'amount': 20.0,
            'tax_exigibility': 'on_payment',
            'cash_basis_transition_account_id': self.safe_copy(tax_account).id,
            'invoice_repartition_line_ids': [
                (0, 0, {
                    'repartition_type': 'base',
                }),
                (0, 0, {
                    'repartition_type': 'tax',
                    'account_id': tax_account.id,
                }),
            ],
            'refund_repartition_line_ids': [
                (0, 0, {
                    'repartition_type': 'base',
                }),
                (0, 0, {
                    'repartition_type': 'tax',
                    'account_id': tax_account.id,
                }),
            ],
        })

        st_line = self._create_st_line(120.0)

        wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
        line = wizard.line_ids.filtered(lambda x: x.flag == 'auto_balance')
        wizard._js_action_mount_line_in_edit(line.index)
        line.account_id = self.account_revenue1
        line.tax_ids = [Command.link(caba_tax.id)]
        wizard._line_value_changed_tax_ids(line)

        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',   'balance': 120.0,  'account_id': st_line.journal_id.default_account_id.id},
            {'flag': 'manual',      'balance': -100.0, 'account_id': self.account_revenue1.id},
            {'flag': 'tax_line',    'balance': -20.0,  'account_id': tax_account.id},
        ])

        self.assertRecordValues(wizard, [{'state': 'valid'}])

        wizard._action_validate()
        self.assertRecordValues(st_line.line_ids, [
            # pylint: disable=C0326
            {'balance': 120.0,  'tax_ids': [],           'tax_line_id': False,       'account_id': st_line.journal_id.default_account_id.id},
            {'balance': -100.0, 'tax_ids': caba_tax.ids, 'tax_line_id': False,       'account_id': self.account_revenue1.id},
            {'balance': -20.0,  'tax_ids': [],           'tax_line_id': caba_tax.id, 'account_id': tax_account.id},
        ])
        self.assertRecordValues(wizard, [{'state': 'reconciled'}])

    def test_validation_changed_default_account(self):
        st_line = self._create_st_line(100.0, partner_id=self.partner_a.id)
        original_journal_account_id = st_line.journal_id.default_account_id
        # Change the default account of the journal (exceptional case)
        st_line.journal_id.default_account_id = self.company_data['default_journal_cash'].default_account_id
        wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
        self.assertRecordValues(wizard, [{'state': 'valid'}])
        # Validate and check the statement line.
        wizard._action_validate()
        liquidity_line, _suspense_line, _other_line = st_line._seek_for_lines()
        self.assertRecordValues(liquidity_line, [
            {'account_id': original_journal_account_id.id, 'balance': 100.0},
        ])
        self.assertRecordValues(wizard, [{'state': 'reconciled'}])

    def test_apply_taxes_with_reco_model(self):
        st_line = self._create_st_line(1000.0)

        tax_21 = self.env['account.tax'].create({
            'name': "tax_21",
            'amount': 21,
        })

        reco_model = self.env['account.reconcile.model'].create({
            'name': "test_apply_taxes_with_reco_model",
            'rule_type': 'writeoff_button',
            'line_ids': [Command.create({
                'account_id': self.account_revenue1.id,
                'tax_ids': [Command.set(tax_21.ids)],
            })],
        })

        wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
        wizard._action_select_reconcile_model(reco_model)

        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',   'balance': 1000.0},
            {'flag': 'manual',      'balance': -826.45},
            {'flag': 'tax_line',    'balance': -173.55},
        ])

    def test_percentage_st_line_with_reco_model(self):
        journal_curr = self.other_currency
        foreign_curr = self.other_currency_2
        self.company_data['default_journal_bank'].currency_id = journal_curr

        # Setup triple currency.
        st_line = self._create_st_line(
            1000.0,
            date='2018-01-01',
            foreign_currency_id=foreign_curr.id,
            amount_currency=4000.0,
        )

        reco_model = self.env['account.reconcile.model'].create({
            'name': "test_percentage_st_line_with_reco_model",
            'rule_type': 'writeoff_button',
            'line_ids': [
                Command.create({
                    'amount_type': 'percentage_st_line',
                    'amount_string': str(percentage),
                    'label': str(i),
                    'account_id': self.account_revenue1.id,
                })
                for i, percentage in enumerate((74.0, 24.0, 12.0, -10.0))
            ],
        })

        wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
        wizard._action_select_reconcile_model(reco_model)

        self.assertRecordValues(wizard.line_ids, [
            {'flag': 'liquidity',   'currency_id': journal_curr.id,     'amount_currency': 1000.0,      'balance': 500.0},
            {'flag': 'manual',      'currency_id': journal_curr.id,     'amount_currency': -740.0,      'balance': -370.0},
            {'flag': 'manual',      'currency_id': journal_curr.id,     'amount_currency': -240.0,      'balance': -120.0},
            {'flag': 'manual',      'currency_id': journal_curr.id,     'amount_currency': -120.0,      'balance': -60.0},
            {'flag': 'manual',      'currency_id': journal_curr.id,     'amount_currency': 100.0,       'balance': 50.0},
        ])

    def test_manual_edits_not_replaced(self):
        """ 2 partial payments should keep the edited balance """
        st_line = self._create_st_line(
            1200.0,
            date='2017-02-01',
        )
        inv_line_1 = self._create_invoice_line(
            'out_invoice',
            invoice_date='2016-01-01',
            invoice_line_ids=[{'price_unit': 3000.0}],
        )
        inv_line_2 = self._create_invoice_line(
            'out_invoice',
            invoice_date='2017-01-01',
            invoice_line_ids=[{'price_unit': 4000.0}],
        )

        wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
        wizard._action_add_new_amls(inv_line_1)
        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',    'balance': 1200.0},
            {'flag': 'new_aml',      'balance':-1200.0},
        ])

        line = wizard.line_ids.filtered(lambda x: x.flag == 'new_aml')
        wizard._js_action_mount_line_in_edit(line.index)
        line.balance = -600.0
        wizard._line_value_changed_balance(line)

        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',    'balance': 1200.0},
            {'flag': 'new_aml',      'balance': -600.0},
            {'flag': 'auto_balance', 'balance': -600.0},
        ])

        wizard._action_add_new_amls(inv_line_2)

        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',    'balance': 1200.0},
            {'flag': 'new_aml',      'balance': -600.0},
            {'flag': 'new_aml',      'balance': -600.0},
        ])

    def test_manual_edits_not_replaced_multicurrency(self):
        """ 2 partial payments should keep the edited amount_currency """
        st_line = self._create_st_line(
            1200.0,
            date='2018-01-01',
            foreign_currency_id=self.other_currency_2.id,
            amount_currency=6000.0,  # rate 5:1
        )

        inv_line_1 = self._create_invoice_line(
            'out_invoice',
            invoice_date='2016-01-01',
            currency_id=self.other_currency_2.id,
            invoice_line_ids=[{'price_unit': 6000.0}],  # 1000 company curr (rate 6:1)
        )
        inv_line_2 = self._create_invoice_line(
            'out_invoice',
            invoice_date='2017-01-01',
            currency_id=self.other_currency_2.id,
            invoice_line_ids=[{'price_unit': 4000.0}], # 1000 company curr (rate 4:1)
        )

        wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
        wizard._action_add_new_amls(inv_line_1)
        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',     'amount_currency': 1200.0, 'balance': 1200.0},
            {'flag': 'new_aml',       'amount_currency':-6000.0, 'balance':-1000.0},
            {'flag': 'exchange_diff', 'amount_currency':    0.0, 'balance': -200.0},
        ])

        line = wizard.line_ids.filtered(lambda x: x.flag == 'new_aml')
        wizard._js_action_mount_line_in_edit(line.index)
        line.amount_currency = -3000.0
        wizard._line_value_changed_amount_currency(line)

        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',     'amount_currency': 1200.0, 'balance': 1200.0},
            {'flag': 'new_aml',       'amount_currency':-3000.0, 'balance': -500.0},
            {'flag': 'exchange_diff', 'amount_currency':    0.0, 'balance': -100.0},
            {'flag': 'auto_balance',  'amount_currency':-3000.0, 'balance': -600.0},
        ])

        wizard._action_add_new_amls(inv_line_2)

        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',     'amount_currency': 1200.0, 'balance': 1200.0},
            {'flag': 'new_aml',       'amount_currency':-3000.0, 'balance': -500.0},
            {'flag': 'exchange_diff', 'amount_currency':    0.0, 'balance': -100.0},
            {'flag': 'new_aml',       'amount_currency':-3000.0, 'balance': -750.0},
            {'flag': 'exchange_diff', 'amount_currency':    0.0, 'balance':  150.0},
        ])

    def test_creating_manual_line_multi_currencies(self):
        # 6300.0 curr2 == 1800.0 comp_curr (bank rate 3.5:1 instead of the odoo rate 4:1)
        st_line = self._create_st_line(
            1800.0,
            date='2017-01-01',
            foreign_currency_id=self.other_currency_2.id,
            amount_currency=6300.0,
        )

        wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',       'amount_currency': 1800.0,  'currency_id': self.company_data['currency'].id,    'balance': 1800.0},
            {'flag': 'auto_balance',    'amount_currency': -6300.0, 'currency_id': self.other_currency_2.id,            'balance': -1800.0},
        ])

        # Custom balance.
        line = wizard.line_ids.filtered(lambda x: x.flag == 'auto_balance')
        wizard._js_action_mount_line_in_edit(line.index)
        line.balance = -1500.0
        wizard._line_value_changed_balance(line)
        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',       'amount_currency': 1800.0,  'currency_id': self.company_data['currency'].id,    'balance': 1800.0},
            {'flag': 'manual',          'amount_currency': -6300.0, 'currency_id': self.other_currency_2.id,            'balance': -1500.0},
            {'flag': 'auto_balance',    'amount_currency': 0.0,     'currency_id': self.other_currency_2.id,            'balance': -300.0},
        ])

        # Custom amount_currency.
        line = wizard.line_ids.filtered(lambda x: x.flag == 'manual')
        wizard._js_action_mount_line_in_edit(line.index)
        line.amount_currency = -4200.0
        wizard._line_value_changed_amount_currency(line)
        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',       'amount_currency': 1800.0,  'currency_id': self.company_data['currency'].id,    'balance': 1800.0},
            {'flag': 'manual',          'amount_currency': -4200.0, 'currency_id': self.other_currency_2.id,            'balance': -1200.0},
            {'flag': 'auto_balance',    'amount_currency': -2100.0, 'currency_id': self.other_currency_2.id,            'balance': -600.0},
        ])

        # Custom currency_id.
        line = wizard.line_ids.filtered(lambda x: x.flag == 'manual')
        wizard._js_action_mount_line_in_edit(line.index)
        line.currency_id = self.other_currency
        wizard._line_value_changed_currency_id(line)
        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',       'amount_currency': 1800.0,  'currency_id': self.company_data['currency'].id,    'balance': 1800.0},
            {'flag': 'manual',          'amount_currency': -4200.0, 'currency_id': self.other_currency.id,              'balance': -2100.0},
            {'flag': 'auto_balance',    'amount_currency': 1050.0,  'currency_id': self.other_currency_2.id,            'balance': 300.0},
        ])

        # Custom balance.
        line = wizard.line_ids.filtered(lambda x: x.flag == 'manual')
        wizard._js_action_mount_line_in_edit(line.index)
        line.balance = -1800.0
        wizard._line_value_changed_balance(line)
        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',       'amount_currency': 1800.0,  'currency_id': self.company_data['currency'].id,    'balance': 1800.0},
            {'flag': 'manual',          'amount_currency': -4200.0, 'currency_id': self.other_currency.id,              'balance': -1800.0},
        ])

    def test_auto_reconcile_cron(self):
        self.env['account.reconcile.model'].search([('company_id', '=', self.company_data['company'].id)]).unlink()
        cron = self.env.ref('fusion_accounting.auto_reconcile_bank_statement_line')
        self.env['ir.cron.trigger'].search([('cron_id', '=', cron.id)]).unlink()

        st_line = self._create_st_line(1234.0, partner_id=self.partner_a.id, date='2017-01-01')
        self.assertEqual(len(self.env['ir.cron.trigger'].search([('cron_id', '=', cron.id)])), 1)

        self._create_invoice_line(
            'out_invoice',
            invoice_date='2017-01-01',
            invoice_line_ids=[{'price_unit': 1234.0}],
        )

        rule = self.env['account.reconcile.model'].create({
            'name': "test_auto_reconcile_cron",
            'rule_type': 'writeoff_suggestion',
            'auto_reconcile': False,
            'line_ids': [Command.create({'account_id': self.account_revenue1.id})],
        })

        # The CRON is not doing anything since the model is not auto reconcile.
        with freeze_time('2017-01-01'):
            self.env['account.bank.statement.line']._cron_try_auto_reconcile_statement_lines()
        self.assertRecordValues(st_line, [{'is_reconciled': False, 'cron_last_check': False}])
        self.assertEqual(len(self.env['ir.cron.trigger'].search([('cron_id', '=', cron.id)])), 1)

        rule.auto_reconcile = True

        # The CRON don't consider old statement lines.
        with freeze_time('2017-06-01'):
            self.env['account.bank.statement.line']._cron_try_auto_reconcile_statement_lines()
        self.assertRecordValues(st_line, [{'is_reconciled': False, 'cron_last_check': False}])
        self.assertEqual(len(self.env['ir.cron.trigger'].search([('cron_id', '=', cron.id)])), 1)

        # The CRON will auto-reconcile the line.
        with freeze_time('2017-01-02'):
            self.env['account.bank.statement.line']._cron_try_auto_reconcile_statement_lines()
        self.assertRecordValues(st_line, [{'is_reconciled': True, 'cron_last_check': fields.Datetime.from_string('2017-01-02 00:00:00')}])
        self.assertEqual(len(self.env['ir.cron.trigger'].search([('cron_id', '=', cron.id)])), 1)

        st_line1 = self._create_st_line(1234.0, partner_id=self.partner_a.id, date='2018-01-01')
        self.assertEqual(len(self.env['ir.cron.trigger'].search([('cron_id', '=', cron.id)])), 2)
        self._create_invoice_line(
            'out_invoice',
            invoice_date='2018-01-01',
            invoice_line_ids=[{'price_unit': 1234.0}],
        )
        st_line2 = self._create_st_line(1234.0, partner_id=self.partner_a.id, date='2018-01-01')
        self.assertEqual(len(self.env['ir.cron.trigger'].search([('cron_id', '=', cron.id)])), 3)
        self._create_invoice_line(
            'out_invoice',
            invoice_date='2018-01-01',
            invoice_line_ids=[{'price_unit': 1234.0}],
        )

        # Simulate the cron already tried to process 'st_line1' before.
        with freeze_time('2017-12-31'):
            st_line1.cron_last_check = fields.Datetime.now()

        # The statement line with no 'cron_last_check' must be processed before others.
        with freeze_time('2018-01-02'):
            self.env['account.bank.statement.line']._cron_try_auto_reconcile_statement_lines(batch_size=1)

        self.assertRecordValues(st_line1 + st_line2, [
            {'is_reconciled': False, 'cron_last_check': fields.Datetime.from_string('2017-12-31 00:00:00')},
            {'is_reconciled': True, 'cron_last_check': fields.Datetime.from_string('2018-01-02 00:00:00')},
        ])
        self.assertEqual(len(self.env['ir.cron.trigger'].search([('cron_id', '=', cron.id)])), 4)

        with freeze_time('2018-01-03'):
            self.env['account.bank.statement.line']._cron_try_auto_reconcile_statement_lines(batch_size=1)

        self.assertRecordValues(st_line1, [{'is_reconciled': True, 'cron_last_check': fields.Datetime.from_string('2018-01-03 00:00:00')}])
        self.assertEqual(len(self.env['ir.cron.trigger'].search([('cron_id', '=', cron.id)])), 4)

        st_line3 = self._create_st_line(1234.0, date='2018-01-01')
        self.assertEqual(len(self.env['ir.cron.trigger'].search([('cron_id', '=', cron.id)])), 5)
        self._create_invoice_line(
            'out_invoice',
            invoice_date='2018-01-01',
            invoice_line_ids=[{'price_unit': 1234.0}],
        )
        st_line4 = self._create_st_line(1234.0, date='2018-01-01')
        self.assertEqual(len(self.env['ir.cron.trigger'].search([('cron_id', '=', cron.id)])), 6)
        self._create_invoice_line(
            'out_invoice',
            invoice_date='2018-01-01',
            invoice_line_ids=[{'price_unit': 1234.0}],
        )

        # Make sure the CRON is no longer applicable.
        rule.match_partner = True
        rule.match_partner_ids = [Command.set(self.partner_a.ids)]
        with freeze_time('2018-01-01'):
            self.env['account.bank.statement.line']._cron_try_auto_reconcile_statement_lines(batch_size=1)

        self.assertRecordValues(st_line3 + st_line4, [
            {'is_reconciled': False, 'cron_last_check': fields.Datetime.from_string('2018-01-01 00:00:00')},
            {'is_reconciled': False, 'cron_last_check': False},
        ])
        self.assertEqual(len(self.env['ir.cron.trigger'].search([('cron_id', '=', cron.id)])), 7)

        # Make sure the statement lines are reconciled by the cron in the right order.
        self.assertRecordValues(st_line3 + st_line4, [
            {'is_reconciled': False, 'cron_last_check': fields.Datetime.from_string('2018-01-01 00:00:00')},
            {'is_reconciled': False, 'cron_last_check': False},
        ])

        # st_line4 is processed because cron_last_check is null.
        with freeze_time('2018-01-02'):
            self.env['account.bank.statement.line']._cron_try_auto_reconcile_statement_lines(batch_size=1)

        self.assertRecordValues(st_line3 + st_line4, [
            {'is_reconciled': False, 'cron_last_check': fields.Datetime.from_string('2018-01-01 00:00:00')},
            {'is_reconciled': False, 'cron_last_check': fields.Datetime.from_string('2018-01-02 00:00:00')},
        ])
        self.assertEqual(len(self.env['ir.cron.trigger'].search([('cron_id', '=', cron.id)])), 7)

        # st_line3 is processed because it has the oldest cron_last_check.
        with freeze_time('2018-01-03'):
            self.env['account.bank.statement.line']._cron_try_auto_reconcile_statement_lines(batch_size=1)

        self.assertRecordValues(st_line3 + st_line4, [
            {'is_reconciled': False, 'cron_last_check': fields.Datetime.from_string('2018-01-03 00:00:00')},
            {'is_reconciled': False, 'cron_last_check': fields.Datetime.from_string('2018-01-02 00:00:00')},
        ])
        self.assertEqual(len(self.env['ir.cron.trigger'].search([('cron_id', '=', cron.id)])), 7)

    def test_duplicate_amls_constraint(self):
        st_line = self._create_st_line(1000.0)
        inv_line = self._create_invoice_line(
            'out_invoice',
            invoice_line_ids=[{'price_unit': 1000.0}],
        )

        wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
        wizard._action_add_new_amls(inv_line)
        self.assertTrue(len(wizard.line_ids), 2)

        wizard._action_add_new_amls(inv_line)
        self.assertTrue(len(wizard.line_ids), 2)

    @freeze_time('2017-01-01')
    def test_reconcile_model_with_payment_tolerance(self):
        self.env['account.reconcile.model'].search([('company_id', '=', self.company_data['company'].id)]).unlink()

        invoice_line = self._create_invoice_line(
            'out_invoice',
            invoice_date='2017-01-01',
            invoice_line_ids=[{'price_unit': 1000.0}],
        )
        st_line = self._create_st_line(998.0, partner_id=self.partner_a.id, date='2017-01-01', payment_ref=invoice_line.move_id.name)

        rule = self.env['account.reconcile.model'].create({
            'name': "test_reconcile_model_with_payment_tolerance",
            'rule_type': 'invoice_matching',
            'allow_payment_tolerance': True,
            'payment_tolerance_type': 'percentage',
            'payment_tolerance_param': 2.0,
            'line_ids': [Command.create({'account_id': self.account_revenue1.id})],
        })

        wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
        wizard._action_trigger_matching_rules()
        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',       'balance': 998.0,   'reconcile_model_id': False},
            {'flag': 'new_aml',         'balance': -1000.0, 'reconcile_model_id': rule.id},
            {'flag': 'manual',          'balance': 2.0,     'reconcile_model_id': rule.id},
        ])

    @freeze_time('2017-01-01')
    def test_auto_reconcile_model_with_archived_partner(self):
        self.env['account.reconcile.model'].search([('company_id', '=', self.company_data['company'].id)]).unlink()
        self.env['res.partner.bank'].search([('company_id', '=', self.company_data['company'].id)]).unlink()

        # Needed as partner_a does not have a company and we need a company in order to find matching partner from account_number
        self.partner_a.company_id = self.company_data['company'].id
        self.env['res.partner.bank'].create({
            'partner_id': self.partner_a.id,
            'acc_number': '12345',
        })
        invoice_line = self._create_invoice_line(
            'out_invoice',
            invoice_date='2017-01-01',
            invoice_line_ids=[{'price_unit': 1000.0}],
        )
        st_line = self._create_st_line(1000.0, account_number='12345', date='2017-01-01', payment_ref=invoice_line.move_id.name)

        self.env['account.reconcile.model'].create({
            'name': "test_reconcile_model_with_archived_partner",
            'rule_type': 'invoice_matching',
            'auto_reconcile': True,
            'match_partner': True,
        })

        wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
        wizard._action_trigger_matching_rules()
        self.assertEqual(wizard.partner_id, self.partner_a)
        self.assertTrue(wizard.matching_rules_allow_auto_reconcile)

        # archive partner and trigger again, partner should still be set (match based on account_number), because it's the only match
        self.partner_a.active = False
        wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
        wizard._action_trigger_matching_rules()
        self.assertEqual(wizard.partner_id, self.partner_a)  # Partner should still be set
        self.assertTrue(wizard.matching_rules_allow_auto_reconcile)  # no special process because the partner is unactive

    def test_early_payment_included_multi_currency(self):
        self.env['account.reconcile.model'].search([('company_id', '=', self.company_data['company'].id)]).unlink()
        self.early_payment_term.early_pay_discount_computation = 'included'
        income_exchange_account = self.env.company.income_currency_exchange_account_id
        expense_exchange_account = self.env.company.expense_currency_exchange_account_id

        inv_line1_with_epd = self._create_invoice_line(
            'out_invoice',
            currency_id=self.other_currency_2.id,
            partner_id=self.partner_a.id,
            invoice_payment_term_id=self.early_payment_term.id,
            invoice_date='2016-12-01',
            invoice_line_ids=[
                {
                    'price_unit': 4800.0,
                    'account_id': self.account_revenue1.id,
                    'tax_ids': [Command.set(self.company_data['default_tax_sale'].ids)],
                },
                {
                    'price_unit': 9600.0,
                    'account_id': self.account_revenue2.id,
                    'tax_ids': [Command.set(self.company_data['default_tax_sale'].ids)],
                },
            ],
        )
        inv_line1_with_epd_rec_lines = inv_line1_with_epd.move_id.line_ids\
            .filtered(lambda x: x.account_type == 'asset_receivable')\
            .sorted(lambda x: x.discount_date or x.date_maturity)
        self.assertRecordValues(
            inv_line1_with_epd_rec_lines,
            [
                {
                    'amount_currency': 16560.0,
                    'balance': 2760.0,
                    'discount_amount_currency': 14904.0,
                    'discount_balance': 2484.0,
                    'discount_date': fields.Date.from_string('2016-12-11'),
                    'date_maturity': fields.Date.from_string('2016-12-21'),
                },
            ],
        )

        inv_line2_with_epd = self._create_invoice_line(
            'out_invoice',
            currency_id=self.other_currency_2.id,
            partner_id=self.partner_a.id,
            invoice_payment_term_id=self.early_payment_term.id,
            invoice_date='2017-01-20',
            invoice_line_ids=[
                {
                    'price_unit': 480.0,
                    'account_id': self.account_revenue1.id,
                    'tax_ids': [Command.set(self.company_data['default_tax_sale'].ids)],
                },
                {
                    'price_unit': 960.0,
                    'account_id': self.account_revenue2.id,
                    'tax_ids': [Command.set(self.company_data['default_tax_sale'].ids)],
                },
            ],
        )
        inv_line2_with_epd_rec_lines = inv_line2_with_epd.move_id.line_ids\
            .filtered(lambda x: x.account_type == 'asset_receivable')\
            .sorted(lambda x: x.discount_date or x.date_maturity)
        self.assertRecordValues(
            inv_line2_with_epd_rec_lines,
            [
                {
                    'amount_currency': 1656.0,
                    'balance': 414.0,
                    'discount_amount_currency': 1490.4,
                    'discount_balance': 372.6,
                    'discount_date': fields.Date.from_string('2017-01-30'),
                    'date_maturity': fields.Date.from_string('2017-02-09'),
                },
            ],
        )

        # inv1: 16560.0 (no epd)
        # inv2: 1490.4 (epd)
        st_line = self._create_st_line(
            4512.0, # instead of 4512.6 (rate 1:4)
            date='2017-01-04',
            foreign_currency_id=self.other_currency_2.id,
            amount_currency=18050.4,
        )

        wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})

        # Add all lines from the first invoice plus the first one from the second one.
        wizard._action_add_new_amls(inv_line1_with_epd_rec_lines + inv_line2_with_epd_rec_lines)
        liquidity_acc = st_line.journal_id.default_account_id
        receivable_acc = self.company_data['default_account_receivable']
        early_pay_acc = self.env.company.account_journal_early_pay_discount_loss_account_id
        tax_acc = self.company_data['default_tax_sale'].invoice_repartition_line_ids.account_id
        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',       'amount_currency': 4512.0,      'balance': 4512.0,      'account_id': liquidity_acc.id},
            {'flag': 'new_aml',         'amount_currency': -16560.0,    'balance': -2760.0,     'account_id': receivable_acc.id},
            {'flag': 'exchange_diff',   'amount_currency': 0.0,         'balance': -1379.45,    'account_id': income_exchange_account.id},
            {'flag': 'new_aml',         'amount_currency': -1656.0,     'balance': -414.0,      'account_id': receivable_acc.id},
            {'flag': 'exchange_diff',   'amount_currency': 0.0,         'balance': 0.06,        'account_id': expense_exchange_account.id},
            {'flag': 'early_payment',   'amount_currency': 144.0,       'balance': 36.0,        'account_id': early_pay_acc.id},
            {'flag': 'early_payment',   'amount_currency': 21.6,        'balance': 5.4,         'account_id': tax_acc.id},
            {'flag': 'early_payment',   'amount_currency': 0.0,         'balance': -0.01,       'account_id': income_exchange_account.id},
        ])

    def test_early_payment_excluded_multi_currency(self):
        self.env['account.reconcile.model'].search([('company_id', '=', self.company_data['company'].id)]).unlink()
        self.early_payment_term.early_pay_discount_computation = 'excluded'
        income_exchange_account = self.env.company.income_currency_exchange_account_id
        expense_exchange_account = self.env.company.expense_currency_exchange_account_id

        inv_line1_with_epd = self._create_invoice_line(
            'out_invoice',
            currency_id=self.other_currency_2.id,
            partner_id=self.partner_a.id,
            invoice_payment_term_id=self.early_payment_term.id,
            invoice_date='2016-12-01',
            invoice_line_ids=[
                {
                    'price_unit': 4800.0,
                    'account_id': self.account_revenue1.id,
                    'tax_ids': [Command.set(self.company_data['default_tax_sale'].ids)],
                },
                {
                    'price_unit': 9600.0,
                    'account_id': self.account_revenue2.id,
                    'tax_ids': [Command.set(self.company_data['default_tax_sale'].ids)],
                },
            ],
        )
        inv_line1_with_epd_rec_lines = inv_line1_with_epd.move_id.line_ids\
            .filtered(lambda x: x.account_type == 'asset_receivable')\
            .sorted(lambda x: x.discount_date or x.date_maturity)
        self.assertRecordValues(
            inv_line1_with_epd_rec_lines,
            [
                {
                    'amount_currency': 16560.0,
                    'balance': 2760.0,
                    'discount_amount_currency': 15120.0,
                    'discount_balance': 2520.0,
                    'discount_date': fields.Date.from_string('2016-12-11'),
                    'date_maturity': fields.Date.from_string('2016-12-21'),
                },
            ],
        )

        inv_line2_with_epd = self._create_invoice_line(
            'out_invoice',
            currency_id=self.other_currency_2.id,
            partner_id=self.partner_a.id,
            invoice_payment_term_id=self.early_payment_term.id,
            invoice_date='2017-01-20',
            invoice_line_ids=[
                {
                    'price_unit': 480.0,
                    'account_id': self.account_revenue1.id,
                    'tax_ids': [Command.set(self.company_data['default_tax_sale'].ids)],
                },
                {
                    'price_unit': 960.0,
                    'account_id': self.account_revenue2.id,
                    'tax_ids': [Command.set(self.company_data['default_tax_sale'].ids)],
                },
            ],
        )
        inv_line2_with_epd_rec_lines = inv_line2_with_epd.move_id.line_ids\
            .filtered(lambda x: x.account_type == 'asset_receivable')\
            .sorted(lambda x: x.discount_date or x.date_maturity)
        self.assertRecordValues(
            inv_line2_with_epd_rec_lines,
            [
                {
                    'amount_currency': 1656.0,
                    'balance': 414.0,
                    'discount_amount_currency': 1512.0,
                    'discount_balance': 378.0,
                    'discount_date': fields.Date.from_string('2017-01-30'),
                    'date_maturity': fields.Date.from_string('2017-02-09'),
                },
            ],
        )

        # inv1: 16560.0 (no epd)
        # inv2: 1512.0 (epd)
        st_line = self._create_st_line(
            4515.0, # instead of 4518.0 (rate 1:4)
            date='2017-01-04',
            foreign_currency_id=self.other_currency_2.id,
            amount_currency=18072.0,
        )

        wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})

        # Add all lines from the first invoice plus the first one from the second one.
        wizard._action_add_new_amls(inv_line1_with_epd_rec_lines + inv_line2_with_epd_rec_lines[:2])
        liquidity_acc = st_line.journal_id.default_account_id
        receivable_acc = self.company_data['default_account_receivable']
        early_pay_acc = self.env.company.account_journal_early_pay_discount_loss_account_id
        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',       'amount_currency': 4515.0,      'balance': 4515.0,      'account_id': liquidity_acc.id},
            {'flag': 'new_aml',         'amount_currency': -16560.0,    'balance': -2760.0,     'account_id': receivable_acc.id},
            {'flag': 'exchange_diff',   'amount_currency': 0.0,         'balance': -1377.25,    'account_id': income_exchange_account.id},
            {'flag': 'new_aml',         'amount_currency': -1656.0,     'balance': -414.0,      'account_id': receivable_acc.id},
            {'flag': 'exchange_diff',   'amount_currency': 0.0,         'balance': 0.27,        'account_id': expense_exchange_account.id},
            {'flag': 'early_payment',   'amount_currency': 144.0,       'balance': 36.0,        'account_id': early_pay_acc.id},
            {'flag': 'early_payment',   'amount_currency': 0.0,         'balance': -0.02,       'account_id': income_exchange_account.id},
        ])

    def test_early_payment_mixed_multi_currency(self):
        self.env['account.reconcile.model'].search([('company_id', '=', self.company_data['company'].id)]).unlink()
        self.early_payment_term.early_pay_discount_computation = 'mixed'
        income_exchange_account = self.env.company.income_currency_exchange_account_id
        expense_exchange_account = self.env.company.expense_currency_exchange_account_id

        inv_line1_with_epd = self._create_invoice_line(
            'out_invoice',
            currency_id=self.other_currency_2.id,
            partner_id=self.partner_a.id,
            invoice_payment_term_id=self.early_payment_term.id,
            invoice_date='2016-12-01',
            invoice_line_ids=[
                {
                    'price_unit': 4800.0,
                    'account_id': self.account_revenue1.id,
                    'tax_ids': [Command.set(self.company_data['default_tax_sale'].ids)],
                },
                {
                    'price_unit': 9600.0,
                    'account_id': self.account_revenue2.id,
                    'tax_ids': [Command.set(self.company_data['default_tax_sale'].ids)],
                },
            ],
        )
        inv_line1_with_epd_rec_lines = inv_line1_with_epd.move_id.line_ids\
            .filtered(lambda x: x.account_type == 'asset_receivable')\
            .sorted(lambda x: x.discount_date or x.date_maturity)
        self.assertRecordValues(
            inv_line1_with_epd_rec_lines,
            [
                {
                    'amount_currency': 16344.0,
                    'balance': 2724.0,
                    'discount_amount_currency': 14904.0,
                    'discount_balance': 2484.0,
                    'discount_date': fields.Date.from_string('2016-12-11'),
                    'date_maturity': fields.Date.from_string('2016-12-21'),
                },
            ],
        )

        inv_line2_with_epd = self._create_invoice_line(
            'out_invoice',
            currency_id=self.other_currency_2.id,
            partner_id=self.partner_a.id,
            invoice_payment_term_id=self.early_payment_term.id,
            invoice_date='2017-01-20',
            invoice_line_ids=[
                {
                    'price_unit': 480.0,
                    'account_id': self.account_revenue1.id,
                    'tax_ids': [Command.set(self.company_data['default_tax_sale'].ids)],
                },
                {
                    'price_unit': 960.0,
                    'account_id': self.account_revenue2.id,
                    'tax_ids': [Command.set(self.company_data['default_tax_sale'].ids)],
                },
            ],
        )
        inv_line2_with_epd_rec_lines = inv_line2_with_epd.move_id.line_ids\
            .filtered(lambda x: x.account_type == 'asset_receivable')\
            .sorted(lambda x: x.discount_date or x.date_maturity)
        self.assertRecordValues(
            inv_line2_with_epd_rec_lines,
            [
                {
                    'amount_currency': 1634.4,
                    'balance': 408.6,
                    'discount_amount_currency': 1490.4,
                    'discount_balance': 372.6,
                    'discount_date': fields.Date.from_string('2017-01-30'),
                    'date_maturity': fields.Date.from_string('2017-02-09'),
                },
            ],
        )

        # inv1: 16344.0 (no epd)
        # inv2: 1490.4 (epd)
        st_line = self._create_st_line(
            4458.0, # instead of 4458.6 (rate 1:4)
            date='2017-01-04',
            foreign_currency_id=self.other_currency_2.id,
            amount_currency=17834.4,
        )

        wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})

        # Add all lines from the first invoice plus the first one from the second one.
        wizard._action_add_new_amls(inv_line1_with_epd_rec_lines + inv_line2_with_epd_rec_lines[:2])
        liquidity_acc = st_line.journal_id.default_account_id
        receivable_acc = self.company_data['default_account_receivable']
        early_pay_acc = self.env.company.account_journal_early_pay_discount_loss_account_id
        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',       'amount_currency': 4458.0,      'balance': 4458.0,      'account_id': liquidity_acc.id},
            {'flag': 'new_aml',         'amount_currency': -16344.0,    'balance': -2724.0,     'account_id': receivable_acc.id},
            {'flag': 'exchange_diff',   'amount_currency': 0.0,         'balance': -1361.45,    'account_id': income_exchange_account.id},
            {'flag': 'new_aml',         'amount_currency': -1634.4,     'balance': -408.6,      'account_id': receivable_acc.id},
            {'flag': 'exchange_diff',   'amount_currency': 0.0,         'balance': 0.05,        'account_id': expense_exchange_account.id},
            {'flag': 'early_payment',   'amount_currency': 144.0,       'balance': 36.0,        'account_id': early_pay_acc.id},
        ])

    def test_early_payment_included_intracomm_bill(self):
        tax_tags = self.env['account.account.tag'].create({
            'name': f'tax_tag_{i}',
            'applicability': 'taxes',
            'country_id': self.env.company.account_fiscal_country_id.id,
        } for i in range(6))

        intracomm_tax = self.env['account.tax'].create({
            'name': 'tax20',
            'amount_type': 'percent',
            'amount': 20,
            'type_tax_use': 'purchase',
            'invoice_repartition_line_ids': [
                # pylint: disable=bad-whitespace
                Command.create({'repartition_type': 'base', 'factor_percent': 100.0,    'tag_ids': [Command.set(tax_tags[0].ids)]}),
                Command.create({'repartition_type': 'tax',  'factor_percent': 100.0,    'tag_ids': [Command.set(tax_tags[1].ids)]}),
                Command.create({'repartition_type': 'tax',  'factor_percent': -100.0,   'tag_ids': [Command.set(tax_tags[2].ids)]}),
            ],
            'refund_repartition_line_ids': [
                # pylint: disable=bad-whitespace
                Command.create({'repartition_type': 'base', 'factor_percent': 100.0,    'tag_ids': [Command.set(tax_tags[3].ids)]}),
                Command.create({'repartition_type': 'tax',  'factor_percent': 100.0,    'tag_ids': [Command.set(tax_tags[4].ids)]}),
                Command.create({'repartition_type': 'tax',  'factor_percent': -100.0,   'tag_ids': [Command.set(tax_tags[5].ids)]}),
            ],
        })

        early_payment_term = self.env['account.payment.term'].create({
            'name': "early_payment_term",
            'company_id': self.company_data['company'].id,
            'early_pay_discount_computation': 'included',
            'early_discount': True,
            'discount_percentage': 2,
            'discount_days': 7,
            'line_ids': [
                Command.create({
                    'value': 'percent',
                    'value_amount': 100.0,
                    'nb_days': 30,
                }),
            ],
        })

        bill = self.env['account.move'].create({
            'move_type': 'in_invoice',
            'partner_id': self.partner_a.id,
            'invoice_payment_term_id': early_payment_term.id,
            'invoice_date': '2019-01-01',
            'date': '2019-01-01',
            'invoice_line_ids': [
                Command.create({
                    'name': 'line',
                    'price_unit': 1000.0,
                    'tax_ids': [Command.set(intracomm_tax.ids)],
                }),
            ],
        })
        bill.action_post()

        st_line = self._create_st_line(
            -980.0,
            date='2017-01-01',
        )

        wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
        wizard._action_add_new_amls(bill.line_ids.filtered(lambda x: x.account_type == 'liability_payable'))
        wizard._action_validate()

        self.assertRecordValues(st_line.line_ids.sorted('balance'), [
            # pylint: disable=bad-whitespace
            {'amount_currency': -980.0, 'tax_ids': [],                  'tax_tag_ids': [],              'tax_tag_invert': False},
            {'amount_currency': -20.0,  'tax_ids': intracomm_tax.ids,   'tax_tag_ids': tax_tags[3].ids, 'tax_tag_invert': True},
            {'amount_currency': -4.0,   'tax_ids': [],                  'tax_tag_ids': tax_tags[4].ids, 'tax_tag_invert': True},
            {'amount_currency': 4.0,    'tax_ids': [],                  'tax_tag_ids': tax_tags[5].ids, 'tax_tag_invert': True},
            {'amount_currency': 1000.0, 'tax_ids': [],                  'tax_tag_ids': [],              'tax_tag_invert': False},
        ])

    def test_multi_currencies_with_custom_rate(self):
        self.company_data['default_journal_bank'].currency_id = self.other_currency
        st_line = self._create_st_line(1200.0) # rate 1:2
        self.assertRecordValues(st_line.move_id.line_ids, [
            # pylint: disable=C0326
            {'amount_currency': 1200.0,     'balance': 600.0},
            {'amount_currency': -1200.0,    'balance': -600.0},
        ])

        # invoice with other_currency and rate 1:2
        invoice_line1 = self._create_invoice_line(
            'out_invoice',
            currency_id=self.other_currency.id,
            invoice_date='2017-01-01',
            invoice_line_ids=[{'price_unit': 300.0}], # = 150 USD
        )

        # Remove all rates.
        self.other_currency.rate_ids.unlink()

        wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',       'amount_currency': 1200.0,  'balance': 600.0},
            {'flag': 'auto_balance',    'amount_currency': -1200.0, 'balance': -600.0},
        ])

        # invoice with other_currency_2 and rate 1:6
        invoice_line2 = self._create_invoice_line(
            'out_invoice',
            currency_id=self.other_currency_2.id,
            invoice_date='2016-01-01',
            invoice_line_ids=[{'price_unit': 600.0}], # = 100 USD
        )
        # invoice with other_currency_2 and rate 1:4
        invoice_line3 = self._create_invoice_line(
            'out_invoice',
            currency_id=self.other_currency_2.id,
            invoice_date='2017-01-01',
            invoice_line_ids=[{'price_unit': 400.0}], # = 100 USD
        )

        # Remove all rates.
        self.other_currency_2.rate_ids.unlink()

        # Ensure no conversion rate has been made.
        wizard._action_add_new_amls(invoice_line1 + invoice_line2 + invoice_line3)
        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',       'amount_currency': 1200.0,  'balance': 600.0},
            {'flag': 'new_aml',         'amount_currency': -300.0,  'balance': -150.0},
            {'flag': 'new_aml',         'amount_currency': -600.0,  'balance': -100.0},
            {'flag': 'new_aml',         'amount_currency': -400.0,  'balance': -100.0},
            {'flag': 'auto_balance',    'amount_currency': -500.0,  'balance': -250.0},
        ])

    def test_partial_reconciliation_suggestion_with_mixed_invoice_and_refund(self):
        """ Test the partial reconciliation suggestion is well recomputed when adding another
        line. For example, when adding 2 invoices having an higher amount then a refund. In that
        case, the partial on the second invoice should be removed since the difference is filled
        by the newly added refund.
        """
        st_line = self._create_st_line(
            1800.0,
            date='2017-01-01',
            foreign_currency_id=self.other_currency.id,
            amount_currency=3600.0,
        )

        inv1 = self._create_invoice_line(
            'out_invoice',
            currency_id=self.other_currency.id,
            invoice_date='2016-01-01',
            invoice_line_ids=[{'price_unit': 2400.0}],
        )
        inv2 = self._create_invoice_line(
            'out_invoice',
            currency_id=self.other_currency.id,
            invoice_date='2016-01-01',
            invoice_line_ids=[{'price_unit': 2400.0}],
        )
        refund = self._create_invoice_line(
            'out_refund',
            currency_id=self.other_currency.id,
            invoice_date='2016-01-01',
            invoice_line_ids=[{'price_unit': 1200.0}],
        )

        wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
        wizard._action_add_new_amls(inv1 + inv2)
        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',       'amount_currency': 1800.0,  'balance': 1800.0},
            {'flag': 'new_aml',         'amount_currency': -2400.0, 'balance': -800.0},
            {'flag': 'exchange_diff',   'amount_currency': 0.0,     'balance': -400.0},
            {'flag': 'new_aml',         'amount_currency': -1200.0, 'balance': -400.0},
            {'flag': 'exchange_diff',   'amount_currency': 0.0,     'balance': -200.0},
        ])
        wizard._action_add_new_amls(refund)
        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',       'amount_currency': 1800.0,  'balance': 1800.0},
            {'flag': 'new_aml',         'amount_currency': -2400.0, 'balance': -800.0},
            {'flag': 'exchange_diff',   'amount_currency': 0.0,     'balance': -400.0},
            {'flag': 'new_aml',         'amount_currency': -2400.0, 'balance': -800.0},
            {'flag': 'exchange_diff',   'amount_currency': 0.0,     'balance': -400.0},
            {'flag': 'new_aml',         'amount_currency': 1200.0,  'balance': 400.0},
            {'flag': 'exchange_diff',   'amount_currency': 0.0,     'balance': 200.0},
        ])

    def test_auto_reconcile_cron_with_time_limit(self):
        self.env['account.reconcile.model'].search([('company_id', '=', self.company_data['company'].id)]).unlink()
        cron = self.env.ref('fusion_accounting.auto_reconcile_bank_statement_line')
        self.env['ir.cron.trigger'].search([('cron_id', '=', cron.id)]).unlink()

        st_line1 = self._create_st_line(1234.0, partner_id=self.partner_a.id, date='2017-01-01')
        self.assertEqual(len(self.env['ir.cron.trigger'].search([('cron_id', '=', cron.id)])), 1)
        st_line2 = self._create_st_line(5678.0, partner_id=self.partner_a.id, date='2017-01-02')
        self.assertEqual(len(self.env['ir.cron.trigger'].search([('cron_id', '=', cron.id)])), 2)

        self._create_invoice_line(
            'out_invoice',
            invoice_date='2017-01-01',
            invoice_line_ids=[{'price_unit': 1234.0}],
        )
        self._create_invoice_line(
            'out_invoice',
            invoice_date='2017-01-01',
            invoice_line_ids=[{'price_unit': 5678.0}],
        )
        self.env['account.reconcile.model'].create({
            'name': "test_auto_reconcile_cron_with_time_limit",
            'rule_type': 'writeoff_suggestion',
            'auto_reconcile': True,
            'line_ids': [Command.create({'account_id': self.account_revenue1.id})],
        })

        with freeze_time('2017-01-01 00:00:00') as frozen_time:
            def datetime_now_override():
                frozen_time.tick()
                return frozen_time()
            with patch('odoo.fields.Datetime.now', side_effect=datetime_now_override):
                # we simulate that the time limit is reached after first loop
                self.env['account.bank.statement.line']._cron_try_auto_reconcile_statement_lines(limit_time=1)
        # after first loop, only one statement should be reconciled
        self.assertRecordValues(st_line1, [{'is_reconciled': True, 'cron_last_check': fields.Datetime.from_string('2017-01-01 00:00:01')}])
        # the other one should be in queue for regular cron tigger
        self.assertRecordValues(st_line2, [{'is_reconciled': False, 'cron_last_check': False}])
        self.assertEqual(len(self.env['ir.cron.trigger'].search([('cron_id', '=', cron.id)])), 3)

    def test_auto_reconcile_cron_with_provided_statements_lines(self):
        self.env['account.reconcile.model'].search([('company_id', '=', self.company_data['company'].id)]).unlink()

        st_line1 = self._create_st_line(1234.0, partner_id=self.partner_a.id, date='2017-01-01')
        st_line2 = self._create_st_line(5678.0, partner_id=self.partner_a.id, date='2017-01-02')
        self._create_invoice_line(
            'out_invoice',
            invoice_date='2017-01-01',
            invoice_line_ids=[{'price_unit': 1234.0}],
        )
        self._create_invoice_line(
            'out_invoice',
            invoice_date='2017-01-01',
            invoice_line_ids=[{'price_unit': 5678.0}],
        )
        self.env['account.reconcile.model'].create({
            'name': "test_auto_reconcile_cron_with_time_limit",
            'rule_type': 'writeoff_suggestion',
            'auto_reconcile': True,
            'line_ids': [Command.create({'account_id': self.account_revenue1.id})],
        })
        with freeze_time('2017-01-01 00:00:00'):
            # we call auto reconcile on st_lines1 **only**
            st_line1._cron_try_auto_reconcile_statement_lines()
        self.assertRecordValues(st_line1, [{'is_reconciled': True, 'cron_last_check': fields.Datetime.from_string('2017-01-01 00:00:00')}])
        self.assertRecordValues(st_line2, [{'is_reconciled': False, 'cron_last_check': False}])

    @freeze_time('2019-01-01')
    def test_button_apply_reco_model(self):
        inv_line = self._create_invoice_line(
            'in_invoice',
            invoice_date='2019-01-01',
            invoice_line_ids=[{'price_unit': 980.0}],
        )
        st_line = self._create_st_line(-1000.0, partner_id=self.partner_a.id, date=inv_line.date, payment_ref=inv_line.move_name)

        reco_model = self.env['account.reconcile.model'].create({
            'name': "test_apply_taxes_with_reco_model",
            'rule_type': 'writeoff_button',
            'line_ids': [Command.create({
                'account_id': self.account_revenue1.copy().id,
                'label': 'Bank Fees'
            })],
        })

        wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
        wizard._action_trigger_matching_rules()

        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',    'account_id': st_line.journal_id.default_account_id.id,        'balance': -1000.0},
            {'flag': 'new_aml',      'account_id': inv_line.account_id.id,                          'balance':   980.0},
            {'flag': 'auto_balance', 'account_id': self.company_data['default_account_payable'].id, 'balance':    20.0},
        ])

        wizard._action_select_reconcile_model(reco_model)

        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',    'account_id': st_line.journal_id.default_account_id.id,        'balance': -1000.0},
            {'flag': 'new_aml',      'account_id': inv_line.account_id.id,                          'balance':   980.0},
            {'flag': 'manual',       'account_id': reco_model.line_ids[0].account_id.id,            'balance':    20.0},
        ])

    def test_exchange_diff_on_partial_aml_multi_currency(self):
        self.company_data['default_journal_bank'].currency_id = self.other_currency
        st_line = self._create_st_line(-36000.0) # rate 1:2
        inv_line = self._create_invoice_line(
            'in_invoice',
            invoice_date='2016-01-01', # rate 1:3
            currency_id=self.other_currency.id,
            invoice_line_ids=[{'price_unit': 38000.0}],
        )

        wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
        wizard._action_add_new_amls(inv_line)

        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',       'amount_currency': -36000.0,    'currency_id': self.other_currency.id,   'balance': -18000.0},
            {'flag': 'new_aml',         'amount_currency': 36000.0,     'currency_id': self.other_currency.id,   'balance': 12000.0},
            {'flag': 'exchange_diff',   'amount_currency': 0.0,         'currency_id': self.other_currency.id,   'balance': 6000.0},
        ])

    def test_exchange_diff_on_partial_aml_multi_currency_close_amount(self):
        self.other_currency.rate_ids.rate = 0.9839
        self.company_data['default_journal_bank'].currency_id = self.other_currency

        st_line = self._create_st_line(-37436.50)
        self.assertRecordValues(st_line.line_ids, [
            # pylint: disable=C0326
            {'amount_currency': -37436.50,  'balance': -38049.09},
            {'amount_currency': 37436.50,   'balance': 38049.09},
        ])

        inv_line = self._create_invoice_line(
            'in_invoice',
            invoice_date=st_line.date,
            currency_id=self.other_currency.id,
            invoice_line_ids=[{'price_unit': 37436.52}],
        )
        self.assertRecordValues(inv_line, [{
            'amount_currency': -37436.52,
            'balance': -38049.11,
        }])

        wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
        wizard._action_add_new_amls(inv_line)

        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',       'amount_currency': -37436.50,   'currency_id': self.other_currency.id,   'balance': -38049.09},
            {'flag': 'new_aml',         'amount_currency': 37436.50,    'currency_id': self.other_currency.id,   'balance': 38049.09},
        ])

    def test_matching_zero_amount_misc_entry(self):
        """ Check for division by zero with foreign currencies and some 0 making a broken rate. """
        self.company_data['default_journal_bank'].currency_id = self.other_currency
        st_line = self._create_st_line(0.0, amount_currency=10.0, foreign_currency_id=self.company_data['currency'].id)

        entry = self.env['account.move'].create({
            'date': '2019-01-01',
            'line_ids': [
                Command.create({
                    'account_id': self.company_data['default_account_receivable'].id,
                    'currency_id': self.other_currency.id,
                    'debit': 1.0,
                    'credit': 0.0,
                }),
                Command.create({
                    'account_id': self.company_data['default_account_revenue'].id,
                    'currency_id': self.other_currency.id,
                    'debit': 0.0,
                    'credit': 1.0,
                }),
            ]
        })
        entry.action_post()

        wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
        aml = entry.line_ids.filtered('debit')
        wizard._action_add_new_amls(aml)
        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',       'balance': 10.0},
            {'flag': 'new_aml',         'balance': -1.0},
            {'flag': 'exchange_diff',   'balance': 1.0},
            {'flag': 'auto_balance',    'balance': -10.0},
        ])

    def test_amls_order_with_matching_amount(self):
        """ AML's with a matching amount_residual should be displayed first when the order is not specified. """

        foreign_st_line = self._create_st_line(
            500.0,
            date='2016-01-01',
            foreign_currency_id=self.other_currency.id,
            amount_currency=1500.0,
        )
        st_line = self._create_st_line(
            66.66,
            date='2016-01-01',
        )
        wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=foreign_st_line.id).new({})

        aml1_id = self._create_invoice_line(
            'out_invoice',
            invoice_date='2017-01-30',
            invoice_line_ids=[{'price_unit': 1000.0}],
        ).id
        aml2_id = self._create_invoice_line(
            'out_invoice',
            invoice_date='2017-01-29',
            currency_id=self.other_currency.id,
            invoice_line_ids=[{'price_unit': 1500.0}], # = 100 USD
        ).id
        aml3_id = self._create_invoice_line(
            'out_invoice',
            invoice_date='2017-01-28',
            invoice_line_ids=[{'price_unit': 500.0}],
        ).id
        aml4_id = self._create_invoice_line(
            'out_invoice',
            invoice_date='2017-01-27',
            invoice_line_ids=[{'price_unit': 55.55}], # = 55.550000000000004
        ).id
        aml5_id = self._create_invoice_line(
            'out_invoice',
            invoice_date='2017-01-26',
            invoice_line_ids=[{'price_unit': 66.66}],
        ).id

        # Check the lines without the context key.
        wizard._js_action_mount_st_line(foreign_st_line.id)
        domain = wizard.return_todo_command['amls']['domain']
        amls_list = self.env['account.move.line'].search_fetch(domain=domain, field_names=['id'])
        self.assertEqual(
            [x['id'] for x in amls_list],
            [aml1_id, aml2_id, aml3_id, aml4_id, aml5_id],
        )

        # Check the lines with the context key.
        suspense_line = wizard.line_ids.filtered(lambda l: l.flag == 'auto_balance')
        amls_list = self.env['account.move.line']\
            .with_context(preferred_aml_value=suspense_line.amount_currency * -1, preferred_aml_currency_id=suspense_line.currency_id.id)\
            .search_fetch(domain=domain, field_names=['id'])
        self.assertEqual(
            [x['id'] for x in amls_list],
            [aml2_id, aml1_id, aml3_id, aml4_id, aml5_id],
        )

        # Check the order with limits and offsets
        amls_list = self.env['account.move.line']\
            .with_context(preferred_aml_value=suspense_line.amount_currency * -1, preferred_aml_currency_id=suspense_line.currency_id.id)\
            .search_fetch(domain=domain, field_names=['id'], limit=2)
        self.assertEqual(
            [x['id'] for x in amls_list],
            [aml2_id, aml1_id],
        )
        amls_list = self.env['account.move.line']\
            .with_context(preferred_aml_value=suspense_line.amount_currency * -1, preferred_aml_currency_id=suspense_line.currency_id.id)\
            .search_fetch(domain=domain, field_names=['id'], offset=2, limit=3)
        self.assertEqual(
            [x['id'] for x in amls_list],
            [aml3_id, aml4_id, aml5_id],
        )

        # Check rounding and new suspense line
        wizard._js_action_mount_st_line(st_line.id)
        suspense_line = wizard.line_ids.filtered(lambda l: l.flag == 'auto_balance')
        amls_list = self.env['account.move.line']\
            .with_context(preferred_aml_value=suspense_line.amount_currency * -1, preferred_aml_currency_id=suspense_line.currency_id.id)\
            .search_fetch(domain=domain, field_names=['id'])
        self.assertEqual(
            [x['id'] for x in amls_list],
            [aml5_id, aml1_id, aml2_id, aml3_id, aml4_id],
        )
        wizard._js_action_mount_line_in_edit(suspense_line.index)
        suspense_line.balance = -11.11
        wizard._line_value_changed_balance(suspense_line)
        suspense_line = wizard.line_ids.filtered(lambda l: l.flag == 'auto_balance')
        self.assertEqual(suspense_line.balance, -55.55)
        self.env.cr.execute(f"""
            UPDATE account_move_line SET amount_residual_currency = 55.550000001 WHERE id = {aml4_id};
        """)
        amls_list = self.env['account.move.line']\
            .with_context(preferred_aml_value=55.550003, preferred_aml_currency_id=suspense_line.currency_id.id)\
            .search_fetch(domain=domain, field_names=['id'])
        self.assertEqual(
            [x['id'] for x in amls_list],
            [aml4_id, aml1_id, aml2_id, aml3_id, aml5_id],
        )

        # Check that context keys are not propagated
        action = amls_list[0].action_open_business_doc()
        self.assertFalse(action['context'].get('preferred_aml_value'))

    @freeze_time('2023-12-25')
    def test_analtyic_distribution_model_exchange_diff_line(self):
        """Test that the analytic distribution model is present on the exchange diff line."""
        expense_exchange_account = self.env.company.expense_currency_exchange_account_id
        analytic_plan = self.env['account.analytic.plan'].create({
            'name': 'Plan 1',
            'default_applicability': 'unavailable',
        })
        analytic_account_1 = self.env['account.analytic.account'].create({'name': 'Account 1', 'plan_id': analytic_plan.id})
        analytic_account_2 = self.env['account.analytic.account'].create({'name': 'Account 1', 'plan_id': analytic_plan.id})
        distribution_model = self.env['account.analytic.distribution.model'].create({
            'account_prefix': expense_exchange_account.code,
            'partner_id': self.partner_a.id,
            'analytic_distribution': {analytic_account_1.id: 100},
        })

        # 1200.0 comp_curr = 3600.0 foreign_curr in 2016 (rate 1:3)
        st_line = self._create_st_line(
            1200.0,
            date='2016-01-01',
        )
        # 1800.0 comp_curr = 3600.0 foreign_curr in 2017 (rate 1:2)
        inv_line = self._create_invoice_line(
            'out_invoice',
            currency_id=self.other_currency.id,
            invoice_date='2017-01-01',
            invoice_line_ids=[{'price_unit': 3600.0}],
        )

        wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
        wizard._action_add_new_amls(inv_line)
        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',     'amount_currency': 1200.0,  'currency_id': self.company_data['currency'].id,  'balance': 1200.0,  'analytic_distribution': False},
            {'flag': 'new_aml',       'amount_currency': -3600.0, 'currency_id': self.other_currency.id,            'balance': -1800.0, 'analytic_distribution': False},
            {'flag': 'exchange_diff', 'amount_currency': 0.0,     'currency_id': self.other_currency.id,            'balance': 600.0,   'analytic_distribution': distribution_model.analytic_distribution},
        ])

        # Test that the analytic distribution is kept on the creation of the exchange diff move
        new_distribution = {**distribution_model.analytic_distribution, str(analytic_account_2.id): 100}

        line = wizard.line_ids.filtered(lambda x: x.flag == 'exchange_diff')
        line.analytic_distribution = new_distribution
        wizard._action_validate()

        self.assertRecordValues(inv_line.matched_credit_ids.exchange_move_id.line_ids, [
            {'analytic_distribution': False},
            {'analytic_distribution': new_distribution},
        ])

    def test_access_child_bank_with_user_set_on_child(self):
        """
        Demo user with a Child Company as default company/allowed companies
        should be able to access the Bank set on this same Child Company
        """
        child_company = self.env['res.company'].create({
            'name': 'Childest Company',
            'parent_id': self.env.company.id,
        })
        child_bank_journal = self.env['account.journal'].create({
            'name': 'Child Bank',
            'type': 'bank',
            'company_id': child_company.id,
        })
        self.user.write({
            'company_ids': [Command.set(child_company.ids)],
            'company_id': child_company.id,
            'groups_id': [
                Command.set(self.env.ref('account.group_account_user').ids),
            ]
        })
        res = self.env['bank.rec.widget'].with_user(self.user).collect_global_info_data(child_bank_journal.id)
        self.assertTrue(res, "Journal should be accessible")

    def test_collect_global_info_data_other_company_bank_journal_with_user_on_main_company(self):
        """ The aim of this test is checking that a user who having
            access to 2 companies will have values even when he's
            calling collect_global_info_data function if
            it's current company it's not the one on the journal
            but is still available.
            To do that, we add 2 companies to the user, and try to
            call collect_global_info_data on the journal of the second
            company, even if the main company it's the first one.
        """
        self.user.write({
            'company_ids': [Command.set((self.company_data['company'] + self.company_data_2['company']).ids)],
            'company_id': self.company_data['company'].id,
        })

        result = self.env['bank.rec.widget'].with_user(self.user).collect_global_info_data(self.company_data_2['default_journal_bank'].id)
        self.assertTrue(result['balance_amount'], "Balance amount shouldn't be False value")

    def test_collect_global_info_data_non_existing_bank_journal(self):
        """ The aim of this test is checking that we receive an empty
            string when we call collect_global_info_data function
            with a non-existing journal. This use case could happen
            when we try to open the bank rec widget on a journal that
            is not actually existing. As this function is callable by
            rpc, this usecase could happen.
        """
        result = self.env['bank.rec.widget'].with_user(self.user).collect_global_info_data(99999999)
        self.assertEqual(result['balance_amount'], "", "If no value, the function should return an empty string")

    def test_res_partner_bank_find_create_multi_account(self):
        """ Make sure that we can save multiple bank accounts for a partner. """
        partner = self.env['res.partner'].create({'name': "Zitycard"})

        for acc_number in ("123456789", "123456780"):
            st_line = self._create_st_line(100.0, account_number=acc_number)
            inv_line = self._create_invoice_line(
                'out_invoice',
                partner_id=partner.id,
                invoice_line_ids=[{'price_unit': 100.0, 'tax_ids': []}],
            )
            wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
            wizard._action_add_new_amls(inv_line)
            wizard._action_validate()

        bank_accounts = self.env['res.partner.bank'].sudo().with_context(active_test=False).search([
            ('partner_id', '=', partner.id),
        ])
        self.assertEqual(len(bank_accounts), 2, "Second bank account was not registered!")

    ####################################################
    # RECO MODEL MOVE CREATION
    ####################################################

    def create_test_reco_invoice(self, st_line, reco_model):
        """ Helper method to create a move given a statement line and reconciliation model """
        wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
        wizard._action_select_reconcile_model(reco_model)
        move = self.env['account.move'].browse(wizard.return_todo_command['res_id'])
        return move

    def assert_reco_invoice_values(self, move, st_line, expected_move_type, expected_amount_total=None):
        """ Helper method to assert that values in a move match the information in the given statement line """
        if expected_amount_total is None:
            expected_amount_total = abs(st_line.amount)
        self.assertRecordValues(move, [{
            'amount_total': expected_amount_total,
            'move_type': expected_move_type,
            'partner_id': st_line.partner_id.id,
            'invoice_date': st_line.date,
        }])

    def test_invoice_creation_from_reco_model(self):
        """ Test the created invoice from a sale/purchase reconciliation model. """
        reco_model_invoice = self.env['account.reconcile.model'].create({
            'name': "test reconcile create invoice",
            'rule_type': 'writeoff_button',
            'counterpart_type': 'sale',
            'line_ids': [
                Command.create({'amount_string': '50'}),
                Command.create({'amount_string': '50'}),
            ],
        })

        for st_line_amount, move_type, reco_model in (
            (1000.0, 'out_invoice', reco_model_invoice),
            (-1000.0, 'out_refund', reco_model_invoice),
            (1000.0, 'in_refund', self.reco_model_bill),
            (-1000.0, 'in_invoice', self.reco_model_bill),
        ):
            st_line = self._create_st_line(st_line_amount, partner_id=self.partner_a.id)
            with self.subTest():
                move = self.create_test_reco_invoice(st_line, reco_model)
                self.assert_reco_invoice_values(move, st_line, move_type)

    def test_invoice_reco_model_round_odd(self):
        """ Test if correct move is created when rounding is needed for multiple reco model lines"""
        # Odd amount and a reconciliation model with two lines (50% each line) requires rounding to ensure values match.
        st_line = self._create_st_line(amount=-111.11, partner_id=self.partner_a.id)
        move = self.create_test_reco_invoice(st_line, self.reco_model_bill)
        self.assert_reco_invoice_values(move, st_line, 'in_invoice')

    def test_invoice_reco_model_round_single_line(self):
        """ Test if correct move is created with single-line reco model when rounding is needed """
        # A st_line of $150 with 15% tax (default) requires rounding, as without it the invoice would total $149.99
        reco_model_single_line = self.env['account.reconcile.model'].create({
            'name': "single line reco model",
            'rule_type': 'writeoff_button',
            'counterpart_type': 'purchase',
            'line_ids': [Command.create({})],
        })
        st_line = self._create_st_line(amount=150, partner_id=self.partner_a.id)
        move = self.create_test_reco_invoice(st_line, reco_model_single_line)
        self.assert_reco_invoice_values(move, st_line, 'in_refund')

    def test_invoice_reco_model_round_large_percentage(self):
        """ Test if total move amount is correctly rounded when reco model lines go above 100% of st_line amount """
        # Reco model with two 100% st_line amount lines
        reco_model_two_lines = self.env['account.reconcile.model'].create({
            'name': "two lines reco model",
            'rule_type': 'writeoff_button',
            'counterpart_type': 'purchase',
            'line_ids': [Command.create({}), Command.create({})],
        })
        # Reco model with one 200% st_line amount line
        reco_model_single_line = self.env['account.reconcile.model'].create({
            'name': "single line reco model",
            'rule_type': 'writeoff_button',
            'counterpart_type': 'purchase',
            'line_ids': [Command.create({'amount_string': '200'})],
        })
        st_line = self._create_st_line(-150, partner_id=self.partner_a.id)
        for reco_model in (reco_model_two_lines, reco_model_single_line):
            with self.subTest():
                move = self.create_test_reco_invoice(st_line, reco_model)
                # Total move amount should equal 2 * st_line amount = 300 in both cases
                self.assert_reco_invoice_values(move, st_line, 'in_invoice', 300.0)

    def test_invoice_reco_model_round_combined(self):
        """ Test if total move amount is correctly rounded when reco model lines are a combination of
        percentage_st_line and fixed amount types """
        # Reco model with 100% amount + fixed amount, total move amount should equal st_line amount + fixed amount
        reco_model_percentage_fixed = self.env['account.reconcile.model'].create({
            'name': "combined percentage + fixed reco model",
            'rule_type': 'writeoff_button',
            'counterpart_type': 'sale',
            'line_ids': [
                Command.create({}),
                Command.create({
                    'amount_type': 'fixed',
                    'amount_string': '50',
                }),
            ],
        })
        st_line = self._create_st_line(amount=100, partner_id=self.partner_a.id)
        move = self.create_test_reco_invoice(st_line, reco_model_percentage_fixed)
        self.assert_reco_invoice_values(move, st_line, 'out_invoice', 150.0)

    def test_invoice_reco_model_multiple_taxes(self):
        """
        Test if correct move is created through reco model writeoff button when the
        reconciliation model has more than one tax per line.
        Note: this only works if taxes are all price_include or all not price_include
        """
        tax_21 = self.env['account.tax'].create({
            'name': "tax_21",
            'amount': 21,
        })
        reco_model_bill_mult_taxes = self.env['account.reconcile.model'].create({
            'name': "test reconcile bill multiple taxes",
            'rule_type': 'writeoff_button',
            'counterpart_type': 'purchase',
            'line_ids': [
                Command.create({
                    'tax_ids': [Command.set((tax_21 + self.company_data['default_tax_purchase']).ids)],
                }),
            ],
        })
        st_line = self._create_st_line(amount=-100, partner_id=self.partner_a.id)
        move = self.create_test_reco_invoice(st_line, reco_model_bill_mult_taxes)
        self.assert_reco_invoice_values(move, st_line, 'in_invoice')

    def test_invoice_creation_reco_model_percentage(self):
        """ Test if correct move is created when rounding is needed """
        # Odd amount and a reconciliation model with two lines (50% each line) requires rounding to ensure values match.
        st_line = self._create_st_line(amount=150, partner_id=self.partner_a.id)
        reco_model_bill_balance = self.env['account.reconcile.model'].create({
            'name': "test balance",
            'rule_type': 'writeoff_button',
            'counterpart_type': 'purchase',
            'line_ids': [
                Command.create({
                    'amount_type': 'percentage',
                    'amount_string': '50',
                }),
                Command.create({
                    'amount_type': 'percentage',
                    'amount_string': '50',
                }),
            ],
        })
        move = self.create_test_reco_invoice(st_line, reco_model_bill_balance)
        self.assert_reco_invoice_values(move, st_line, 'in_refund', 112.5)

    def test_invoice_creation_reco_model_fixed(self):
        """ Test if correct move is created when rounding is needed """
        # Odd amount and a reconciliation model with two lines (50% each line) requires rounding to ensure values match.
        st_line = self._create_st_line(amount=-150, partner_id=self.partner_a.id)
        reco_model_invoice_fixed = self.env['account.reconcile.model'].create({
            'name': "test fixed",
            'rule_type': 'writeoff_button',
            'counterpart_type': 'sale',
            'line_ids': [
                Command.create({
                    'amount_type': 'fixed',
                    'amount_string': '100',
                }),
            ],
        })
        move = self.create_test_reco_invoice(st_line, reco_model_invoice_fixed)
        self.assert_reco_invoice_values(move, st_line, 'out_refund', 100.0)

    def test_invoice_creation_reco_model_regex(self):
        """ Test if correct move is created when rounding is needed """
        # Odd amount and a reconciliation model with two lines (50% each line) requires rounding to ensure values match.
        st_line = self._create_st_line(amount=150, partner_id=self.partner_a.id, payment_ref="150 invoice", statement_name='150 invoice')
        reco_model_invoice_regex = self.env['account.reconcile.model'].create({
            'name': "test label/regex",
            'rule_type': 'writeoff_button',
            'counterpart_type': 'sale',
            'line_ids': [
                Command.create({
                    'amount_type': 'regex',
                    'amount_string': r'([\d,.]+)',
                }),
            ],
        })
        move = self.create_test_reco_invoice(st_line, reco_model_invoice_regex)
        self.assert_reco_invoice_values(move, st_line, 'out_invoice', 150.0)

    def test_unreconciled_with_other_lines(self):
        """Test that other lines are shown in the widget if they exist."""
        st_line = self._create_st_line(
            1000.0,
            date='2017-01-01',
        )

        # Edit the associated move to partially reconcile some of the suspense amount; i.e. we add another line
        liquidity_line, suspense_line, other_line = st_line._seek_for_lines()
        other_account = st_line.journal_id.company_id.default_cash_difference_income_account_id
        self.assertFalse(other_line)
        move = st_line.move_id
        move.button_draft()
        move.write({'line_ids': [
            Command.create({'account_id': other_account.id, 'credit': 100.0}),
            Command.update(suspense_line.id, {'credit': 900.0}),
        ]})
        move.action_post()
        liquidity_line, suspense_line, other_line = st_line._seek_for_lines()
        self.assertTrue(other_line)

        # Check that the wizard displays the new line
        wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=st_line.id).new({})
        self.assertRecordValues(wizard.line_ids, [
            # pylint: disable=C0326
            {'flag': 'liquidity',      'account_id': liquidity_line.account_id.id,   'amount_currency': 1000.0},
            {'flag': 'aml',            'account_id': other_line.account_id.id,       'amount_currency': -100.0},
            {'flag': 'auto_balance',   'account_id': suspense_line.account_id.id,    'amount_currency': -900.0},
        ])
