# -*- coding: utf-8 -*- # pylint: disable=C0326 from contextlib import contextmanager from odoo.addons.account.tests.common import AccountTestInvoicingCommon from odoo.tests import tagged from odoo.tests.common import Form from odoo import fields, Command @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.receivable_account = cls.company_data['default_account_receivable'] cls.payable_account = cls.company_data['default_account_payable'] cls.expense_account = cls.company_data['default_account_expense'] cls.revenue_account = cls.company_data['default_account_revenue'] cls.extra_receivable_account_1 = cls.copy_account(cls.receivable_account) cls.extra_receivable_account_2 = cls.copy_account(cls.receivable_account) 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']) cls.exch_income_account = cls.env.company.income_currency_exchange_account_id cls.exch_expense_account = cls.env.company.expense_currency_exchange_account_id # ==== 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) cls.currency_data_3 = cls.setup_multi_currency_data(default_values={ 'name': 'Sand', 'symbol': 'S', 'currency_unit_label': 'Sand', 'currency_subunit_label': 'Sand', }, rate2016=0.0001, rate2017=0.00001) # ==== Cash Basis Taxes setup ==== cls.cash_basis_base_account = cls.env['account.account'].create({ 'code': 'cash.basis.base.account', 'name': 'cash_basis_base_account', 'account_type': 'income', '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', 'account_type': 'income', 'reconcile': True, 'company_id': cls.company_data['company'].id, }) cls.tax_account_1 = cls.env['account.account'].create({ 'code': 'tax.account.1', 'name': 'tax_account_1', 'account_type': 'income', 'company_id': cls.company_data['company'].id, }) cls.tax_account_2 = cls.env['account.account'].create({ 'code': 'tax.account.2', 'name': 'tax_account_2', 'account_type': 'income', '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.company_data['company'].account_fiscal_country_id.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, { 'repartition_type': 'base', 'tag_ids': [(6, 0, cls.tax_tags[0].ids)], }), (0, 0, { '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, { 'repartition_type': 'base', 'tag_ids': [(6, 0, cls.tax_tags[2].ids)], }), (0, 0, { '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, { 'repartition_type': 'base', 'tag_ids': [(6, 0, cls.tax_tags[4].ids)], }), (0, 0, { '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, { 'repartition_type': 'base', 'tag_ids': [(6, 0, cls.tax_tags[6].ids)], }), (0, 0, { '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 assertFullReconcileAccount(self, full_reconcile, account): self.assertFullReconcile(full_reconcile, self.env['account.move.line'].search([('account_id', '=', account.id)])) 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, ) # ------------------------------------------------------------------------- # Test creation of account.partial.reconcile/account.full.reconcile # during the reconciliation. # ------------------------------------------------------------------------- def _get_partials(self, amls): return (amls.matched_debit_ids | amls.matched_credit_ids).sorted() def _get_caba_moves(self, moves): return moves.search([('tax_cash_basis_origin_move_id', 'in', moves.ids)]) def test_full_reconcile_bunch_lines(self): """ Test the reconciliation with multiple lines at the same time and ensure the result is always a full reconcile whatever the number of involved currencies. """ comp_curr = self.company_data['currency'] foreign_curr1 = self.currency_data['currency'] foreign_curr2 = self.currency_data_2['currency'] line_1 = self.create_line_for_reconciliation(1000.0, 1000.0, comp_curr, '2016-01-01') line_2 = self.create_line_for_reconciliation(-300.0, -300.0, comp_curr, '2016-01-01') line_3 = self.create_line_for_reconciliation(-400.0, -400.0, comp_curr, '2016-01-01') line_4 = self.create_line_for_reconciliation(-500.0, -500.0, comp_curr, '2016-01-01') line_5 = self.create_line_for_reconciliation(200.0, 200.0, comp_curr, '2016-01-01') comp_curr_batch = line_1 + line_2 + line_3 + line_4 + line_5 line_1 = self.create_line_for_reconciliation(1200.0, 3600.0, foreign_curr1, '2016-01-01') line_2 = self.create_line_for_reconciliation(-240.0, -480.0, foreign_curr1, '2017-01-01') line_3 = self.create_line_for_reconciliation(-720.0, -1440.0, foreign_curr1, '2017-01-01') line_4 = self.create_line_for_reconciliation(-1020.0, -2040.0, foreign_curr1, '2017-01-01') line_5 = self.create_line_for_reconciliation(120.0, 360.0, foreign_curr1, '2016-01-01') same_curr_batch = line_1 + line_2 + line_3 + line_4 + line_5 line_1 = self.create_line_for_reconciliation(1200.0, 3600.0, foreign_curr1, '2016-01-01') line_2 = self.create_line_for_reconciliation(780.0, 2340.0, foreign_curr1, '2016-01-01') line_3 = self.create_line_for_reconciliation(-240.0, -960.0, foreign_curr2, '2017-01-01') line_4 = self.create_line_for_reconciliation(-720.0, -2880.0, foreign_curr2, '2017-01-01') line_5 = self.create_line_for_reconciliation(-1020.0, -4080.0, foreign_curr2, '2017-01-01') multi_curr_batch = line_1 + line_2 + line_3 + line_4 + line_5 for batch, sub_test_name in ( (comp_curr_batch, "Batch in company currency"), (same_curr_batch, "Batch in foreign currency"), (multi_curr_batch, "Batch with multiple currencies"), (comp_curr_batch + same_curr_batch + multi_curr_batch, "All batches"), ): with self.subTest(sub_test_name=sub_test_name): batch.reconcile() self.assertTrue(batch.full_reconcile_id) self.assertRecordValues( batch, [{'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}] * len(batch), ) batch.remove_move_reconcile() def test_reconcile_lines_multiple_in_foreign_currency(self): currency = self.currency_data['currency'] rates = (1/3, 1/2) for rate1 in rates: for rate2 in rates: for rate3 in rates: with self.subTest(rate1=rate1, rate2=rate2, rate3=rate3): line_1 = self.create_line_for_reconciliation(120.0 * rate1, 120.0, currency, '2017-01-01') line_2 = self.create_line_for_reconciliation(120.0 * rate2, 120.0, currency, '2017-01-01') line_3 = self.create_line_for_reconciliation(-240.0 * rate3, -240.0, currency, '2017-01-01') (line_1 + line_2 + line_3).reconcile() self.assertTrue(line_1.full_reconcile_id) def test_reverse_exchange_difference(self): """ Test the reversing of the exchange difference to ensure there is no unexpected recursion inside the 'reconcile' method. """ comp_curr = self.company_data['currency'] foreign_curr = self.currency_data['currency'] for line1_vals, line2_vals in ( ((60.0, 120.0, foreign_curr, '2017-01-01'), (-40.0, -120.0, foreign_curr, '2016-01-01')), ((-60.0, -120.0, foreign_curr, '2017-01-01'), (40.0, 120.0, foreign_curr, '2016-01-01')), ((60.0, 60.0, comp_curr, '2017-01-01'), (-40.0, -120.0, foreign_curr, '2016-01-01')), ((-60.0, -60.0, comp_curr, '2017-01-01'), (40.0, 120.0, foreign_curr, '2016-01-01')), ): line1 = self.create_line_for_reconciliation(*line1_vals) line2 = self.create_line_for_reconciliation(*line2_vals) with self.subTest(line1_vals=line1_vals, line2_vals=line2_vals): (line1 + line2).reconcile() # Reconcile. # Don't check the result since this is already checked by another tests. partials = self._get_partials(line1 + line2) exchange_diff = partials.exchange_move_id self.assertTrue(exchange_diff) # Unreconcile. # A reversal is created to cancel the exchange difference journal entry. line1.remove_move_reconcile() reverse_exchange_diff_lines = exchange_diff.line_ids.matched_debit_ids.debit_move_id \ + exchange_diff.line_ids.matched_credit_ids.credit_move_id reverse_exchange_diff = reverse_exchange_diff_lines.move_id self.assertTrue(reverse_exchange_diff) self.assertRecordValues(exchange_diff.line_ids + reverse_exchange_diff.line_ids, [ {'reconciled': True}, {'reconciled': False}, {'reconciled': True}, {'reconciled': False}, ]) def test_invoice_draft_fully_paid_if_zero(self): """ Tests that Invoices with zero balance are marked as paid and reconciled """ zero_invoice = self.env['account.move'].create({ 'move_type': 'out_invoice', 'partner_id': self.partner_a.id, 'invoice_date': fields.Date.from_string('2019-01-01'), 'invoice_line_ids': [Command.create({'name': 'Nope', 'price_unit': 0})], }) payment_term_line = zero_invoice.line_ids.filtered(lambda l: l.display_type == 'payment_term') self.assertRecordValues(zero_invoice, [{ 'state': 'draft', 'payment_state': 'not_paid', 'amount_total': 0.0, }]) self.assertTrue(payment_term_line.reconciled) zero_invoice.action_post() self.assertRecordValues(zero_invoice, [{ 'state': 'posted', 'payment_state': 'paid', 'amount_total': 0.0, }]) self.assertTrue(payment_term_line.reconciled) def test_reconcile_lines_corner_case_1_zero_balance_same_foreign_currency(self): """ Test the reconciliation of lines having a zero balance in different currencies. In that case, the reconciliation should not be full until an additional move is added with the right foreign currency amount. """ currency = self.currency_data['currency'] line_1 = self.create_line_for_reconciliation(0.0, -0.02, currency, '2017-01-01') line_2 = self.create_line_for_reconciliation(0.0, 0.01, currency, '2017-01-01') (line_1 + line_2).reconcile() self.assertFalse(line_1.full_reconcile_id + line_2.full_reconcile_id, "The reconciliation should not be considered full, as 0.01 still remain open in foreign currency.") line_3 = self.create_line_for_reconciliation(0.0, 0.01, currency, '2017-01-01') (line_1 + line_3).reconcile() self.assertTrue(line_1.full_reconcile_id) self.assertEqual(line_1.full_reconcile_id, line_2.full_reconcile_id) self.assertEqual(line_2.full_reconcile_id, line_3.full_reconcile_id) def test_reconcile_lines_corner_case_1_zero_balance_different_foreign_currency(self): """ Test the reconciliation of lines having a zero balance in different currencies. In that case, we don't reconcile anything. """ currency_1 = self.currency_data['currency'] currency_2 = self.setup_multi_currency_data({ 'name': 'Bretonnian Ecu', 'symbol': '👑', 'currency_unit_label': 'Ecu', 'currency_subunit_label': 'Bretonnian Denier', })['currency'] line_1 = self.create_line_for_reconciliation(0.0, -0.01, currency_1, '2017-01-01') line_2 = self.create_line_for_reconciliation(0.0, 0.02, currency_2, '2016-01-01') amls = line_1 + line_2 amls.reconcile() partials = self._get_partials(amls) self.assertFalse(partials) def test_reconcile_lines_corner_case_2_zero_amount_currency_same_foreign_currency(self): """ Test a corner case when both lines have something to reconcile in company currency but nothing in foreign currency. It could be due to: - a bad usage of the `no_exchange_difference` context key - a partial reconciliation made before migrating to this version - some rounding error when dealing with currencies having != decimal places - strange journal items made by the user """ currency = self.currency_data['currency'] line_1 = self.create_line_for_reconciliation(-0.01, 0.0, currency, '2017-01-01') line_2 = self.create_line_for_reconciliation(0.02, 0.0, currency, '2016-01-01') amls = line_1 + line_2 amls.reconcile() partials = self._get_partials(amls) full_reconcile = amls.full_reconcile_id self.assertTrue(full_reconcile) self.assertRecordValues(partials, [ { 'amount': 0.01, 'debit_amount_currency': 0.0, 'credit_amount_currency': 0.0, 'debit_move_id': line_2.id, 'credit_move_id': line_1.id, }, { 'amount': 0.01, 'debit_amount_currency': 0.0, 'credit_amount_currency': 0.0, 'debit_move_id': line_2.id, 'credit_move_id': full_reconcile.exchange_move_id.line_ids[0].id, }, ]) self.assertRecordValues(full_reconcile.exchange_move_id, [{'date': fields.Date.from_string('2017-01-31')}]) self.assertRecordValues(full_reconcile.exchange_move_id.line_ids, [ { 'debit': 0.0, 'credit': 0.01, 'amount_currency': 0.0, 'currency_id': currency.id, 'account_id': line_1.account_id.id, }, { 'debit': 0.01, 'credit': 0.0, 'amount_currency': 0.0, 'currency_id': currency.id, 'account_id': self.exch_expense_account.id, }, ]) self.assertRecordValues(amls, [ {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, ]) def test_reconcile_lines_corner_case_3_zero_balance_one_foreign_currency(self): """ Test the special case when one line (credit) has a zero residual amount in company currency probably due to some rounding issues or accumulated rounding due to multiple reconciliations. This line is matched with another line (debit) using the company currency. """ comp_curr = self.company_data['currency'] foreign_curr = self.currency_data['currency'] line_1 = self.create_line_for_reconciliation(-0.01, -0.01, comp_curr, '2017-01-01') line_2 = self.create_line_for_reconciliation(0.0, 0.03, foreign_curr, '2016-01-01') amls = line_1 + line_2 amls.reconcile() partials = self._get_partials(amls) full_reconcile = amls.full_reconcile_id self.assertTrue(full_reconcile) self.assertRecordValues(partials, [ { 'amount': 0.0, 'debit_amount_currency': 0.02, 'credit_amount_currency': 0.0, 'debit_move_id': line_2.id, 'credit_move_id': line_1.id, }, { 'amount': 0.01, 'debit_amount_currency': 0.01, 'credit_amount_currency': 0.01, 'debit_move_id': partials.exchange_move_id.line_ids[0].id, 'credit_move_id': line_1.id, }, { 'amount': 0.0, 'debit_amount_currency': 0.01, 'credit_amount_currency': 0.01, 'debit_move_id': line_2.id, 'credit_move_id': full_reconcile.exchange_move_id.line_ids[0].id, }, ]) self.assertRecordValues(partials.exchange_move_id, [{'date': fields.Date.from_string('2017-01-31')}]) self.assertRecordValues(partials.exchange_move_id.line_ids, [ { 'debit': 0.01, 'credit': 0.0, 'amount_currency': 0.01, 'currency_id': comp_curr.id, 'account_id': line_1.account_id.id, }, { 'debit': 0.0, 'credit': 0.01, 'amount_currency': -0.01, 'currency_id': comp_curr.id, 'account_id': self.exch_income_account.id, }, ]) self.assertRecordValues(full_reconcile.exchange_move_id, [{'date': fields.Date.from_string('2017-01-31')}]) self.assertRecordValues(full_reconcile.exchange_move_id.line_ids, [ { 'debit': 0.0, 'credit': 0.0, 'amount_currency': -0.01, 'currency_id': foreign_curr.id, 'account_id': line_2.account_id.id, }, { 'debit': 0.0, 'credit': 0.0, 'amount_currency': 0.01, 'currency_id': foreign_curr.id, 'account_id': self.exch_expense_account.id, }, ]) self.assertRecordValues(amls, [ {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, ]) def test_reconcile_lines_corner_case_4_zero_amount_currency_multiple_currencies(self): foreign_curr1 = self.currency_data['currency'] foreign_curr2 = self.currency_data_2['currency'] line_1 = self.create_line_for_reconciliation(-0.01, 0.0, foreign_curr2, '2017-01-01') line_2 = self.create_line_for_reconciliation(0.01, 0.03, foreign_curr1, '2016-01-01') amls = line_1 + line_2 amls.reconcile() partials = self._get_partials(amls) self.assertRecordValues(partials, [{ 'amount': 0.01, 'debit_amount_currency': 0.03, 'credit_amount_currency': 0.0, 'debit_move_id': line_2.id, 'credit_move_id': line_1.id, 'exchange_move_id': None, }]) self.assertRecordValues(amls, [ {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, ]) def test_reconcile_lines_corner_case_5_zero_balance_in_one_line_same_foreign_currency(self): """ Test a corner case when both lines have something to reconcile in company currency but one has nothing in foreign currency. It could be due to: - a bad usage of the `no_exchange_difference` context key - a partial reconciliation made before migrating to this version - some rounding error when dealing with currencies having != decimal places - strange journal items made by the user """ currency = self.currency_data['currency'] line_1 = self.create_line_for_reconciliation(-0.06, 0.0, currency, '2017-01-01') line_2 = self.create_line_for_reconciliation(0.12, 0.24, currency, '2017-01-01') amls = line_1 + line_2 amls.reconcile() partials = self._get_partials(amls) self.assertRecordValues(partials, [ { 'amount': 0.06, 'debit_amount_currency': 0.0, 'credit_amount_currency': 0.0, 'debit_move_id': line_2.id, 'credit_move_id': line_1.id, }, ]) self.assertRecordValues(amls, [ {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, {'amount_residual': 0.06, 'amount_residual_currency': 0.24, 'reconciled': False}, ]) def test_reconcile_exchange_difference_on_partial_same_foreign_currency_debit_expense_partial_payment(self): currency = self.currency_data['currency'] line_1 = self.create_line_for_reconciliation(60.0, 120.0, currency, '2017-01-01') line_2 = self.create_line_for_reconciliation(-80.0, -240.0, currency, '2016-01-01') amls = line_1 + line_2 amls.reconcile() partials = self._get_partials(amls) self.assertRecordValues(partials, [ { 'amount': 40.0, 'debit_amount_currency': 120.0, 'credit_amount_currency': 120.0, 'debit_move_id': line_1.id, 'credit_move_id': line_2.id, }, { 'amount': 20.0, 'debit_amount_currency': 0.0, 'credit_amount_currency': 0.0, 'debit_move_id': line_1.id, 'credit_move_id': partials.exchange_move_id.line_ids[0].id, }, ]) self.assertRecordValues(partials.exchange_move_id, [{'date': fields.Date.from_string('2017-01-31')}]) self.assertRecordValues(partials.exchange_move_id.line_ids, [ { 'debit': 0.0, 'credit': 20.0, 'amount_currency': 0.0, 'currency_id': currency.id, 'account_id': line_1.account_id.id, }, { 'debit': 20.0, 'credit': 0.0, 'amount_currency': 0.0, 'currency_id': currency.id, 'account_id': self.exch_expense_account.id, }, ]) self.assertRecordValues(amls, [ {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, {'amount_residual': -40.0, 'amount_residual_currency': -120.0, 'reconciled': False}, ]) def test_reconcile_exchange_difference_on_partial_same_foreign_currency_debit_income_partial_payment(self): currency = self.currency_data['currency'] line_1 = self.create_line_for_reconciliation(40.0, 120.0, currency, '2016-01-01') line_2 = self.create_line_for_reconciliation(-120.0, -240.0, currency, '2017-01-01') amls = line_1 + line_2 amls.reconcile() partials = self._get_partials(amls) self.assertRecordValues(partials, [ { 'amount': 40.0, 'debit_amount_currency': 120.0, 'credit_amount_currency': 120.0, 'debit_move_id': line_1.id, 'credit_move_id': line_2.id, }, { 'amount': 20.0, 'debit_amount_currency': 0.0, 'credit_amount_currency': 0.0, 'debit_move_id': partials.exchange_move_id.line_ids[0].id, 'credit_move_id': line_2.id, }, ]) self.assertRecordValues(partials.exchange_move_id, [{'date': fields.Date.from_string('2017-01-31')}]) self.assertRecordValues(partials.exchange_move_id.line_ids, [ { 'debit': 20.0, 'credit': 0.0, 'amount_currency': 0.0, 'currency_id': currency.id, 'account_id': line_2.account_id.id, }, { 'debit': 0.0, 'credit': 20.0, 'amount_currency': 0.0, 'currency_id': currency.id, 'account_id': self.exch_income_account.id, }, ]) self.assertRecordValues(amls, [ {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, {'amount_residual': -60.0, 'amount_residual_currency': -120.0, 'reconciled': False}, ]) def test_reconcile_exchange_difference_on_partial_same_foreign_currency_credit_expense_partial_payment(self): currency = self.currency_data['currency'] line_1 = self.create_line_for_reconciliation(-40.0, -120.0, currency, '2016-01-01') line_2 = self.create_line_for_reconciliation(120.0, 240.0, currency, '2017-01-01') amls = line_1 + line_2 amls.reconcile() partials = self._get_partials(amls) self.assertRecordValues(partials, [ { 'amount': 40.0, 'debit_amount_currency': 120.0, 'credit_amount_currency': 120.0, 'debit_move_id': line_2.id, 'credit_move_id': line_1.id, }, { 'amount': 20.0, 'debit_amount_currency': 0.0, 'credit_amount_currency': 0.0, 'debit_move_id': line_2.id, 'credit_move_id': partials.exchange_move_id.line_ids[0].id, }, ]) self.assertRecordValues(partials.exchange_move_id, [{'date': fields.Date.from_string('2017-01-31')}]) self.assertRecordValues(partials.exchange_move_id.line_ids, [ { 'debit': 0.0, 'credit': 20.0, 'amount_currency': 0.0, 'currency_id': currency.id, 'account_id': line_2.account_id.id, }, { 'debit': 20.0, 'credit': 0.0, 'amount_currency': 0.0, 'currency_id': currency.id, 'account_id': self.exch_expense_account.id, }, ]) self.assertRecordValues(amls, [ {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, {'amount_residual': 60.0, 'amount_residual_currency': 120.0, 'reconciled': False}, ]) def test_reconcile_exchange_difference_on_partial_same_foreign_currency_credit_income_partial_payment(self): currency = self.currency_data['currency'] line_1 = self.create_line_for_reconciliation(-60.0, -120.0, currency, '2017-01-01') line_2 = self.create_line_for_reconciliation(80.0, 240.0, currency, '2016-01-01') amls = line_1 + line_2 amls.reconcile() partials = self._get_partials(amls) self.assertRecordValues(partials, [ { 'amount': 40.0, 'debit_amount_currency': 120.0, 'credit_amount_currency': 120.0, 'debit_move_id': line_2.id, 'credit_move_id': line_1.id, }, { 'amount': 20.0, 'debit_amount_currency': 0.0, 'credit_amount_currency': 0.0, 'debit_move_id': partials.exchange_move_id.line_ids[0].id, 'credit_move_id': line_1.id, }, ]) self.assertRecordValues(partials.exchange_move_id, [{'date': fields.Date.from_string('2017-01-31')}]) self.assertRecordValues(partials.exchange_move_id.line_ids, [ { 'debit': 20.0, 'credit': 0.0, 'amount_currency': 0.0, 'currency_id': currency.id, 'account_id': line_1.account_id.id, }, { 'debit': 0.0, 'credit': 20.0, 'amount_currency': 0.0, 'currency_id': currency.id, 'account_id': self.exch_income_account.id, }, ]) self.assertRecordValues(amls, [ {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, {'amount_residual': 40.0, 'amount_residual_currency': 120.0, 'reconciled': False}, ]) def test_reconcile_exchange_difference_on_partial_one_debit_foreign_currency_debit_expense_partial_payment(self): comp_curr = self.company_data['currency'] foreign_curr = self.currency_data['currency'] line_1 = self.create_line_for_reconciliation(60.0, 120.0, foreign_curr, '2017-01-01') line_2 = self.create_line_for_reconciliation(-80.0, -80.0, comp_curr, '2016-01-01') amls = line_1 + line_2 amls.reconcile() partials = self._get_partials(amls) self.assertRecordValues(partials, [ { 'amount': 40.0, 'debit_amount_currency': 120.0, 'credit_amount_currency': 40.0, 'debit_move_id': line_1.id, 'credit_move_id': line_2.id, }, { 'amount': 20.0, 'debit_amount_currency': 0.0, 'credit_amount_currency': 0.0, 'debit_move_id': line_1.id, 'credit_move_id': partials.exchange_move_id.line_ids[0].id, }, ]) self.assertRecordValues(partials.exchange_move_id, [{'date': fields.Date.from_string('2017-01-31')}]) self.assertRecordValues(partials.exchange_move_id.line_ids, [ { 'debit': 0.0, 'credit': 20.0, 'amount_currency': 0.0, 'currency_id': foreign_curr.id, 'account_id': line_1.account_id.id, }, { 'debit': 20.0, 'credit': 0.0, 'amount_currency': 0.0, 'currency_id': foreign_curr.id, 'account_id': self.exch_expense_account.id, }, ]) self.assertRecordValues(amls, [ {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, {'amount_residual': -40.0, 'amount_residual_currency': -40.0, 'reconciled': False}, ]) def test_reconcile_exchange_difference_on_partial_one_credit_foreign_currency_debit_expense_partial_payment(self): comp_curr = self.company_data['currency'] foreign_curr = self.currency_data['currency'] line_1 = self.create_line_for_reconciliation(60.0, 60.0, comp_curr, '2017-01-01') line_2 = self.create_line_for_reconciliation(-80.0, -240.0, foreign_curr, '2016-01-01') amls = line_1 + line_2 amls.reconcile() partials = self._get_partials(amls) self.assertRecordValues(partials, [ { 'amount': 40.0, 'debit_amount_currency': 40.0, 'credit_amount_currency': 120.0, 'debit_move_id': line_1.id, 'credit_move_id': line_2.id, }, { 'amount': 20.0, 'debit_amount_currency': 20.0, 'credit_amount_currency': 20.0, 'debit_move_id': line_1.id, 'credit_move_id': partials.exchange_move_id.line_ids[0].id, }, ]) self.assertRecordValues(partials.exchange_move_id, [{'date': fields.Date.from_string('2017-01-31')}]) self.assertRecordValues(partials.exchange_move_id.line_ids, [ { 'debit': 0.0, 'credit': 20.0, 'amount_currency': -20.0, 'currency_id': comp_curr.id, 'account_id': line_1.account_id.id, }, { 'debit': 20.0, 'credit': 0.0, 'amount_currency': 20.0, 'currency_id': comp_curr.id, 'account_id': self.exch_expense_account.id, }, ]) self.assertRecordValues(amls, [ {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, {'amount_residual': -40.0, 'amount_residual_currency': -120.0, 'reconciled': False}, ]) def test_reconcile_exchange_difference_on_partial_one_debit_foreign_currency_debit_income_partial_payment(self): comp_curr = self.company_data['currency'] foreign_curr = self.currency_data['currency'] line_1 = self.create_line_for_reconciliation(40.0, 120.0, foreign_curr, '2016-01-01') line_2 = self.create_line_for_reconciliation(-120.0, -120.0, comp_curr, '2017-01-01') amls = line_1 + line_2 amls.reconcile() partials = self._get_partials(amls) self.assertRecordValues(partials, [ { 'amount': 40.0, 'debit_amount_currency': 120.0, 'credit_amount_currency': 40.0, 'debit_move_id': line_1.id, 'credit_move_id': line_2.id, }, { 'amount': 20.0, 'debit_amount_currency': 20.0, 'credit_amount_currency': 20.0, 'debit_move_id': partials.exchange_move_id.line_ids[0].id, 'credit_move_id': line_2.id, }, ]) self.assertRecordValues(partials.exchange_move_id, [{'date': fields.Date.from_string('2017-01-31')}]) self.assertRecordValues(partials.exchange_move_id.line_ids, [ { 'debit': 20.0, 'credit': 0.0, 'amount_currency': 20.0, 'currency_id': comp_curr.id, 'account_id': line_2.account_id.id, }, { 'debit': 0.0, 'credit': 20.0, 'amount_currency': -20.0, 'currency_id': comp_curr.id, 'account_id': self.exch_income_account.id, }, ]) self.assertRecordValues(amls, [ {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, {'amount_residual': -60.0, 'amount_residual_currency': -60.0, 'reconciled': False}, ]) def test_reconcile_exchange_difference_on_partial_one_credit_foreign_currency_debit_income_partial_payment(self): comp_curr = self.company_data['currency'] foreign_curr = self.currency_data['currency'] line_1 = self.create_line_for_reconciliation(40.0, 40.0, comp_curr, '2016-01-01') line_2 = self.create_line_for_reconciliation(-120.0, -240.0, foreign_curr, '2017-01-01') amls = line_1 + line_2 amls.reconcile() partials = self._get_partials(amls) self.assertRecordValues(partials, [ { 'amount': 40.0, 'debit_amount_currency': 40.0, 'credit_amount_currency': 120.0, 'debit_move_id': line_1.id, 'credit_move_id': line_2.id, }, { 'amount': 20.0, 'debit_amount_currency': 0.0, 'credit_amount_currency': 0.0, 'debit_move_id': partials.exchange_move_id.line_ids[0].id, 'credit_move_id': line_2.id, }, ]) self.assertRecordValues(partials.exchange_move_id, [{'date': fields.Date.from_string('2017-01-31')}]) self.assertRecordValues(partials.exchange_move_id.line_ids, [ { 'debit': 20.0, 'credit': 0.0, 'amount_currency': 0.0, 'currency_id': foreign_curr.id, 'account_id': line_2.account_id.id, }, { 'debit': 0.0, 'credit': 20.0, 'amount_currency': 0.0, 'currency_id': foreign_curr.id, 'account_id': self.exch_income_account.id, }, ]) self.assertRecordValues(amls, [ {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, {'amount_residual': -60.0, 'amount_residual_currency': -120.0, 'reconciled': False}, ]) def test_reconcile_exchange_difference_on_partial_one_credit_foreign_currency_credit_expense_partial_payment(self): comp_curr = self.company_data['currency'] foreign_curr = self.currency_data['currency'] line_1 = self.create_line_for_reconciliation(-40.0, -120.0, foreign_curr, '2016-01-01') line_2 = self.create_line_for_reconciliation(120.0, 120.0, comp_curr, '2017-01-01') amls = line_1 + line_2 amls.reconcile() partials = self._get_partials(amls) self.assertRecordValues(partials, [ { 'amount': 40.0, 'debit_amount_currency': 40.0, 'credit_amount_currency': 120.0, 'debit_move_id': line_2.id, 'credit_move_id': line_1.id, }, { 'amount': 20.0, 'debit_amount_currency': 20.0, 'credit_amount_currency': 20.0, 'debit_move_id': line_2.id, 'credit_move_id': partials.exchange_move_id.line_ids[0].id, }, ]) self.assertRecordValues(partials.exchange_move_id, [{'date': fields.Date.from_string('2017-01-31')}]) self.assertRecordValues(partials.exchange_move_id.line_ids, [ { 'debit': 0.0, 'credit': 20.0, 'amount_currency': -20.0, 'currency_id': comp_curr.id, 'account_id': line_2.account_id.id, }, { 'debit': 20.0, 'credit': 0.0, 'amount_currency': 20.0, 'currency_id': comp_curr.id, 'account_id': self.exch_expense_account.id, }, ]) self.assertRecordValues(amls, [ {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, {'amount_residual': 60.0, 'amount_residual_currency': 60.0, 'reconciled': False}, ]) def test_reconcile_exchange_difference_on_partial_one_debit_foreign_currency_credit_expense_partial_payment(self): comp_curr = self.company_data['currency'] foreign_curr = self.currency_data['currency'] line_1 = self.create_line_for_reconciliation(-40.0, -40.0, comp_curr, '2016-01-01') line_2 = self.create_line_for_reconciliation(120.0, 240.0, foreign_curr, '2017-01-01') amls = line_1 + line_2 amls.reconcile() partials = self._get_partials(amls) self.assertRecordValues(partials, [ { 'amount': 40.0, 'debit_amount_currency': 120.0, 'credit_amount_currency': 40.0, 'debit_move_id': line_2.id, 'credit_move_id': line_1.id, }, { 'amount': 20.0, 'debit_amount_currency': 0.0, 'credit_amount_currency': 0.0, 'debit_move_id': line_2.id, 'credit_move_id': partials.exchange_move_id.line_ids[0].id, }, ]) self.assertRecordValues(partials.exchange_move_id, [{'date': fields.Date.from_string('2017-01-31')}]) self.assertRecordValues(partials.exchange_move_id.line_ids, [ { 'debit': 0.0, 'credit': 20.0, 'amount_currency': 0.0, 'currency_id': foreign_curr.id, 'account_id': line_2.account_id.id, }, { 'debit': 20.0, 'credit': 0.0, 'amount_currency': 0.0, 'currency_id': foreign_curr.id, 'account_id': self.exch_expense_account.id, }, ]) self.assertRecordValues(amls, [ {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, {'amount_residual': 60.0, 'amount_residual_currency': 120.0, 'reconciled': False}, ]) def test_reconcile_exchange_difference_on_partial_one_debit_foreign_currency_credit_income_partial_payment(self): comp_curr = self.company_data['currency'] foreign_curr = self.currency_data['currency'] line_1 = self.create_line_for_reconciliation(-60.0, -60.0, comp_curr, '2017-01-01') line_2 = self.create_line_for_reconciliation(80.0, 240.0, foreign_curr, '2016-01-01') amls = line_1 + line_2 amls.reconcile() partials = self._get_partials(amls) self.assertRecordValues(partials, [ { 'amount': 40.0, 'debit_amount_currency': 120.0, 'credit_amount_currency': 40.0, 'debit_move_id': line_2.id, 'credit_move_id': line_1.id, }, { 'amount': 20.0, 'debit_amount_currency': 20.0, 'credit_amount_currency': 20.0, 'debit_move_id': partials.exchange_move_id.line_ids[0].id, 'credit_move_id': line_1.id, }, ]) self.assertRecordValues(partials.exchange_move_id, [{'date': fields.Date.from_string('2017-01-31')}]) self.assertRecordValues(partials.exchange_move_id.line_ids, [ { 'debit': 20.0, 'credit': 0.0, 'amount_currency': 20.0, 'currency_id': comp_curr.id, 'account_id': line_1.account_id.id, }, { 'debit': 0.0, 'credit': 20.0, 'amount_currency': -20.0, 'currency_id': comp_curr.id, 'account_id': self.exch_income_account.id, }, ]) self.assertRecordValues(amls, [ {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, {'amount_residual': 40.0, 'amount_residual_currency': 120.0, 'reconciled': False}, ]) def test_reconcile_exchange_difference_on_partial_one_credit_foreign_currency_credit_income_partial_payment(self): comp_curr = self.company_data['currency'] foreign_curr = self.currency_data['currency'] line_1 = self.create_line_for_reconciliation(-60.0, -120.0, foreign_curr, '2017-01-01') line_2 = self.create_line_for_reconciliation(80.0, 80.0, comp_curr, '2016-01-01') amls = line_1 + line_2 amls.reconcile() partials = self._get_partials(amls) self.assertRecordValues(partials, [ { 'amount': 40.0, 'debit_amount_currency': 40.0, 'credit_amount_currency': 120.0, 'debit_move_id': line_2.id, 'credit_move_id': line_1.id, }, { 'amount': 20.0, 'debit_amount_currency': 0.0, 'credit_amount_currency': 0.0, 'debit_move_id': partials.exchange_move_id.line_ids[0].id, 'credit_move_id': line_1.id, }, ]) self.assertRecordValues(partials.exchange_move_id, [{'date': fields.Date.from_string('2017-01-31')}]) self.assertRecordValues(partials.exchange_move_id.line_ids, [ { 'debit': 20.0, 'credit': 0.0, 'amount_currency': 0.0, 'currency_id': foreign_curr.id, 'account_id': line_1.account_id.id, }, { 'debit': 0.0, 'credit': 20.0, 'amount_currency': 0.0, 'currency_id': foreign_curr.id, 'account_id': self.exch_income_account.id, }, ]) self.assertRecordValues(amls, [ {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, {'amount_residual': 40.0, 'amount_residual_currency': 40.0, 'reconciled': False}, ]) def test_reconcile_one_foreign_currency_fallback_company_currency(self): comp_curr = self.company_data['currency'] foreign_curr = self.currency_data_3['currency'] line_1 = self.create_line_for_reconciliation(-10.0, -10.0, comp_curr, '2017-01-01') line_2 = self.create_line_for_reconciliation(1000000.0, 100.0, foreign_curr, '2017-01-01') amls = line_1 + line_2 amls.reconcile() partials = self._get_partials(amls) self.assertRecordValues(partials, [{ 'amount': 10.0, 'debit_amount_currency': 0.001, 'credit_amount_currency': 10.0, 'debit_move_id': line_2.id, 'credit_move_id': line_1.id, }]) self.assertFalse(partials.exchange_move_id) self.assertRecordValues(line_1 + line_2, [ {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, {'amount_residual': 999990.0, 'amount_residual_currency': 99.999, 'reconciled': False}, ]) def test_reconcile_exchange_difference_on_partial_same_foreign_currency_debit_expense_full_payment(self): currency = self.currency_data['currency'] line_1 = self.create_line_for_reconciliation(60.0, 120.0, currency, '2017-01-01') line_2 = self.create_line_for_reconciliation(-40.0, -120.0, currency, '2016-01-01') amls = line_1 + line_2 amls.reconcile() partials = self._get_partials(amls) full_reconcile = amls.full_reconcile_id self.assertTrue(full_reconcile) self.assertRecordValues(full_reconcile, [{'exchange_move_id': False}]) self.assertRecordValues(partials, [ { 'amount': 40.0, 'debit_amount_currency': 120.0, 'credit_amount_currency': 120.0, 'debit_move_id': line_1.id, 'credit_move_id': line_2.id, }, { 'amount': 20.0, 'debit_amount_currency': 0.0, 'credit_amount_currency': 0.0, 'debit_move_id': line_1.id, 'credit_move_id': partials.exchange_move_id.line_ids[0].id, }, ]) self.assertRecordValues(partials.exchange_move_id, [{'date': fields.Date.from_string('2017-01-31')}]) self.assertRecordValues(partials.exchange_move_id.line_ids, [ { 'debit': 0.0, 'credit': 20.0, 'amount_currency': 0.0, 'currency_id': currency.id, 'account_id': line_1.account_id.id, }, { 'debit': 20.0, 'credit': 0.0, 'amount_currency': 0.0, 'currency_id': currency.id, 'account_id': self.exch_expense_account.id, }, ]) self.assertRecordValues(amls, [ {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, ]) def test_reconcile_exchange_difference_on_partial_same_foreign_currency_debit_income_full_payment(self): currency = self.currency_data['currency'] line_1 = self.create_line_for_reconciliation(40.0, 120.0, currency, '2016-01-01') line_2 = self.create_line_for_reconciliation(-60.0, -120.0, currency, '2017-01-01') amls = line_1 + line_2 amls.reconcile() partials = self._get_partials(amls) full_reconcile = amls.full_reconcile_id self.assertTrue(full_reconcile) self.assertRecordValues(full_reconcile, [{'exchange_move_id': False}]) self.assertRecordValues(partials, [ { 'amount': 40.0, 'debit_amount_currency': 120.0, 'credit_amount_currency': 120.0, 'debit_move_id': line_1.id, 'credit_move_id': line_2.id, }, { 'amount': 20.0, 'debit_amount_currency': 0.0, 'credit_amount_currency': 0.0, 'debit_move_id': partials.exchange_move_id.line_ids[0].id, 'credit_move_id': line_2.id, }, ]) self.assertRecordValues(partials.exchange_move_id, [{'date': fields.Date.from_string('2017-01-31')}]) self.assertRecordValues(partials.exchange_move_id.line_ids, [ { 'debit': 20.0, 'credit': 0.0, 'amount_currency': 0.0, 'currency_id': currency.id, 'account_id': line_2.account_id.id, }, { 'debit': 0.0, 'credit': 20.0, 'amount_currency': 0.0, 'currency_id': currency.id, 'account_id': self.exch_income_account.id, }, ]) self.assertRecordValues(amls, [ {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, ]) def test_reconcile_exchange_difference_on_partial_same_foreign_currency_credit_expense_full_payment(self): currency = self.currency_data['currency'] line_1 = self.create_line_for_reconciliation(-40.0, -120.0, currency, '2016-01-01') line_2 = self.create_line_for_reconciliation(60.0, 120.0, currency, '2017-01-01') amls = line_1 + line_2 amls.reconcile() partials = self._get_partials(amls) full_reconcile = amls.full_reconcile_id self.assertTrue(full_reconcile) self.assertRecordValues(full_reconcile, [{'exchange_move_id': False}]) self.assertRecordValues(partials, [ { 'amount': 40.0, 'debit_amount_currency': 120.0, 'credit_amount_currency': 120.0, 'debit_move_id': line_2.id, 'credit_move_id': line_1.id, }, { 'amount': 20.0, 'debit_amount_currency': 0.0, 'credit_amount_currency': 0.0, 'debit_move_id': line_2.id, 'credit_move_id': partials.exchange_move_id.line_ids[0].id, } ]) self.assertRecordValues(partials.exchange_move_id, [{'date': fields.Date.from_string('2017-01-31')}]) self.assertRecordValues(partials.exchange_move_id.line_ids, [ { 'debit': 0.0, 'credit': 20.0, 'amount_currency': 0.0, 'currency_id': currency.id, 'account_id': line_2.account_id.id, }, { 'debit': 20.0, 'credit': 0.0, 'amount_currency': 0.0, 'currency_id': currency.id, 'account_id': self.exch_expense_account.id, }, ]) self.assertRecordValues(amls, [ {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, ]) def test_reconcile_exchange_difference_on_partial_same_foreign_currency_credit_income_full_payment(self): currency = self.currency_data['currency'] line_1 = self.create_line_for_reconciliation(-60.0, -120.0, currency, '2017-01-01') line_2 = self.create_line_for_reconciliation(40.0, 120.0, currency, '2016-01-01') amls = line_1 + line_2 amls.reconcile() partials = self._get_partials(amls) full_reconcile = amls.full_reconcile_id self.assertTrue(full_reconcile) self.assertRecordValues(full_reconcile, [{'exchange_move_id': False}]) self.assertRecordValues(partials, [ { 'amount': 40.0, 'debit_amount_currency': 120.0, 'credit_amount_currency': 120.0, 'debit_move_id': line_2.id, 'credit_move_id': line_1.id, }, { 'amount': 20.0, 'debit_amount_currency': 0.0, 'credit_amount_currency': 0.0, 'debit_move_id': partials.exchange_move_id.line_ids[0].id, 'credit_move_id': line_1.id, } ]) self.assertRecordValues(partials.exchange_move_id, [{'date': fields.Date.from_string('2017-01-31')}]) self.assertRecordValues(partials.exchange_move_id.line_ids, [ { 'debit': 20.0, 'credit': 0.0, 'amount_currency': 0.0, 'currency_id': currency.id, 'account_id': line_1.account_id.id, }, { 'debit': 0.0, 'credit': 20.0, 'amount_currency': 0.0, 'currency_id': currency.id, 'account_id': self.exch_income_account.id, }, ]) self.assertRecordValues(line_1 + line_2, [ {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, ]) def test_reconcile_exchange_difference_on_partial_one_debit_foreign_currency_debit_expense_full_payment(self): comp_curr = self.company_data['currency'] foreign_curr = self.currency_data['currency'] line_1 = self.create_line_for_reconciliation(60.0, 120.0, foreign_curr, '2017-01-01') line_2 = self.create_line_for_reconciliation(-40.0, -40.0, comp_curr, '2016-01-01') amls = line_1 + line_2 amls.reconcile() partials = self._get_partials(amls) full_reconcile = amls.full_reconcile_id self.assertTrue(full_reconcile) self.assertRecordValues(full_reconcile, [{'exchange_move_id': False}]) self.assertRecordValues(partials, [ { 'amount': 40.0, 'debit_amount_currency': 120.0, 'credit_amount_currency': 40.0, 'debit_move_id': line_1.id, 'credit_move_id': line_2.id, }, { 'amount': 20.0, 'debit_amount_currency': 0.0, 'credit_amount_currency': 0.0, 'debit_move_id': line_1.id, 'credit_move_id': partials.exchange_move_id.line_ids[0].id, }, ]) self.assertRecordValues(partials.exchange_move_id, [{'date': fields.Date.from_string('2017-01-31')}]) self.assertRecordValues(partials.exchange_move_id.line_ids, [ { 'debit': 0.0, 'credit': 20.0, 'amount_currency': 0.0, 'currency_id': foreign_curr.id, 'account_id': line_1.account_id.id, }, { 'debit': 20.0, 'credit': 0.0, 'amount_currency': 0.0, 'currency_id': foreign_curr.id, 'account_id': self.exch_expense_account.id, }, ]) self.assertRecordValues(amls, [ {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, ]) def test_reconcile_exchange_difference_on_partial_one_credit_foreign_currency_debit_expense_full_payment(self): comp_curr = self.company_data['currency'] foreign_curr = self.currency_data['currency'] line_1 = self.create_line_for_reconciliation(60.0, 60.0, comp_curr, '2017-01-01') line_2 = self.create_line_for_reconciliation(-40.0, -120.0, foreign_curr, '2016-01-01') amls = line_1 + line_2 amls.reconcile() partials = self._get_partials(amls) full_reconcile = amls.full_reconcile_id self.assertTrue(full_reconcile) self.assertRecordValues(full_reconcile, [{'exchange_move_id': False}]) self.assertRecordValues(partials, [ { 'amount': 40.0, 'debit_amount_currency': 40.0, 'credit_amount_currency': 120.0, 'debit_move_id': line_1.id, 'credit_move_id': line_2.id, }, { 'amount': 20.0, 'debit_amount_currency': 20.0, 'credit_amount_currency': 20.0, 'debit_move_id': line_1.id, 'credit_move_id': partials.exchange_move_id.line_ids[0].id, }, ]) self.assertRecordValues(partials.exchange_move_id, [{'date': fields.Date.from_string('2017-01-31')}]) self.assertRecordValues(partials.exchange_move_id.line_ids, [ { 'debit': 0.0, 'credit': 20.0, 'amount_currency': -20.0, 'currency_id': comp_curr.id, 'account_id': line_1.account_id.id, }, { 'debit': 20.0, 'credit': 0.0, 'amount_currency': 20.0, 'currency_id': comp_curr.id, 'account_id': self.exch_expense_account.id, }, ]) self.assertRecordValues(amls, [ {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, ]) def test_reconcile_exchange_difference_on_partial_one_debit_foreign_currency_debit_income_full_payment(self): comp_curr = self.company_data['currency'] foreign_curr = self.currency_data['currency'] line_1 = self.create_line_for_reconciliation(40.0, 120.0, foreign_curr, '2016-01-01') line_2 = self.create_line_for_reconciliation(-60.0, -60.0, comp_curr, '2017-01-01') amls = line_1 + line_2 amls.reconcile() partials = self._get_partials(amls) full_reconcile = amls.full_reconcile_id self.assertTrue(full_reconcile) self.assertRecordValues(full_reconcile, [{'exchange_move_id': False}]) self.assertRecordValues(partials, [ { 'amount': 40.0, 'debit_amount_currency': 120.0, 'credit_amount_currency': 40.0, 'debit_move_id': line_1.id, 'credit_move_id': line_2.id, }, { 'amount': 20.0, 'debit_amount_currency': 20.0, 'credit_amount_currency': 20.0, 'debit_move_id': partials.exchange_move_id.line_ids[0].id, 'credit_move_id': line_2.id, }, ]) self.assertRecordValues(partials.exchange_move_id, [{'date': fields.Date.from_string('2017-01-31')}]) self.assertRecordValues(partials.exchange_move_id.line_ids, [ { 'debit': 20.0, 'credit': 0.0, 'amount_currency': 20.0, 'currency_id': comp_curr.id, 'account_id': line_2.account_id.id, }, { 'debit': 0.0, 'credit': 20.0, 'amount_currency': -20.0, 'currency_id': comp_curr.id, 'account_id': self.exch_income_account.id, }, ]) self.assertRecordValues(amls, [ {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, ]) def test_reconcile_exchange_difference_on_partial_one_credit_foreign_currency_debit_income_full_payment(self): comp_curr = self.company_data['currency'] foreign_curr = self.currency_data['currency'] line_1 = self.create_line_for_reconciliation(40.0, 40.0, comp_curr, '2016-01-01') line_2 = self.create_line_for_reconciliation(-60.0, -120.0, foreign_curr, '2017-01-01') amls = line_1 + line_2 amls.reconcile() partials = self._get_partials(amls) full_reconcile = amls.full_reconcile_id self.assertTrue(full_reconcile) self.assertRecordValues(full_reconcile, [{'exchange_move_id': False}]) self.assertRecordValues(partials, [ { 'amount': 40.0, 'debit_amount_currency': 40.0, 'credit_amount_currency': 120.0, 'debit_move_id': line_1.id, 'credit_move_id': line_2.id, }, { 'amount': 20.0, 'debit_amount_currency': 0.0, 'credit_amount_currency': 0.0, 'debit_move_id': partials.exchange_move_id.line_ids[0].id, 'credit_move_id': line_2.id, }, ]) self.assertRecordValues(partials.exchange_move_id, [{'date': fields.Date.from_string('2017-01-31')}]) self.assertRecordValues(partials.exchange_move_id.line_ids, [ { 'debit': 20.0, 'credit': 0.0, 'amount_currency': 0.0, 'currency_id': foreign_curr.id, 'account_id': line_2.account_id.id, }, { 'debit': 0.0, 'credit': 20.0, 'amount_currency': 0.0, 'currency_id': foreign_curr.id, 'account_id': self.exch_income_account.id, }, ]) self.assertRecordValues(amls, [ {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, ]) def test_reconcile_exchange_difference_on_partial_one_debit_foreign_currency_credit_expense_full_payment(self): comp_curr = self.company_data['currency'] foreign_curr = self.currency_data['currency'] line_1 = self.create_line_for_reconciliation(-40.0, -40.0, comp_curr, '2016-01-01') line_2 = self.create_line_for_reconciliation(60.0, 120.0, foreign_curr, '2017-01-01') amls = line_1 + line_2 amls.reconcile() partials = self._get_partials(amls) full_reconcile = amls.full_reconcile_id self.assertTrue(full_reconcile) self.assertRecordValues(full_reconcile, [{'exchange_move_id': False}]) self.assertRecordValues(partials, [ { 'amount': 40.0, 'debit_amount_currency': 120.0, 'credit_amount_currency': 40.0, 'debit_move_id': line_2.id, 'credit_move_id': line_1.id, }, { 'amount': 20.0, 'debit_amount_currency': 0.0, 'credit_amount_currency': 0.0, 'debit_move_id': line_2.id, 'credit_move_id': partials.exchange_move_id.line_ids[0].id, }, ]) self.assertRecordValues(partials.exchange_move_id, [{'date': fields.Date.from_string('2017-01-31')}]) self.assertRecordValues(partials.exchange_move_id.line_ids, [ { 'debit': 0.0, 'credit': 20.0, 'amount_currency': 0.0, 'currency_id': foreign_curr.id, 'account_id': line_2.account_id.id, }, { 'debit': 20.0, 'credit': 0.0, 'amount_currency': 0.0, 'currency_id': foreign_curr.id, 'account_id': self.exch_expense_account.id, }, ]) self.assertRecordValues(amls, [ {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, ]) def test_reconcile_exchange_difference_on_partial_one_credit_foreign_currency_credit_expense_full_payment(self): comp_curr = self.company_data['currency'] foreign_curr = self.currency_data['currency'] line_1 = self.create_line_for_reconciliation(-40.0, -120.0, foreign_curr, '2016-01-01') line_2 = self.create_line_for_reconciliation(60.0, 60.0, comp_curr, '2017-01-01') amls = line_1 + line_2 amls.reconcile() partials = self._get_partials(amls) full_reconcile = amls.full_reconcile_id self.assertTrue(full_reconcile) self.assertRecordValues(full_reconcile, [{'exchange_move_id': False}]) self.assertRecordValues(partials, [ { 'amount': 40.0, 'debit_amount_currency': 40.0, 'credit_amount_currency': 120.0, 'debit_move_id': line_2.id, 'credit_move_id': line_1.id, }, { 'amount': 20.0, 'debit_amount_currency': 20.0, 'credit_amount_currency': 20.0, 'debit_move_id': line_2.id, 'credit_move_id': partials.exchange_move_id.line_ids[0].id, }, ]) self.assertRecordValues(partials.exchange_move_id, [{'date': fields.Date.from_string('2017-01-31')}]) self.assertRecordValues(partials.exchange_move_id.line_ids, [ { 'debit': 0.0, 'credit': 20.0, 'amount_currency': -20.0, 'currency_id': comp_curr.id, 'account_id': line_2.account_id.id, }, { 'debit': 20.0, 'credit': 0.0, 'amount_currency': 20.0, 'currency_id': comp_curr.id, 'account_id': self.exch_expense_account.id, }, ]) self.assertRecordValues(amls, [ {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, ]) def test_reconcile_exchange_difference_on_partial_one_debit_foreign_currency_credit_income_full_payment(self): comp_curr = self.company_data['currency'] foreign_curr = self.currency_data['currency'] line_1 = self.create_line_for_reconciliation(-60.0, -60.0, comp_curr, '2017-01-01') line_2 = self.create_line_for_reconciliation(40.0, 120.0, foreign_curr, '2016-01-01') amls = line_1 + line_2 amls.reconcile() partials = self._get_partials(amls) full_reconcile = amls.full_reconcile_id self.assertTrue(full_reconcile) self.assertRecordValues(full_reconcile, [{'exchange_move_id': False}]) self.assertRecordValues(partials, [ { 'amount': 40.0, 'debit_amount_currency': 120.0, 'credit_amount_currency': 40.0, 'debit_move_id': line_2.id, 'credit_move_id': line_1.id, }, { 'amount': 20.0, 'debit_amount_currency': 20.0, 'credit_amount_currency': 20.0, 'debit_move_id': partials.exchange_move_id.line_ids[0].id, 'credit_move_id': line_1.id, }, ]) self.assertRecordValues(partials.exchange_move_id, [{'date': fields.Date.from_string('2017-01-31')}]) self.assertRecordValues(partials.exchange_move_id.line_ids, [ { 'debit': 20.0, 'credit': 0.0, 'amount_currency': 20.0, 'currency_id': comp_curr.id, 'account_id': line_1.account_id.id, }, { 'debit': 0.0, 'credit': 20.0, 'amount_currency': -20.0, 'currency_id': comp_curr.id, 'account_id': self.exch_income_account.id, }, ]) self.assertRecordValues(amls, [ {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, ]) def test_reconcile_exchange_difference_on_partial_one_credit_foreign_currency_credit_income_full_payment(self): comp_curr = self.company_data['currency'] foreign_curr = self.currency_data['currency'] line_1 = self.create_line_for_reconciliation(-60.0, -120.0, foreign_curr, '2017-01-01') line_2 = self.create_line_for_reconciliation(40.0, 40.0, comp_curr, '2016-01-01') amls = line_1 + line_2 amls.reconcile() partials = self._get_partials(amls) full_reconcile = amls.full_reconcile_id self.assertTrue(full_reconcile) self.assertRecordValues(full_reconcile, [{'exchange_move_id': False}]) self.assertRecordValues(partials, [ { 'amount': 40.0, 'debit_amount_currency': 40.0, 'credit_amount_currency': 120.0, 'debit_move_id': line_2.id, 'credit_move_id': line_1.id, }, { 'amount': 20.0, 'debit_amount_currency': 0.0, 'credit_amount_currency': 0.0, 'debit_move_id': partials.exchange_move_id.line_ids[0].id, 'credit_move_id': line_1.id, }, ]) self.assertRecordValues(partials.exchange_move_id, [{'date': fields.Date.from_string('2017-01-31')}]) self.assertRecordValues(partials.exchange_move_id.line_ids, [ { 'debit': 20.0, 'credit': 0.0, 'amount_currency': 0.0, 'currency_id': foreign_curr.id, 'account_id': line_1.account_id.id, }, { 'debit': 0.0, 'credit': 20.0, 'amount_currency': 0.0, 'currency_id': foreign_curr.id, 'account_id': self.exch_income_account.id, }, ]) self.assertRecordValues(amls, [ {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, ]) def test_reconcile_invoice_company_curr_payment_foreign_curr(self): """ Test we always use the payment rate in priority when performing a reconciliation. """ comp_curr = self.company_data['currency'] foreign_curr = self.currency_data['currency'] invoice = self.env['account.move'].create({ 'move_type': 'out_invoice', 'invoice_date': '2017-01-01', 'date': '2017-01-01', 'partner_id': self.partner_a.id, 'currency_id': comp_curr.id, 'invoice_line_ids': [Command.create({ 'product_id': self.product_a.id, 'price_unit': 60.0, 'tax_ids': [], })], }) invoice.action_post() payment = self.env['account.payment.register']\ .with_context(active_model=invoice._name, active_ids=invoice.ids)\ .create({ 'payment_date': '2016-01-01', 'amount': 90.0, 'currency_id': foreign_curr.id, })\ ._create_payments() lines = (invoice + payment.move_id).line_ids\ .filtered(lambda x: x.account_id.account_type == 'asset_receivable') self.assertRecordValues(lines, [ # pylint: disable=bad-whitespace {'amount_residual': 30.0, 'amount_residual_currency': 30.0, 'reconciled': False}, {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, ]) def test_reverse_with_multiple_lines(self): """ Test if all lines from a reversed entry are fully reconciled """ move = self.env['account.move'].create({ 'move_type': 'entry', 'line_ids': [ (0, 0, { 'debit': 1200.0, 'credit': 0.0, 'amount_currency': 3600.0, 'currency_id': self.currency_data['currency'].id, 'account_id': self.company_data['default_account_receivable'].id, }), (0, 0, { 'debit': 0.0, 'credit': 200.0, 'account_id': self.company_data['default_account_payable'].id, }), (0, 0, { 'debit': 0.0, 'credit': 400.0, 'account_id': self.company_data['default_account_payable'].id, }), (0, 0, { 'debit': 0.0, 'credit': 600.0, 'account_id': self.company_data['default_account_payable'].id, }), ], }) move.action_post() lines_to_reconcile = move.line_ids.filtered(lambda x: (x.account_id.reconcile or x.account_id.account_type in ('asset_cash', 'liability_credit_card')) and not x.reconciled) self.assertRecordValues(lines_to_reconcile, [ {'debit': 1200.0, 'credit': 0.0, 'reconciled': False}, {'debit': 0.0, 'credit': 200.0, 'reconciled': False}, {'debit': 0.0, 'credit': 400.0, 'reconciled': False}, {'debit': 0.0, 'credit': 600.0, 'reconciled': False}, ]) reversed_move = move._reverse_moves(cancel=True) reversed_lines = reversed_move.line_ids.filtered(lambda x: ( x.account_id.reconcile or x.account_id.account_type in ('asset_cash', 'liability_credit_card') )) self.assertRecordValues(reversed_lines, [ {'debit': 0.0, 'credit': 1200.0, 'reconciled': True}, {'debit': 200.0, 'credit': 0.0, 'reconciled': True}, {'debit': 400.0, 'credit': 0.0, 'reconciled': True}, {'debit': 600.0, 'credit': 0.0, 'reconciled': True}, ]) self.assertTrue(all([line.full_reconcile_id for line in reversed_lines])) def test_reconcile_special_mexican_workflow_1(self): comp_curr = self.company_data['currency'] foreign_curr = self.env['res.currency'].create({ 'name': "Sushi", 'symbol': '🍣', 'rounding': 0.01, 'rate_ids': [ Command.create({'name': '2019-09-24', 'rate': 0.050800000000}), Command.create({'name': '2019-06-28', 'rate': 0.052235000000}), Command.create({'name': '2019-06-24', 'rate': 0.052686000000}), Command.create({'name': '2019-06-20', 'rate': 0.052353000000}), Command.create({'name': '2019-06-12', 'rate': 0.052072000000}), ], }) refund1 = self.env['account.move'].create({ 'move_type': 'out_refund', 'invoice_date': '2019-06-12', 'date': '2019-06-12', 'partner_id': self.partner_a.id, 'currency_id': self.company_data['currency'].id, 'invoice_line_ids': [Command.create({ 'product_id': self.product_a.id, 'price_unit': 1385.92, 'tax_ids': [], })], }) refund1.action_post() refund1_rec_line = refund1.line_ids.filtered(lambda x: x.account_id.account_type == 'asset_receivable') inv1 = self.env['account.move'].create({ 'move_type': 'out_invoice', 'invoice_date': '2019-06-20', 'date': '2019-06-20', 'partner_id': self.partner_a.id, 'currency_id': self.company_data['currency'].id, 'invoice_line_ids': [Command.create({ 'product_id': self.product_a.id, 'price_unit': 839.40, 'tax_ids': [], })], }) inv1.action_post() inv1_rec_line = inv1.line_ids.filtered(lambda x: x.account_id.account_type == 'asset_receivable') inv2 = self.env['account.move'].create({ 'move_type': 'out_invoice', 'invoice_date': '2019-06-24', 'date': '2019-06-24', 'partner_id': self.partner_a.id, 'currency_id': foreign_curr.id, 'invoice_line_ids': [Command.create({ 'product_id': self.product_a.id, 'price_unit': 1935.72, 'tax_ids': [], })], }) inv2.action_post() inv2_rec_line = inv2.line_ids.filtered(lambda x: x.account_id.account_type == 'asset_receivable') pay1 = self.env['account.payment'].create({ 'partner_type': 'customer', 'payment_type': 'inbound', 'date': '2019-06-28', 'amount': 1907.17, 'partner_id': self.partner_a.id, 'currency_id': foreign_curr.id, }) pay1_liquidity_line = pay1.line_ids.filtered(lambda x: x.account_id.account_type != 'asset_receivable') pay1_rec_line = pay1.line_ids.filtered(lambda x: x.account_id.account_type == 'asset_receivable') pay1.action_post() pay1.write({'line_ids': [ Command.update(pay1_liquidity_line.id, {'debit': 36511.34}), Command.update(pay1_rec_line.id, {'credit': 36511.34}), ]}) pay2 = self.env['account.payment'].create({ 'partner_type': 'customer', 'payment_type': 'inbound', 'date': '2019-09-24', 'amount': 0.09, 'partner_id': self.partner_a.id, 'currency_id': foreign_curr.id, }) pay2.action_post() pay2_rec_line = pay2.line_ids.filtered(lambda x: x.account_id.account_type == 'asset_receivable') # 1st reconciliation refund1 + inv1 self.assert_invoice_outstanding_to_reconcile_widget(refund1, { inv1.id: 839.40, inv2.id: 36740.69, }) self.assertRecordValues(refund1_rec_line + inv1_rec_line, [ {'amount_residual': -1385.92, 'amount_residual_currency': -1385.92, 'reconciled': False}, {'amount_residual': 839.40, 'amount_residual_currency': 839.40, 'reconciled': False}, ]) (refund1_rec_line + inv1_rec_line).reconcile() partials = self._get_partials(refund1_rec_line + inv1_rec_line) self.assertRecordValues(partials, [{ 'amount': 839.4, 'debit_amount_currency': 839.4, 'credit_amount_currency': 839.4, 'debit_move_id': inv1_rec_line.id, 'credit_move_id': refund1_rec_line.id, 'exchange_move_id': None, }]) self.assertRecordValues(refund1_rec_line + inv1_rec_line, [ {'amount_residual': -546.52, 'amount_residual_currency': -546.52, 'reconciled': False}, {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, ]) self.assert_invoice_outstanding_reconciled_widget(refund1, { inv1.id: 839.40, }) # 2th reconciliation refund1 + inv2 self.assert_invoice_outstanding_to_reconcile_widget(refund1, { inv2.id: 36740.69, }) self.assertRecordValues(refund1_rec_line + inv2_rec_line, [ {'amount_residual': -546.52, 'amount_residual_currency': -546.52, 'reconciled': False}, {'amount_residual': 36740.69, 'amount_residual_currency': 1935.72, 'reconciled': False}, ]) partials = self._get_partials(refund1_rec_line + inv2_rec_line) (refund1_rec_line + inv2_rec_line).reconcile() partials = self._get_partials(refund1_rec_line + inv2_rec_line) - partials self.assertRecordValues(partials, [ { 'amount': 540.18, 'debit_amount_currency': 28.46, 'credit_amount_currency': 540.18, 'debit_move_id': inv2_rec_line.id, 'credit_move_id': refund1_rec_line.id, }, { 'amount': 6.34, 'debit_amount_currency': 6.34, 'credit_amount_currency': 6.34, 'debit_move_id': partials.exchange_move_id.line_ids[0].id, 'credit_move_id': refund1_rec_line.id, }, ]) self.assertRecordValues(partials.exchange_move_id, [{'date': fields.Date.from_string('2019-06-30')}]) self.assertRecordValues(partials.exchange_move_id.line_ids, [ { 'debit': 6.34, 'credit': 0.0, 'amount_currency': 6.34, 'currency_id': comp_curr.id, 'account_id': refund1_rec_line.account_id.id, }, { 'debit': 0.0, 'credit': 6.34, 'amount_currency': -6.34, 'currency_id': comp_curr.id, 'account_id': self.exch_income_account.id, }, ]) self.assertRecordValues(refund1_rec_line + inv2_rec_line, [ {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, {'amount_residual': 36200.51, 'amount_residual_currency': 1907.26, 'reconciled': False}, ]) self.assert_invoice_outstanding_reconciled_widget(refund1, { inv1.id: 839.40, inv2.id: 540.18, partials.exchange_move_id.id: 6.34, }) self.assert_invoice_outstanding_to_reconcile_widget(refund1, {}) # 3th reconciliation inv1 + pay1 self.assert_invoice_outstanding_reconciled_widget(inv2, { refund1.id: 28.46, partials.exchange_move_id.id: 6.34, }) self.assert_invoice_outstanding_to_reconcile_widget(inv2, { pay1.move_id.id: 1907.17, pay2.move_id.id: 0.09, }) self.assertRecordValues(inv2_rec_line + pay1_rec_line, [ {'amount_residual': 36200.51, 'amount_residual_currency': 1907.26, 'reconciled': False}, {'amount_residual': -36511.34, 'amount_residual_currency': -1907.17, 'reconciled': False}, ]) partials = self._get_partials(inv2_rec_line + pay1_rec_line) (inv2_rec_line + pay1_rec_line).reconcile() partials = self._get_partials(inv2_rec_line + pay1_rec_line) - partials self.assertRecordValues(partials, [ { 'amount': 36198.80, 'debit_amount_currency': 1907.17, 'credit_amount_currency': 1907.17, 'debit_move_id': inv2_rec_line.id, 'credit_move_id': pay1_rec_line.id, }, { 'amount': 312.54, 'debit_amount_currency': 0.0, 'credit_amount_currency': 0.0, 'debit_move_id': partials.exchange_move_id.line_ids[0].id, 'credit_move_id': pay1_rec_line.id, }, ]) self.assertRecordValues(partials.exchange_move_id, [{'date': fields.Date.from_string('2019-06-30')}]) self.assertRecordValues(partials.exchange_move_id.line_ids, [ { 'debit': 312.54, 'credit': 0.0, 'amount_currency': 0.0, 'currency_id': foreign_curr.id, 'account_id': pay1_rec_line.account_id.id, }, { 'debit': 0.0, 'credit': 312.54, 'amount_currency': 0.0, 'currency_id': foreign_curr.id, 'account_id': self.exch_income_account.id, }, ]) self.assertRecordValues(inv2_rec_line + pay1_rec_line, [ {'amount_residual': 1.71, 'amount_residual_currency': 0.09, 'reconciled': False}, {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, ]) payment_exchange_id = inv2_rec_line.matched_credit_ids.filtered(lambda x: x not in partials) self.assert_invoice_outstanding_reconciled_widget(inv2, { refund1.id: 28.46, pay1.move_id.id: 1907.17, partials.exchange_move_id.id: 312.54, payment_exchange_id[0].exchange_move_id.id: 6.34, }) # 4th reconciliation inv2 + pay2 self.assert_invoice_outstanding_to_reconcile_widget(inv2, { pay2.move_id.id: 0.09, }) self.assertRecordValues(inv2_rec_line + pay2_rec_line, [ {'amount_residual': 1.71, 'amount_residual_currency': 0.09, 'reconciled': False}, {'amount_residual': -1.77, 'amount_residual_currency': -0.09, 'reconciled': False}, ]) partials = self._get_partials(inv2_rec_line + pay2_rec_line) (inv2_rec_line + pay2_rec_line).reconcile() partials = self._get_partials(inv2_rec_line + pay2_rec_line) - partials self.assertRecordValues(partials, [ { 'amount': 1.71, 'debit_amount_currency': 0.09, 'credit_amount_currency': 0.09, 'debit_move_id': inv2_rec_line.id, 'credit_move_id': pay2_rec_line.id, }, { 'amount': 0.06, 'debit_amount_currency': 0.0, 'credit_amount_currency': 0.0, 'debit_move_id': partials.exchange_move_id.line_ids[0].id, 'credit_move_id': pay2_rec_line.id, }, ]) self.assertRecordValues(partials.exchange_move_id, [{'date': fields.Date.from_string('2019-09-30')}]) self.assertRecordValues(partials.exchange_move_id.line_ids, [ { 'debit': 0.06, 'credit': 0.0, 'amount_currency': 0.0, 'currency_id': foreign_curr.id, 'account_id': inv2_rec_line.account_id.id, }, { 'debit': 0.0, 'credit': 0.06, 'amount_currency': 0.0, 'currency_id': foreign_curr.id, 'account_id': self.exch_income_account.id, }, ]) self.assertRecordValues(inv2_rec_line + pay2_rec_line, [ {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, ]) payment_exchange_id = inv2_rec_line.matched_credit_ids.filtered(lambda x: x not in partials) self.assert_invoice_outstanding_reconciled_widget(inv2, { refund1.id: 28.46, pay1.move_id.id: 1907.17, pay2.move_id.id: 0.09, partials.exchange_move_id.id: 0.06, payment_exchange_id[0].exchange_move_id.id: 6.34, payment_exchange_id[1].exchange_move_id.id: 312.54, }) self.assert_invoice_outstanding_to_reconcile_widget(inv2, {}) self.assertRecordValues(inv2_rec_line.full_reconcile_id, [{'exchange_move_id': None}]) def test_reconcile_special_mexican_workflow_2(self): comp_curr = self.company_data['currency'] foreign_curr = self.env['res.currency'].create({ 'name': "Sushi", 'symbol': '🍣', 'rounding': 0.01, 'rate_ids': [ Command.create({'name': '2019-09-24', 'rate': 0.050800000000}), Command.create({'name': '2019-06-28', 'rate': 0.052235000000}), Command.create({'name': '2019-06-24', 'rate': 0.052686000000}), Command.create({'name': '2019-06-20', 'rate': 0.052353000000}), Command.create({'name': '2019-06-12', 'rate': 0.052072000000}), ], }) refund1 = self.env['account.move'].create({ 'move_type': 'out_refund', 'invoice_date': '2019-06-12', 'date': '2019-06-12', 'partner_id': self.partner_a.id, 'currency_id': self.company_data['currency'].id, 'invoice_line_ids': [Command.create({ 'product_id': self.product_a.id, 'price_unit': 1385.92, 'tax_ids': [], })], }) refund1.action_post() refund1_rec_line = refund1.line_ids.filtered(lambda x: x.account_id.account_type == 'asset_receivable') inv1 = self.env['account.move'].create({ 'move_type': 'out_invoice', 'invoice_date': '2019-06-20', 'date': '2019-06-20', 'partner_id': self.partner_a.id, 'currency_id': self.company_data['currency'].id, 'invoice_line_ids': [Command.create({ 'product_id': self.product_a.id, 'price_unit': 839.40, 'tax_ids': [], })], }) inv1.action_post() inv1_rec_line = inv1.line_ids.filtered(lambda x: x.account_id.account_type == 'asset_receivable') inv2 = self.env['account.move'].create({ 'move_type': 'out_invoice', 'invoice_date': '2019-06-24', 'date': '2019-06-24', 'partner_id': self.partner_a.id, 'currency_id': foreign_curr.id, 'invoice_line_ids': [Command.create({ 'product_id': self.product_a.id, 'price_unit': 1935.72, 'tax_ids': [], })], }) inv2.action_post() inv2_rec_line = inv2.line_ids.filtered(lambda x: x.account_id.account_type == 'asset_receivable') pay1 = self.env['account.payment'].create({ 'partner_type': 'customer', 'payment_type': 'inbound', 'date': '2019-06-28', 'amount': 1907.17, 'partner_id': self.partner_a.id, 'currency_id': foreign_curr.id, }) pay1_liquidity_line = pay1.line_ids.filtered(lambda x: x.account_id.account_type != 'asset_receivable') pay1_rec_line = pay1.line_ids.filtered(lambda x: x.account_id.account_type == 'asset_receivable') pay1.action_post() pay1.write({'line_ids': [ Command.update(pay1_liquidity_line.id, {'debit': 36511.34}), Command.update(pay1_rec_line.id, {'credit': 36511.34}), ]}) pay2 = self.env['account.payment'].create({ 'partner_type': 'customer', 'payment_type': 'inbound', 'date': '2019-09-24', 'amount': 0.09, 'partner_id': self.partner_a.id, 'currency_id': foreign_curr.id, }) pay2.action_post() pay2_rec_line = pay2.line_ids.filtered(lambda x: x.account_id.account_type == 'asset_receivable') self.assertRecordValues(refund1_rec_line + inv1_rec_line + inv2_rec_line + pay1_rec_line + pay2_rec_line, [ {'amount_residual': -1385.92, 'amount_residual_currency': -1385.92}, {'amount_residual': 839.40, 'amount_residual_currency': 839.40}, {'amount_residual': 36740.69, 'amount_residual_currency': 1935.72}, {'amount_residual': -36511.34, 'amount_residual_currency': -1907.17}, {'amount_residual': -1.77, 'amount_residual_currency': -0.09}, ]) # 1st reconciliation refund1 + inv1 self.assert_invoice_outstanding_to_reconcile_widget(refund1, { inv1.id: 839.40, inv2.id: 36740.69, }) self.assertRecordValues(refund1_rec_line + inv1_rec_line, [ {'amount_residual': -1385.92, 'amount_residual_currency': -1385.92, 'reconciled': False}, {'amount_residual': 839.40, 'amount_residual_currency': 839.40, 'reconciled': False}, ]) (refund1_rec_line + inv1_rec_line).reconcile() partials = self._get_partials(refund1_rec_line + inv1_rec_line) self.assertRecordValues(partials, [{ 'amount': 839.4, 'debit_amount_currency': 839.4, 'credit_amount_currency': 839.4, 'debit_move_id': inv1_rec_line.id, 'credit_move_id': refund1_rec_line.id, 'exchange_move_id': None, }]) self.assertRecordValues(refund1_rec_line + inv1_rec_line, [ {'amount_residual': -546.52, 'amount_residual_currency': -546.52, 'reconciled': False}, {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, ]) self.assert_invoice_outstanding_reconciled_widget(refund1, { inv1.id: 839.40, }) # 2th reconciliation refund1 + inv2 self.assert_invoice_outstanding_to_reconcile_widget(inv2, { refund1.id: 28.46, pay1.move_id.id: 1907.17, pay2.move_id.id: 0.09, }) self.assertRecordValues(refund1_rec_line + inv2_rec_line, [ {'amount_residual': -546.52, 'amount_residual_currency': -546.52, 'reconciled': False}, {'amount_residual': 36740.69, 'amount_residual_currency': 1935.72, 'reconciled': False}, ]) (inv2_rec_line + pay1_rec_line).reconcile() partials = self._get_partials(inv2_rec_line + pay1_rec_line) self.assertRecordValues(partials, [ { 'amount': 36198.8, 'debit_amount_currency': 1907.17, 'credit_amount_currency': 1907.17, 'debit_move_id': inv2_rec_line.id, 'credit_move_id': pay1_rec_line.id, }, { 'amount': 312.54, 'debit_amount_currency': 0.0, 'credit_amount_currency': 0.0, 'debit_move_id': partials.exchange_move_id.line_ids[0].id, 'credit_move_id': pay1_rec_line.id, }, ]) self.assertRecordValues(partials.exchange_move_id, [{'date': fields.Date.from_string('2019-06-30')}]) self.assertRecordValues(partials.exchange_move_id.line_ids, [ { 'debit': 312.54, 'credit': 0.0, 'amount_currency': 0.0, 'currency_id': foreign_curr.id, 'account_id': inv2_rec_line.account_id.id, }, { 'debit': 0.0, 'credit': 312.54, 'amount_currency': 0.0, 'currency_id': foreign_curr.id, 'account_id': self.exch_income_account.id, }, ]) self.assertRecordValues(inv2_rec_line + pay1_rec_line, [ {'amount_residual': 541.89, 'amount_residual_currency': 28.55, 'reconciled': False}, {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, ]) self.assert_invoice_outstanding_reconciled_widget(inv2, { pay1.move_id.id: 1907.17, partials.exchange_move_id.id: 312.54, }) self.assert_invoice_outstanding_to_reconcile_widget(inv2, { refund1.id: 28.46, pay2.move_id.id: 0.09, }) # 3th reconciliation refund1 + inv2 self.assert_invoice_outstanding_to_reconcile_widget(refund1, { inv2.id: 541.89, }) self.assertRecordValues(refund1_rec_line + inv2_rec_line, [ {'amount_residual': -546.52, 'amount_residual_currency': -546.52, 'reconciled': False}, {'amount_residual': 541.89, 'amount_residual_currency': 28.55, 'reconciled': False}, ]) partials = self._get_partials(refund1_rec_line + inv2_rec_line) (refund1_rec_line + inv2_rec_line).reconcile() partials = self._get_partials(refund1_rec_line + inv2_rec_line) - partials self.assertRecordValues(partials, [ { 'amount': 540.18, 'debit_amount_currency': 28.46, 'credit_amount_currency': 540.18, 'debit_move_id': inv2_rec_line.id, 'credit_move_id': refund1_rec_line.id, }, { 'amount': 6.34, 'debit_amount_currency': 6.34, 'credit_amount_currency': 6.34, 'debit_move_id': partials.exchange_move_id.line_ids[0].id, 'credit_move_id': refund1_rec_line.id, }, ]) self.assertRecordValues(partials.exchange_move_id, [{'date': fields.Date.from_string('2019-06-30')}]) self.assertRecordValues(partials.exchange_move_id.line_ids, [ { 'debit': 6.34, 'credit': 0.0, 'amount_currency': 6.34, 'currency_id': comp_curr.id, 'account_id': refund1_rec_line.account_id.id, }, { 'debit': 0.0, 'credit': 6.34, 'amount_currency': -6.34, 'currency_id': comp_curr.id, 'account_id': self.exch_income_account.id, }, ]) self.assertRecordValues(refund1_rec_line + inv2_rec_line, [ {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, {'amount_residual': 1.71, 'amount_residual_currency': 0.09, 'reconciled': False}, ]) self.assert_invoice_outstanding_reconciled_widget(refund1, { inv1.id: 839.40, inv2.id: 540.18, partials.exchange_move_id.id: 6.34, }) self.assert_invoice_outstanding_to_reconcile_widget(refund1, {}) self.assert_invoice_outstanding_to_reconcile_widget(inv2, { pay2.move_id.id: 0.09, }) partials = self._get_partials(inv2_rec_line + pay2_rec_line) (inv2_rec_line + pay2_rec_line).reconcile() partials = self._get_partials(inv2_rec_line + pay2_rec_line) - partials self.assertRecordValues(partials, [ { 'amount': 1.71, 'debit_amount_currency': 0.09, 'credit_amount_currency': 0.09, 'debit_move_id': inv2_rec_line.id, 'credit_move_id': pay2_rec_line.id, }, { 'amount': 0.06, 'debit_amount_currency': 0.0, 'credit_amount_currency': 0.0, 'debit_move_id': partials.exchange_move_id.line_ids[0].id, 'credit_move_id': pay2_rec_line.id, }, ]) self.assertRecordValues(partials.exchange_move_id, [{'date': fields.Date.from_string('2019-09-30')}]) self.assertRecordValues(partials.exchange_move_id.line_ids, [ { 'debit': 0.06, 'credit': 0.0, 'amount_currency': 0.0, 'currency_id': foreign_curr.id, 'account_id': inv2_rec_line.account_id.id, }, { 'debit': 0.0, 'credit': 0.06, 'amount_currency': 0.0, 'currency_id': foreign_curr.id, 'account_id': self.exch_income_account.id, }, ]) self.assertRecordValues(inv2_rec_line + pay2_rec_line, [ {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, ]) payment_exchange_id = inv2_rec_line.matched_credit_ids.filtered(lambda x: x not in partials) self.assert_invoice_outstanding_reconciled_widget(inv2, { refund1.id: 28.46, pay1.move_id.id: 1907.17, pay2.move_id.id: 0.09, partials.exchange_move_id.id: 0.06, payment_exchange_id[0].exchange_move_id.id: 312.54, payment_exchange_id[1].exchange_move_id.id: 6.34, }) self.assert_invoice_outstanding_to_reconcile_widget(inv2, {}) self.assertRecordValues(inv2_rec_line.full_reconcile_id, [{'exchange_move_id': None}]) def test_migration_to_new_reconciliation_same_foreign_currency(self): foreign_curr = self.currency_data['currency'] line_1 = self.create_line_for_reconciliation(-60.0, -120.0, foreign_curr, '2017-01-01') line_2 = self.create_line_for_reconciliation(80.0, 240.0, foreign_curr, '2016-01-01') # Create the partial as it should be created in previous version. self.env['account.partial.reconcile'].create({ 'amount': 60.0, 'debit_amount_currency': 120.0, 'credit_amount_currency': 120.0, 'debit_move_id': line_2.id, 'credit_move_id': line_1.id, }) self.assertRecordValues(line_1 + line_2, [ {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, {'amount_residual': 20.0, 'amount_residual_currency': 120.0, 'reconciled': False}, ]) # Reconcile using the "new" reconciliation. line_3 = self.create_line_for_reconciliation(-15.0, -30.0, foreign_curr, '2017-01-01') (line_2 + line_3).reconcile() self.assertRecordValues(line_2 + line_3, [ {'amount_residual': 10.0, 'amount_residual_currency': 90.0, 'reconciled': False}, {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, ]) line_4 = self.create_line_for_reconciliation(-30.0, -90.0, foreign_curr, '2016-01-01') (line_2 + line_4).reconcile() 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_migration_to_new_reconciliation_multiple_currencies_fix_residual_with_writeoff(self): comp_curr = self.company_data['currency'] foreign_curr1 = self.currency_data['currency'] line_1 = self.create_line_for_reconciliation(600.0, 1200.0, foreign_curr1, '2017-01-01') line_2 = self.create_line_for_reconciliation(-800.0, -2400.0, foreign_curr1, '2016-01-01') line_3 = self.create_line_for_reconciliation(400.0, 400.0, comp_curr, '2016-01-01') # Create the partials as it should be created in previous version. self.env['account.partial.reconcile'].create([ { 'amount': 600.0, 'debit_amount_currency': 1200.0, 'credit_amount_currency': 1200.0, 'debit_move_id': line_1.id, 'credit_move_id': line_2.id, }, { 'amount': 200.0, 'debit_amount_currency': 200.0, 'credit_amount_currency': 600.0, 'debit_move_id': line_3.id, 'credit_move_id': line_2.id, }, ]) self.assertRecordValues(line_1 + line_2 + line_3, [ {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, {'amount_residual': 0.0, 'amount_residual_currency': -600.0, 'reconciled': False}, {'amount_residual': 200.0, 'amount_residual_currency': 200.0, 'reconciled': False}, ]) # Fix 'line_2' & 'line_4' using the "new" reconciliation. line_4 = self.create_line_for_reconciliation(0.0, 600.0, foreign_curr1, '2016-01-01') line_5 = self.create_line_for_reconciliation(-200.0, -200.0, comp_curr, '2016-01-01') (line_2 + line_3 + line_4 + line_5).reconcile() self.assertRecordValues(line_1 + line_2 + line_3 + line_4 + line_5, [ {'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': True}, {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, ]) def test_reconcile_rounding_issue(self): rate = 1/1.5289 currency = self.setup_multi_currency_data(default_values={ 'name': 'XXX', 'symbol': 'XXX', 'currency_unit_label': 'XX', 'currency_subunit_label': 'X', 'rounding': 0.01, }, rate2016=rate, rate2017=rate)['currency'] # Create an invoice 26.45 XXX = 40.43 USD invoice = self.env['account.move'].create({ 'move_type': 'out_invoice', 'partner_id': self.partner_a.id, 'currency_id': currency.id, 'date': '2017-01-01', 'invoice_date': '2017-01-01', 'invoice_line_ids': [(0, 0, { 'product_id': self.product_a.id, 'price_unit': 23.0, 'tax_ids': [(6, 0, self.company_data['default_tax_sale'].ids)], })], }) invoice.action_post() # Pay it with 100.0 USD self.env['account.payment.register']\ .with_context(active_model='account.move', active_ids=invoice.ids)\ .create({'amount': 100.0, 'currency_id': self.company_data['currency'].id})\ ._create_payments() self.assertTrue(invoice.payment_state in ('in_payment', 'paid')) def test_reconcile_plan(self): @contextmanager def rollback(): savepoint = self.cr.savepoint() yield savepoint.rollback() comp_curr = self.company_data['currency'] line_1 = self.create_line_for_reconciliation(600.0, 600.0, comp_curr, '2017-01-01') line_2 = self.create_line_for_reconciliation(-100.0, -100.0, comp_curr, '2017-01-02') line_3 = self.create_line_for_reconciliation(700.0, 700.0, comp_curr, '2017-01-03') line_5 = self.create_line_for_reconciliation(-700.0, -700.0, comp_curr, '2017-01-04') line_4 = self.create_line_for_reconciliation(-500.0, -500.0, comp_curr, '2017-01-05') with rollback(): # 5 batches of 1 aml. This won't reconcile anything. self.env['account.move.line']._reconcile_plan([line_1, line_2, line_3, line_4, line_5]) self.assertFalse(self._get_partials(line_1 + line_2 + line_3 + line_4 + line_5)) with rollback(): # one batch of 5 amls. self.env['account.move.line']._reconcile_plan([line_1 + line_2 + line_3 + line_4 + line_5]) self.assertRecordValues( self._get_partials(line_1 + line_2 + line_3 + line_4 + line_5), [ {'amount': 100.0, 'debit_move_id': line_1.id, 'credit_move_id': line_2.id}, {'amount': 500.0, 'debit_move_id': line_1.id, 'credit_move_id': line_5.id}, {'amount': 200.0, 'debit_move_id': line_3.id, 'credit_move_id': line_5.id}, {'amount': 500.0, 'debit_move_id': line_3.id, 'credit_move_id': line_4.id}, ], ) with rollback(): # Reconcile line_3 + line_5 and line_1 + line_4. line_2 is alone so will not be reconciled. self.env['account.move.line']._reconcile_plan([line_3 + line_5, line_1 + line_4, line_2]) self.assertRecordValues( self._get_partials(line_1 + line_2 + line_3 + line_4 + line_5), [ {'amount': 700.0, 'debit_move_id': line_3.id, 'credit_move_id': line_5.id}, {'amount': 500.0, 'debit_move_id': line_1.id, 'credit_move_id': line_4.id}, ], ) with rollback(): # Reconcile line_3 + line_5 first, then line_1 + line_4, then the remaining amls with line_2. self.env['account.move.line']._reconcile_plan([[line_3 + line_5, line_1 + line_4, line_2]]) self.assertRecordValues( self._get_partials(line_1 + line_2 + line_3 + line_4 + line_5), [ {'amount': 700.0, 'debit_move_id': line_3.id, 'credit_move_id': line_5.id}, {'amount': 500.0, 'debit_move_id': line_1.id, 'credit_move_id': line_4.id}, {'amount': 100.0, 'debit_move_id': line_1.id, 'credit_move_id': line_2.id}, ], ) with rollback(): # Same as the previous test but with a lot of sub-plan to test the result is the same. self.env['account.move.line']._reconcile_plan([[[line_3 + line_5], [[line_1 + line_4], line_2]]]) self.assertRecordValues( self._get_partials(line_1 + line_2 + line_3 + line_4 + line_5), [ {'amount': 700.0, 'debit_move_id': line_3.id, 'credit_move_id': line_5.id}, {'amount': 500.0, 'debit_move_id': line_1.id, 'credit_move_id': line_4.id}, {'amount': 100.0, 'debit_move_id': line_1.id, 'credit_move_id': line_2.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. ''' self.env.company.tax_exigibility = True self.cash_basis_tax_tiny_amount.amount = 0.01 cash_basis_move = self.env['account.move'].with_context(skip_invoice_sync=True).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 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, }), (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, }), # 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, }), ] }) cash_basis_move.line_ids.flush_model() 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) tax_cash_basis_moves = self._get_caba_moves(receivable_lines_1.move_id) receivable_lines_1.reconcile() tax_cash_basis_moves = self._get_caba_moves(receivable_lines_1.move_id) - tax_cash_basis_moves self.assertFullReconcile(receivable_lines_1.full_reconcile_id, receivable_lines_1) self.assertEqual(len(tax_cash_basis_moves), 2) self.assertRecordValues(tax_cash_basis_moves[0].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.assertRecordValues(tax_cash_basis_moves[1].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.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) tax_cash_basis_moves = self._get_caba_moves(receivable_lines_2.move_id) receivable_lines_2.reconcile() tax_cash_basis_moves = self._get_caba_moves(receivable_lines_2.move_id) - tax_cash_basis_moves self.assertFullReconcile(receivable_lines_2.full_reconcile_id, receivable_lines_2) self.assertEqual(len(tax_cash_basis_moves), 3) self.assertRecordValues(tax_cash_basis_moves[0].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(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(tax_cash_basis_moves[2].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.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) tax_cash_basis_moves = self._get_caba_moves(payable_lines_1.move_id) payable_lines_1.reconcile() tax_cash_basis_moves = self._get_caba_moves(payable_lines_1.move_id) - tax_cash_basis_moves self.assertFullReconcile(payable_lines_1.full_reconcile_id, payable_lines_1) self.assertEqual(len(tax_cash_basis_moves), 1) self.assertRecordValues(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(payable_lines_1.full_reconcile_id.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. ''' self.env.company.tax_exigibility = True 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'].with_context(skip_invoice_sync=True).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 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, }), (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, }), # 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) receivable_lines_1.reconcile() tax_cash_basis_moves = self._get_caba_moves(receivable_lines_1.move_id) self.assertFullReconcileAccount(receivable_lines_1.full_reconcile_id, self.extra_receivable_account_1) self.assertEqual(len(tax_cash_basis_moves), 2) self.assertRecordValues(tax_cash_basis_moves[0].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.assertRecordValues(tax_cash_basis_moves[1].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}, ]) caba_transition_lines_1 = tax_cash_basis_moves.line_ids.filtered(lambda x: x.account_id == self.cash_basis_transfer_account) caba_transition_exchange_moves_1 = caba_transition_lines_1.matched_credit_ids.exchange_move_id self.assertEqual(len(caba_transition_exchange_moves_1), 2) self.assertRecordValues(caba_transition_exchange_moves_1[0].line_ids, [ {'debit': 0.0, 'credit': 1.39, 'amount_currency': 0.0, 'currency_id': currency_id, 'account_id': self.cash_basis_transfer_account.id}, {'debit': 1.39, 'credit': 0.0, 'amount_currency': 0.0, 'currency_id': currency_id, 'account_id': self.env.company.expense_currency_exchange_account_id.id}, ]) self.assertRecordValues(caba_transition_exchange_moves_1[1].line_ids, [ {'debit': 0.0, 'credit': 0.48, 'amount_currency': 0.0, 'currency_id': currency_id, 'account_id': self.cash_basis_transfer_account.id}, {'debit': 0.48, 'credit': 0.0, 'amount_currency': 0.0, 'currency_id': currency_id, 'account_id': self.env.company.expense_currency_exchange_account_id.id}, ]) self.assertAmountsGroupByAccount([ # Account Balance Amount Currency (self.cash_basis_transfer_account, -7.41, -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) tax_cash_basis_moves = self._get_caba_moves(receivable_lines_2.move_id) receivable_lines_2.reconcile() tax_cash_basis_moves = self._get_caba_moves(receivable_lines_2.move_id) - tax_cash_basis_moves self.assertFullReconcileAccount(receivable_lines_2.full_reconcile_id, self.extra_receivable_account_2) self.assertEqual(len(tax_cash_basis_moves), 3) self.assertRecordValues(tax_cash_basis_moves[0].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(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(tax_cash_basis_moves[2].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}, ]) caba_transition_lines_2 = tax_cash_basis_moves.line_ids.filtered(lambda x: x.account_id == self.cash_basis_transfer_account) caba_transition_exchange_moves_2 = caba_transition_lines_2.matched_credit_ids.exchange_move_id self.assertEqual(len(caba_transition_exchange_moves_2), 3) self.assertRecordValues(caba_transition_exchange_moves_2[0].line_ids, [ {'debit': 0.01, 'credit': 0.0, 'amount_currency': 0.0, 'currency_id': currency_id, 'account_id': self.cash_basis_transfer_account.id}, {'debit': 0.0, 'credit': 0.01, 'amount_currency': 0.0, 'currency_id': currency_id, 'account_id': self.env.company.income_currency_exchange_account_id.id}, ]) self.assertRecordValues(caba_transition_exchange_moves_2[1].line_ids, [ {'debit': 0.0, 'credit': 1.86, 'amount_currency': 0.0, 'currency_id': currency_id, 'account_id': self.cash_basis_transfer_account.id}, {'debit': 1.86, 'credit': 0.0, 'amount_currency': 0.0, 'currency_id': currency_id, 'account_id': self.env.company.expense_currency_exchange_account_id.id}, ]) self.assertRecordValues(caba_transition_exchange_moves_2[2].line_ids, [ {'debit': 0.0, 'credit': 1.85, 'amount_currency': 0.0, 'currency_id': currency_id, 'account_id': self.cash_basis_transfer_account.id}, {'debit': 1.85, 'credit': 0.0, 'amount_currency': 0.0, 'currency_id': currency_id, 'account_id': self.env.company.expense_currency_exchange_account_id.id}, ]) self.assertAmountsGroupByAccount([ # Account Balance Amount Currency (self.cash_basis_transfer_account, 0.0, -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) tax_cash_basis_moves = self._get_caba_moves(payable_lines_1.move_id) payable_lines_1.reconcile() tax_cash_basis_moves = self._get_caba_moves(payable_lines_1.move_id) - tax_cash_basis_moves self.assertFullReconcile(payable_lines_1.full_reconcile_id, payable_lines_1) self.assertEqual(len(tax_cash_basis_moves), 1) self.assertRecordValues(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(payable_lines_1.full_reconcile_id.exchange_move_id.line_ids, [ {'account_id': self.cash_basis_base_account.id, 'debit': 0.0, 'credit': 0.0, 'amount_currency': -0.001, 'tax_ids': taxes.ids, 'tax_line_id': False}, {'account_id': self.cash_basis_base_account.id, 'debit': 0.0, 'credit': 0.0, 'amount_currency': 0.001, 'tax_ids': [], 'tax_line_id': False}, ]) # No exchange move should have been created when reconciling the transition account caba_transition_lines_3 = tax_cash_basis_moves.line_ids.filtered(lambda x: x.account_id == self.cash_basis_transfer_account) self.assertFalse(caba_transition_lines_3.matched_credit_ids.exchange_move_id) self.assertAmountsGroupByAccount([ # Account Balance Amount Currency (self.cash_basis_transfer_account, 0.0, 0.0), (self.tax_account_1, -16.68, -33.33), (self.tax_account_2, 0.0, -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 reconcilable. ''' self.env.company.tax_exigibility = True 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 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, }), # 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) receivable_lines.reconcile() tax_cash_basis_moves = self._get_caba_moves(receivable_lines.move_id) self.assertEqual(len(tax_cash_basis_moves), 1) self.assertRecordValues(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) receivable_lines2.reconcile() self.assertTrue(receivable_lines2.full_reconcile_id) exchange_diff = receivable_lines2.full_reconcile_id.exchange_move_id caba_rounding_correction = 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.assertFalse(caba_rounding_correction, "No cash basis rounding correction should have been created, as the difference between amounts is only due to exchange difference.") self.assertAmountsGroupByAccount([ # Account Balance Amount Currency (self.cash_basis_transfer_account, 0.0, 0.0), (self.tax_account_1, -50.0, -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. ''' self.env.company.tax_exigibility = True 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'].with_context(skip_invoice_sync=True).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 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, }), # 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_line_id': self.inbound_payment_method_line.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.account_type == 'asset_receivable') self.assertTrue(receivable_line.full_reconcile_id, "Invoice should be fully paid") self.assertRecordValues(partial_rec.exchange_move_id.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}, ]) exchange_move = receivable_line.full_reconcile_id.exchange_move_id self.assertFalse(exchange_move, "No exchange move difference should be created for the full reconcile object ,as there is no cash basis rounding.") self.assertAmountsGroupByAccount([ # Account Balance Amount Currency (self.cash_basis_transfer_account, 0.0, 0.0), (self.tax_account_1, -15.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. ''' self.env.company.tax_exigibility = True 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'].with_context(skip_invoice_sync=True).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 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, }), # 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() self.env['account.payment.register']\ .with_context(active_model='account.move', active_ids=caba_inv.ids)\ .create({ 'payment_date': '2017-01-01', 'currency_id': currency_id, 'amount': 110.0, })\ ._create_payments() receivable_line = caba_inv.line_ids.filtered(lambda x: x.account_id.account_type == 'asset_receivable') partial_rec = caba_inv.line_ids.matched_credit_ids self.assertTrue(receivable_line.full_reconcile_id, "Invoice should be fully paid") 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': 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}, ]) self.assertRecordValues(partial_rec.exchange_move_id.line_ids, [ {'account_id': self.extra_receivable_account_1.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}, ]) exchange_move = receivable_line.full_reconcile_id.exchange_move_id self.assertFalse(exchange_move, "No exchange move difference should be created for the full reconcile object ,as there is no cash basis rounding.") self.assertAmountsGroupByAccount([ # Account Balance Amount Currency (self.cash_basis_transfer_account, 0.0, 0.0), (self.tax_account_1, -15.0, -10.0), ]) def test_reconcile_cash_basis_exchange_difference_transfer_account_check_entries_4(self): ''' Test the generation of the exchange difference for a tax cash basis journal entry when the tax account is a reconcile one. ''' self.env.company.tax_exigibility = True currency_id = self.currency_data['currency'].id cash_basis_transition_account = self.env['account.account'].create({ 'code': '209.01.01', 'name': 'Cash Basis Transition Account', 'account_type': 'liability_current', 'company_id': self.company_data['company'].id, 'reconcile': True, }) self.cash_basis_tax_a_third_amount.write({ 'cash_basis_transition_account_id': cash_basis_transition_account.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 line (0, 0, { 'debit': 0.0, 'credit': 33.33, 'amount_currency': -100.0, 'currency_id': currency_id, 'account_id': cash_basis_transition_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, }), # 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': 200.0, 'amount_currency': -400.0, 'currency_id': currency_id, 'account_id': self.extra_receivable_account_1.id, }), (0, 0, { 'debit': 200.0, 'credit': 0.0, 'amount_currency': 400.0, 'currency_id': currency_id, 'account_id': self.company_data['default_account_revenue'].id, }), ] }) (cash_basis_move + payment_move).action_post() self.assertAmountsGroupByAccount([ # Account Balance Amount Currency (cash_basis_transition_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) receivable_lines.reconcile() tax_cash_basis_moves = self._get_caba_moves(receivable_lines.move_id) self.assertEqual(len(tax_cash_basis_moves), 1) # Tax values based on payment # Invoice amount 300 (amount currency) with payment rate 2 (400 payment amount divided by 200 invoice balance) # - Base amount: 150 company currency # - Tax amount: 50 company currency self.assertRecordValues(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': cash_basis_transition_account.id}, {'debit': 0.0, 'credit': 50.0, 'amount_currency': -100.0, 'currency_id': currency_id, 'account_id': self.tax_account_1.id}, ]) partial_rec = cash_basis_move.line_ids.matched_credit_ids self.assertRecordValues(partial_rec.exchange_move_id.line_ids, [ {'debit': 66.67, 'credit': 0.0, 'currency_id': currency_id, 'account_id': self.extra_receivable_account_1.id}, {'debit': 0.0, 'credit': 66.67, 'currency_id': currency_id, 'account_id': self.company_data['company'].income_currency_exchange_account_id.id}, ]) # Exchange difference self.assertFalse(receivable_lines.full_reconcile_id.exchange_move_id, "No exchange move difference should be created for the full reconcile object ,as there is no cash basis rounding.") def test_reconcile_cash_basis_refund_multicurrency(self): self.env.company.tax_exigibility = True rates_data = self.setup_multi_currency_data(default_values={ 'name': 'Playmock', 'symbol': '🦌', 'rounding': 0.01, 'currency_unit_label': 'Playmock', 'currency_subunit_label': 'Cent', }, rate2016=0.5, rate2017=0.33333333333333333) invoice = self.env['account.move'].create({ 'move_type': 'out_invoice', 'partner_id': self.partner_a.id, 'currency_id': rates_data['currency'].id, 'invoice_date': '2016-01-01', 'invoice_line_ids': [(0, 0, { 'name': 'dudu', 'account_id': self.company_data['default_account_revenue'].id, 'price_unit': 100.0, 'tax_ids': [(6, 0, self.cash_basis_tax_a_third_amount.ids)], })], }) refund = self.env['account.move'].create({ 'move_type': 'out_refund', 'partner_id': self.partner_a.id, 'currency_id': rates_data['currency'].id, 'invoice_date': '2017-01-01', 'invoice_line_ids': [(0, 0, { 'name': 'dudu', 'account_id': self.company_data['default_account_revenue'].id, 'price_unit': 100.0, 'tax_ids': [(6, 0, self.cash_basis_tax_a_third_amount.ids)], })], }) invoice.action_post() refund.action_post() (refund + invoice).line_ids\ .filtered(lambda x: x.account_id.account_type == 'asset_receivable')\ .reconcile() # Check the cash basis moves self.assertRecordValues( self.env['account.move'].search([('tax_cash_basis_origin_move_id', '=', invoice.id)]).line_ids, [ { 'debit': 200, 'credit': 0, 'amount_currency': 100, 'currency_id': rates_data['currency'].id, 'tax_ids': [], 'tax_repartition_line_id': None, 'tax_tag_ids': [], }, { 'debit': 0, 'credit': 200, 'amount_currency': -100, 'currency_id': rates_data['currency'].id, 'tax_ids': self.cash_basis_tax_a_third_amount.ids, 'tax_repartition_line_id': None, 'tax_tag_ids': self.tax_tags[0].ids, }, { 'debit': 66.66, 'credit': 0, 'amount_currency': 33.33, 'currency_id': rates_data['currency'].id, 'tax_ids': [], 'tax_repartition_line_id': None, 'tax_tag_ids': [], }, { 'debit': 0, 'credit': 66.66, 'amount_currency': -33.33, 'currency_id': rates_data['currency'].id, 'tax_ids': [], 'tax_repartition_line_id': self.cash_basis_tax_a_third_amount.invoice_repartition_line_ids.filtered(lambda x: x.repartition_type == 'tax').id, 'tax_tag_ids': self.tax_tags[1].ids, }, ] ) self.assertRecordValues( self.env['account.move'].search([('tax_cash_basis_origin_move_id', '=', refund.id)]).line_ids, [ { 'debit': 0, 'credit': 300, 'amount_currency': -100, 'currency_id': rates_data['currency'].id, 'tax_ids': [], 'tax_repartition_line_id': None, 'tax_tag_ids': [], }, { 'debit': 300, 'credit': 0, 'amount_currency': 100, 'currency_id': rates_data['currency'].id, 'tax_ids': self.cash_basis_tax_a_third_amount.ids, 'tax_repartition_line_id': None, 'tax_tag_ids': self.tax_tags[2].ids, }, { 'debit': 0, 'credit': 99.99, 'amount_currency': -33.33, 'currency_id': rates_data['currency'].id, 'tax_ids': [], 'tax_repartition_line_id': None, 'tax_tag_ids': [], }, { 'debit': 99.99, 'credit': 0, 'amount_currency': 33.33, 'currency_id': rates_data['currency'].id, 'tax_ids': [], 'tax_repartition_line_id': self.cash_basis_tax_a_third_amount.refund_repartition_line_ids.filtered(lambda x: x.repartition_type == 'tax').id, 'tax_tag_ids': self.tax_tags[3].ids, }, ] ) # Check the exchange difference move, to be sure no cash basis rounding data was added into it self.assertRecordValues( invoice.line_ids.filtered(lambda x: x.account_id.account_type == 'asset_receivable').matched_credit_ids.exchange_move_id.line_ids, [ { 'debit': 133.33, 'credit': 0, 'amount_currency': 0, 'currency_id': rates_data['currency'].id, 'tax_ids': [], 'tax_repartition_line_id': None, 'tax_tag_ids': [], }, { 'debit': 0, 'credit': 133.33, 'amount_currency': 0, 'currency_id': rates_data['currency'].id, 'tax_ids': [], 'tax_repartition_line_id': None, 'tax_tag_ids': [], }, ] ) self.assertFalse(invoice.line_ids.full_reconcile_id.exchange_move_id) def test_reconcile_cash_basis_revert(self): ''' Ensure the cash basis journal entry can be reverted. ''' self.env.company.tax_exigibility = True self.cash_basis_transfer_account.reconcile = True self.cash_basis_tax_a_third_amount.cash_basis_transition_account_id = self.tax_account_1 invoice_move = self.env['account.move'].with_context(skip_invoice_sync=True).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 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, }), # Receivable line (0, 0, { 'debit': 133.33, 'credit': 0.0, 'account_id': self.extra_receivable_account_1.id, }), ] }) payment_move = self.env['account.move'].with_context(skip_invoice_sync=True).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) receivable_lines.reconcile() tax_cash_basis_moves = self._get_caba_moves(receivable_lines.move_id) # == Check reconciliation of invoice with payment == self.assertFullReconcile(receivable_lines.full_reconcile_id, receivable_lines) self.assertEqual(len(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 = 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(receivable_lines.full_reconcile_id, 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): self.env.company.tax_exigibility = 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 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, }), # 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': [ Command.create({ 'price_unit': 100.0, 'account_id': self.company_data['default_account_revenue'].id, 'tax_ids': [(6, 0, self.cash_basis_tax_a_third_amount.ids)], }), ] }) refund_move.line_ids.filtered(lambda l: l.display_type == 'payment_term').account_id = self.extra_receivable_account_1 (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) receivable_lines.reconcile() tax_cash_basis_moves = self._get_caba_moves(receivable_lines.move_id) self.assertFullReconcile(receivable_lines.full_reconcile_id, receivable_lines) self.assertEqual(len(tax_cash_basis_moves), 2) tax_cash_basis_moves = tax_cash_basis_moves.sorted(lambda move: move.tax_cash_basis_origin_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}, ]) def test_reconcile_cash_basis_tax_grid_reversal(self): invoice = self.env['account.move'].create({ 'move_type': 'out_invoice', 'partner_id': self.partner_a.id, 'date': '2016-01-01', 'invoice_line_ids': [(0, 0, { 'product_id': self.product_a.id, 'price_unit': 1000.0, 'tax_ids': [(6, 0, self.cash_basis_tax_a_third_amount.ids)], })], }) invoice.action_post() self.assertRecordValues(invoice.line_ids.sorted('balance'), [ {'debit': 0.0, 'credit': 1000.0, 'tax_tag_ids': [], 'account_id': self.company_data['default_account_revenue'].id}, {'debit': 0.0, 'credit': 333.33, 'tax_tag_ids': [], 'account_id': self.cash_basis_transfer_account.id}, {'debit': 1333.33, 'credit': 0.0, 'tax_tag_ids': [], 'account_id': self.company_data['default_account_receivable'].id}, ]) reversal_wizard = self.env['account.move.reversal']\ .with_context(active_model='account.move', active_ids=invoice.ids)\ .create({ 'reason': "test_reconcile_cash_basis_tax_grid_reversal", 'journal_id': invoice.journal_id.id, }) refund = self.env['account.move'].browse(reversal_wizard.refund_moves()['res_id']) refund.action_post() self.assertRecordValues(refund.line_ids.sorted('balance'), [ {'debit': 0.0, 'credit': 1333.33, 'tax_tag_ids': [], 'account_id': self.company_data['default_account_receivable'].id}, {'debit': 333.33, 'credit': 0.0, 'tax_tag_ids': [], 'account_id': self.cash_basis_transfer_account.id}, {'debit': 1000.0, 'credit': 0.0, 'tax_tag_ids': [], 'account_id': self.company_data['default_account_revenue'].id}, ]) reversal_wizard = self.env['account.move.reversal']\ .with_context(active_model='account.move', active_ids=refund.ids)\ .create({ 'reason': "test_reconcile_cash_basis_tax_grid_reversal", 'journal_id': refund.journal_id.id, }) reversed_refund = self.env['account.move'].browse(reversal_wizard.refund_moves()['res_id']) self.assertRecordValues(reversed_refund.line_ids.sorted('balance'), [ {'debit': 0.0, 'credit': 1000.0, 'tax_tag_ids': [], 'account_id': self.company_data['default_account_revenue'].id}, {'debit': 0.0, 'credit': 333.33, 'tax_tag_ids': [], 'account_id': self.cash_basis_transfer_account.id}, {'debit': 1333.33, 'credit': 0.0, 'tax_tag_ids': [], 'account_id': self.company_data['default_account_receivable'].id}, ]) def test_reconcile_cash_basis_tax_grid_multi_taxes(self): ''' Test the tax grid when reconciling an invoice with multiple taxes/tax repartition. ''' self.env.company.tax_exigibility = True 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'].with_context(skip_invoice_sync=True).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 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, }), (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, }), # 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) receivable_lines.reconcile() tax_cash_basis_moves = self._get_caba_moves(receivable_lines.move_id) self.assertFullReconcile(receivable_lines.full_reconcile_id, receivable_lines) self.assertEqual(len(tax_cash_basis_moves), 1) self.assertRecordValues(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}, ]) def test_matching_number_full_reconcile(self): currency = self.env.company.currency_id line_a = self.create_line_for_reconciliation(1000, 1000, currency, '2016-01-01') line_b = self.create_line_for_reconciliation(-1000, -1000, currency, '2016-01-01') (line_a + line_b).reconcile() self.assertFullReconcile(line_a.full_reconcile_id, (line_a + line_b)) self.assertEqual(line_a.matching_number, str(line_a.full_reconcile_id.id)) self.assertEqual(line_a.matching_number, line_b.matching_number) def test_matching_number_partial_single_reconcile(self): currency = self.env.company.currency_id line_a = self.create_line_for_reconciliation(1000, 1000, currency, '2016-01-01') line_b = self.create_line_for_reconciliation(-500, -500, currency, '2016-01-01') (line_a + line_b).reconcile() self.assertEqual(line_a.matching_number, f'P{line_a.matched_credit_ids.id}') self.assertEqual(line_a.matching_number, line_b.matching_number) def test_matching_number_partial_multi_reconcile(self): currency = self.env.company.currency_id line_a = self.create_line_for_reconciliation(1000, 1000, currency, '2016-01-01') line_b = self.create_line_for_reconciliation(-500, -500, currency, '2016-01-01') line_c = self.create_line_for_reconciliation(-1000, -1000, currency, '2016-01-01') line_d = self.create_line_for_reconciliation(1000, 1000, currency, '2016-01-01') (line_a + line_b).reconcile() (line_a + line_c).reconcile() (line_c + line_d).reconcile() self.assertEqual(line_a.matching_number, f'P{line_a.matched_credit_ids.ids[0]}') self.assertEqual(line_b.matching_number, line_a.matching_number) self.assertEqual(line_c.matching_number, line_a.matching_number) self.assertEqual(line_d.matching_number, line_a.matching_number) line_b.remove_move_reconcile() self.assertEqual(line_a.matching_number, f'P{line_a.matched_credit_ids.ids[0]}') self.assertEqual(line_b.matching_number, False) self.assertEqual(line_c.matching_number, f'P{line_c.matched_debit_ids.ids[0]}') self.assertEqual(line_d.matching_number, line_c.matching_number) (line_a + line_b).reconcile() # everything should be matched again self.assertEqual(line_a.matching_number, line_c.matching_number) self.assertEqual(line_b.matching_number, line_c.matching_number) self.assertEqual(line_c.matching_number, f'P{line_c.matched_debit_ids.ids[0]}') self.assertEqual(line_d.matching_number, line_c.matching_number) def test_matching_number_partial_multi_separate_reconcile(self): currency = self.env.company.currency_id line_a = self.create_line_for_reconciliation(1000, 1000, currency, '2016-01-01') line_b = self.create_line_for_reconciliation(-500, -500, currency, '2016-01-01') (line_a + line_b).reconcile() self.assertEqual(line_a.matching_number, f'P{line_a.matched_credit_ids.id}') self.assertEqual(line_a.matching_number, line_b.matching_number) line_c = self.create_line_for_reconciliation(-300, -300, currency, '2016-01-01') (line_a + line_c).reconcile() self.assertEqual(line_a.matching_number, f'P{line_a.matched_credit_ids.ids[0]}') self.assertEqual(line_a.matching_number, line_b.matching_number) self.assertEqual(line_a.matching_number, line_c.matching_number) def test_matching_number_unreconcile_single(self): currency = self.env.company.currency_id full_line_a = self.create_line_for_reconciliation(200, 200, currency, '2016-01-01') full_line_b = self.create_line_for_reconciliation(-200, -200, currency, '2016-01-01') partial_line_a = self.create_line_for_reconciliation(1000, 1000, currency, '2016-01-01') partial_line_b = self.create_line_for_reconciliation(-500, -500, currency, '2016-01-01') (full_line_a + full_line_b).reconcile() (partial_line_a + partial_line_b).reconcile() (full_line_a + full_line_b + partial_line_a + partial_line_b).remove_move_reconcile() self.assertFalse(full_line_a.matching_number) self.assertFalse(full_line_b.matching_number) self.assertFalse(partial_line_a.matching_number) self.assertFalse(partial_line_b.matching_number) def test_matching_number_unreconcile_multi(self): currency = self.env.company.currency_id line_a = self.create_line_for_reconciliation(-500, -500, currency, '2016-01-01') line_b = self.create_line_for_reconciliation(1000, 1000, currency, '2016-01-01') line_c = self.create_line_for_reconciliation(-1000, -1000, currency, '2016-01-01') line_d = self.create_line_for_reconciliation(300, 300, currency, '2016-01-01') (line_a + line_b).reconcile() (line_b + line_c).reconcile() (line_c + line_d).reconcile() previous_matching_number = line_a.matching_number line_a.remove_move_reconcile() self.assertFalse(line_a.matching_number) self.assertNotEqual(previous_matching_number, line_b.matching_number) self.assertEqual(line_b.matching_number, line_c.matching_number) self.assertEqual(line_b.matching_number, line_d.matching_number) previous_matching_number = line_b.matching_number line_b.remove_move_reconcile() self.assertFalse(line_b.matching_number) self.assertNotEqual(previous_matching_number, line_c.matching_number) self.assertEqual(line_c.matching_number, line_d.matching_number) line_c.remove_move_reconcile() self.assertFalse(line_c.matching_number) self.assertFalse(line_d.matching_number) def test_matching_loop(self): currency = self.env.company.currency_id wrong_credit = self.create_line_for_reconciliation(-500, -500, currency, '2016-01-01') debit_a = self.create_line_for_reconciliation(1000, 1000, currency, '2016-01-01') credit_a = self.create_line_for_reconciliation(-1000, -1000, currency, '2016-01-01') debit_b = self.create_line_for_reconciliation(1000, 1000, currency, '2016-01-01') credit_b = self.create_line_for_reconciliation(-1000, -1000, currency, '2016-01-01') (wrong_credit + debit_a).reconcile() (debit_a + credit_a).reconcile() wrong_credit.remove_move_reconcile() # now there is an open amount on both the payment and the invoice (credit_a + debit_b).reconcile() (debit_b + credit_b).reconcile() # Everything is reconciled but some amounts are still open self.assertEqual(len(set((debit_a + debit_b + credit_a + credit_b).mapped('matching_number'))), 1) self.assertFalse(all(aml.amount_residual == 0 for aml in (debit_a, debit_b, credit_a, credit_b))) # Now this should create a loop, it should still work, and the residual amounts should now reach 0 (credit_b + debit_a).reconcile() self.assertTrue(all(aml.amount_residual == 0 for aml in (debit_a, debit_b, credit_a, credit_b))) 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 self.env.company.tax_exigibility = 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, {'repartition_type': 'base'}), (0,0, { 'repartition_type': 'tax', 'account_id': self.tax_account_1.id, }), ], 'refund_repartition_line_ids': [ (0,0, {'repartition_type': 'base'}), (0,0, { '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_line_id': self.inbound_payment_method_line.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', 'in', partial_rec.ids)]) # 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 """ # Required for `analytic_account_id` to be visible in the view self.env.user.groups_id += self.env.ref('analytic.group_analytic_accounting') # Make the tax account reconcilable self.tax_account_1.reconcile = True self.env.company.tax_exigibility = 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')) 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 analytic_plan = self.env['account.analytic.plan'].create({'name': 'Plan Test'}) test_analytic_account = self.env['account.analytic.account'].create({'name': 'test_analytic_account', 'plan_id': analytic_plan.id}) 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_distribution = {test_analytic_account.id: 100} 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_line_id': self.inbound_payment_method_line.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', 'in', partial_rec.ids)]) 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. """ self.env.company.tax_exigibility = True 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_line_id': self.inbound_payment_method_line.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', 'in', partial_rec.ids)]) 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 self.env.company.tax_exigibility = 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_line_id': self.inbound_payment_method_line.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', 'in', partial_rec.ids)]) # 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() def test_caba_foreign_vat(self): self.env.company.tax_exigibility = True test_country = self.env['res.country'].create({ 'name': "Bretonnia", 'code': 'wh', }) foreign_vat_fpos = self.env['account.fiscal.position'].create({ 'name': "Fiscal Position to the Holy Grail", 'country_id': test_country.id, 'foreign_vat': 'WH1234', }) self.env['account.tax.group'].create({ 'name': 'tax_group', 'country_id': test_country.id, }) foreign_caba_tax = self.env['account.tax'].create({ 'name': 'foreign tax_1', 'amount': 33.3333, 'company_id': self.company_data['company'].id, 'cash_basis_transition_account_id': self.cash_basis_transfer_account.id, 'tax_exigibility': 'on_payment', 'country_id': test_country.id, 'invoice_repartition_line_ids': [ (0, 0, {'repartition_type': 'base'}), (0, 0, {'repartition_type': 'tax'}), ], 'refund_repartition_line_ids': [ (0, 0, {'repartition_type': 'base'}), (0, 0, {'repartition_type': 'tax'}), ], }) invoice = self.env['account.move'].create({ 'move_type': 'out_invoice', 'partner_id': self.partner_a.id, 'invoice_date': '2021-07-01', 'fiscal_position_id': foreign_vat_fpos.id, 'invoice_line_ids': [ Command.create({ 'name': "test", 'price_unit': 100, 'tax_ids': [Command.set(foreign_caba_tax.ids)], }), ] }) invoice.action_post() self.env['account.payment.register'].with_context(active_ids=invoice.ids, active_model='account.move').create({ 'payment_date': invoice.date, })._create_payments() caba_move = self.env['account.move'].search([('tax_cash_basis_origin_move_id', '=', invoice.id)]) self.assertEqual(caba_move.fiscal_position_id, foreign_vat_fpos, "The foreign VAT fiscal position should be kept in the cash basis move.") def test_caba_tax_group(self): """ Test the CABA entries generated from an invoice with a tax group """ self.env.company.tax_exigibility = True # 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')) move_form.invoice_date = fields.Date.from_string('2019-01-01') move_form.partner_id = self.partner_a tax_a = self.cash_basis_tax_a_third_amount tax_b = self.cash_basis_tax_tiny_amount tax_group = self.env['account.tax'].create({ 'name': 'tax group', 'amount_type': 'group', 'company_id': self.company_data['company'].id, 'children_tax_ids': [Command.set([tax_a.id, tax_b.id])], }) # line with analytic account, will generate 2 lines in CABA move invoice = self.env['account.move'].with_context(skip_invoice_sync=True).create({ 'partner_id': self.partner_a.id, 'invoice_date': fields.Date.from_string('2019-01-01'), 'move_type': 'entry', 'line_ids': [ # Base Tax line Command.create({ 'debit': 0.0, 'credit': 3000.0, 'account_id': self.company_data['default_account_revenue'].id, 'tax_ids': [Command.set(tax_group.ids)], }), # Tax line A Command.create({ 'debit': 0.0, 'credit': 1000.0, 'account_id': self.cash_basis_transfer_account.id, 'tax_repartition_line_id': tax_a.invoice_repartition_line_ids.filtered(lambda line: line.repartition_type == 'tax').id, }), # Tax line B Command.create({ 'debit': 0.0, 'credit': 1.0, 'account_id': self.cash_basis_transfer_account.id, 'tax_repartition_line_id': tax_b.invoice_repartition_line_ids.filtered(lambda line: line.repartition_type == 'tax').id, }), # Receivable lines Command.create({ 'debit': 4001.0, 'credit': 0.0, 'account_id': self.extra_receivable_account_1.id, }), ] }) invoice.action_post() pmt_wizard = self.env['account.payment.register'].with_context(active_model='account.move', active_ids=invoice.ids).create({}) pmt_wizard._create_payments() caba_move = self.env['account.move'].search([('tax_cash_basis_origin_move_id', '=', invoice.id)]) self.assertEqual(len(caba_move.line_ids), 6, "All lines should be there") tax_group_base_tags = (tax_a | tax_b).invoice_repartition_line_ids.filtered(lambda l: l.repartition_type == 'base').tag_ids.ids tax_a_tax_tag = tax_a.invoice_repartition_line_ids.filtered(lambda l: l.repartition_type == 'tax').tag_ids.ids tax_b_tax_tag = tax_b.invoice_repartition_line_ids.filtered(lambda l: l.repartition_type == 'tax').tag_ids.ids self.assertRecordValues(caba_move.line_ids, [ {'balance': 3000.0, 'tax_line_id': False, 'tax_tag_ids': [], 'tax_ids': []}, {'balance': -3000.0, 'tax_line_id': False, 'tax_tag_ids': tax_group_base_tags, 'tax_ids': (tax_a | tax_b).ids}, {'balance': 1000.0, 'tax_line_id': False, 'tax_tag_ids': [], 'tax_ids': []}, {'balance': -1000.0, 'tax_line_id': tax_a.id, 'tax_tag_ids': tax_a_tax_tag, 'tax_ids': []}, {'balance': 1.0, 'tax_line_id': False, 'tax_tag_ids': [], 'tax_ids': []}, {'balance': -1.0, 'tax_line_id': tax_b.id, 'tax_tag_ids': tax_b_tax_tag, 'tax_ids': []}, ]) # No exchange journal entry created for CABA. exchange_difference_move = invoice.line_ids.filtered(lambda line: line.account_id.account_type == 'receivable').full_reconcile_id.exchange_move_id self.assertFalse(exchange_difference_move) def test_caba_rounding_adjustment_monocurrency(self): self.env.company.tax_exigibility = True invoice = self.env['account.move'].create({ 'move_type': 'out_invoice', 'partner_id': self.partner_a.id, 'invoice_date': fields.Date.from_string('2016-01-01'), 'invoice_line_ids': [Command.create({ 'name': 'caba test', 'quantity': 1, 'price_unit': 99.99, 'tax_ids': [Command.set(self.cash_basis_tax_a_third_amount.ids)], })], }) invoice.action_post() payment_date_1 = fields.Date.from_string('2017-01-01') payment_date_2 = fields.Date.from_string('2018-01-01') pmt_wizard = self.env['account.payment.register'].with_context(active_model='account.move', active_ids=invoice.ids).create({ 'amount': 66.66, 'payment_date': payment_date_1, }) pmt_wizard._create_payments() pmt_wizard = self.env['account.payment.register'].with_context(active_model='account.move', active_ids=invoice.ids).create({ 'amount': 66.66, 'payment_date': payment_date_2, }) pmt_wizard._create_payments() self.assertRecordValues(invoice.tax_cash_basis_created_move_ids.filtered(lambda x: x.date == payment_date_1).line_ids, [ # pylint: disable=bad-whitespace {'account_id': self.cash_basis_base_account.id, 'debit': 50.0, 'credit': 0.0}, {'account_id': self.cash_basis_base_account.id, 'debit': 0.0, 'credit': 50.0}, {'account_id': self.cash_basis_transfer_account.id, 'debit': 16.67, 'credit': 0.0}, {'account_id': self.tax_account_1.id, 'debit': 0.0, 'credit': 16.67}, ]) self.assertRecordValues(invoice.tax_cash_basis_created_move_ids.filtered(lambda x: x.date == payment_date_2).line_ids, [ # pylint: disable=bad-whitespace {'account_id': self.cash_basis_base_account.id, 'debit': 50.0, 'credit': 0.0}, {'account_id': self.cash_basis_base_account.id, 'debit': 0.0, 'credit': 50.0}, {'account_id': self.cash_basis_transfer_account.id, 'debit': 16.67, 'credit': 0.0}, {'account_id': self.tax_account_1.id, 'debit': 0.0, 'credit': 16.67}, ]) # Check the CABA adjustment made in the receivable account's full reconcile's exchange move self.assertRecordValues( # pylint: disable=bad-whitespace invoice.line_ids.filtered(lambda x: x.account_id.account_type == 'asset_receivable').full_reconcile_id.exchange_move_id.line_ids, [ {'account_id': self.cash_basis_base_account.id, 'debit': 0.01, 'credit': 0.0}, {'account_id': self.cash_basis_base_account.id, 'debit': 0.0, 'credit': 0.01}, {'account_id': self.tax_account_1.id, 'debit': 0.01, 'credit': 0.0}, {'account_id': self.cash_basis_transfer_account.id, 'debit': 0.0, 'credit': 0.01}, ] ) self.assertTrue( invoice.line_ids.filtered(lambda x: x.account_id == self.cash_basis_transfer_account).full_reconcile_id, "The cash basis transition account line of the invoice should be fully reconciled with the CABA moves and the adjustment." ) self.assertAmountsGroupByAccount([ # pylint: disable=bad-whitespace # Account Balance Amount Currency (self.cash_basis_transfer_account, 0.0, 0.0), (self.tax_account_1, -33.33, -33.33), (self.cash_basis_base_account, 0.0, 0.0) ]) def test_caba_rounding_adjustment_multicurrency(self): self.env.company.tax_exigibility = True # Rates are 1/3 for 2016, 1/2 for 2017 and 5/1 in 2018 currency_id = self.setup_multi_currency_data({'name': 'Minovsky Dollar', 'rounding': 0.01})['currency'].id self.env['res.currency.rate'].create({ 'name': '2018-01-01', 'rate': 0.2, 'currency_id': currency_id, 'company_id': self.env.company.id, }) invoice = self.env['account.move'].create({ 'move_type': 'out_invoice', 'partner_id': self.partner_a.id, 'invoice_date': fields.Date.from_string('2016-01-01'), 'currency_id': currency_id, 'invoice_line_ids': [Command.create({ 'name': 'caba test', 'quantity': 1, 'price_unit': 99.99, 'tax_ids': [Command.set(self.cash_basis_tax_a_third_amount.ids)], })], }) invoice.action_post() payment_date_1 = fields.Date.from_string('2017-01-01') payment_date_2 = fields.Date.from_string('2018-01-01') pmt_wizard = self.env['account.payment.register'].with_context(active_model='account.move', active_ids=invoice.ids).create({ 'amount': 66.66, 'currency_id': currency_id, 'payment_date': payment_date_1, }) pmt_wizard._create_payments() pmt_wizard = self.env['account.payment.register'].with_context(active_model='account.move', active_ids=invoice.ids).create({ 'amount': 66.66, 'currency_id': currency_id, 'payment_date': payment_date_2, }) pmt_wizard._create_payments() self.assertRecordValues(invoice.tax_cash_basis_created_move_ids.filtered(lambda x: x.date == payment_date_1).line_ids, [ # pylint: disable=bad-whitespace {'account_id': self.cash_basis_base_account.id, 'debit': 25.0, 'credit': 0.0, 'amount_currency': 50.0, 'currency_id': currency_id}, {'account_id': self.cash_basis_base_account.id, 'debit': 0.0, 'credit': 25.0, 'amount_currency': -50.0, 'currency_id': currency_id}, {'account_id': self.cash_basis_transfer_account.id, 'debit': 8.34, 'credit': 0.0, 'amount_currency': 16.67, 'currency_id': currency_id}, {'account_id': self.tax_account_1.id, 'debit': 0.0, 'credit': 8.34, 'amount_currency': -16.67, 'currency_id': currency_id}, ]) self.assertRecordValues(invoice.tax_cash_basis_created_move_ids.filtered(lambda x: x.date == payment_date_2).line_ids, [ # pylint: disable=bad-whitespace {'account_id': self.cash_basis_base_account.id, 'debit': 250.0, 'credit': 0.0, 'amount_currency': 50.0, 'currency_id': currency_id}, {'account_id': self.cash_basis_base_account.id, 'debit': 0.0, 'credit': 250.0, 'amount_currency': -50.0, 'currency_id': currency_id}, {'account_id': self.cash_basis_transfer_account.id, 'debit': 83.35, 'credit': 0.0, 'amount_currency': 16.67, 'currency_id': currency_id}, {'account_id': self.tax_account_1.id, 'debit': 0.0, 'credit': 83.35, 'amount_currency': -16.67, 'currency_id': currency_id}, ]) # Check the CABA adjustment made in the receivable account's full reconcile's exchange move self.assertRecordValues( # pylint: disable=bad-whitespace invoice.line_ids.filtered(lambda x: x.account_id.account_type == 'asset_receivable').full_reconcile_id.exchange_move_id.line_ids, [ {'account_id': self.cash_basis_base_account.id, 'debit': 0.05, 'credit': 0.0, 'amount_currency': 0.01, 'currency_id': currency_id}, {'account_id': self.cash_basis_base_account.id, 'debit': 0.0, 'credit': 0.05, 'amount_currency': -0.01, 'currency_id': currency_id}, {'account_id': self.tax_account_1.id, 'debit': 0.05, 'credit': 0.0, 'amount_currency': 0.01, 'currency_id': currency_id}, {'account_id': self.cash_basis_transfer_account.id, 'debit': 0.0, 'credit': 0.05, 'amount_currency': -0.01, 'currency_id': currency_id}, ] ) self.assertTrue( invoice.line_ids.filtered(lambda x: x.account_id == self.cash_basis_transfer_account).full_reconcile_id, "The cash basis transition account line of the invoice should be fully reconciled with the CABA moves and the adjustment." ) self.assertAmountsGroupByAccount([ # pylint: disable=bad-whitespace # Account Balance Amount Currency (self.cash_basis_transfer_account, 0.0, 0.0), (self.tax_account_1, -91.64, -33.33), (self.cash_basis_base_account, 0.0, 0.0), ]) def test_cash_basis_taxline_without_account(self): """ Make sure that cash basis taxlines that don't have an account are handled properly. """ self.env.company.tax_exigibility = True tax = self.env['account.tax'].create({ 'name': 'cash basis 20%', 'type_tax_use': 'purchase', 'amount': 20, 'tax_exigibility': 'on_payment', 'cash_basis_transition_account_id': self.cash_basis_transfer_account.id, 'invoice_repartition_line_ids': [ (0, 0, { 'factor_percent': 100, 'repartition_type': 'base', }), (0, 0, { 'factor_percent': 40, 'account_id': self.tax_account_1.id, 'repartition_type': 'tax', }), (0, 0, { 'factor_percent': 60, 'repartition_type': 'tax', }), ], 'refund_repartition_line_ids': [ (0, 0, { 'factor_percent': 100, 'repartition_type': 'base', }), (0, 0, { 'factor_percent': 40, 'account_id': self.tax_account_1.id, 'repartition_type': 'tax', }), (0, 0, { 'factor_percent': 60, 'repartition_type': 'tax', }), ], }) # create invoice move_form = Form(self.env['account.move'].with_context( default_move_type='in_invoice')) move_form.partner_id = self.partner_a move_form.invoice_date = fields.Date.from_string('2017-01-01') with move_form.invoice_line_ids.new() as line_form: line_form.product_id = self.product_a line_form.tax_ids.clear() line_form.tax_ids.add(tax) invoice = move_form.save() invoice.action_post() # make payment self.env['account.payment.register'].with_context(active_model='account.move', active_ids=invoice.ids).create({ 'payment_date': invoice.date, })._create_payments() # check caba move partial_rec = invoice.mapped('line_ids.matched_debit_ids') caba_move = self.env['account.move'].search( [('tax_cash_basis_rec_id', '=', partial_rec.id)]) expected_values = [ { 'account_id': self.cash_basis_base_account.id, 'debit': 0.0, 'credit': 800.0 }, { 'account_id': self.cash_basis_base_account.id, 'debit': 800.0, 'credit': 0.0 }, { 'account_id': self.cash_basis_transfer_account.id, 'debit': 0.0, 'credit': 64.0 }, { 'account_id': self.tax_account_1.id, 'debit': 64.0, 'credit': 0.0}, { 'account_id': self.cash_basis_transfer_account.id, 'debit': 0.0, 'credit': 96.0 }, { 'account_id': self.cash_basis_base_account.id, 'debit': 96.0, 'credit': 0.0 } ] self.assertRecordValues(caba_move.line_ids, expected_values) def test_cash_basis_full_refund(self): """ Ensure the caba entry and the exchange difference journal entry for caba are not created in case of full refund. """ self.env.company.tax_exigibility = True tax = self.env['account.tax'].create({ 'name': 'cash basis 20%', 'type_tax_use': 'purchase', 'amount': 20, 'tax_exigibility': 'on_payment', 'cash_basis_transition_account_id': self.cash_basis_transfer_account.id, }) invoice = self.init_invoice('out_invoice', post=True, amounts=[1000.0], taxes=tax) # Reverse completely the invoice. credit_note_wizard = self.env['account.move.reversal']\ .with_context({'active_ids': invoice.ids, 'active_model': 'account.move'})\ .create({ 'reason': 'test_cash_basis_full_refund', 'journal_id': invoice.journal_id.id, }) action_values = credit_note_wizard.modify_moves() self.assertRecordValues(invoice, [{'payment_state': 'reversed'}]) # Check no CABA move has been created. cash_basis_moves = self.env['account.move']\ .search([('tax_cash_basis_origin_move_id', 'in', (invoice.id, action_values['res_id']))]) self.assertFalse(cash_basis_moves) # No exchange journal entry created for CABA. caba_transfer_amls = self.env['account.move.line'].search([ ('account_id', '=', self.cash_basis_transfer_account.id), ('move_id.move_type', '=', 'entry'), ]) self.assertFalse(caba_transfer_amls.move_id) def test_reconcile_import(self): """Test that the import of matchings does a real matching upon posting""" comp_curr = self.company_data['currency'] line_1 = self.create_line_for_reconciliation(1000.0, 1000.0, comp_curr, '2016-01-01') line_1.move_id.button_draft() line_2 = self.create_line_for_reconciliation(-300.0, -300.0, comp_curr, '2016-01-01') line_3 = self.create_line_for_reconciliation(-400.0, -400.0, comp_curr, '2016-01-01') line_4 = self.create_line_for_reconciliation(-500.0, -500.0, comp_curr, '2016-01-01') line_4.move_id.button_draft() line_5 = self.create_line_for_reconciliation(200.0, 200.0, comp_curr, '2016-01-01') (line_1 + line_2 + line_3).matching_number = '11111' # Will be converted to a temporary number (line_4 + line_5).matching_number = '22222' # Will be converted to a temporary number # posting triggers the matching of the imported values (line_1 + line_4).move_id.action_post() self.assertRegex(line_1.matching_number, r'^P\d+') self.assertRegex(line_4.matching_number, r'^P\d+') (line_1 + line_4).reconcile() self.assertRegex(line_1.matching_number, r'^\d+') self.assertTrue(line_1.full_reconcile_id) def test_reconcile_payment_custom_rate(self): """When reconciling a payment we want to take the accounting rate and not the odoo rate. Most likely the payment information are derived from information of the bank, therefore have the relevant rate. """ company_currency = self.company_data['currency'] foreign_currency = self.currency_data['currency'] invoice = self.env['account.move'].create({ 'move_type': 'out_invoice', 'invoice_date': '2017-01-01', 'date': '2017-01-01', 'partner_id': self.partner_a.id, 'currency_id': company_currency.id, 'invoice_line_ids': [Command.create({ 'product_id': self.product_a.id, 'price_unit': 400.0, 'tax_ids': [], })], }) invoice.action_post() payment = self.env['account.payment'].create({ 'date': invoice.date, 'amount': 800.0, 'currency_id': foreign_currency.id, 'partner_id': self.partner_a.id, }) payment.action_post() # unlink the rate to simulate a custom rate on the payment self.env['res.currency.rate'].search([('currency_id', '=', foreign_currency.id)]).unlink() lines_to_reconcile = (invoice + payment.move_id).line_ids.filtered(lambda x: x.account_id.account_type == 'asset_receivable') lines_to_reconcile.reconcile() self.assertTrue(all(lines_to_reconcile.mapped('reconciled')), "All lines should be fully reconciled")