import re from odoo import Command, fields from odoo.exceptions import UserError from odoo.tests import tagged from odoo.addons.account.tests.common import AccountTestInvoicingCommon @tagged('post_install', '-at_install') class TestAccountReconcileWizard(AccountTestInvoicingCommon): """ Tests the account reconciliation and its wizard. """ @classmethod def setUpClass(cls, chart_template_ref=None): super().setUpClass(chart_template_ref=chart_template_ref) cls.receivable_account = cls.company_data['default_account_receivable'] cls.payable_account = cls.company_data['default_account_payable'] cls.revenue_account = cls.company_data['default_account_revenue'] cls.payable_account_2 = cls.env['account.account'].create({ 'name': 'Payable Account 2', 'account_type': 'liability_current', 'code': 'PAY2.TEST', 'reconcile': True }) cls.write_off_account = cls.env['account.account'].create({ 'name': 'Write-Off Account', 'account_type': 'liability_current', 'code': 'WO.TEST', 'reconcile': False }) cls.misc_journal = cls.company_data['default_journal_misc'] cls.test_date = fields.Date.from_string('2016-01-01') cls.company_currency = cls.company_data['currency'] cls.foreign_currency = cls.currency_data['currency'] cls.foreign_currency_2 = cls.setup_multi_currency_data(default_values={ 'name': 'Dark Chocolate Coin', 'symbol': '🍫', 'currency_unit_label': 'Dark Choco', 'currency_subunit_label': 'Dark Cacao Powder', }, rate2016=6.0, rate2017=4.0)['currency'] # ------------------------------------------------------------------------- # HELPERS # ------------------------------------------------------------------------- def assertWizardReconcileValues(self, selected_lines, input_values, wo_expected_values, expected_transfer_values=None): wizard = self.env['account.reconcile.wizard'].with_context( active_model='account.move.line', active_ids=selected_lines.ids, ).new(input_values) if expected_transfer_values: transfer_move = wizard.create_transfer() # transfer move values self.assertRecordValues(transfer_move.line_ids.sorted('balance'), expected_transfer_values) # transfer warning message self.assertTrue(wizard.transfer_warning_message) regex_match = re.findall(r'([+-]*\d*,*\d+\.*\d+)', wizard.transfer_warning_message) # match transferred amount self.assertEqual( float(regex_match[0].replace(',', '')), transfer_move.amount_total_in_currency_signed or transfer_move.amount_total_signed ) transfer_from_account = transfer_move.line_ids.filtered(lambda aml: 'Transfer from' in aml.name).account_id transfer_to_account = transfer_move.line_ids.account_id - transfer_from_account transfer_from_amls = transfer_move.line_ids.filtered(lambda aml: aml.account_id == transfer_from_account) transfer_amount = sum(aml.balance for aml in transfer_from_amls) # match account codes if transfer_amount > 0: self.assertEqual(regex_match[1:], [transfer_from_account.code, transfer_to_account.code]) else: self.assertEqual(regex_match[1:], [transfer_to_account.code, transfer_from_account.code]) write_off_move = wizard.create_write_off() self.assertRecordValues(write_off_move.line_ids.sorted('balance'), wo_expected_values) wizard.reconcile() self.assertTrue(selected_lines.full_reconcile_id) self.assertRecordValues( selected_lines, [{'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}] * len(selected_lines), ) # ------------------------------------------------------------------------- # TESTS # ------------------------------------------------------------------------- def test_wizard_should_not_open(self): """ Test that when we reconcile two lines that belong to the same account and have a 0 balance should reconcile silently and not open the write-off wizard. """ line_1 = self.create_line_for_reconciliation(1000.0, 1000.0, self.company_currency, '2016-01-01') line_2 = self.create_line_for_reconciliation(-1000.0, -1000.0, self.company_currency, '2016-01-01') (line_1 + line_2).action_reconcile() self.assertRecordValues( line_1 + line_2, [{'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}] * 2 ) def test_wizard_should_open(self): """ Test that when a write-off is required (because of transfer or non-zero balance) the wizard opens. """ line_1 = self.create_line_for_reconciliation(1000.0, 1000.0, self.company_currency, '2016-01-01') line_2 = self.create_line_for_reconciliation(-500.0, -500.0, self.company_currency, '2016-01-01') line_3 = self.create_line_for_reconciliation(-500.0, -1500.0, self.foreign_currency, '2016-01-01') line_4 = self.create_line_for_reconciliation(-900.0, -900.0, self.company_currency, '2016-01-01', account_1=self.payable_account) for batch, sub_test_name in ( (line_1 + line_2, 'Batch with non-zero balance in company currency'), (line_1 + line_3, 'Batch with non-zero balance in foreign currency'), (line_1 + line_4, 'Batch with different accounts'), ): with self.subTest(sub_test_name=sub_test_name): returned_action = batch.action_reconcile() self.assertEqual(returned_action.get('res_model'), 'account.reconcile.wizard') def test_reconcile_silently_same_account(self): """ When balance is 0 we can silently reconcile items. """ line_1 = self.create_line_for_reconciliation(1000.0, 1000.0, self.company_currency, '2016-01-01') line_2 = self.create_line_for_reconciliation(-1000.0, -1000.0, self.company_currency, '2016-01-01') lines = (line_1 + line_2) lines.action_reconcile() self.assertTrue(lines.full_reconcile_id) self.assertRecordValues( lines, [{'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}] * len(lines), ) def test_reconcile_silently_transfer(self): """ When balance is 0, and we need a transfer, we do the transfer+reconcile silently. """ line_1 = self.create_line_for_reconciliation(1000.0, 1000.0, self.company_currency, '2016-01-01') line_2 = self.create_line_for_reconciliation(-1000.0, -1000.0, self.company_currency, '2016-01-01', account_1=self.payable_account) lines = (line_1 + line_2) lines.action_reconcile() self.assertTrue(lines.full_reconcile_id) self.assertRecordValues( lines, [{'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}] * len(lines), ) def test_write_off_same_currency(self): """ Reconciliation of two lines with no transfer/foreign currencies/taxes/reco models.""" line_1 = self.create_line_for_reconciliation(1000.0, 1000.0, self.company_currency, '2016-01-01') line_2 = self.create_line_for_reconciliation(-500.0, -500.0, self.company_currency, '2016-01-01') wizard_input_values = { 'journal_id': self.misc_journal.id, 'account_id': self.write_off_account.id, 'label': 'Write-Off Test Label', 'allow_partials': False, 'date': self.test_date, } write_off_expected_values = [ {'account_id': self.receivable_account.id, 'name': 'Write-Off Test Label', 'balance': -500.0}, {'account_id': self.write_off_account.id, 'name': 'Write-Off Test Label', 'balance': 500.0}, ] self.assertWizardReconcileValues(line_1 + line_2, wizard_input_values, write_off_expected_values) def test_write_off_one_foreign_currency(self): """ Reconciliation of two lines with one of the two using foreign currency should reconcile in foreign currency.""" line_1 = self.create_line_for_reconciliation(1000.0, 1000.0, self.company_currency, '2016-01-01') line_2 = self.create_line_for_reconciliation(-500.0, -1500.0, self.foreign_currency, '2016-01-01') wizard_input_values = { 'journal_id': self.misc_journal.id, 'account_id': self.write_off_account.id, 'label': 'Write-Off Test Label', 'allow_partials': False, 'date': self.test_date, } expected_values = [ {'account_id': self.receivable_account.id, 'name': 'Write-Off Test Label', 'balance': -500.0, 'amount_currency': -1500.0, 'currency_id': self.foreign_currency.id}, {'account_id': self.write_off_account.id, 'name': 'Write-Off Test Label', 'balance': 500.0, 'amount_currency': 1500.0, 'currency_id': self.foreign_currency.id}, ] self.assertWizardReconcileValues(line_1 + line_2, wizard_input_values, expected_values) def test_write_off_mixed_foreign_currencies(self): """ Write off with multiple currencies should reconcile in company currency.""" line_1 = self.create_line_for_reconciliation(1000.0, 1000.0, self.company_currency, '2016-01-01') line_2 = self.create_line_for_reconciliation(-500.0, -1500.0, self.foreign_currency, '2016-01-01') line_3 = self.create_line_for_reconciliation(-400.0, -2400.0, self.foreign_currency_2, '2016-01-01') wizard_input_values = { 'journal_id': self.misc_journal.id, 'account_id': self.write_off_account.id, 'label': 'Write-Off Test Label', 'allow_partials': False, 'date': self.test_date, } expected_values = [ {'account_id': self.receivable_account.id, 'name': 'Write-Off Test Label', 'balance': -100.0, 'amount_currency': -100.0, 'currency_id': self.company_currency.id}, {'account_id': self.write_off_account.id, 'name': 'Write-Off Test Label', 'balance': 100.0, 'amount_currency': 100.0, 'currency_id': self.company_currency.id}, ] self.assertWizardReconcileValues(line_1 + line_2 + line_3, wizard_input_values, expected_values) def test_write_off_one_foreign_currency_change_rate(self): """ Tests that write-off use the correct rate from/at wizard's date. """ foreign_currency = self.setup_multi_currency_data(default_values={ 'name': 'Diamond', 'symbol': '💎', 'currency_unit_label': 'Diamond', 'currency_subunit_label': 'Carbon', }, rate2016=1/2, rate2017=1/3)['currency'] new_date = fields.Date.from_string('2017-02-01') line_1 = self.create_line_for_reconciliation(-2000.0, -2000.0, self.company_currency, '2017-01-01') # conversion in 2017 => -666.67🍫 line_2 = self.create_line_for_reconciliation(2000.0, 1000.0, foreign_currency, '2016-01-01') wizard_input_values = { 'journal_id': self.misc_journal.id, 'account_id': self.write_off_account.id, 'label': 'Write-Off Test Label', 'allow_partials': False, 'date': new_date, } expected_values = [ {'account_id': self.receivable_account.id, 'name': 'Write-Off Test Label', 'balance': -1000.0, 'amount_currency': -333.333, 'currency_id': foreign_currency.id}, {'account_id': self.write_off_account.id, 'name': 'Write-Off Test Label', 'balance': 1000.0, 'amount_currency': 333.333, 'currency_id': foreign_currency.id}, ] self.assertWizardReconcileValues(line_1 + line_2, wizard_input_values, expected_values) def test_write_off_mixed_foreign_currencies_change_rate(self): """ Tests that write-off use the correct rate from/at wizard's date. """ new_date = fields.Date.from_string('2017-02-01') line_1 = self.create_line_for_reconciliation(1000.0, 1000.0, self.company_currency, '2016-01-01') line_2 = self.create_line_for_reconciliation(-500.0, -1500.0, self.foreign_currency, '2016-01-01') line_3 = self.create_line_for_reconciliation(-400.0, -2400.0, self.foreign_currency_2, '2016-01-01') wizard_input_values = { 'journal_id': self.misc_journal.id, 'account_id': self.write_off_account.id, 'label': 'Write-Off Test Label', 'allow_partials': False, 'date': new_date, } expected_values = [ {'account_id': self.receivable_account.id, 'name': 'Write-Off Test Label', 'balance': -100.0, 'amount_currency': -100.0, 'currency_id': self.company_currency.id}, {'account_id': self.write_off_account.id, 'name': 'Write-Off Test Label', 'balance': 100.0, 'amount_currency': 100.0, 'currency_id': self.company_currency.id}, ] self.assertWizardReconcileValues(line_1 + line_2 + line_3, wizard_input_values, expected_values) def test_write_off_with_transfer_account_same_currency(self): line_1 = self.create_line_for_reconciliation(1000.0, 1000.0, self.company_currency, '2016-01-01') line_2 = self.create_line_for_reconciliation(100.0, 100.0, self.company_currency, '2016-01-01', account_1=self.payable_account) wizard_input_values = { 'journal_id': self.misc_journal.id, 'account_id': self.write_off_account.id, 'label': 'Write-Off Test Label', 'allow_partials': False, 'date': self.test_date, } expected_transfer_values = [ {'account_id': self.payable_account.id, 'name': f'Transfer to {self.receivable_account.display_name}', 'balance': -100.0, 'amount_currency': -100.0, 'currency_id': self.company_currency.id}, {'account_id': self.receivable_account.id, 'name': f'Transfer from {self.payable_account.display_name}', 'balance': 100.0, 'amount_currency': 100.0, 'currency_id': self.company_currency.id}, ] expected_values = [ {'account_id': self.receivable_account.id, 'name': 'Write-Off Test Label', 'balance': -1100.0, 'amount_currency': -1100.0, 'currency_id': self.company_currency.id}, {'account_id': self.write_off_account.id, 'name': 'Write-Off Test Label', 'balance': 1100.0, 'amount_currency': 1100.0, 'currency_id': self.company_currency.id}, ] self.assertWizardReconcileValues(line_1 + line_2, wizard_input_values, expected_values, expected_transfer_values=expected_transfer_values) def test_write_off_with_transfer_account_one_foreign_currency(self): line_1 = self.create_line_for_reconciliation(1100.0, 1100.0, self.company_currency, '2016-01-01') line_2 = self.create_line_for_reconciliation(100.0, 300.0, self.foreign_currency, '2016-01-01', account_1=self.payable_account) wizard_input_values = { 'journal_id': self.misc_journal.id, 'account_id': self.write_off_account.id, 'label': 'Write-Off Test Label', 'allow_partials': False, 'date': self.test_date, } expected_transfer_values = [ {'account_id': self.payable_account.id, 'name': f'Transfer to {self.receivable_account.display_name}', 'balance': -100.0, 'amount_currency': -300.0, 'currency_id': self.foreign_currency.id}, {'account_id': self.receivable_account.id, 'name': f'Transfer from {self.payable_account.display_name}', 'balance': 100.0, 'amount_currency': 300.0, 'currency_id': self.foreign_currency.id}, ] expected_values = [ {'account_id': self.receivable_account.id, 'name': 'Write-Off Test Label', 'balance': -1200.0, 'amount_currency': -3600.0, 'currency_id': self.foreign_currency.id}, {'account_id': self.write_off_account.id, 'name': 'Write-Off Test Label', 'balance': 1200.0, 'amount_currency': 3600.0, 'currency_id': self.foreign_currency.id}, ] self.assertWizardReconcileValues(line_1 + line_2, wizard_input_values, expected_values, expected_transfer_values=expected_transfer_values) def test_write_off_with_complex_transfer(self): partner_1 = self.env['res.partner'].create({'name': 'Test Partner 1'}) partner_2 = self.env['res.partner'].create({'name': 'Test Partner 2'}) line_1 = self.create_line_for_reconciliation(1000.0, 1000.0, self.company_currency, '2016-01-01', partner=partner_2) line_2 = self.create_line_for_reconciliation(-100.0, -300.0, self.foreign_currency, '2016-01-01', account_1=self.payable_account, partner=partner_1) line_3 = self.create_line_for_reconciliation(-200.0, -200.0, self.company_currency, '2016-01-01', account_1=self.payable_account, partner=partner_2) line_4 = self.create_line_for_reconciliation(-200.0, -600.0, self.foreign_currency, '2016-01-01', account_1=self.payable_account, partner=partner_2) line_5 = self.create_line_for_reconciliation(-200.0, -600.0, self.foreign_currency, '2016-01-01', account_1=self.payable_account, partner=partner_2) wizard_input_values = { 'journal_id': self.misc_journal.id, 'account_id': self.write_off_account.id, 'label': 'Write-Off Test Label', 'allow_partials': False, 'date': self.test_date, } expected_transfer_values = [ {'account_id': self.receivable_account.id, 'name': f'Transfer from {self.payable_account.display_name}', 'balance': -400.0, 'amount_currency': -1200.0, 'currency_id': self.foreign_currency.id, 'partner_id': partner_2.id}, {'account_id': self.receivable_account.id, 'name': f'Transfer from {self.payable_account.display_name}', 'balance': -200.0, 'amount_currency': -200.0, 'currency_id': self.company_currency.id, 'partner_id': partner_2.id}, {'account_id': self.receivable_account.id, 'name': f'Transfer from {self.payable_account.display_name}', 'balance': -100.0, 'amount_currency': -300.0, 'currency_id': self.foreign_currency.id, 'partner_id': partner_1.id}, {'account_id': self.payable_account.id, 'name': f'Transfer to {self.receivable_account.display_name}', 'balance': 100.0, 'amount_currency': 300.0, 'currency_id': self.foreign_currency.id, 'partner_id': partner_1.id}, {'account_id': self.payable_account.id, 'name': f'Transfer to {self.receivable_account.display_name}', 'balance': 200.0, 'amount_currency': 200.0, 'currency_id': self.company_currency.id, 'partner_id': partner_2.id}, {'account_id': self.payable_account.id, 'name': f'Transfer to {self.receivable_account.display_name}', 'balance': 400.0, 'amount_currency': 1200.0, 'currency_id': self.foreign_currency.id, 'partner_id': partner_2.id}, ] expected_values = [ {'account_id': self.receivable_account.id, 'name': 'Write-Off Test Label', 'balance': -300.0, 'amount_currency': -900.0, 'currency_id': self.foreign_currency.id}, {'account_id': self.write_off_account.id, 'name': 'Write-Off Test Label', 'balance': 300.0, 'amount_currency': 900.0, 'currency_id': self.foreign_currency.id}, ] self.assertWizardReconcileValues(line_1 + line_2 + line_3 + line_4 + line_5, wizard_input_values, expected_values, expected_transfer_values=expected_transfer_values) def test_write_off_with_tax(self): """ Tests write-off with a tax set on the wizard. """ line_1 = self.create_line_for_reconciliation(1000.0, 1000.0, self.company_currency, '2016-01-01') line_2 = self.create_line_for_reconciliation(-500.0, -500.0, self.company_currency, '2016-01-01') tax_recover_account_id = self.env['account.account'].create({ 'name': 'Tax Account Test', 'account_type': 'liability_current', 'code': 'TAX.TEST', 'reconcile': False }) base_tag = self.env['account.account.tag'].create({ 'applicability': 'taxes', 'name': 'base_tax_tag', 'country_id': self.company_data['company'].country_id.id, }) tax_tag = self.env['account.account.tag'].create({ 'applicability': 'taxes', 'name': 'tax_tax_tag', 'country_id': self.company_data['company'].country_id.id, }) tax_id = self.env['account.tax'].create({ 'name': 'tax_test', 'amount_type': 'percent', 'amount': 25.0, 'type_tax_use': 'sale', 'company_id': self.company_data['company'].id, 'invoice_repartition_line_ids': [ Command.create({'factor_percent': 100, 'repartition_type': 'base', 'tag_ids': [Command.set(base_tag.ids)]}), Command.create({'factor_percent': 100, 'account_id': tax_recover_account_id.id, 'tag_ids': [Command.set(tax_tag.ids)]}), ], 'refund_repartition_line_ids': [ Command.create({'factor_percent': 100, 'repartition_type': 'base', 'tag_ids': [Command.set(base_tag.ids)]}), Command.create({'factor_percent': 100, 'account_id': tax_recover_account_id.id, 'tag_ids': [Command.set(tax_tag.ids)]}), ], }) wizard_input_values = { 'journal_id': self.misc_journal.id, 'account_id': self.write_off_account.id, 'label': 'Write-Off Test Label', 'tax_id': tax_id.id, 'allow_partials': False, 'date': self.test_date, } write_off_expected_values = [ {'account_id': self.receivable_account.id, 'name': 'Write-Off Test Label', 'balance': -500.0}, {'account_id': tax_recover_account_id.id, 'name': f'{tax_id.name}', 'balance': 100.0, 'tax_tag_ids': 'tax_tax_tag'}, {'account_id': self.write_off_account.id, 'name': 'Write-Off Test Label', 'balance': 400.0, 'tax_tag_ids': 'base_tax_tag', 'tax_ids': tax_id.ids}, ] self.assertWizardReconcileValues(line_1 + line_2, wizard_input_values, write_off_expected_values) def test_reconcile_partials_allowed(self): line_1 = self.create_line_for_reconciliation(1000.0, 1000.0, self.company_currency, '2016-01-01') line_2 = self.create_line_for_reconciliation(-500.0, -500.0, self.company_currency, '2016-01-01') lines = line_1 + line_2 wizard_input_values = { 'allow_partials': True, } wizard = self.env['account.reconcile.wizard'].with_context( active_model='account.move.line', active_ids=lines.ids, ).new(wizard_input_values) wizard.reconcile() self.assertTrue(len(lines.matched_debit_ids) > 0 or len(lines.matched_credit_ids) > 0) def test_raise_lock_date_violation(self): """ If a write-off violates the lock date we display a banner and change the date afterwards. """ company_id = self.company_data['company'] company_id.fiscalyear_lock_date = fields.Date.from_string('2016-12-01') line_1 = self.create_line_for_reconciliation(1000.0, 1000.0, self.company_currency, '2016-06-01') line_2 = self.create_line_for_reconciliation(-500.0, -500.0, self.company_currency, '2016-06-01') wizard = self.env['account.reconcile.wizard'].with_context( active_model='account.move.line', active_ids=(line_1 + line_2).ids, ).new({'date': self.test_date}) self.assertTrue(bool(wizard.lock_date_violated_warning_message)) def test_raise_reconcile_too_many_accounts(self): """ If you try to reconcile lines from more than 2 accounts, it should raise an error. """ line_1 = self.create_line_for_reconciliation(1000.0, 1000.0, self.company_currency, '2016-01-01') line_2 = self.create_line_for_reconciliation(-500.0, -500.0, self.company_currency, '2016-01-01', account_1=self.payable_account) line_3 = self.create_line_for_reconciliation(-500.0, -500.0, self.company_currency, '2016-01-01', account_1=self.payable_account_2) with self.assertRaises(UserError): (line_1 + line_2 + line_3).action_reconcile() def test_reconcile_no_receivable_no_payable_account(self): """ If you try to reconcile lines in an account that is neither from payable nor receivable it should reconcile in company currency. """ account = self.company_data['default_account_expense'] account.reconcile = True line_1 = self.create_line_for_reconciliation(1000.0, 1000.0, self.company_currency, '2016-01-01', account_1=account) line_2 = self.create_line_for_reconciliation(-500.0, -1500.0, self.foreign_currency, '2016-01-01', account_1=account) wizard_input_values = { 'journal_id': self.misc_journal.id, 'account_id': self.write_off_account.id, 'label': 'Write-Off Test Label', 'allow_partials': False, 'date': self.test_date, } expected_values = [ {'account_id': account.id, 'name': 'Write-Off Test Label', 'balance': -500.0, 'amount_currency': -500.0, 'currency_id': self.company_currency.id}, {'account_id': self.write_off_account.id, 'name': 'Write-Off Test Label', 'balance': 500.0, 'amount_currency': 500.0, 'currency_id': self.company_currency.id}, ] self.assertWizardReconcileValues(line_1 + line_2, wizard_input_values, expected_values) def test_reconcile_exchange_diff_foreign_currency(self): """ When reconciling exchange_diff with amount_residual_currency = 0 we need to reconcile in company_currency. """ exchange_gain_account = self.company_data['company'].income_currency_exchange_account_id exchange_gain_account.reconcile = True line_1 = self.create_line_for_reconciliation(150.0, 0.0, self.foreign_currency, '2016-01-01') line_2 = self.create_line_for_reconciliation(-100.0, 0.0, self.foreign_currency, '2016-01-01', account_1=exchange_gain_account) wizard_input_values = { 'journal_id': self.misc_journal.id, 'account_id': self.write_off_account.id, 'label': 'Write-Off Test Label', 'allow_partials': False, 'date': self.test_date, } # Note the transfer will always be in the currency of the line transferred expected_transfer_values = [ {'account_id': self.receivable_account.id, 'name': f'Transfer from {exchange_gain_account.display_name}', 'balance': -100.0, 'amount_currency': 0.0, 'currency_id': self.foreign_currency.id}, {'account_id': exchange_gain_account.id, 'name': f'Transfer to {self.receivable_account.display_name}', 'balance': 100.0, 'amount_currency': 0.0, 'currency_id': self.foreign_currency.id}, ] expected_values = [ {'account_id': self.receivable_account.id, 'name': 'Write-Off Test Label', 'balance': -50.0, 'amount_currency': -50.0, 'currency_id': self.company_currency.id}, {'account_id': self.write_off_account.id, 'name': 'Write-Off Test Label', 'balance': 50.0, 'amount_currency': 50.0, 'currency_id': self.company_currency.id}, ] self.assertWizardReconcileValues(line_1 + line_2, wizard_input_values, expected_values, expected_transfer_values=expected_transfer_values) def test_write_off_on_same_account(self): """ When creating a write-off in the same account than the one used by the lines to reconcile, the lines and the write-off should be fully reconciled. """ line_1 = self.create_line_for_reconciliation(1000.0, 1000.0, self.company_currency, '2016-01-01') line_2 = self.create_line_for_reconciliation(2000.0, 2000.0, self.company_currency, '2016-01-01') wizard_input_values = { 'journal_id': self.misc_journal.id, 'account_id': self.receivable_account.id, 'label': 'Write-Off Test Label', 'allow_partials': False, 'date': self.test_date, } write_off_expected_values = [ {'account_id': self.receivable_account.id, 'name': 'Write-Off Test Label', 'balance': -3000.0}, {'account_id': self.receivable_account.id, 'name': 'Write-Off Test Label', 'balance': 3000.0}, ] self.assertWizardReconcileValues(line_1 + line_2, wizard_input_values, write_off_expected_values) def test_reconcile_exchange_diff_foreign_currency_full(self): """ When reconciling exchange_diff with amount_residual_currency = 0 we need to reconcile in company_currency. """ exchange_gain_account = self.company_data['company'].income_currency_exchange_account_id exchange_gain_account.reconcile = True line_1 = self.create_line_for_reconciliation(100.0, 0.0, self.foreign_currency, '2016-01-01') line_2 = self.create_line_for_reconciliation(-100.0, 0.0, self.foreign_currency, '2016-01-01', account_1=exchange_gain_account) lines = line_1 + line_2 lines.action_reconcile() self.assertTrue(lines.full_reconcile_id) self.assertRecordValues( lines, [{'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}] * len(lines), ) def test_write_off_kpmg_case(self): """ Test that write-off does a full reconcile with 2 foreign currencies using a custom exchange rate. """ new_date = fields.Date.from_string('2017-02-01') line_1 = self.create_line_for_reconciliation(1000.0, 1500.0, self.foreign_currency, '2016-01-01') line_2 = self.create_line_for_reconciliation(-900.0, -5400.0, self.foreign_currency_2, '2016-01-01') wizard_input_values = { 'journal_id': self.misc_journal.id, 'account_id': self.write_off_account.id, 'label': 'Write-Off Test Label', 'allow_partials': False, 'date': new_date, } self.assertWizardReconcileValues(line_1 + line_2, wizard_input_values, [ { 'account_id': self.receivable_account.id, 'balance': -100.0, 'amount_currency': -150.0, 'currency_id': self.foreign_currency.id, }, { 'account_id': self.write_off_account.id, 'balance': 100.0, 'amount_currency': 150.0, 'currency_id': self.foreign_currency.id, }, ]) def test_write_off_multi_curr_multi_residuals_force_partials(self): """ Test that we raise an error when trying to reconcile lines with multiple residuals. Here debit1 will be reconciled with credit1 first as they have the same currency. Then residual of debit1 will try to reconcile with debit2 which is impossible => 2 residuals both in foreign currency, we don't know in which currency we should make the write-off => We should only allow partial reconciliation. """ debit_1 = self.create_line_for_reconciliation(2000.0, 12000.0, self.foreign_currency_2, '2016-01-01') credit_1 = self.create_line_for_reconciliation(-1000.0, -6000.0, self.foreign_currency_2, '2016-01-01') debit_2 = self.create_line_for_reconciliation(2000.0, 3000.0, self.foreign_currency, '2016-01-01') wizard = self.env['account.reconcile.wizard'].with_context( active_model='account.move.line', active_ids=(debit_1 + debit_2 + credit_1).ids, ).new() self.assertRecordValues(wizard, [{'force_partials': True, 'allow_partials': True}]) def test_write_off_multi_curr_multi_residuals_exch_diff_force_partials(self): debit_1 = self.create_line_for_reconciliation(2000.0, 0.0, self.foreign_currency_2, '2016-01-01') credit_1 = self.create_line_for_reconciliation(-1000.0, 0.0, self.foreign_currency_2, '2016-01-01') debit_2 = self.create_line_for_reconciliation(2000.0, 0.0, self.foreign_currency, '2016-01-01') wizard = self.env['account.reconcile.wizard'].with_context( active_model='account.move.line', active_ids=(debit_1 + debit_2 + credit_1).ids, ).new() self.assertRecordValues(wizard, [{'force_partials': True, 'allow_partials': True}]) def test_reconcile_same_currency_same_side_not_recpay(self): """ Test the reconciliation with two lines on the same side (debit/credit), same currency and not on a receivable/payable account """ current_assets_account = self.company_data['default_account_assets'].copy({'name': 'Current Assets', 'account_type': 'asset_current', 'reconcile': True}) line_1 = self.create_line_for_reconciliation(200, 200, self.company_currency, '2016-01-01', current_assets_account) line_2 = self.create_line_for_reconciliation(200, 200, self.company_currency, '2016-01-01', current_assets_account) # Test the opening of the wizard without input values wizard = self.env['account.reconcile.wizard'].with_context( active_model='account.move.line', active_ids=(line_1 + line_2).ids, ).new() self.assertRecordValues(wizard, [{'is_write_off_required': True, 'amount': 400, 'amount_currency': 400}]) wizard_input_values = { 'journal_id': self.misc_journal.id, 'account_id': self.write_off_account.id, 'label': 'Write-Off Test Label', 'allow_partials': False, 'date': self.test_date, } expected_values = [ {'account_id': current_assets_account.id, 'name': 'Write-Off Test Label', 'balance': -400.0, 'amount_currency': -400.0, 'currency_id': self.company_currency.id}, {'account_id': self.write_off_account.id, 'name': 'Write-Off Test Label', 'balance': 400.0, 'amount_currency': 400.0, 'currency_id': self.company_currency.id}, ] self.assertWizardReconcileValues(line_1 + line_2, wizard_input_values, expected_values) def test_reconcile_foreign_currency_same_side_not_recpay(self): """ Test the reconciliation with two lines on the same side (debit/credit), one foreign currency and not on a receivable/payable account """ current_assets_account = self.company_data['default_account_assets'].copy({'name': 'Current Assets', 'account_type': 'asset_current', 'reconcile': True}) line_1 = self.create_line_for_reconciliation(200, 300, self.foreign_currency, '2016-01-01', current_assets_account) line_2 = self.create_line_for_reconciliation(200, 200, self.company_currency, '2016-01-01', current_assets_account) # Test the opening of the wizard without input values wizard = self.env['account.reconcile.wizard'].with_context( active_model='account.move.line', active_ids=(line_1 + line_2).ids, ).new() self.assertRecordValues(wizard, [{'is_write_off_required': True, 'amount': 400, 'amount_currency': 400}]) wizard_input_values = { 'journal_id': self.misc_journal.id, 'account_id': self.write_off_account.id, 'label': 'Write-Off Test Label', 'allow_partials': False, 'date': self.test_date, } expected_values = [ {'account_id': current_assets_account.id, 'name': 'Write-Off Test Label', 'balance': -400.0, 'amount_currency': -400.0, 'currency_id': self.company_currency.id}, {'account_id': self.write_off_account.id, 'name': 'Write-Off Test Label', 'balance': 400.0, 'amount_currency': 400.0, 'currency_id': self.company_currency.id}, ] self.assertWizardReconcileValues(line_1 + line_2, wizard_input_values, expected_values) def test_reconcile_same_side_exch_diff(self): """ Test the reconciliation with two lines on the same side (debit/credit), one exchange diff in foreign currency, one regular aml in company currency """ exchange_gain_account = self.company_data['company'].income_currency_exchange_account_id exchange_gain_account.reconcile = True line_1 = self.create_line_for_reconciliation(150.0, 150.0, self.company_currency, '2016-01-01') line_2 = self.create_line_for_reconciliation(100.0, 0.0, self.foreign_currency, '2016-01-01', account_1=exchange_gain_account) wizard_input_values = { 'journal_id': self.misc_journal.id, 'account_id': self.write_off_account.id, 'label': 'Write-Off Test Label', 'allow_partials': False, 'date': self.test_date, } # Note the transfer will always be in the currency of the line transferred expected_transfer_values = [ {'account_id': exchange_gain_account.id, 'name': f'Transfer to {self.receivable_account.display_name}', 'balance': -100.0, 'amount_currency': 0.0, 'currency_id': self.foreign_currency.id}, {'account_id': self.receivable_account.id, 'name': f'Transfer from {exchange_gain_account.display_name}', 'balance': 100.0, 'amount_currency': 0.0, 'currency_id': self.foreign_currency.id}, ] expected_values = [ {'account_id': self.receivable_account.id, 'name': 'Write-Off Test Label', 'balance': -250.0, 'amount_currency': -250.0, 'currency_id': self.company_currency.id}, {'account_id': self.write_off_account.id, 'name': 'Write-Off Test Label', 'balance': 250.0, 'amount_currency': 250.0, 'currency_id': self.company_currency.id}, ] self.assertWizardReconcileValues(line_1 + line_2, wizard_input_values, expected_values, expected_transfer_values=expected_transfer_values)