diff options
| author | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
|---|---|---|
| committer | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
| commit | 3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch) | |
| tree | a44932296ef4a9b71d5f010906253d8c53727726 /addons/account/tests/test_account_move_reconcile.py | |
| parent | 0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff) | |
initial commit 2
Diffstat (limited to 'addons/account/tests/test_account_move_reconcile.py')
| -rw-r--r-- | addons/account/tests/test_account_move_reconcile.py | 2457 |
1 files changed, 2457 insertions, 0 deletions
diff --git a/addons/account/tests/test_account_move_reconcile.py b/addons/account/tests/test_account_move_reconcile.py new file mode 100644 index 00000000..140d97d0 --- /dev/null +++ b/addons/account/tests/test_account_move_reconcile.py @@ -0,0 +1,2457 @@ +# -*- coding: utf-8 -*- +from odoo.addons.account.tests.common import AccountTestInvoicingCommon +from odoo.tests import tagged +from odoo.tests.common import Form +from odoo import fields + + +@tagged('post_install', '-at_install') +class TestAccountMoveReconcile(AccountTestInvoicingCommon): + ''' Tests about the account.partial.reconcile model, not the reconciliation itself but mainly the computation of + the residual amounts on account.move.line. + ''' + + @classmethod + def setUpClass(cls, chart_template_ref=None): + super().setUpClass(chart_template_ref=chart_template_ref) + + cls.extra_receivable_account_1 = cls.copy_account(cls.company_data['default_account_receivable']) + cls.extra_receivable_account_2 = cls.copy_account(cls.company_data['default_account_receivable']) + cls.extra_payable_account_1 = cls.copy_account(cls.company_data['default_account_payable']) + cls.extra_payable_account_2 = cls.copy_account(cls.company_data['default_account_payable']) + + # ==== Multi-currency setup ==== + + cls.currency_data_2 = cls.setup_multi_currency_data(default_values={ + 'name': 'Diamond', + 'symbol': '💎', + 'currency_unit_label': 'Diamond', + 'currency_subunit_label': 'Carbon', + }, rate2016=6.0, rate2017=4.0) + + # ==== Cash Basis Taxes setup ==== + + cls.cash_basis_base_account = cls.env['account.account'].create({ + 'code': 'cash_basis_base_account', + 'name': 'cash_basis_base_account', + 'user_type_id': cls.env.ref('account.data_account_type_revenue').id, + 'company_id': cls.company_data['company'].id, + }) + cls.company_data['company'].account_cash_basis_base_account_id = cls.cash_basis_base_account + + cls.cash_basis_transfer_account = cls.env['account.account'].create({ + 'code': 'cash_basis_transfer_account', + 'name': 'cash_basis_transfer_account', + 'user_type_id': cls.env.ref('account.data_account_type_revenue').id, + 'company_id': cls.company_data['company'].id, + }) + + cls.tax_account_1 = cls.env['account.account'].create({ + 'code': 'tax_account_1', + 'name': 'tax_account_1', + 'user_type_id': cls.env.ref('account.data_account_type_revenue').id, + 'company_id': cls.company_data['company'].id, + }) + + cls.tax_account_2 = cls.env['account.account'].create({ + 'code': 'tax_account_2', + 'name': 'tax_account_2', + 'user_type_id': cls.env.ref('account.data_account_type_revenue').id, + 'company_id': cls.company_data['company'].id, + }) + + cls.fake_country = cls.env['res.country'].create({ + 'name': "The Island of the Fly", + 'code': 'YY', + }) + + cls.tax_tags = cls.env['account.account.tag'].create({ + 'name': 'tax_tag_%s' % str(i), + 'applicability': 'taxes', + 'country_id': cls.fake_country.id, + } for i in range(8)) + + cls.cash_basis_tax_a_third_amount = cls.env['account.tax'].create({ + 'name': 'tax_1', + 'amount': 33.3333, + 'company_id': cls.company_data['company'].id, + 'cash_basis_transition_account_id': cls.cash_basis_transfer_account.id, + 'tax_exigibility': 'on_payment', + 'invoice_repartition_line_ids': [ + (0, 0, { + 'factor_percent': 100, + 'repartition_type': 'base', + 'tag_ids': [(6, 0, cls.tax_tags[0].ids)], + }), + + (0, 0, { + 'factor_percent': 100, + 'repartition_type': 'tax', + 'account_id': cls.tax_account_1.id, + 'tag_ids': [(6, 0, cls.tax_tags[1].ids)], + }), + ], + 'refund_repartition_line_ids': [ + (0, 0, { + 'factor_percent': 100, + 'repartition_type': 'base', + 'tag_ids': [(6, 0, cls.tax_tags[2].ids)], + }), + + (0, 0, { + 'factor_percent': 100, + 'repartition_type': 'tax', + 'account_id': cls.tax_account_1.id, + 'tag_ids': [(6, 0, cls.tax_tags[3].ids)], + }), + ], + }) + + cls.cash_basis_tax_tiny_amount = cls.env['account.tax'].create({ + 'name': 'cash_basis_tax_tiny_amount', + 'amount': 0.0001, + 'company_id': cls.company_data['company'].id, + 'cash_basis_transition_account_id': cls.cash_basis_transfer_account.id, + 'tax_exigibility': 'on_payment', + 'invoice_repartition_line_ids': [ + (0, 0, { + 'factor_percent': 100, + 'repartition_type': 'base', + 'tag_ids': [(6, 0, cls.tax_tags[4].ids)], + }), + + (0, 0, { + 'factor_percent': 100, + 'repartition_type': 'tax', + 'account_id': cls.tax_account_2.id, + 'tag_ids': [(6, 0, cls.tax_tags[5].ids)], + }), + ], + 'refund_repartition_line_ids': [ + (0, 0, { + 'factor_percent': 100, + 'repartition_type': 'base', + 'tag_ids': [(6, 0, cls.tax_tags[6].ids)], + }), + + (0, 0, { + 'factor_percent': 100, + 'repartition_type': 'tax', + 'account_id': cls.tax_account_2.id, + 'tag_ids': [(6, 0, cls.tax_tags[7].ids)], + }), + ], + }) + + # ------------------------------------------------------------------------- + # HELPERS + # ------------------------------------------------------------------------- + + def assertFullReconcile(self, full_reconcile, lines): + exchange_difference_move = full_reconcile.exchange_move_id + partials = lines.mapped('matched_debit_ids') + lines.mapped('matched_credit_ids') + + if full_reconcile.exchange_move_id: + lines += exchange_difference_move.line_ids.filtered(lambda line: line.account_id == lines[0].account_id) + + # Use sets to not depend of the order. + self.assertEqual(set(full_reconcile.partial_reconcile_ids), set(partials)) + self.assertEqual(set(full_reconcile.reconciled_line_ids), set(lines)) + + # Ensure there is no residual amount left. + self.assertRecordValues(lines, [{ + 'amount_residual': 0.0, + 'amount_residual_currency': 0.0, + 'reconciled': bool(line.account_id.reconcile), + } for line in lines]) + + def assertPartialReconcile(self, partials, expected_vals_list): + partials = partials.sorted(lambda part: ( + part.amount, + part.debit_amount_currency, + part.credit_amount_currency, + )) + self.assertRecordValues(partials, expected_vals_list) + + def assertAmountsGroupByAccount(self, amount_per_account): + expected_values = {account.id: (account, balance, amount_currency) for account, balance, amount_currency in amount_per_account} + + if not expected_values: + return + + self.cr.execute(''' + SELECT + line.account_id, + COALESCE(SUM(line.balance), 0.0) AS total_balance, + COALESCE(SUM(line.amount_currency), 0.0) AS total_amount_currency + FROM account_move_line line + WHERE line.account_id IN %s + GROUP BY line.account_id + ''', [tuple(expected_values.keys())]) + for account_id, total_balance, total_amount_currency in self.cr.fetchall(): + account, expected_balance, expected_amount_currency = expected_values[account_id] + self.assertEqual( + total_balance, + expected_balance, + "Balance of %s is incorrect" % account.name, + ) + self.assertEqual( + total_amount_currency, + expected_amount_currency, + "Amount currency of %s is incorrect" % account.name, + ) + + def assertTaxGridAmounts(self, amount_per_tag): + expected_values = {tag.id: (tag, balance) for tag, balance in amount_per_tag} + + if not expected_values: + return + + self.cr.execute(''' + SELECT + rel.account_account_tag_id, + SUM(line.balance) + FROM account_account_tag_account_move_line_rel rel + JOIN account_move_line line ON line.id = rel.account_move_line_id + WHERE line.tax_exigible IS TRUE + AND line.company_id IN %(company_ids)s + GROUP BY rel.account_account_tag_id + ''', { + 'company_ids': tuple(self.env.companies.ids), + }) + + for tag_id, total_balance in self.cr.fetchall(): + tag, expected_balance = expected_values[tag_id] + self.assertEqual( + total_balance, + expected_balance, + "Balance of %s is incorrect" % tag.name, + ) + + # ------------------------------------------------------------------------- + # Test creation of account.partial.reconcile/account.full.reconcile + # during the reconciliation. + # ------------------------------------------------------------------------- + + def test_reconcile_single_currency(self): + account_id = self.company_data['default_account_receivable'].id + + move = self.env['account.move'].create({ + 'move_type': 'entry', + 'date': '2016-01-01', + 'line_ids': [ + (0, 0, {'debit': 1000.0, 'credit': 0.0, 'account_id': account_id}), + (0, 0, {'debit': 200.0, 'credit': 0.0, 'account_id': account_id}), + (0, 0, {'debit': 0.0, 'credit': 300.0, 'account_id': account_id}), + (0, 0, {'debit': 0.0, 'credit': 400.0, 'account_id': account_id}), + (0, 0, {'debit': 0.0, 'credit': 500.0, 'account_id': account_id}), + ] + }) + move.action_post() + + line_1 = move.line_ids.filtered(lambda line: line.debit == 1000.0) + line_2 = move.line_ids.filtered(lambda line: line.debit == 200.0) + line_3 = move.line_ids.filtered(lambda line: line.credit == 300.0) + line_4 = move.line_ids.filtered(lambda line: line.credit == 400.0) + line_5 = move.line_ids.filtered(lambda line: line.credit == 500.0) + + self.assertRecordValues(line_1 + line_2 + line_3 + line_4 + line_5, [ + {'amount_residual': 1000.0, 'amount_residual_currency': 1000.0, 'reconciled': False}, + {'amount_residual': 200.0, 'amount_residual_currency': 200.0, 'reconciled': False}, + {'amount_residual': -300.0, 'amount_residual_currency': -300.0, 'reconciled': False}, + {'amount_residual': -400.0, 'amount_residual_currency': -400.0, 'reconciled': False}, + {'amount_residual': -500.0, 'amount_residual_currency': -500.0, 'reconciled': False}, + ]) + + res = (line_1 + line_3).reconcile() + + self.assertPartialReconcile(res['partials'], [{ + 'amount': 300.0, + 'debit_amount_currency': 300.0, + 'credit_amount_currency': 300.0, + 'debit_move_id': line_1.id, + 'credit_move_id': line_3.id, + }]) + + self.assertRecordValues(line_1 + line_3, [ + {'amount_residual': 700.0, 'amount_residual_currency': 700.0, 'reconciled': False}, + {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, + ]) + + res = (line_1 + line_4).reconcile() + + self.assertPartialReconcile(res['partials'], [{ + 'amount': 400.0, + 'debit_amount_currency': 400.0, + 'credit_amount_currency': 400.0, + 'debit_move_id': line_1.id, + 'credit_move_id': line_4.id, + }]) + + self.assertRecordValues(line_1 + line_4, [ + {'amount_residual': 300.0, 'amount_residual_currency': 300.0, 'reconciled': False}, + {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, + ]) + + res = (line_1 + line_5).reconcile() + + self.assertPartialReconcile(res['partials'], [{ + 'amount': 300.0, + 'debit_amount_currency': 300.0, + 'credit_amount_currency': 300.0, + 'debit_move_id': line_1.id, + 'credit_move_id': line_5.id, + }]) + + self.assertRecordValues(line_1 + line_5, [ + {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, + {'amount_residual': -200.0, 'amount_residual_currency': -200.0, 'reconciled': False}, + ]) + + res = (line_2 + line_5).reconcile() + + self.assertPartialReconcile(res['partials'], [{ + 'amount': 200.0, + 'debit_amount_currency': 200.0, + 'credit_amount_currency': 200.0, + 'debit_move_id': line_2.id, + 'credit_move_id': line_5.id, + }]) + + self.assertRecordValues(res['full_reconcile'], [{'exchange_move_id': False}]) + self.assertFullReconcile(res['full_reconcile'], line_1 + line_2 + line_3 + line_4 + line_5) + + def test_reconcile_same_foreign_currency(self): + account_id = self.company_data['default_account_receivable'].id + + # Rate is 3.0 in 2016, 2.0 in 2017. + currency_id = self.currency_data['currency'].id + + moves = self.env['account.move'].create([ + { + 'move_type': 'entry', + 'date': '2016-01-01', + 'line_ids': [ + (0, 0, {'debit': 1200.0, 'credit': 0.0, 'amount_currency': 3600.0, 'account_id': account_id, 'currency_id': currency_id}), + (0, 0, {'debit': 120.0, 'credit': 0.0, 'amount_currency': 360.0, 'account_id': account_id, 'currency_id': currency_id}), + + (0, 0, {'debit': 0.0, 'credit': 1320.0, 'account_id': account_id}), + ] + }, + { + 'move_type': 'entry', + 'date': '2017-01-01', + 'line_ids': [ + (0, 0, {'debit': 0.0, 'credit': 240.0, 'amount_currency': -480.0, 'account_id': account_id, 'currency_id': currency_id}), + (0, 0, {'debit': 0.0, 'credit': 720.0, 'amount_currency': -1440.0, 'account_id': account_id, 'currency_id': currency_id}), + (0, 0, {'debit': 0.0, 'credit': 1020.0, 'amount_currency': -2040.0, 'account_id': account_id, 'currency_id': currency_id}), + + (0, 0, {'debit': 1980.0, 'credit': 0.0, 'account_id': account_id}), + ] + } + ]) + + moves.action_post() + + line_1 = moves.line_ids.filtered(lambda line: line.debit == 1200.0) + line_2 = moves.line_ids.filtered(lambda line: line.debit == 120.0) + line_3 = moves.line_ids.filtered(lambda line: line.credit == 240.0) + line_4 = moves.line_ids.filtered(lambda line: line.credit == 720.0) + line_5 = moves.line_ids.filtered(lambda line: line.credit == 1020.0) + + self.assertRecordValues(line_1 + line_2 + line_3 + line_4 + line_5, [ + {'amount_residual': 1200.0, 'amount_residual_currency': 3600.0, 'reconciled': False}, + {'amount_residual': 120.0, 'amount_residual_currency': 360.0, 'reconciled': False}, + {'amount_residual': -240.0, 'amount_residual_currency': -480.0, 'reconciled': False}, + {'amount_residual': -720.0, 'amount_residual_currency': -1440.0, 'reconciled': False}, + {'amount_residual': -1020.0, 'amount_residual_currency': -2040.0, 'reconciled': False}, + ]) + + res = (line_1 + line_3 + line_4).reconcile() + + self.assertPartialReconcile(res['partials'], [ + # Partial generated when reconciling line_1 & line_3: + { + 'amount': 240.0, + 'debit_amount_currency': 480.0, + 'credit_amount_currency': 480.0, + 'debit_move_id': line_1.id, + 'credit_move_id': line_3.id, + }, + # Partial generated when reconciling line_1 & line_4: + { + 'amount': 720.0, + 'debit_amount_currency': 1440.0, + 'credit_amount_currency': 1440.0, + 'debit_move_id': line_1.id, + 'credit_move_id': line_4.id, + }, + ]) + + self.assertRecordValues(line_1 + line_3 + line_4, [ + {'amount_residual': 240.0, 'amount_residual_currency': 1680.0, 'reconciled': False}, + {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, + {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, + ]) + + res = (line_1 + line_5).reconcile() + + self.assertPartialReconcile(res['partials'], [{ + 'amount': 240.0, + 'debit_amount_currency': 1680.0, + 'credit_amount_currency': 1680.0, + 'debit_move_id': line_1.id, + 'credit_move_id': line_5.id, + }]) + + self.assertRecordValues(line_1 + line_5, [ + {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, + {'amount_residual': -780.0, 'amount_residual_currency': -360.0, 'reconciled': False}, + ]) + + res = (line_2 + line_5).reconcile() + + exchange_diff = res['full_reconcile'].exchange_move_id + exchange_diff_lines = exchange_diff.line_ids.sorted(lambda line: (line.currency_id, abs(line.amount_currency), -line.amount_currency)) + + self.assertRecordValues(exchange_diff_lines, [ + # Fix line_2: + { + 'debit': 660.0, + 'credit': 0.0, + 'amount_currency': 0.0, + 'currency_id': currency_id, + 'account_id': account_id, + }, + { + 'debit': 0.0, + 'credit': 660.0, + 'amount_currency': 0.0, + 'currency_id': currency_id, + 'account_id': exchange_diff.journal_id.company_id.income_currency_exchange_account_id.id, + }, + ]) + + self.assertPartialReconcile(res['partials'], [ + # Partial generated when reconciling line_2 & line_5: + { + 'amount': 120.0, + 'debit_amount_currency': 360.0, + 'credit_amount_currency': 360.0, + 'debit_move_id': line_2.id, + 'credit_move_id': line_5.id, + }, + # Partial fixing line_4 (exchange difference): + { + 'amount': 660.0, + 'debit_amount_currency': 0.0, + 'credit_amount_currency': 0.0, + 'debit_move_id': exchange_diff_lines[0].id, + 'credit_move_id': line_5.id, + }, + ]) + + self.assertFullReconcile(res['full_reconcile'], line_1 + line_2 + line_3 + line_4 + line_5) + + def test_reconcile_multiple_currencies(self): + account_id = self.company_data['default_account_receivable'].id + + # Rate is 3.0 in 2016, 2.0 in 2017. + currency1_id = self.currency_data['currency'].id + # Rate is 6.0 in 2016, 4.0 in 2017. + currency2_id = self.currency_data_2['currency'].id + + moves = self.env['account.move'].create([ + { + 'move_type': 'entry', + 'date': '2016-01-01', + 'line_ids': [ + (0, 0, {'debit': 1200.0, 'credit': 0.0, 'amount_currency': 3600.0, 'account_id': account_id, 'currency_id': currency1_id}), + (0, 0, {'debit': 780.0, 'credit': 0.0, 'amount_currency': 2340.0, 'account_id': account_id, 'currency_id': currency1_id}), + + (0, 0, {'debit': 0.0, 'credit': 1980.0, 'account_id': account_id}), + ] + }, + { + 'move_type': 'entry', + 'date': '2017-01-01', + 'line_ids': [ + (0, 0, {'debit': 0.0, 'credit': 240.0, 'amount_currency': -960.0, 'account_id': account_id, 'currency_id': currency2_id}), + (0, 0, {'debit': 0.0, 'credit': 720.0, 'amount_currency': -2880.0, 'account_id': account_id, 'currency_id': currency2_id}), + (0, 0, {'debit': 0.0, 'credit': 1020.0, 'amount_currency': -4080.0, 'account_id': account_id, 'currency_id': currency2_id}), + + (0, 0, {'debit': 1980.0, 'credit': 0.0, 'account_id': account_id}), + ] + } + ]) + + moves.action_post() + + line_1 = moves.line_ids.filtered(lambda line: line.debit == 1200.0) + line_2 = moves.line_ids.filtered(lambda line: line.debit == 780.0) + line_3 = moves.line_ids.filtered(lambda line: line.credit == 240.0) + line_4 = moves.line_ids.filtered(lambda line: line.credit == 720.0) + line_5 = moves.line_ids.filtered(lambda line: line.credit == 1020.0) + + self.assertRecordValues(line_1 + line_2 + line_3 + line_4 + line_5, [ + {'amount_residual': 1200.0, 'amount_residual_currency': 3600.0, 'reconciled': False}, + {'amount_residual': 780.0, 'amount_residual_currency': 2340.0, 'reconciled': False}, + {'amount_residual': -240.0, 'amount_residual_currency': -960.0, 'reconciled': False}, + {'amount_residual': -720.0, 'amount_residual_currency': -2880.0, 'reconciled': False}, + {'amount_residual': -1020.0, 'amount_residual_currency': -4080.0, 'reconciled': False}, + ]) + + res = (line_1 + line_3 + line_4).reconcile() + + self.assertPartialReconcile(res['partials'], [ + # Partial generated when reconciling line_1 & line_3: + { + 'amount': 240.0, + 'debit_amount_currency': 480.0, + 'credit_amount_currency': 1440.0, + 'debit_move_id': line_1.id, + 'credit_move_id': line_3.id, + }, + # Partial generated when reconciling line_1 & line_4: + { + 'amount': 720.0, + 'debit_amount_currency': 1440.0, + 'credit_amount_currency': 4320.0, + 'debit_move_id': line_1.id, + 'credit_move_id': line_4.id, + }, + ]) + + self.assertRecordValues(line_1 + line_3 + line_4, [ + {'amount_residual': 240.0, 'amount_residual_currency': 1680.0, 'reconciled': False}, + {'amount_residual': 0.0, 'amount_residual_currency': 480.0, 'reconciled': False}, + {'amount_residual': 0.0, 'amount_residual_currency': 1440.0, 'reconciled': False}, + ]) + + res = (line_1 + line_5).reconcile() + + self.assertPartialReconcile(res['partials'], [{ + 'amount': 240.0, + 'debit_amount_currency': 480.0, + 'credit_amount_currency': 1440.0, + 'debit_move_id': line_1.id, + 'credit_move_id': line_5.id, + }]) + + self.assertRecordValues(line_1 + line_5, [ + {'amount_residual': 0.0, 'amount_residual_currency': 1200.0, 'reconciled': False}, + {'amount_residual': -780.0, 'amount_residual_currency': -2640.0, 'reconciled': False}, + ]) + + res = (line_2 + line_5).reconcile() + + exchange_diff = res['full_reconcile'].exchange_move_id + exchange_diff_lines = exchange_diff.line_ids.sorted(lambda line: (line.currency_id, abs(line.amount_currency), -line.amount_currency)) + + self.assertRecordValues(exchange_diff_lines, [ + # Fix line_2: + { + 'debit': 0.0, + 'credit': 0.0, + 'amount_currency': 780.0, + 'currency_id': currency1_id, + 'account_id': exchange_diff.journal_id.company_id.expense_currency_exchange_account_id.id, + }, + { + 'debit': 0.0, + 'credit': 0.0, + 'amount_currency': -780.0, + 'currency_id': currency1_id, + 'account_id': account_id, + }, + # Fix line_3: + { + 'debit': 0.0, + 'credit': 0.0, + 'amount_currency': 480.0, + 'currency_id': currency2_id, + 'account_id': exchange_diff.journal_id.company_id.expense_currency_exchange_account_id.id, + }, + { + 'debit': 0.0, + 'credit': 0.0, + 'amount_currency': -480.0, + 'currency_id': currency2_id, + 'account_id': account_id, + }, + # Fix line_4: + { + 'debit': 0.0, + 'credit': 0.0, + 'amount_currency': 1440.0, + 'currency_id': currency2_id, + 'account_id': exchange_diff.journal_id.company_id.expense_currency_exchange_account_id.id, + }, + { + 'debit': 0.0, + 'credit': 0.0, + 'amount_currency': -1440.0, + 'currency_id': currency2_id, + 'account_id': account_id, + }, + # Fix line_5: + { + 'debit': 0.0, + 'credit': 0.0, + 'amount_currency': 2040.0, + 'currency_id': currency2_id, + 'account_id': exchange_diff.journal_id.company_id.expense_currency_exchange_account_id.id, + }, + { + 'debit': 0.0, + 'credit': 0.0, + 'amount_currency': -2040.0, + 'currency_id': currency2_id, + 'account_id': account_id, + }, + # Fix line_1: + { + 'debit': 0.0, + 'credit': 0.0, + 'amount_currency': 1200.0, + 'currency_id': currency1_id, + 'account_id': exchange_diff.journal_id.company_id.expense_currency_exchange_account_id.id, + }, + { + 'debit': 0.0, + 'credit': 0.0, + 'amount_currency': -1200.0, + 'currency_id': currency1_id, + 'account_id': account_id, + }, + ]) + + self.assertPartialReconcile(res['partials'], [ + # Partial fixing line_3 (exchange difference): + { + 'amount': 0.0, + 'debit_amount_currency': 480.0, + 'credit_amount_currency': 480.0, + 'debit_move_id': line_3.id, + 'credit_move_id': exchange_diff_lines[3].id, + }, + # Partial fixing line_2 (exchange difference): + { + 'amount': 0.0, + 'debit_amount_currency': 780.0, + 'credit_amount_currency': 780.0, + 'debit_move_id': line_2.id, + 'credit_move_id': exchange_diff_lines[1].id, + }, + # Partial fixing line_1 (exchange difference): + { + 'amount': 0.0, + 'debit_amount_currency': 1200.0, + 'credit_amount_currency': 1200.0, + 'debit_move_id': line_1.id, + 'credit_move_id': exchange_diff_lines[9].id, + }, + # Partial fixing line_4 (exchange difference): + { + 'amount': 0.0, + 'debit_amount_currency': 1440.0, + 'credit_amount_currency': 1440.0, + 'debit_move_id': line_4.id, + 'credit_move_id': exchange_diff_lines[5].id, + }, + # Partial fixing line_5 (exchange difference): + { + 'amount': 0.0, + 'debit_amount_currency': 2040.0, + 'credit_amount_currency': 2040.0, + 'debit_move_id': line_5.id, + 'credit_move_id': exchange_diff_lines[7].id, + }, + # Partial generated when reconciling line_2 & line_5: + { + 'amount': 780.0, + 'debit_amount_currency': 1560.0, + 'credit_amount_currency': 4680.0, + 'debit_move_id': line_2.id, + 'credit_move_id': line_5.id, + }, + ]) + + self.assertFullReconcile(res['full_reconcile'], line_1 + line_2 + line_3 + line_4 + line_5) + + def test_reconcile_asymetric_rate_change(self): + account_id = self.company_data['default_account_receivable'].id + + # Rate is 3.0 in 2016, 2.0 in 2017. + currency1_id = self.currency_data['currency'].id + # Rate is 6.0 in 2016, 4.0 in 2017. + currency2_id = self.currency_data_2['currency'].id + + # Create rate changes for 2018: currency1 rate increases while currency2 rate decreases. + self.env['res.currency.rate'].create({ + 'name': '2018-01-01', + 'rate': 8.0, + 'currency_id': currency1_id, + 'company_id': self.company_data['company'].id, + }) + + self.env['res.currency.rate'].create({ + 'name': '2018-01-01', + 'rate': 2.0, + 'currency_id': currency2_id, + 'company_id': self.company_data['company'].id, + }) + + moves = self.env['account.move'].create([ + { + 'move_type': 'entry', + 'date': '2018-01-01', + 'line_ids': [ + (0, 0, { + 'debit': 1200.0, + 'credit': 0.0, + 'amount_currency': 9600.0, + 'account_id': account_id, + 'currency_id': currency1_id, + }), + (0, 0, { + 'debit': 960.0, + 'credit': 0.0, + 'amount_currency': 1920.0, + 'account_id': account_id, + 'currency_id': currency2_id, + }), + (0, 0, { + 'debit': 0.0, + 'credit': 2160.0, + 'account_id': account_id, + }), + ] + }, + { + 'move_type': 'entry', + 'date': '2017-01-01', + 'line_ids': [ + (0, 0, { + 'debit': 0.0, + 'credit': 1200.0, + 'amount_currency': -4800.0, + 'account_id': account_id, + 'currency_id': currency2_id, + }), + (0, 0, { + 'debit': 0.0, + 'credit': 960.0, + 'amount_currency': -1920.0, + 'account_id': account_id, + 'currency_id': currency1_id, + }), + (0, 0, { + 'debit': 2160.0, + 'credit': 0.0, + 'account_id': account_id, + }), + ] + } + ]) + + moves.action_post() + + line_1 = moves.line_ids.filtered(lambda line: line.debit == 1200.0) + line_2 = moves.line_ids.filtered(lambda line: line.debit == 960.0) + line_3 = moves.line_ids.filtered(lambda line: line.credit == 1200.0) + line_4 = moves.line_ids.filtered(lambda line: line.credit == 960.0) + + self.assertRecordValues(line_1 + line_2 + line_3 + line_4, [ + {'amount_residual': 1200.0, 'amount_residual_currency': 9600.0, 'reconciled': False}, + {'amount_residual': 960.0, 'amount_residual_currency': 1920.0, 'reconciled': False}, + {'amount_residual': -1200.0, 'amount_residual_currency': -4800.0, 'reconciled': False}, + {'amount_residual': -960.0, 'amount_residual_currency': -1920.0, 'reconciled': False}, + ]) + + # Reconcile with debit_line currency rate increased and credit_line currency rate decreased between + # credit_line.date and debit_line.date. + + res = (line_1 + line_3).reconcile() + + exchange_diff = res['full_reconcile'].exchange_move_id + exchange_diff_lines = exchange_diff.line_ids.sorted(lambda line: (line.currency_id, abs(line.amount_currency), -line.amount_currency)) + + self.assertRecordValues(exchange_diff_lines, [ + { + 'debit': 0.0, + 'credit': 0.0, + 'amount_currency': 2400.0, + 'currency_id': currency2_id, + 'account_id': account_id, + }, + { + 'debit': 0.0, + 'credit': 0.0, + 'amount_currency': -2400.0, + 'currency_id': currency2_id, + 'account_id': exchange_diff.journal_id.company_id.income_currency_exchange_account_id.id, + }, + { + 'debit': 0.0, + 'credit': 0.0, + 'amount_currency': 7200.0, + 'currency_id': currency1_id, + 'account_id': exchange_diff.journal_id.company_id.expense_currency_exchange_account_id.id, + }, + { + 'debit': 0.0, + 'credit': 0.0, + 'amount_currency': -7200.0, + 'currency_id': currency1_id, + 'account_id': account_id, + } + ]) + + self.assertPartialReconcile(res['partials'], [ + { + 'amount': 0.0, + 'debit_amount_currency': 2400.0, + 'credit_amount_currency': 2400.0, + 'debit_move_id': exchange_diff_lines[0].id, + 'credit_move_id': line_3.id, + }, + { + 'amount': 0.0, + 'debit_amount_currency': 7200.0, + 'credit_amount_currency': 7200.0, + 'debit_move_id': line_1.id, + 'credit_move_id': exchange_diff_lines[3].id, + }, + { + 'amount': 1200.0, + 'debit_amount_currency': 2400.0, + 'credit_amount_currency': 2400.0, + 'debit_move_id': line_1.id, + 'credit_move_id': line_3.id, + }, + ]) + + self.assertRecordValues(line_1 + line_3, [ + {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, + {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, + ]) + + # Reconcile with debit_line currency rate decreased and credit_line currency rate increased between + # credit_line.date and debit_line.date. + + res = (line_2 + line_4).reconcile() + + exchange_diff = res['full_reconcile'].exchange_move_id + exchange_diff_lines = exchange_diff.line_ids.sorted(lambda line: (line.currency_id, abs(line.amount_currency), -line.amount_currency)) + + self.assertRecordValues(exchange_diff_lines, [ + { + 'debit': 0.0, + 'credit': 0.0, + 'amount_currency': 5760.0, + 'currency_id': currency1_id, + 'account_id': exchange_diff.journal_id.company_id.expense_currency_exchange_account_id.id, + }, + { + 'debit': 0.0, + 'credit': 0.0, + 'amount_currency': -5760.0, + 'currency_id': currency1_id, + 'account_id': account_id, + }, + { + 'debit': 0.0, + 'credit': 0.0, + 'amount_currency': 1920.0, + 'currency_id': currency2_id, + 'account_id': account_id, + }, + { + 'debit': 0.0, + 'credit': 0.0, + 'amount_currency': -1920.0, + 'currency_id': currency2_id, + 'account_id': exchange_diff.journal_id.company_id.income_currency_exchange_account_id.id, + } + ]) + + self.assertPartialReconcile(res['partials'], [ + { + 'amount': 0.0, + 'debit_amount_currency': 1920.0, + 'credit_amount_currency': 1920.0, + 'debit_move_id': exchange_diff_lines[2].id, + 'credit_move_id': line_2.id, + }, + { + 'amount': 0.0, + 'debit_amount_currency': 5760.0, + 'credit_amount_currency': 5760.0, + 'debit_move_id': line_4.id, + 'credit_move_id': exchange_diff_lines[1].id, + }, + { + 'amount': 960.0, + 'debit_amount_currency': 3840.0, + 'credit_amount_currency': 7680.0, + 'debit_move_id': line_2.id, + 'credit_move_id': line_4.id, + }, + ]) + + self.assertRecordValues(line_2 + line_4, [ + {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, + {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, + ]) + + def test_reverse_exchange_difference_same_foreign_currency(self): + move_2016 = self.env['account.move'].create({ + 'move_type': 'entry', + 'date': '2016-01-01', + 'line_ids': [ + (0, 0, { + 'debit': 1200.0, + 'credit': 0.0, + 'amount_currency': 3600.0, + 'account_id': self.company_data['default_account_receivable'].id, + 'currency_id': self.currency_data['currency'].id, + }), + (0, 0, { + 'debit': 0.0, + 'credit': 1200.0, + 'account_id': self.company_data['default_account_revenue'].id, + }), + ], + }) + move_2017 = self.env['account.move'].create({ + 'move_type': 'entry', + 'date': '2017-01-01', + 'line_ids': [ + (0, 0, { + 'debit': 0.0, + 'credit': 1800.0, + 'amount_currency': -3600.0, + 'account_id': self.company_data['default_account_receivable'].id, + 'currency_id': self.currency_data['currency'].id, + }), + (0, 0, { + 'debit': 1800.0, + 'credit': 0.0, + 'account_id': self.company_data['default_account_revenue'].id, + }), + ], + }) + (move_2016 + move_2017).action_post() + + rec_line_2016 = move_2016.line_ids.filtered(lambda line: line.account_id.internal_type == 'receivable') + rec_line_2017 = move_2017.line_ids.filtered(lambda line: line.account_id.internal_type == 'receivable') + + self.assertRecordValues(rec_line_2016 + rec_line_2017, [ + {'amount_residual': 1200.0, 'amount_residual_currency': 3600.0, 'reconciled': False}, + {'amount_residual': -1800.0, 'amount_residual_currency': -3600.0, 'reconciled': False}, + ]) + + # Reconcile. + + res = (rec_line_2016 + rec_line_2017).reconcile() + + self.assertRecordValues(rec_line_2016 + rec_line_2017, [ + {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, + {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, + ]) + + exchange_diff = res['full_reconcile'].exchange_move_id + exchange_diff_lines = exchange_diff.line_ids.sorted('balance') + + self.assertRecordValues(exchange_diff_lines, [ + { + 'debit': 0.0, + 'credit': 600.0, + 'amount_currency': 0.0, + 'currency_id': self.currency_data['currency'].id, + 'account_id': exchange_diff.journal_id.company_id.income_currency_exchange_account_id.id, + }, + { + 'debit': 600.0, + 'credit': 0.0, + 'amount_currency': 0.0, + 'currency_id': self.currency_data['currency'].id, + 'account_id': self.company_data['default_account_receivable'].id, + }, + ]) + + self.assertRecordValues(exchange_diff_lines, [ + {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': False}, + {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, + ]) + + # Unreconcile. + # A reversal is created to cancel the exchange difference journal entry. + + (rec_line_2016 + rec_line_2017).remove_move_reconcile() + + reverse_exchange_diff = exchange_diff_lines[1].matched_credit_ids.credit_move_id.move_id + reverse_exchange_diff_lines = reverse_exchange_diff.line_ids.sorted('balance') + + self.assertRecordValues(reverse_exchange_diff_lines, [ + { + 'debit': 0.0, + 'credit': 600.0, + 'amount_currency': 0.0, + 'currency_id': self.currency_data['currency'].id, + 'account_id': self.company_data['default_account_receivable'].id, + }, + { + 'debit': 600.0, + 'credit': 0.0, + 'amount_currency': 0.0, + 'currency_id': self.currency_data['currency'].id, + 'account_id': exchange_diff.journal_id.company_id.income_currency_exchange_account_id.id, + }, + ]) + + self.assertRecordValues(exchange_diff_lines + reverse_exchange_diff_lines, [ + {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': False}, + {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, + {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, + {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': False}, + ]) + + partials = reverse_exchange_diff_lines.matched_debit_ids + reverse_exchange_diff_lines.matched_credit_ids + self.assertPartialReconcile(partials, [{ + 'amount': 600.0, + 'debit_amount_currency': 0.0, + 'credit_amount_currency': 0.0, + 'debit_move_id': exchange_diff_lines[1].id, + 'credit_move_id': reverse_exchange_diff_lines[0].id, + }]) + + def test_reverse_exchange_multiple_foreign_currencies(self): + move_2016 = self.env['account.move'].create({ + 'move_type': 'entry', + 'date': '2016-01-01', + 'line_ids': [ + (0, 0, { + 'debit': 1200.0, + 'credit': 0.0, + 'amount_currency': 7200.0, + 'account_id': self.company_data['default_account_receivable'].id, + 'currency_id': self.currency_data_2['currency'].id, + }), + (0, 0, { + 'debit': 0.0, + 'credit': 1200.0, + 'account_id': self.company_data['default_account_revenue'].id, + }), + ], + }) + move_2017 = self.env['account.move'].create({ + 'move_type': 'entry', + 'date': '2017-01-01', + 'line_ids': [ + (0, 0, { + 'debit': 0.0, + 'credit': 1200.0, + 'amount_currency': -2400.0, + 'account_id': self.company_data['default_account_receivable'].id, + 'currency_id': self.currency_data['currency'].id, + }), + (0, 0, { + 'debit': 1200.0, + 'credit': 0.0, + 'account_id': self.company_data['default_account_revenue'].id, + }), + ], + }) + (move_2016 + move_2017).action_post() + + rec_line_2016 = move_2016.line_ids.filtered(lambda line: line.account_id.internal_type == 'receivable') + rec_line_2017 = move_2017.line_ids.filtered(lambda line: line.account_id.internal_type == 'receivable') + + self.assertRecordValues(rec_line_2016 + rec_line_2017, [ + {'amount_residual': 1200.0, 'amount_residual_currency': 7200.0, 'reconciled': False}, + {'amount_residual': -1200.0, 'amount_residual_currency': -2400.0, 'reconciled': False}, + ]) + + # Reconcile. + + res = (rec_line_2016 + rec_line_2017).reconcile() + + self.assertRecordValues(rec_line_2016 + rec_line_2017, [ + {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, + {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, + ]) + + exchange_diff = res['full_reconcile'].exchange_move_id + exchange_diff_lines = exchange_diff.line_ids.sorted('amount_currency') + + self.assertRecordValues(exchange_diff_lines, [ + { + 'debit': 0.0, + 'credit': 0.0, + 'amount_currency': -2400.0, + 'currency_id': self.currency_data_2['currency'].id, + 'account_id': self.company_data['default_account_receivable'].id, + }, + { + 'debit': 0.0, + 'credit': 0.0, + 'amount_currency': -1200.0, + 'currency_id': self.currency_data['currency'].id, + 'account_id': self.company_data['default_account_receivable'].id, + }, + { + 'debit': 0.0, + 'credit': 0.0, + 'amount_currency': 1200.0, + 'currency_id': self.currency_data['currency'].id, + 'account_id': exchange_diff.journal_id.company_id.expense_currency_exchange_account_id.id, + }, + { + 'debit': 0.0, + 'credit': 0.0, + 'amount_currency': 2400.0, + 'currency_id': self.currency_data_2['currency'].id, + 'account_id': exchange_diff.journal_id.company_id.expense_currency_exchange_account_id.id, + }, + ]) + + self.assertRecordValues(exchange_diff_lines, [ + {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, + {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, + {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': False}, + {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': False}, + ]) + + # Unreconcile. + # A reversal is created to cancel the exchange difference journal entry. + + (rec_line_2016 + rec_line_2017).remove_move_reconcile() + + reverse_exchange_diff = exchange_diff_lines[1].matched_debit_ids.debit_move_id.move_id + reverse_exchange_diff_lines = reverse_exchange_diff.line_ids.sorted('amount_currency') + + self.assertRecordValues(reverse_exchange_diff_lines, [ + { + 'debit': 0.0, + 'credit': 0.0, + 'amount_currency': -2400.0, + 'currency_id': self.currency_data_2['currency'].id, + 'account_id': exchange_diff.journal_id.company_id.expense_currency_exchange_account_id.id, + }, + { + 'debit': 0.0, + 'credit': 0.0, + 'amount_currency': -1200.0, + 'currency_id': self.currency_data['currency'].id, + 'account_id': exchange_diff.journal_id.company_id.expense_currency_exchange_account_id.id, + }, + { + 'debit': 0.0, + 'credit': 0.0, + 'amount_currency': 1200.0, + 'currency_id': self.currency_data['currency'].id, + 'account_id': self.company_data['default_account_receivable'].id, + }, + { + 'debit': 0.0, + 'credit': 0.0, + 'amount_currency': 2400.0, + 'currency_id': self.currency_data_2['currency'].id, + 'account_id': self.company_data['default_account_receivable'].id, + }, + ]) + + self.assertRecordValues(exchange_diff_lines + reverse_exchange_diff_lines, [ + {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, + {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, + {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': False}, + {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': False}, + {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': False}, + {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': False}, + {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, + {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, + ]) + + partials = reverse_exchange_diff_lines.matched_debit_ids + reverse_exchange_diff_lines.matched_credit_ids + self.assertPartialReconcile(partials, [ + { + 'amount': 0.0, + 'debit_amount_currency': 1200.0, + 'credit_amount_currency': 1200.0, + 'debit_move_id': reverse_exchange_diff_lines[2].id, + 'credit_move_id': exchange_diff_lines[1].id, + }, + { + 'amount': 0.0, + 'debit_amount_currency': 2400.0, + 'credit_amount_currency': 2400.0, + 'debit_move_id': reverse_exchange_diff_lines[3].id, + 'credit_move_id': exchange_diff_lines[0].id, + }, + ]) + + # ------------------------------------------------------------------------- + # Test creation of extra journal entries during the reconciliation to + # deal with taxes that are exigible on payment (cash basis). + # ------------------------------------------------------------------------- + + def test_reconcile_cash_basis_workflow_single_currency(self): + ''' Test the generated journal entries during the reconciliation to manage the cash basis taxes. + Also, + - Test the case when there is multiple receivable/payable accounts. + - Test the reconciliation with tiny amounts. + - Check there is no rounding issue when making the percentage. + - Check there is no lost cents when the journal entry is fully reconciled. + ''' + cash_basis_move = self.env['account.move'].create({ + 'move_type': 'entry', + 'date': '2016-01-01', + 'line_ids': [ + # Base Tax line + (0, 0, { + 'debit': 0.0, + 'credit': 100.0, + 'account_id': self.company_data['default_account_revenue'].id, + 'tax_ids': [(6, 0, (self.cash_basis_tax_a_third_amount + self.cash_basis_tax_tiny_amount).ids)], + 'tax_exigible': False, + }), + + # Tax lines + (0, 0, { + 'debit': 0.0, + 'credit': 33.33, + 'account_id': self.cash_basis_transfer_account.id, + 'tax_repartition_line_id': self.cash_basis_tax_a_third_amount.invoice_repartition_line_ids.filtered(lambda line: line.repartition_type == 'tax').id, + 'tax_exigible': False, + }), + (0, 0, { + 'debit': 0.0, + 'credit': 0.01, + 'account_id': self.cash_basis_transfer_account.id, + 'tax_repartition_line_id': self.cash_basis_tax_tiny_amount.invoice_repartition_line_ids.filtered(lambda line: line.repartition_type == 'tax').id, + 'tax_exigible': False, + }), + + # Receivable lines + (0, 0, { + 'debit': 44.45, + 'credit': 0.0, + 'account_id': self.extra_receivable_account_1.id, + }), + (0, 0, { + 'debit': 44.45, + 'credit': 0.0, + 'account_id': self.extra_receivable_account_2.id, + }), + (0, 0, { + 'debit': 44.45, + 'credit': 0.0, + 'account_id': self.extra_receivable_account_2.id, + }), + (0, 0, { + 'debit': 0.0, + 'credit': 0.01, + 'account_id': self.extra_payable_account_1.id, + }), + ] + }) + + payment_move = self.env['account.move'].create({ + 'move_type': 'entry', + 'date': '2017-01-01', + 'line_ids': [ + (0, 0, {'debit': 0.0, 'credit': 33.34, 'account_id': self.extra_receivable_account_1.id}), + (0, 0, {'debit': 0.0, 'credit': 11.11, 'account_id': self.extra_receivable_account_1.id}), + (0, 0, {'debit': 0.0, 'credit': 88.89, 'account_id': self.extra_receivable_account_2.id}), + (0, 0, {'debit': 0.0, 'credit': 0.01, 'account_id': self.extra_receivable_account_2.id}), + (0, 0, {'debit': 0.01, 'credit': 0.0, 'account_id': self.extra_payable_account_1.id}), + (0, 0, {'debit': 133.34, 'credit': 0.0, 'account_id': self.company_data['default_account_revenue'].id}), + ] + }) + + (cash_basis_move + payment_move).action_post() + + # Initial amounts by accounts: + + self.assertAmountsGroupByAccount([ + # Account Balance Amount Currency + (self.cash_basis_transfer_account, -33.34, -33.34), + (self.tax_account_1, 0.0, 0.0), + (self.tax_account_2, 0.0, 0.0), + (self.cash_basis_base_account, 0.0, 0.0), + ]) + + # There is 44.45 + 44.45 + 44.45 + 0.01 = 133.36 to reconcile on 'cash_basis_move'. + # Reconciling all the amount in extra_receivable_account_1 should compute 2 percentages: + # 33.34 / 133.36 = 0.25 + # 11.11 / 133.36 = 0.083308338 + + receivable_lines_1 = (cash_basis_move + payment_move).line_ids\ + .filtered(lambda line: line.account_id == self.extra_receivable_account_1) + res = receivable_lines_1.reconcile() + + self.assertFullReconcile(res['full_reconcile'], receivable_lines_1) + self.assertEqual(len(res.get('tax_cash_basis_moves', [])), 2) + self.assertRecordValues(res['tax_cash_basis_moves'][0].line_ids, [ + # Base amount of tax_1 & tax_2: + {'debit': 25.0, 'credit': 0.0, 'account_id': self.cash_basis_base_account.id}, + {'debit': 0.0, 'credit': 25.0, 'account_id': self.cash_basis_base_account.id}, + # tax_1: + {'debit': 8.33, 'credit': 0.0, 'account_id': self.cash_basis_transfer_account.id}, + {'debit': 0.0, 'credit': 8.33, 'account_id': self.tax_account_1.id}, + # tax_2: + {'debit': 0.0, 'credit': 0.0, 'account_id': self.cash_basis_transfer_account.id}, + {'debit': 0.0, 'credit': 0.0, 'account_id': self.tax_account_2.id}, + ]) + self.assertRecordValues(res['tax_cash_basis_moves'][1].line_ids, [ + # Base amount of tax_1 & tax_2: + {'debit': 8.33, 'credit': 0.0, 'account_id': self.cash_basis_base_account.id}, + {'debit': 0.0, 'credit': 8.33, 'account_id': self.cash_basis_base_account.id}, + # tax_1: + {'debit': 2.78, 'credit': 0.0, 'account_id': self.cash_basis_transfer_account.id}, + {'debit': 0.0, 'credit': 2.78, 'account_id': self.tax_account_1.id}, + # tax_2: + {'debit': 0.0, 'credit': 0.0, 'account_id': self.cash_basis_transfer_account.id}, + {'debit': 0.0, 'credit': 0.0, 'account_id': self.tax_account_2.id}, + ]) + + self.assertAmountsGroupByAccount([ + # Account Balance Amount Currency + (self.cash_basis_transfer_account, -22.23, -22.23), + (self.tax_account_1, -11.11, -11.11), + (self.tax_account_2, 0.0, 0.0), + ]) + + # Reconciling all the amount in extra_receivable_account_2 should compute 3 percentages: + # 44.45 / 133.36 = 0.333308338 + # 44.44 / 133.36 = 0.333233353 + # 0.01 / 133.36 = 0.000074985 + + receivable_lines_2 = (cash_basis_move + payment_move).line_ids\ + .filtered(lambda line: line.account_id == self.extra_receivable_account_2) + res = receivable_lines_2.reconcile() + + self.assertFullReconcile(res['full_reconcile'], receivable_lines_2) + self.assertEqual(len(res.get('tax_cash_basis_moves', [])), 3) + self.assertRecordValues(res['tax_cash_basis_moves'][0].line_ids, [ + # Base amount of tax_1 & tax_2: + {'debit': 33.33, 'credit': 0.0, 'account_id': self.cash_basis_base_account.id}, + {'debit': 0.0, 'credit': 33.33, 'account_id': self.cash_basis_base_account.id}, + # tax_1: + {'debit': 11.11, 'credit': 0.0, 'account_id': self.cash_basis_transfer_account.id}, + {'debit': 0.0, 'credit': 11.11, 'account_id': self.tax_account_1.id}, + # tax_2: + {'debit': 0.0, 'credit': 0.0, 'account_id': self.cash_basis_transfer_account.id}, + {'debit': 0.0, 'credit': 0.0, 'account_id': self.tax_account_2.id}, + ]) + self.assertRecordValues(res['tax_cash_basis_moves'][1].line_ids, [ + # Base amount of tax_1 & tax_2: + {'debit': 33.32, 'credit': 0.0, 'account_id': self.cash_basis_base_account.id}, + {'debit': 0.0, 'credit': 33.32, 'account_id': self.cash_basis_base_account.id}, + # tax_1: + {'debit': 11.11, 'credit': 0.0, 'account_id': self.cash_basis_transfer_account.id}, + {'debit': 0.0, 'credit': 11.11, 'account_id': self.tax_account_1.id}, + # tax_2: + {'debit': 0.0, 'credit': 0.0, 'account_id': self.cash_basis_transfer_account.id}, + {'debit': 0.0, 'credit': 0.0, 'account_id': self.tax_account_2.id}, + ]) + self.assertRecordValues(res['tax_cash_basis_moves'][2].line_ids, [ + # Base amount of tax_1 & tax_2: + {'debit': 0.01, 'credit': 0.0, 'account_id': self.cash_basis_base_account.id}, + {'debit': 0.0, 'credit': 0.01, 'account_id': self.cash_basis_base_account.id}, + # tax_1: + {'debit': 0.0, 'credit': 0.0, 'account_id': self.cash_basis_transfer_account.id}, + {'debit': 0.0, 'credit': 0.0, 'account_id': self.tax_account_1.id}, + # tax_2: + {'debit': 0.0, 'credit': 0.0, 'account_id': self.cash_basis_transfer_account.id}, + {'debit': 0.0, 'credit': 0.0, 'account_id': self.tax_account_2.id}, + ]) + + self.assertAmountsGroupByAccount([ + # Account Balance Amount Currency + (self.cash_basis_transfer_account, -0.01, -0.01), + (self.tax_account_1, -33.33, -33.33), + (self.tax_account_2, 0.0, 0.0), + ]) + + # Reconciling all the amount in extra_payable_account_1 should trigger the matching number and ensure all + # the base amount has been covered without any rounding issue. + + payable_lines_1 = (cash_basis_move + payment_move).line_ids\ + .filtered(lambda line: line.account_id == self.extra_payable_account_1) + res = payable_lines_1.reconcile() + + self.assertFullReconcile(res['full_reconcile'], payable_lines_1) + self.assertEqual(len(res.get('tax_cash_basis_moves', [])), 1) + self.assertRecordValues(res['tax_cash_basis_moves'].line_ids, [ + # Base amount of tax_1 & tax_2: + {'debit': 0.01, 'credit': 0.0, 'account_id': self.cash_basis_base_account.id}, + {'debit': 0.0, 'credit': 0.01, 'account_id': self.cash_basis_base_account.id}, + # tax_1: + {'debit': 0.0, 'credit': 0.0, 'account_id': self.cash_basis_transfer_account.id}, + {'debit': 0.0, 'credit': 0.0, 'account_id': self.tax_account_1.id}, + # tax_2: + {'debit': 0.0, 'credit': 0.0, 'account_id': self.cash_basis_transfer_account.id}, + {'debit': 0.0, 'credit': 0.0, 'account_id': self.tax_account_2.id}, + ]) + + self.assertRecordValues(res['full_reconcile'].exchange_move_id.line_ids, [ + {'account_id': self.tax_account_2.id, 'debit': 0.0, 'credit': 0.01, 'tax_ids': [], 'tax_line_id': self.cash_basis_tax_tiny_amount.id}, + {'account_id': self.cash_basis_transfer_account.id, 'debit': 0.01, 'credit': 0.0, 'tax_ids': [], 'tax_line_id': False}, + ]) + + self.assertAmountsGroupByAccount([ + # Account Balance Amount Currency + (self.cash_basis_transfer_account, 0.0, 0.0), + (self.tax_account_1, -33.33, -33.33), + (self.tax_account_2, -0.01, -0.01), + ]) + + def test_reconcile_cash_basis_workflow_multi_currency(self): + ''' Same as before with a foreign currency. ''' + + currency_id = self.currency_data['currency'].id + taxes = self.cash_basis_tax_a_third_amount + self.cash_basis_tax_tiny_amount + + cash_basis_move = self.env['account.move'].create({ + 'move_type': 'entry', + 'date': '2016-01-01', + 'line_ids': [ + # Base Tax line + (0, 0, { + 'debit': 0.0, + 'credit': 33.34, + 'amount_currency': -100.0, + 'currency_id': currency_id, + 'account_id': self.company_data['default_account_revenue'].id, + 'tax_ids': [(6, 0, taxes.ids)], + 'tax_exigible': False, + }), + + # Tax lines + (0, 0, { + 'debit': 0.0, + 'credit': 11.10, + 'amount_currency': -33.33, + 'currency_id': currency_id, + 'account_id': self.cash_basis_transfer_account.id, + 'tax_repartition_line_id': self.cash_basis_tax_a_third_amount.invoice_repartition_line_ids.filtered(lambda line: line.repartition_type == 'tax').id, + 'tax_exigible': False, + }), + (0, 0, { + 'debit': 0.0, + 'credit': 0.01, + 'amount_currency': -0.01, + 'currency_id': currency_id, + 'account_id': self.cash_basis_transfer_account.id, + 'tax_repartition_line_id': self.cash_basis_tax_tiny_amount.invoice_repartition_line_ids.filtered(lambda line: line.repartition_type == 'tax').id, + 'tax_exigible': False, + }), + + # Receivable lines + (0, 0, { + 'debit': 14.82, + 'credit': 0.0, + 'amount_currency': 44.45, + 'currency_id': currency_id, + 'account_id': self.extra_receivable_account_1.id, + }), + (0, 0, { + 'debit': 14.82, + 'credit': 0.0, + 'amount_currency': 44.45, + 'currency_id': currency_id, + 'account_id': self.extra_receivable_account_2.id, + }), + (0, 0, { + 'debit': 14.82, + 'credit': 0.0, + 'amount_currency': 44.45, + 'currency_id': currency_id, + 'account_id': self.extra_receivable_account_2.id, + }), + (0, 0, { + 'debit': 0.0, + 'credit': 0.01, + 'amount_currency': -0.01, + 'currency_id': currency_id, + 'account_id': self.extra_payable_account_1.id, + }), + ] + }) + + payment_move = self.env['account.move'].create({ + 'move_type': 'entry', + 'date': '2017-01-01', + 'line_ids': [ + (0, 0, {'debit': 0.0, 'credit': 16.67, 'amount_currency': -33.34, 'currency_id': currency_id, 'account_id': self.extra_receivable_account_1.id}), + (0, 0, {'debit': 0.0, 'credit': 5.6, 'amount_currency': -11.11, 'currency_id': currency_id, 'account_id': self.extra_receivable_account_1.id}), + (0, 0, {'debit': 0.0, 'credit': 44.45, 'amount_currency': -88.89, 'currency_id': currency_id, 'account_id': self.extra_receivable_account_2.id}), + (0, 0, {'debit': 0.0, 'credit': 0.01, 'amount_currency': -0.01, 'currency_id': currency_id, 'account_id': self.extra_receivable_account_2.id}), + (0, 0, {'debit': 0.01, 'credit': 0.0, 'amount_currency': 0.01, 'currency_id': currency_id, 'account_id': self.extra_payable_account_1.id}), + (0, 0, {'debit': 66.72, 'credit': 0.0, 'account_id': self.company_data['default_account_revenue'].id}), + ] + }) + + (cash_basis_move + payment_move).action_post() + + # Initial amounts by accounts: + + self.assertAmountsGroupByAccount([ + # Account Balance Amount Currency + (self.cash_basis_transfer_account, -11.11, -33.34), + (self.tax_account_1, 0.0, 0.0), + (self.tax_account_2, 0.0, 0.0), + ]) + + # There is 44.45 + 44.45 + 44.45 + 0.01 = 133.36 to reconcile on 'cash_basis_move'. + # Reconciling all the amount in extra_receivable_account_1 should compute 2 percentages: + # 33.34 / 133.36 = 0.25 + # 11.11 / 133.36 = 0.083308338 + + receivable_lines_1 = (cash_basis_move + payment_move).line_ids\ + .filtered(lambda line: line.account_id == self.extra_receivable_account_1) + res = receivable_lines_1.reconcile() + + self.assertFullReconcile(res['full_reconcile'], receivable_lines_1) + self.assertEqual(len(res.get('tax_cash_basis_moves', [])), 2) + self.assertRecordValues(res['tax_cash_basis_moves'][0].line_ids, [ + # Base amount of tax_1 & tax_2: + {'debit': 12.5, 'credit': 0.0, 'amount_currency': 25.0, 'currency_id': currency_id, 'account_id': self.cash_basis_base_account.id}, + {'debit': 0.0, 'credit': 12.5, 'amount_currency': -25.0, 'currency_id': currency_id, 'account_id': self.cash_basis_base_account.id}, + # tax_1: + {'debit': 4.17, 'credit': 0.0, 'amount_currency': 8.333, 'currency_id': currency_id, 'account_id': self.cash_basis_transfer_account.id}, + {'debit': 0.0, 'credit': 4.17, 'amount_currency': -8.333, 'currency_id': currency_id, 'account_id': self.tax_account_1.id}, + # tax_2: + {'debit': 0.0, 'credit': 0.0, 'amount_currency': 0.003, 'currency_id': currency_id, 'account_id': self.cash_basis_transfer_account.id}, + {'debit': 0.0, 'credit': 0.0, 'amount_currency': -0.003, 'currency_id': currency_id, 'account_id': self.tax_account_2.id}, + ]) + self.assertRecordValues(res['tax_cash_basis_moves'][1].line_ids, [ + # Base amount of tax_1 & tax_2: + {'debit': 4.2, 'credit': 0.0, 'amount_currency': 8.331, 'currency_id': currency_id, 'account_id': self.cash_basis_base_account.id}, + {'debit': 0.0, 'credit': 4.2, 'amount_currency': -8.331, 'currency_id': currency_id, 'account_id': self.cash_basis_base_account.id}, + # tax_1: + {'debit': 1.4, 'credit': 0.0, 'amount_currency': 2.777, 'currency_id': currency_id, 'account_id': self.cash_basis_transfer_account.id}, + {'debit': 0.0, 'credit': 1.4, 'amount_currency': -2.777, 'currency_id': currency_id, 'account_id': self.tax_account_1.id}, + # tax_2: + {'debit': 0.0, 'credit': 0.0, 'amount_currency': 0.001, 'currency_id': currency_id, 'account_id': self.cash_basis_transfer_account.id}, + {'debit': 0.0, 'credit': 0.0, 'amount_currency': -0.001, 'currency_id': currency_id, 'account_id': self.tax_account_2.id}, + ]) + + self.assertAmountsGroupByAccount([ + # Account Balance Amount Currency + (self.cash_basis_transfer_account, -5.54, -22.226), + (self.tax_account_1, -5.57, -11.11), + (self.tax_account_2, 0.0, -0.004), + ]) + + # Reconciling all the amount in extra_receivable_account_2 should compute 3 percentages: + # 44.45 / 133.36 = 0.333308338 + # 44.44 / 133.36 = 0.333233353 + # 0.01 / 133.36 = 0.000074985 + + receivable_lines_2 = (cash_basis_move + payment_move).line_ids\ + .filtered(lambda line: line.account_id == self.extra_receivable_account_2) + res = receivable_lines_2.reconcile() + + self.assertFullReconcile(res['full_reconcile'], receivable_lines_2) + self.assertEqual(len(res.get('tax_cash_basis_moves', [])), 3) + self.assertRecordValues(res['tax_cash_basis_moves'][0].line_ids, [ + # Base amount of tax_1 & tax_2: + {'debit': 16.67, 'credit': 0.0, 'amount_currency': 33.331, 'currency_id': currency_id, 'account_id': self.cash_basis_base_account.id}, + {'debit': 0.0, 'credit': 16.67, 'amount_currency': -33.331, 'currency_id': currency_id, 'account_id': self.cash_basis_base_account.id}, + # tax_1: + {'debit': 5.56, 'credit': 0.0, 'amount_currency': 11.109, 'currency_id': currency_id, 'account_id': self.cash_basis_transfer_account.id}, + {'debit': 0.0, 'credit': 5.56, 'amount_currency': -11.109, 'currency_id': currency_id, 'account_id': self.tax_account_1.id}, + # tax_2: + {'debit': 0.0, 'credit': 0.0, 'amount_currency': 0.003, 'currency_id': currency_id, 'account_id': self.cash_basis_transfer_account.id}, + {'debit': 0.0, 'credit': 0.0, 'amount_currency': -0.003, 'currency_id': currency_id, 'account_id': self.tax_account_2.id}, + ]) + self.assertRecordValues(res['tax_cash_basis_moves'][1].line_ids, [ + # Base amount of tax_1 & tax_2: + {'debit': 16.66, 'credit': 0.0, 'amount_currency': 33.323, 'currency_id': currency_id, 'account_id': self.cash_basis_base_account.id}, + {'debit': 0.0, 'credit': 16.66, 'amount_currency': -33.323, 'currency_id': currency_id, 'account_id': self.cash_basis_base_account.id}, + # tax_1: + {'debit': 5.55, 'credit': 0.0, 'amount_currency': 11.107, 'currency_id': currency_id, 'account_id': self.cash_basis_transfer_account.id}, + {'debit': 0.0, 'credit': 5.55, 'amount_currency': -11.107, 'currency_id': currency_id, 'account_id': self.tax_account_1.id}, + # tax_2: + {'debit': 0.0, 'credit': 0.0, 'amount_currency': 0.003, 'currency_id': currency_id, 'account_id': self.cash_basis_transfer_account.id}, + {'debit': 0.0, 'credit': 0.0, 'amount_currency': -0.003, 'currency_id': currency_id, 'account_id': self.tax_account_2.id}, + ]) + self.assertRecordValues(res['tax_cash_basis_moves'][2].line_ids, [ + # Base amount of tax_1 & tax_2: + {'debit': 0.01, 'credit': 0.0, 'amount_currency': 0.007, 'currency_id': currency_id, 'account_id': self.cash_basis_base_account.id}, + {'debit': 0.0, 'credit': 0.01, 'amount_currency': -0.007, 'currency_id': currency_id, 'account_id': self.cash_basis_base_account.id}, + # tax_1: + {'debit': 0.0, 'credit': 0.0, 'amount_currency': 0.002, 'currency_id': currency_id, 'account_id': self.cash_basis_transfer_account.id}, + {'debit': 0.0, 'credit': 0.0, 'amount_currency': -0.002, 'currency_id': currency_id, 'account_id': self.tax_account_1.id}, + # tax_2: + {'debit': 0.0, 'credit': 0.0, 'amount_currency': 0.0, 'currency_id': currency_id, 'account_id': self.cash_basis_transfer_account.id}, + {'debit': 0.0, 'credit': 0.0, 'amount_currency': 0.0, 'currency_id': currency_id, 'account_id': self.tax_account_2.id}, + ]) + + self.assertAmountsGroupByAccount([ + # Account Balance Amount Currency + (self.cash_basis_transfer_account, 5.57, -0.002), + (self.tax_account_1, -16.68, -33.328), + (self.tax_account_2, 0.0, -0.01), + ]) + + # Reconciling all the amount in extra_payable_account_1 should trigger the matching number and ensure all + # the base amount has been covered without any rounding issue. + + payable_lines_1 = (cash_basis_move + payment_move).line_ids\ + .filtered(lambda line: line.account_id == self.extra_payable_account_1) + res = payable_lines_1.reconcile() + + self.assertFullReconcile(res['full_reconcile'], payable_lines_1) + self.assertEqual(len(res.get('tax_cash_basis_moves', [])), 1) + self.assertRecordValues(res['tax_cash_basis_moves'].line_ids, [ + # Base amount of tax_1 & tax_2: + {'debit': 0.01, 'credit': 0.0, 'amount_currency': 0.007, 'currency_id': currency_id, 'account_id': self.cash_basis_base_account.id}, + {'debit': 0.0, 'credit': 0.01, 'amount_currency': -0.007, 'currency_id': currency_id, 'account_id': self.cash_basis_base_account.id}, + # tax_1: + {'debit': 0.0, 'credit': 0.0, 'amount_currency': 0.002, 'currency_id': currency_id, 'account_id': self.cash_basis_transfer_account.id}, + {'debit': 0.0, 'credit': 0.0, 'amount_currency': -0.002, 'currency_id': currency_id, 'account_id': self.tax_account_1.id}, + # tax_2: + {'debit': 0.0, 'credit': 0.0, 'amount_currency': 0.0, 'currency_id': currency_id, 'account_id': self.cash_basis_transfer_account.id}, + {'debit': 0.0, 'credit': 0.0, 'amount_currency': 0.0, 'currency_id': currency_id, 'account_id': self.tax_account_2.id}, + ]) + + self.assertRecordValues(res['full_reconcile'].exchange_move_id.line_ids, [ + {'account_id': self.cash_basis_base_account.id, 'debit': 16.71, 'credit': 0.0, 'tax_ids': taxes.ids, 'tax_line_id': False}, + {'account_id': self.cash_basis_base_account.id, 'debit': 0.0, 'credit': 16.71, 'tax_ids': [], 'tax_line_id': False}, + {'account_id': self.tax_account_1.id, 'debit': 5.58, 'credit': 0.0, 'tax_ids': [], 'tax_line_id': self.cash_basis_tax_a_third_amount.id}, + {'account_id': self.cash_basis_transfer_account.id, 'debit': 0.0, 'credit': 5.58, 'tax_ids': [], 'tax_line_id': False}, + {'account_id': self.tax_account_2.id, 'debit': 0.0, 'credit': 0.01, 'tax_ids': [], 'tax_line_id': self.cash_basis_tax_tiny_amount.id}, + {'account_id': self.cash_basis_transfer_account.id, 'debit': 0.01, 'credit': 0.0, 'tax_ids': [], 'tax_line_id': False}, + ]) + + self.assertAmountsGroupByAccount([ + # Account Balance Amount Currency + (self.cash_basis_transfer_account, 0.0, 0.0), + (self.tax_account_1, -11.1, -33.33), + (self.tax_account_2, -0.01, -0.01), + ]) + + def test_reconcile_cash_basis_exchange_difference_transfer_account_check_entries_1(self): + ''' Test the generation of the exchange difference for a tax cash basis journal entry when the transfer + account is not a reconcile one. + ''' + currency_id = self.currency_data['currency'].id + + # Rate 1/3 in 2016. + cash_basis_move = self.env['account.move'].create({ + 'move_type': 'entry', + 'date': '2016-01-01', + 'line_ids': [ + # Base Tax line + (0, 0, { + 'debit': 0.0, + 'credit': 100.0, + 'amount_currency': -300.0, + 'currency_id': currency_id, + 'account_id': self.company_data['default_account_revenue'].id, + 'tax_ids': [(6, 0, self.cash_basis_tax_a_third_amount.ids)], + 'tax_exigible': False, + }), + + # Tax line + (0, 0, { + 'debit': 0.0, + 'credit': 33.33, + 'amount_currency': -100.0, + 'currency_id': currency_id, + 'account_id': self.cash_basis_transfer_account.id, + 'tax_repartition_line_id': self.cash_basis_tax_a_third_amount.invoice_repartition_line_ids.filtered(lambda line: line.repartition_type == 'tax').id, + 'tax_exigible': False, + }), + + # Receivable lines + (0, 0, { + 'debit': 133.33, + 'credit': 0.0, + 'amount_currency': 400.0, + 'currency_id': currency_id, + 'account_id': self.extra_receivable_account_1.id, + }), + ] + }) + + # Rate 1/2 in 2017. + payment_move = self.env['account.move'].create({ + 'move_type': 'entry', + 'date': '2017-01-01', + 'line_ids': [ + (0, 0, { + 'debit': 0.0, + 'credit': 201.0, + 'amount_currency': -402.0, # Don't create the full reconcile directly. + 'currency_id': currency_id, + 'account_id': self.extra_receivable_account_1.id, + }), + (0, 0, { + 'debit': 201.0, + 'credit': 0.0, + 'account_id': self.company_data['default_account_revenue'].id, + }), + ] + }) + + # Move making the payment fully paid. + end_move = self.env['account.move'].create({ + 'move_type': 'entry', + 'date': '2017-01-01', + 'line_ids': [ + (0, 0, { + 'debit': 1.0, + 'credit': 0.0, + 'amount_currency': 2.0, + 'currency_id': currency_id, + 'account_id': self.extra_receivable_account_1.id, + }), + (0, 0, { + 'debit': 0.0, + 'credit': 1.0, + 'account_id': self.company_data['default_account_revenue'].id, + }), + ] + }) + + (cash_basis_move + payment_move + end_move).action_post() + + self.assertAmountsGroupByAccount([ + # Account Balance Amount Currency + (self.cash_basis_transfer_account, -33.33, -100.0), + (self.tax_account_1, 0.0, 0.0), + ]) + + receivable_lines = (cash_basis_move + payment_move).line_ids\ + .filtered(lambda line: line.account_id == self.extra_receivable_account_1) + res = receivable_lines.reconcile() + + self.assertEqual(len(res.get('tax_cash_basis_moves', [])), 1) + self.assertRecordValues(res['tax_cash_basis_moves'].line_ids, [ + # Base amount: + {'debit': 150.0, 'credit': 0.0, 'amount_currency': 300.0, 'currency_id': currency_id, 'account_id': self.cash_basis_base_account.id}, + {'debit': 0.0, 'credit': 150.0, 'amount_currency': -300.0, 'currency_id': currency_id, 'account_id': self.cash_basis_base_account.id}, + # tax: + {'debit': 50.0, 'credit': 0.0, 'amount_currency': 100.0, 'currency_id': currency_id, 'account_id': self.cash_basis_transfer_account.id}, + {'debit': 0.0, 'credit': 50.0, 'amount_currency': -100.0, 'currency_id': currency_id, 'account_id': self.tax_account_1.id}, + ]) + + receivable_lines2 = (payment_move + end_move).line_ids\ + .filtered(lambda line: line.account_id == self.extra_receivable_account_1) + res = receivable_lines2.reconcile() + + self.assertFullReconcile(res['full_reconcile'], receivable_lines + receivable_lines2) + + exchange_diff = res['full_reconcile'].exchange_move_id + exchange_diff_lines = exchange_diff.line_ids\ + .filtered(lambda line: line.account_id == self.cash_basis_transfer_account)\ + .sorted(lambda line: (line.account_id, line.debit, line.credit)) + + self.assertRecordValues(exchange_diff_lines, [ + { + 'debit': 0.0, + 'credit': 16.67, + 'amount_currency': 0.0, + 'currency_id': currency_id, + 'account_id': self.cash_basis_transfer_account.id, + }, + ]) + + self.assertAmountsGroupByAccount([ + # Account Balance Amount Currency + (self.cash_basis_transfer_account, 0.0, 0.0), + (self.tax_account_1, -33.33, -100.0), + ]) + + def test_reconcile_cash_basis_exchange_difference_transfer_account_check_entries_2(self): + ''' Test the generation of the exchange difference for a tax cash basis journal entry when the transfer + account is not a reconcile one. + ''' + currency_id = self.setup_multi_currency_data(default_values={ + 'name': 'bitcoin', + 'symbol': 'bc', + 'currency_unit_label': 'Bitcoin', + 'currency_subunit_label': 'Tiny bitcoin', + }, rate2016=0.5, rate2017=0.66666666666666)['currency'].id + + # Rate 2/1 in 2016. + caba_inv = self.env['account.move'].create({ + 'move_type': 'entry', + 'date': '2016-01-01', + 'line_ids': [ + # Base Tax line + (0, 0, { + 'debit': 0.0, + 'credit': 200.0, + 'amount_currency': -100.0, + 'currency_id': currency_id, + 'account_id': self.company_data['default_account_revenue'].id, + 'tax_ids': [(6, 0, self.cash_basis_tax_a_third_amount.ids)], + 'tax_exigible': False, + }), + + # Tax line + (0, 0, { + 'debit': 0.0, + 'credit': 20.0, + 'amount_currency': -10.0, + 'currency_id': currency_id, + 'account_id': self.cash_basis_transfer_account.id, + 'tax_repartition_line_id': self.cash_basis_tax_a_third_amount.invoice_repartition_line_ids.filtered(lambda line: line.repartition_type == 'tax').id, + 'tax_exigible': False, + }), + + # Receivable lines + (0, 0, { + 'debit': 220.0, + 'credit': 0.0, + 'amount_currency': 110.0, + 'currency_id': currency_id, + 'account_id': self.extra_receivable_account_1.id, + }), + ] + }) + caba_inv.action_post() + + # Rate 3/2 in 2017. Full payment of 110 in foreign currency + pmt_wizard = self.env['account.payment.register'].with_context(active_model='account.move', active_ids=caba_inv.ids).create({ + 'payment_date': '2017-01-01', + 'journal_id': self.company_data['default_journal_bank'].id, + 'payment_method_id': self.env.ref('account.account_payment_method_manual_in').id, + }) + pmt_wizard._create_payments() + partial_rec = caba_inv.mapped('line_ids.matched_credit_ids') + caba_move = self.env['account.move'].search([('tax_cash_basis_rec_id', 'in', partial_rec.ids)]) + + self.assertRecordValues(caba_move.line_ids, [ + {'account_id': self.cash_basis_base_account.id, 'debit': 150.0, 'credit': 0.0, 'amount_currency': 100.0, 'tax_ids': [], 'tax_line_id': False}, + {'account_id': self.cash_basis_base_account.id, 'debit': 0.0, 'credit': 150.0, 'amount_currency': -100.0, 'tax_ids': self.cash_basis_tax_a_third_amount.ids, 'tax_line_id': False}, + {'account_id': self.cash_basis_transfer_account.id, 'debit': 15.0, 'credit': 0.0, 'amount_currency': 10.0, 'tax_ids': [], 'tax_line_id': False}, + {'account_id': self.tax_account_1.id, 'debit': 0.0, 'credit': 15.0, 'amount_currency': -10.0, 'tax_ids': [], 'tax_line_id': self.cash_basis_tax_a_third_amount.id}, + ]) + + receivable_line = caba_inv.line_ids.filtered(lambda x: x.account_id.internal_type == 'receivable') + self.assertTrue(receivable_line.full_reconcile_id, "Invoice should be fully paid") + + exchange_move = receivable_line.full_reconcile_id.exchange_move_id + self.assertTrue(exchange_move, "There should be an exchange difference move created") + self.assertRecordValues(exchange_move.line_ids, [ + {'account_id': receivable_line.account_id.id, 'debit': 0.0, 'credit': 55.0, 'amount_currency': 0.0, 'tax_ids': [], 'tax_line_id': False}, + {'account_id': caba_move.company_id.expense_currency_exchange_account_id.id, 'debit': 55.0, 'credit': 0.0, 'amount_currency': 0.0, 'tax_ids': [], 'tax_line_id': False}, + {'account_id': self.cash_basis_base_account.id, 'debit': 0.0, 'credit': 50.0, 'amount_currency': 0.0, 'tax_ids': self.cash_basis_tax_a_third_amount.ids, 'tax_line_id': False}, + {'account_id': self.cash_basis_base_account.id, 'debit': 50.0, 'credit': 0.0, 'amount_currency': 0.0, 'tax_ids': [], 'tax_line_id': False}, + {'account_id': self.tax_account_1.id, 'debit': 0.0, 'credit': 5.0, 'amount_currency': 0.0, 'tax_ids': [], 'tax_line_id': self.cash_basis_tax_a_third_amount.id}, + {'account_id': self.cash_basis_transfer_account.id, 'debit': 5.0, 'credit': 0.0, 'amount_currency': 0.0, 'tax_ids': [], 'tax_line_id': False}, + ]) + + self.assertAmountsGroupByAccount([ + # Account Balance Amount Currency + (self.cash_basis_transfer_account, 0.0, 0.0), + (self.tax_account_1, -20.0, -10.0), + ]) + + def test_reconcile_cash_basis_exchange_difference_transfer_account_check_entries_3(self): + ''' Test the generation of the exchange difference for a tax cash basis journal entry when the transfer + account is not a reconcile one. + ''' + currency_id = self.setup_multi_currency_data(default_values={ + 'name': 'bitcoin', + 'symbol': 'bc', + 'currency_unit_label': 'Bitcoin', + 'currency_subunit_label': 'Tiny bitcoin', + 'rounding': 0.01, + }, rate2016=0.5, rate2017=0.66666666666666)['currency'].id + + # Rate 2/1 in 2016. + caba_inv = self.env['account.move'].create({ + 'move_type': 'entry', + 'date': '2016-01-01', + 'line_ids': [ + # Base Tax line + (0, 0, { + 'debit': 0.0, + 'credit': 200.0, + 'amount_currency': -100.0, + 'currency_id': currency_id, + 'account_id': self.company_data['default_account_revenue'].id, + 'tax_ids': [(6, 0, self.cash_basis_tax_a_third_amount.ids)], + 'tax_exigible': False, + }), + + # Tax line + (0, 0, { + 'debit': 0.0, + 'credit': 20.0, + 'amount_currency': -10.0, + 'currency_id': currency_id, + 'account_id': self.cash_basis_transfer_account.id, + 'tax_repartition_line_id': self.cash_basis_tax_a_third_amount.invoice_repartition_line_ids.filtered(lambda line: line.repartition_type == 'tax').id, + 'tax_exigible': False, + }), + + # Receivable lines + (0, 0, { + 'debit': 220.0, + 'credit': 0.0, + 'amount_currency': 110.0, + 'currency_id': currency_id, + 'account_id': self.extra_receivable_account_1.id, + }), + ] + }) + caba_inv.action_post() + + # Rate 3/2 in 2017. Full payment of 220 in company currency + pmt_wizard = self.env['account.payment.register'].with_context(active_model='account.move', active_ids=caba_inv.ids).create({ + 'payment_date': '2017-01-01', + 'journal_id': self.company_data['default_journal_bank'].id, + 'payment_method_id': self.env.ref('account.account_payment_method_manual_in').id, + 'currency_id': self.company_data['currency'].id, + 'amount': 220.0, + }) + pmt_wizard._create_payments() + + caba_move = self.env['account.move'].search([('tax_cash_basis_rec_id', 'in', caba_inv.line_ids.matched_credit_ids.ids)]) + self.assertRecordValues(caba_move.line_ids, [ + {'account_id': self.cash_basis_base_account.id, 'debit': 200.01, 'credit': 0.0, 'amount_currency': 133.34, 'tax_ids': [], 'tax_line_id': False}, + {'account_id': self.cash_basis_base_account.id, 'debit': 0.0, 'credit': 200.01, 'amount_currency': -133.34, 'tax_ids': self.cash_basis_tax_a_third_amount.ids, 'tax_line_id': False}, + {'account_id': self.cash_basis_transfer_account.id, 'debit': 20.0, 'credit': 0.0, 'amount_currency': 13.33, 'tax_ids': [], 'tax_line_id': False}, + {'account_id': self.tax_account_1.id, 'debit': 0.0, 'credit': 20.0, 'amount_currency': -13.33, 'tax_ids': [], 'tax_line_id': self.cash_basis_tax_a_third_amount.id}, + ]) + + receivable_line = caba_inv.line_ids.filtered(lambda x: x.account_id.internal_type == 'receivable') + self.assertTrue(receivable_line.full_reconcile_id, "Invoice should be fully paid") + + exchange_move = receivable_line.full_reconcile_id.exchange_move_id + self.assertRecordValues(exchange_move.line_ids, [ + {'account_id': self.extra_receivable_account_1.id, 'debit': 0.0, 'credit': 0.0, 'amount_currency': 36.67, 'tax_ids': [], 'tax_line_id': False}, + {'account_id': caba_move.company_id.income_currency_exchange_account_id.id, 'debit': 0.0, 'credit': 0.0, 'amount_currency': -36.67, 'tax_ids': [], 'tax_line_id': False}, + {'account_id': self.cash_basis_base_account.id, 'debit': 0.01, 'credit': 0.0, 'amount_currency': 0.0, 'tax_ids': self.cash_basis_tax_a_third_amount.ids, 'tax_line_id': False}, + {'account_id': self.cash_basis_base_account.id, 'debit': 0.0, 'credit': 0.01, 'amount_currency': 0.0, 'tax_ids': [], 'tax_line_id': False}, + ]) + + self.assertAmountsGroupByAccount([ + # Account Balance Amount Currency + (self.cash_basis_transfer_account, 0.0, 3.33), + (self.tax_account_1, -20.0, -13.33), + ]) + + def test_reconcile_cash_basis_revert(self): + ''' Ensure the cash basis journal entry can be reverted. ''' + self.cash_basis_transfer_account.reconcile = True + + invoice_move = self.env['account.move'].create({ + 'move_type': 'entry', + 'date': '2016-01-01', + 'line_ids': [ + # Base Tax line + (0, 0, { + 'debit': 0.0, + 'credit': 100.0, + 'account_id': self.company_data['default_account_revenue'].id, + 'tax_ids': [(6, 0, self.cash_basis_tax_a_third_amount.ids)], + 'tax_exigible': False, + }), + + # Tax line + (0, 0, { + 'debit': 0.0, + 'credit': 33.33, + 'account_id': self.cash_basis_transfer_account.id, + 'tax_repartition_line_id': self.cash_basis_tax_a_third_amount.invoice_repartition_line_ids.filtered(lambda line: line.repartition_type == 'tax').id, + 'tax_exigible': False, + }), + + # Receivable line + (0, 0, { + 'debit': 133.33, + 'credit': 0.0, + 'account_id': self.extra_receivable_account_1.id, + }), + ] + }) + + payment_move = self.env['account.move'].create({ + 'move_type': 'entry', + 'date': '2016-01-01', + 'line_ids': [ + (0, 0, {'debit': 0.0, 'credit': 133.33, 'account_id': self.extra_receivable_account_1.id}), + (0, 0, {'debit': 133.33, 'credit': 0.0, 'account_id': self.company_data['default_account_revenue'].id}), + ] + }) + + (invoice_move + payment_move).action_post() + + receivable_lines = (invoice_move + payment_move).line_ids\ + .filtered(lambda line: line.account_id == self.extra_receivable_account_1) + res = receivable_lines.reconcile() + + # == Check reconciliation of invoice with payment == + + self.assertFullReconcile(res['full_reconcile'], receivable_lines) + self.assertEqual(len(res.get('tax_cash_basis_moves', [])), 1) + + # == Check the reconciliation of invoice with tax cash basis journal entry. + # /!\ We make the assumption the tax cash basis journal entry is well created. + + tax_cash_basis_move = res['tax_cash_basis_moves'] + + taxes_lines = (invoice_move.line_ids + tax_cash_basis_move.line_ids.filtered('debit'))\ + .filtered(lambda line: line.account_id == self.cash_basis_transfer_account) + taxes_full_reconcile = taxes_lines.matched_debit_ids.full_reconcile_id + + self.assertTrue(taxes_full_reconcile) + self.assertFullReconcile(taxes_full_reconcile, taxes_lines) + + # == Check the reconciliation after the reverse == + + tax_cash_basis_move_reverse = tax_cash_basis_move._reverse_moves(cancel=True) + + self.assertFullReconcile(res['full_reconcile'], receivable_lines) + + # == Check the reconciliation of the tax cash basis journal entry with its reverse == + + reversed_taxes_lines = (tax_cash_basis_move + tax_cash_basis_move_reverse).line_ids\ + .filtered(lambda line: line.account_id == self.cash_basis_transfer_account) + + reversed_taxes_full_reconcile = reversed_taxes_lines.matched_debit_ids.full_reconcile_id + + self.assertTrue(reversed_taxes_full_reconcile) + self.assertFullReconcile(reversed_taxes_full_reconcile, reversed_taxes_lines) + + def test_reconcile_cash_basis_tax_grid_refund(self): + invoice_move = self.env['account.move'].create({ + 'move_type': 'entry', + 'date': '2016-01-01', + 'line_ids': [ + # Base Tax line + (0, 0, { + 'debit': 0.0, + 'credit': 100.0, + 'account_id': self.company_data['default_account_revenue'].id, + 'tax_ids': [(6, 0, self.cash_basis_tax_a_third_amount.ids)], + 'tax_tag_ids': [(6, 0, self.tax_tags[0].ids)], + 'tax_exigible': False, + }), + + # Tax line + (0, 0, { + 'debit': 0.0, + 'credit': 33.33, + 'account_id': self.cash_basis_transfer_account.id, + 'tax_repartition_line_id': self.cash_basis_tax_a_third_amount.invoice_repartition_line_ids.filtered(lambda line: line.repartition_type == 'tax').id, + 'tax_tag_ids': [(6, 0, self.tax_tags[1].ids)], + 'tax_exigible': False, + }), + + # Receivable line + (0, 0, { + 'debit': 133.33, + 'credit': 0.0, + 'account_id': self.extra_receivable_account_1.id, + }), + ] + }) + + refund_move = self.env['account.move'].create({ + 'move_type': 'out_refund', + 'partner_id': self.partner_a.id, + 'invoice_date': '2016-01-01', + 'date': '2016-01-01', + 'line_ids': [ + # Base Tax line + (0, 0, { + 'debit': 100.0, + 'credit': 0.0, + 'account_id': self.company_data['default_account_revenue'].id, + 'tax_ids': [(6, 0, self.cash_basis_tax_a_third_amount.ids)], + 'tax_tag_ids': [(6, 0, self.tax_tags[2].ids)], + 'tax_exigible': False, + }), + + # Tax line + (0, 0, { + 'debit': 33.33, + 'credit': 0.0, + 'account_id': self.cash_basis_transfer_account.id, + 'tax_repartition_line_id': self.cash_basis_tax_a_third_amount.invoice_repartition_line_ids.filtered(lambda line: line.repartition_type == 'tax').id, + 'tax_tag_ids': [(6, 0, self.tax_tags[3].ids)], + 'tax_exigible': False, + }), + + # Receivable line + (0, 0, { + 'debit': 0.0, + 'credit': 133.33, + 'account_id': self.extra_receivable_account_1.id, + }), + ] + }) + + (invoice_move + refund_move).action_post() + + receivable_lines = (invoice_move + refund_move).line_ids\ + .filtered(lambda line: line.account_id == self.extra_receivable_account_1) + res = receivable_lines.reconcile() + + self.assertFullReconcile(res['full_reconcile'], receivable_lines) + self.assertEqual(len(res.get('tax_cash_basis_moves', [])), 2) + + tax_cash_basis_moves = res['tax_cash_basis_moves'].sorted(lambda move: move.tax_cash_basis_move_id.id) + + # Invoice: + cb_lines = tax_cash_basis_moves[0].line_ids.sorted(lambda line: (-abs(line.balance), -line.debit, line.account_id)) + self.assertRecordValues(cb_lines, [ + # Base amount: + {'debit': 100.0, 'credit': 0.0, 'tax_tag_ids': [], 'account_id': self.cash_basis_base_account.id}, + {'debit': 0.0, 'credit': 100.0, 'tax_tag_ids': self.tax_tags[0].ids, 'account_id': self.cash_basis_base_account.id}, + # tax: + {'debit': 33.33, 'credit': 0.0, 'tax_tag_ids': [], 'account_id': self.cash_basis_transfer_account.id}, + {'debit': 0.0, 'credit': 33.33, 'tax_tag_ids': self.tax_tags[1].ids, 'account_id': self.tax_account_1.id}, + ]) + + # Refund: + cb_lines = tax_cash_basis_moves[1].line_ids.sorted(lambda line: (-abs(line.balance), -line.debit, line.account_id)) + self.assertRecordValues(cb_lines, [ + # Base amount: + {'debit': 100.0, 'credit': 0.0, 'tax_tag_ids': self.tax_tags[2].ids, 'account_id': self.cash_basis_base_account.id}, + {'debit': 0.0, 'credit': 100.0, 'tax_tag_ids': [], 'account_id': self.cash_basis_base_account.id}, + # tax: + {'debit': 33.33, 'credit': 0.0, 'tax_tag_ids': self.tax_tags[3].ids, 'account_id': self.tax_account_1.id}, + {'debit': 0.0, 'credit': 33.33, 'tax_tag_ids': [], 'account_id': self.cash_basis_transfer_account.id}, + ]) + + self.assertTaxGridAmounts([ + # Tag Balance + (self.tax_tags[0], -100.0), + (self.tax_tags[1], -33.33), + (self.tax_tags[2], 100.0), + (self.tax_tags[3], 33.33), + ]) + + def test_reconcile_cash_basis_tax_grid_multi_taxes(self): + ''' Test the tax grid when reconciling an invoice with multiple taxes/tax repartition. ''' + base_taxes = self.cash_basis_tax_a_third_amount + self.cash_basis_tax_tiny_amount + base_tags = self.tax_tags[0] + self.tax_tags[4] + + # An invoice with 2 taxes: + invoice_move = self.env['account.move'].create({ + 'move_type': 'entry', + 'date': '2016-01-01', + 'line_ids': [ + # Base Tax line + (0, 0, { + 'debit': 0.0, + 'credit': 100.0, + 'account_id': self.company_data['default_account_revenue'].id, + 'tax_ids': [(6, 0, base_taxes.ids)], + 'tax_tag_ids': [(6, 0, base_tags.ids)], + 'tax_exigible': False, + }), + + # Tax lines + (0, 0, { + 'debit': 0.0, + 'credit': 33.33, + 'account_id': self.cash_basis_transfer_account.id, + 'tax_repartition_line_id': self.cash_basis_tax_a_third_amount.invoice_repartition_line_ids.filtered(lambda line: line.repartition_type == 'tax').id, + 'tax_tag_ids': [(6, 0, self.tax_tags[1].ids)], + 'tax_exigible': False, + }), + (0, 0, { + 'debit': 0.0, + 'credit': 0.01, + 'account_id': self.cash_basis_transfer_account.id, + 'tax_repartition_line_id': self.cash_basis_tax_tiny_amount.invoice_repartition_line_ids.filtered(lambda line: line.repartition_type == 'tax').id, + 'tax_tag_ids': [(6, 0, self.tax_tags[5].ids)], + 'tax_exigible': False, + }), + + # Receivable lines + (0, 0, { + 'debit': 133.34, + 'credit': 0.0, + 'account_id': self.extra_receivable_account_1.id, + }), + ] + }) + + # A payment paying the full invoice amount. + payment_move = self.env['account.move'].create({ + 'move_type': 'entry', + 'date': '2017-01-01', + 'line_ids': [ + (0, 0, {'debit': 0.0, 'credit': 133.34, 'account_id': self.extra_receivable_account_1.id}), + (0, 0, {'debit': 133.34, 'credit': 0.0, 'account_id': self.company_data['default_account_revenue'].id}), + ] + }) + + (invoice_move + payment_move).action_post() + + receivable_lines = (invoice_move + payment_move).line_ids\ + .filtered(lambda line: line.account_id == self.extra_receivable_account_1) + res = receivable_lines.reconcile() + + self.assertFullReconcile(res['full_reconcile'], receivable_lines) + self.assertEqual(len(res.get('tax_cash_basis_moves', [])), 1) + + self.assertRecordValues(res['tax_cash_basis_moves'].line_ids, [ + # Base amount x 2 because there is two taxes: + {'debit': 100.0, 'credit': 0.0, 'tax_ids': [], 'tax_tag_ids': [], 'account_id': self.cash_basis_base_account.id}, + {'debit': 0.0, 'credit': 100.0, 'tax_ids': base_taxes.ids, 'tax_tag_ids': base_tags.ids, 'account_id': self.cash_basis_base_account.id}, + # tax_1: + {'debit': 33.33, 'credit': 0.0, 'tax_ids': [], 'tax_tag_ids': [], 'account_id': self.cash_basis_transfer_account.id}, + {'debit': 0.0, 'credit': 33.33, 'tax_ids': [], 'tax_tag_ids': self.tax_tags[1].ids, 'account_id': self.tax_account_1.id}, + # tax_2: + {'debit': 0.01, 'credit': 0.0, 'tax_ids': [], 'tax_tag_ids': [], 'account_id': self.cash_basis_transfer_account.id}, + {'debit': 0.0, 'credit': 0.01, 'tax_ids': [], 'tax_tag_ids': self.tax_tags[5].ids, 'account_id': self.tax_account_2.id}, + ]) + + self.assertTaxGridAmounts([ + # Tag Balance + (self.tax_tags[0], -100.0), + (self.tax_tags[1], -33.33), + (self.tax_tags[4], -100.0), + (self.tax_tags[5], -0.01), + ]) + + def test_caba_mix_reconciliation(self): + """ Test the reconciliation of tax lines (when using a reconcilable tax account) + for cases mixing taxes exigible on payment and on invoices. + """ + + # Make the tax account reconcilable + self.tax_account_1.reconcile = True + + # Create a tax using the same accounts as the CABA one + non_caba_tax = self.env['account.tax'].create({ + 'name': 'tax 20%', + 'type_tax_use': 'purchase', + 'company_id': self.company_data['company'].id, + 'amount': 20, + 'tax_exigibility': 'on_invoice', + 'invoice_repartition_line_ids': [ + (0,0, { + 'factor_percent': 100, + 'repartition_type': 'base', + }), + + (0,0, { + 'factor_percent': 100, + 'repartition_type': 'tax', + 'account_id': self.tax_account_1.id, + }), + ], + 'refund_repartition_line_ids': [ + (0,0, { + 'factor_percent': 100, + 'repartition_type': 'base', + }), + + (0,0, { + 'factor_percent': 100, + 'repartition_type': 'tax', + 'account_id': self.tax_account_1.id, + }), + ], + }) + + # Create an invoice with a non-CABA tax + non_caba_inv = self.init_invoice('in_invoice', amounts=[1000], post=True, taxes=non_caba_tax) + + # Create an invoice with a CABA tax using the same tax account and pay it + caba_inv = self.init_invoice('in_invoice', amounts=[300], post=True, taxes=self.cash_basis_tax_a_third_amount) + + pmt_wizard = self.env['account.payment.register'].with_context(active_model='account.move', active_ids=caba_inv.ids).create({ + 'payment_date': caba_inv.date, + 'journal_id': self.company_data['default_journal_bank'].id, + 'payment_method_id': self.env.ref('account.account_payment_method_manual_in').id, + }) + pmt_wizard._create_payments() + + partial_rec = caba_inv.mapped('line_ids.matched_debit_ids') + caba_move = self.env['account.move'].search([('tax_cash_basis_rec_id', '=', partial_rec.id)]) + + # Create a misc operation with a line on the tax account, for full reconcile of those tax lines + misc_move = self.env['account.move'].create({ + 'name': "Misc move", + 'journal_id': self.company_data['default_journal_misc'].id, + 'line_ids': [ + (0, 0, { + 'name': 'line 1', + 'account_id': self.tax_account_1.id, + 'credit': 300, + }), + (0, 0, { + 'name': 'line 2', + 'account_id': self.company_data['default_account_expense'].id, # Whatever the account here + 'debit': 300, + }) + ], + }) + + misc_move.action_post() + + lines_to_reconcile = (misc_move + caba_move + non_caba_inv).mapped('line_ids').filtered(lambda x: x.account_id == self.tax_account_1) + lines_to_reconcile.reconcile() + + # Check full reconciliation + self.assertTrue(all(line.full_reconcile_id for line in lines_to_reconcile), "All tax lines should be fully reconciled") + + def test_caba_double_tax(self): + """ Test the CABA entries generated from an invoice with almost + equal lines, different only on analytic accounting + """ + # Make the tax account reconcilable + self.tax_account_1.reconcile = True + + # Create an invoice with a CABA tax using 'Include in analytic cost' + move_form = Form(self.env['account.move'].with_context(default_move_type='in_invoice', account_predictive_bills_disable_prediction=True)) + move_form.invoice_date = fields.Date.from_string('2019-01-01') + move_form.partner_id = self.partner_a + self.cash_basis_tax_a_third_amount.analytic = True + test_analytic_account = self.env['account.analytic.account'].create({'name': 'test_analytic_account'}) + + tax = self.cash_basis_tax_a_third_amount + + # line with analytic account, will generate 2 lines in CABA move + with move_form.invoice_line_ids.new() as line_form: + line_form.name = "test line with analytic account" + line_form.product_id = self.product_a + line_form.tax_ids.clear() + line_form.tax_ids.add(tax) + line_form.analytic_account_id = test_analytic_account + line_form.price_unit = 100 + + # line with analytic account, will generate other 2 lines in CABA move + # even if the tax is the same + with move_form.invoice_line_ids.new() as line_form: + line_form.name = "test line" + line_form.product_id = self.product_a + line_form.tax_ids.clear() + line_form.tax_ids.add(tax) + line_form.price_unit = 100 + + rslt = move_form.save() + rslt.action_post() + + pmt_wizard = self.env['account.payment.register'].with_context(active_model='account.move', active_ids=rslt.ids).create({ + 'amount': rslt.amount_total, + 'payment_date': rslt.date, + 'journal_id': self.company_data['default_journal_bank'].id, + 'payment_method_id': self.env.ref('account.account_payment_method_manual_in').id, + }) + pmt_wizard._create_payments() + + partial_rec = rslt.mapped('line_ids.matched_debit_ids') + caba_move = self.env['account.move'].search([('tax_cash_basis_rec_id', '=', partial_rec.id)]) + self.assertEqual(len(caba_move.line_ids), 4, "All lines should be there") + self.assertEqual(caba_move.line_ids.filtered(lambda x: x.tax_line_id).balance, 66.66, "Tax amount should take into account both lines") + + def test_caba_double_tax_negative_line(self): + """ Tests making a cash basis invoice with 2 lines using the same tax: a positive and a negative one. + """ + invoice = self.init_invoice('in_invoice', amounts=[300, -60], post=True, taxes=self.cash_basis_tax_a_third_amount) + + pmt_wizard = self.env['account.payment.register'].with_context(active_model='account.move', active_ids=invoice.ids).create({ + 'amount': 320, + 'payment_date': invoice.date, + 'journal_id': self.company_data['default_journal_bank'].id, + 'payment_method_id': self.env.ref('account.account_payment_method_manual_in').id, + }) + + pmt_wizard._create_payments() + + partial_rec = invoice.mapped('line_ids.matched_debit_ids') + caba_move = self.env['account.move'].search([('tax_cash_basis_rec_id', '=', partial_rec.id)]) + + self.assertRecordValues(caba_move.line_ids.sorted(lambda line: (-abs(line.balance), -line.debit, line.account_id)), [ + # Base amount: + {'debit': 240.0, 'credit': 0.0, 'tax_tag_ids': self.tax_tags[0].ids, 'account_id': self.cash_basis_base_account.id}, + {'debit': 0.0, 'credit': 240.0, 'tax_tag_ids': [], 'account_id': self.cash_basis_base_account.id}, + # tax: + {'debit': 80.0, 'credit': 0.0, 'tax_tag_ids': self.tax_tags[1].ids, 'account_id': self.tax_account_1.id}, + {'debit': 0.0, 'credit': 80.0, 'tax_tag_ids': [], 'account_id': self.cash_basis_transfer_account.id}, + ]) + + def test_caba_dest_acc_reconciliation_partial_pmt(self): + """ Test the reconciliation of tax lines (when using a reconcilable tax account) + for partially paid invoices with cash basis taxes. + This test is especially useful to check the implementation of the use case tested by + test_reconciliation_cash_basis_foreign_currency_low_values does not have unwanted side effects. + """ + + # Make the tax account reconcilable + self.tax_account_1.reconcile = True + + # Create an invoice with a CABA tax using the same tax account and pay half of it + caba_inv = self.init_invoice('in_invoice', amounts=[900], post=True, taxes=self.cash_basis_tax_a_third_amount) + + pmt_wizard = self.env['account.payment.register'].with_context(active_model='account.move', active_ids=caba_inv.ids).create({ + 'amount': 600, + 'payment_date': caba_inv.date, + 'journal_id': self.company_data['default_journal_bank'].id, + 'payment_method_id': self.env.ref('account.account_payment_method_manual_in').id, + }) + pmt_wizard._create_payments() + + partial_rec = caba_inv.mapped('line_ids.matched_debit_ids') + caba_move = self.env['account.move'].search([('tax_cash_basis_rec_id', '=', partial_rec.id)]) + + # Create a misc operation with a line on the tax account, for full reconcile with the tax line + misc_move = self.env['account.move'].create({ + 'name': "Misc move", + 'journal_id': self.company_data['default_journal_misc'].id, + 'line_ids': [ + (0, 0, { + 'name': 'line 1', + 'account_id': self.tax_account_1.id, + 'credit': 150, + }), + (0, 0, { + 'name': 'line 2', + 'account_id': self.company_data['default_account_expense'].id, # Whatever the account here + 'debit': 150, + }) + ], + }) + + misc_move.action_post() + + lines_to_reconcile = (misc_move + caba_move).mapped('line_ids').filtered(lambda x: x.account_id == self.tax_account_1) + lines_to_reconcile.reconcile() + + # Check full reconciliation + self.assertTrue(all(line.full_reconcile_id for line in lines_to_reconcile), "All tax lines should be fully reconciled") + + def test_caba_undo_reconciliation(self): + ''' Make sure there is no traceback like "Record has already been deleted" during the deletion of partials. ''' + self.cash_basis_transfer_account.reconcile = True + + bill = self.env['account.move'].create({ + 'move_type': 'in_invoice', + 'partner_id': self.partner_a.id, + 'invoice_date': '2019-01-01', + 'date': '2019-01-01', + 'invoice_line_ids': [(0, 0, { + 'name': 'line', + 'account_id': self.company_data['default_account_expense'].id, + 'price_unit': 1000.0, + 'tax_ids': [(6, 0, self.cash_basis_tax_a_third_amount.ids)], + })], + }) + bill.action_post() + + # Register a payment creating the CABA journal entry on the fly and reconcile it with the tax line. + self.env['account.payment.register']\ + .with_context(active_ids=bill.ids, active_model='account.move')\ + .create({})\ + ._create_payments() + + bill.button_draft() |
