diff --git a/addons/account/i18n/account.pot b/addons/account/i18n/account.pot index f0a6b319a04..295fe8d8fa5 100644 --- a/addons/account/i18n/account.pot +++ b/addons/account/i18n/account.pot @@ -3377,6 +3377,12 @@ msgstr "" msgid "Cash Statement" msgstr "" +#. module: account +#: code:addons/account/models/account_move.py:0 +#, python-format +msgid "Cash basis rounding difference" +msgstr "" + #. module: account #: code:addons/account/models/chart_template.py:0 #, python-format diff --git a/addons/account/models/account_move_line.py b/addons/account/models/account_move_line.py index 9e5cd312f03..86a527b5150 100644 --- a/addons/account/models/account_move_line.py +++ b/addons/account/models/account_move_line.py @@ -2048,8 +2048,8 @@ class AccountMoveLine(models.Model): :param exchange_diff_vals: The current vals of the exchange difference journal entry created by the '_prepare_exchange_difference_move_vals' method. """ + caba_lines_to_reconcile = defaultdict(lambda: self.env['account.move.line']) # in the form {(move, account, repartition_line): move_lines} move_vals = exchange_diff_vals['move_vals'] - for move in self.move_id: account_vals_to_fix = {} @@ -2065,19 +2065,22 @@ class AccountMoveLine(models.Model): # to compute the residual amount for each of them. # ========================================================================== + caba_rounding_diff_label = _("Cash basis rounding difference") move_vals['date'] = max(move_vals['date'], move.date) for caba_treatment, line in move_values['to_process_lines']: vals = { + 'name': caba_rounding_diff_label, 'currency_id': line.currency_id.id, 'partner_id': line.partner_id.id, 'tax_ids': [Command.set(line.tax_ids.ids)], 'tax_tag_ids': [Command.set(line.tax_tag_ids.ids)], 'debit': line.debit, 'credit': line.credit, + 'amount_currency': line.amount_currency, } - if caba_treatment == 'tax' and not line.reconciled: + if caba_treatment == 'tax': # Tax line. grouping_key = self.env['account.partial.reconcile']._get_cash_basis_tax_line_grouping_key_from_record(line) if grouping_key in account_vals_to_fix: @@ -2089,6 +2092,7 @@ class AccountMoveLine(models.Model): 'debit': balance if balance > 0 else 0, 'credit': -balance if balance < 0 else 0, 'tax_base_amount': account_vals_to_fix[grouping_key]['tax_base_amount'] + line.tax_base_amount, + 'amount_currency': account_vals_to_fix[grouping_key]['amount_currency'] + line.amount_currency, }) else: account_vals_to_fix[grouping_key] = { @@ -2097,6 +2101,10 @@ class AccountMoveLine(models.Model): 'tax_base_amount': line.tax_base_amount, 'tax_repartition_line_id': line.tax_repartition_line_id.id, } + + if line.account_id.reconcile: + caba_lines_to_reconcile[(move, line.account_id, line.tax_repartition_line_id)] |= line + elif caba_treatment == 'base': # Base line. account_to_fix = line.company_id.account_cash_basis_base_account_id @@ -2115,6 +2123,7 @@ class AccountMoveLine(models.Model): # cash basis tax is used alone on several lines of the invoices account_vals_to_fix[grouping_key]['debit'] += vals['debit'] account_vals_to_fix[grouping_key]['credit'] += vals['credit'] + account_vals_to_fix[grouping_key]['amount_currency'] += vals['amount_currency'] # ========================================================================== # Subtract the balance of all previously generated cash basis journal entries @@ -2122,14 +2131,17 @@ class AccountMoveLine(models.Model): # ========================================================================== cash_basis_moves = self.env['account.move'].search([('tax_cash_basis_origin_move_id', '=', move.id)]) + caba_transition_accounts = self.env['account.account'] for line in cash_basis_moves.line_ids: grouping_key = None if line.tax_repartition_line_id: # Tax line. + transition_account = line.tax_line_id.cash_basis_transition_account_id grouping_key = self.env['account.partial.reconcile']._get_cash_basis_tax_line_grouping_key_from_record( line, - account=line.tax_line_id.cash_basis_transition_account_id, + account=transition_account, ) + caba_transition_accounts |= transition_account elif line.tax_ids: # Base line. grouping_key = self.env['account.partial.reconcile']._get_cash_basis_base_line_grouping_key_from_record( @@ -2142,6 +2154,13 @@ class AccountMoveLine(models.Model): account_vals_to_fix[grouping_key]['debit'] -= line.debit account_vals_to_fix[grouping_key]['credit'] -= line.credit + account_vals_to_fix[grouping_key]['amount_currency'] -= line.amount_currency + + # Collect the caba lines affecting the transition account. + for transition_line in filter(lambda x: x.account_id in caba_transition_accounts, cash_basis_moves.line_ids): + caba_reconcile_key = (transition_line.move_id, transition_line.account_id, transition_line.tax_repartition_line_id) + caba_lines_to_reconcile[caba_reconcile_key] |= transition_line + # ========================================================================== # Generate the exchange difference journal items: @@ -2149,60 +2168,74 @@ class AccountMoveLine(models.Model): # - fix rounding issues on the tax account/base tax account. # ========================================================================== - for values in account_vals_to_fix.values(): - balance = values['debit'] - values['credit'] + currency = move_values['currency'] - if move.company_currency_id.is_zero(balance): + # To know which rate to use for the adjustment, get the rate used by the most recent cash basis move + last_caba_move = max(cash_basis_moves, key=lambda m: m.date) if cash_basis_moves else self.env['account.move'] + currency_line = last_caba_move.line_ids.filtered(lambda x: x.currency_id == currency)[:1] + currency_rate = currency_line.balance / currency_line.amount_currency if currency_line.amount_currency else 1.0 + + existing_line_vals_list = move_vals['line_ids'] + next_sequence = len(existing_line_vals_list) + for grouping_key, values in account_vals_to_fix.items(): + + if currency.is_zero(values['amount_currency']): continue + # There is a rounding error due to multiple payments on the foreign currency amount + balance = currency.round(currency_rate * values['amount_currency']) + if values.get('tax_repartition_line_id'): - # Tax line. + # Tax line tax_repartition_line = self.env['account.tax.repartition.line'].browse(values['tax_repartition_line_id']) account = tax_repartition_line.account_id or self.env['account.account'].browse(values['account_id']) - sequence = len(move_vals['line_ids']) - move_vals['line_ids'] += [ + existing_line_vals_list.extend([ Command.create({ **values, - 'name': _('Currency exchange rate difference (cash basis)'), 'debit': balance if balance > 0.0 else 0.0, 'credit': -balance if balance < 0.0 else 0.0, + 'amount_currency': values['amount_currency'], 'account_id': account.id, - 'sequence': sequence, + 'sequence': next_sequence, }), Command.create({ **values, - 'name': _('Currency exchange rate difference (cash basis)'), 'debit': -balance if balance < 0.0 else 0.0, 'credit': balance if balance > 0.0 else 0.0, + 'amount_currency': -values['amount_currency'], 'account_id': values['account_id'], 'tax_ids': [], 'tax_tag_ids': [], + 'tax_base_amount': 0, 'tax_repartition_line_id': False, - 'sequence': sequence + 1, + 'sequence': next_sequence + 1, }), - ] + ]) else: - # Base line. - sequence = len(move_vals['line_ids']) - move_vals['line_ids'] += [ + # Base line + existing_line_vals_list.extend([ Command.create({ **values, - 'name': _('Currency exchange rate difference (cash basis)'), 'debit': balance if balance > 0.0 else 0.0, 'credit': -balance if balance < 0.0 else 0.0, - 'sequence': sequence, + 'amount_currency': values['amount_currency'], + 'sequence': next_sequence, }), Command.create({ **values, - 'name': _('Currency exchange rate difference (cash basis)'), 'debit': -balance if balance < 0.0 else 0.0, 'credit': balance if balance > 0.0 else 0.0, + 'amount_currency': -values['amount_currency'], 'tax_ids': [], 'tax_tag_ids': [], - 'sequence': sequence + 1, + 'sequence': next_sequence + 1, }), - ] + ]) + + next_sequence += 2 + + return caba_lines_to_reconcile def reconcile(self): ''' Reconcile the current move lines all together. @@ -2275,19 +2308,22 @@ class AccountMoveLine(models.Model): # ==== Check if a full reconcile is needed ==== - def is_line_reconciled(line): + def is_line_reconciled(line, has_multiple_currencies): # Check if the journal item passed as parameter is now fully reconciled. return line.reconciled \ - or line.currency_id.is_zero(line.amount_residual_currency) \ - or line.company_currency_id.is_zero(line.amount_residual) - - if all(is_line_reconciled(line) for line in involved_lines): + or (line.company_currency_id.is_zero(line.amount_residual) + if has_multiple_currencies + else line.currency_id.is_zero(line.amount_residual_currency) + ) + has_multiple_currencies = len(involved_lines.currency_id) > 1 + if all(is_line_reconciled(line, has_multiple_currencies) for line in involved_lines): # ==== Create the exchange difference move ==== # This part could be bypassed using the 'no_exchange_difference' key inside the context. This is useful # when importing a full accounting including the reconciliation like Winbooks. - exchange_move = None + exchange_move = self.env['account.move'] + caba_lines_to_reconcile = None if not self._context.get('no_exchange_difference'): # In normal cases, the exchange differences are already generated by the partial at this point meaning # there is no journal item left with a zero amount residual in one currency but not in the other. @@ -2313,7 +2349,7 @@ class AccountMoveLine(models.Model): # Exchange difference for cash basis entries. if is_cash_basis_needed: - involved_lines._add_exchange_difference_cash_basis_vals(exchange_diff_vals) + caba_lines_to_reconcile = involved_lines._add_exchange_difference_cash_basis_vals(exchange_diff_vals) # Create the exchange difference. if exchange_diff_vals['move_vals']['line_ids']: @@ -2331,13 +2367,27 @@ class AccountMoveLine(models.Model): results['exchange_partials'] += exchange_diff_partials # ==== Create the full reconcile ==== - results['full_reconcile'] = self.env['account.full.reconcile'].create({ 'exchange_move_id': exchange_move and exchange_move.id, 'partial_reconcile_ids': [(6, 0, involved_partials.ids)], 'reconciled_line_ids': [(6, 0, involved_lines.ids)], }) + # === Cash basis rounding autoreconciliation === + # In case a cash basis rounding difference line got created for the transition account, we reconcile it with the corresponding lines + # on the cash basis moves (so that it reaches full reconciliation and creates an exchange difference entry for this account as well) + + if caba_lines_to_reconcile: + for (dummy, account, repartition_line), amls_to_reconcile in caba_lines_to_reconcile.items(): + if not account.reconcile: + continue + + exchange_line = exchange_move.line_ids.filtered( + lambda l: l.account_id == account and l.tax_repartition_line_id == repartition_line + ) + + (exchange_line + amls_to_reconcile).filtered(lambda l: not l.reconciled).reconcile() + not_paid_invoices.filtered(lambda move: move.payment_state in ('paid', 'in_payment') )._invoice_paid_hook() diff --git a/addons/account/tests/test_account_move_reconcile.py b/addons/account/tests/test_account_move_reconcile.py index c3cc800f6d4..244ff4eeadf 100644 --- a/addons/account/tests/test_account_move_reconcile.py +++ b/addons/account/tests/test_account_move_reconcile.py @@ -48,6 +48,7 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): 'code': 'cash.basis.transfer.account', 'name': 'cash_basis_transfer_account', 'account_type': 'income', + 'reconcile': True, 'company_id': cls.company_data['company'].id, }) @@ -330,47 +331,95 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): ]) def test_reconcile_lines_corner_case_1_zero_balance_same_foreign_currency(self): - """ Test the reconciliation of lines having a zero balance but the same foreign currency. """ + """ 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.01, currency, '2017-01-01') - line_2 = self._create_line_for_reconciliation(0.0, 0.02, currency, '2016-01-01') + 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 enforce full reconciliation. """ + 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') res = (line_1 + line_2).reconcile() self.assertRecordValues(res['partials'], [{ 'amount': 0.0, - 'debit_amount_currency': 0.01, - 'credit_amount_currency': 0.01, + 'debit_amount_currency': 0.0, + 'credit_amount_currency': 0.0, 'debit_move_id': line_2.id, 'credit_move_id': line_1.id, - 'exchange_move_id': None, }]) - self.assertTrue(res.get('full_reconcile')) - self.assertRecordValues(res['full_reconcile'].exchange_move_id, [{'date': fields.Date.from_string('2017-01-01')}]) - self.assertRecordValues(res['full_reconcile'].exchange_move_id.line_ids, [ + self.assertTrue(line_1.full_reconcile_id) + self.assertEqual(line_1.full_reconcile_id, line_2.full_reconcile_id) + self.assertTrue(res['partials'].exchange_move_id, "The partial reconciliation should have created an exchange move") + self.assertRecordValues(res['partials'].exchange_move_id, [{'date': fields.Date.from_string('2017-01-01')}]) + self.assertRecordValues(res['partials'].exchange_move_id.line_ids, [ { 'debit': 0.0, 'credit': 0.0, - 'amount_currency': -0.01, - 'currency_id': currency.id, + 'amount_currency': -0.02, + 'currency_id': currency_2.id, 'account_id': line_2.account_id.id, }, { 'debit': 0.0, 'credit': 0.0, - 'amount_currency': 0.01, - 'currency_id': currency.id, + 'amount_currency': 0.02, + 'currency_id': currency_2.id, 'account_id': self.exch_expense_account.id, }, + { + 'debit': 0.0, + 'credit': 0.0, + 'amount_currency': 0.01, + 'currency_id': currency_1.id, + 'account_id': line_1.account_id.id, + }, + { + 'debit': 0.0, + 'credit': 0.0, + 'amount_currency': -0.01, + 'currency_id': currency_1.id, + 'account_id': self.exch_income_account.id, + }, + ]) + self.assertRecordValues(res['exchange_partials'], [ + { + 'amount': 0.0, + 'debit_amount_currency': 0.02, + 'credit_amount_currency': 0.02, + 'debit_move_id': line_2.id, + 'credit_move_id': res['partials'].exchange_move_id.line_ids[0].id, + }, + { + 'amount': 0.0, + 'debit_amount_currency': 0.01, + 'credit_amount_currency': 0.01, + 'debit_move_id': res['partials'].exchange_move_id.line_ids[2].id, + 'credit_move_id': line_1.id, + }, ]) - self.assertRecordValues(res['exchange_partials'], [{ - 'amount': 0.0, - 'debit_amount_currency': 0.01, - 'credit_amount_currency': 0.01, - 'debit_move_id': line_2.id, - 'credit_move_id': res['full_reconcile'].exchange_move_id.line_ids[0].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}, @@ -2686,9 +2735,21 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): {'debit': 0.0, 'credit': 0.0, 'amount_currency': -0.001, 'currency_id': currency_id, 'account_id': self.tax_account_2.id}, ]) + caba_transition_lines_1 = res['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, -5.54, -22.226), + (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), ]) @@ -2738,9 +2799,25 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): {'debit': 0.0, 'credit': 0.0, 'amount_currency': 0.0, 'currency_id': currency_id, 'account_id': self.tax_account_2.id}, ]) + caba_transition_lines_2 = res['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.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[1].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.assertRecordValues(caba_transition_exchange_moves_2[2].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.assertAmountsGroupByAccount([ # Account Balance Amount Currency - (self.cash_basis_transfer_account, 5.57, -0.002), + (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), ]) @@ -2767,19 +2844,19 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): ]) self.assertRecordValues(res['full_reconcile'].exchange_move_id.line_ids, [ - {'account_id': self.cash_basis_base_account.id, 'debit': 16.71, 'credit': 0.0, 'tax_ids': taxes.ids, 'tax_line_id': False}, - {'account_id': self.cash_basis_base_account.id, 'debit': 0.0, 'credit': 16.71, 'tax_ids': [], 'tax_line_id': False}, - {'account_id': self.tax_account_1.id, 'debit': 5.58, 'credit': 0.0, 'tax_ids': [], 'tax_line_id': self.cash_basis_tax_a_third_amount.id}, - {'account_id': self.cash_basis_transfer_account.id, 'debit': 0.0, 'credit': 5.58, 'tax_ids': [], 'tax_line_id': False}, - {'account_id': self.tax_account_2.id, 'debit': 0.0, 'credit': 0.01, 'tax_ids': [], 'tax_line_id': self.cash_basis_tax_tiny_amount.id}, - {'account_id': self.cash_basis_transfer_account.id, 'debit': 0.01, 'credit': 0.0, 'tax_ids': [], 'tax_line_id': False}, + {'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 = res['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, -11.1, -33.33), - (self.tax_account_2, -0.01, -0.01), + (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): @@ -2893,24 +2970,16 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): self.assertTrue(res.get('full_reconcile')) exchange_diff = res['full_reconcile'].exchange_move_id - exchange_diff_lines = exchange_diff.line_ids\ + 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.assertRecordValues(exchange_diff_lines, [ - { - 'debit': 0.0, - 'credit': 16.67, - 'amount_currency': 0.0, - 'currency_id': currency_id, - 'account_id': self.cash_basis_transfer_account.id, - }, - ]) + self.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, -33.33, -100.0), + (self.tax_account_1, -50.0, -100.0), ]) def test_reconcile_cash_basis_exchange_difference_transfer_account_check_entries_2(self): @@ -2988,18 +3057,12 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): ]) exchange_move = receivable_line.full_reconcile_id.exchange_move_id - self.assertTrue(exchange_move, "There should be an exchange difference move created") - self.assertRecordValues(exchange_move.line_ids, [ - {'account_id': self.cash_basis_base_account.id, 'debit': 0.0, 'credit': 50.0, 'amount_currency': 0.0, 'tax_ids': self.cash_basis_tax_a_third_amount.ids, 'tax_line_id': False}, - {'account_id': self.cash_basis_base_account.id, 'debit': 50.0, 'credit': 0.0, 'amount_currency': 0.0, 'tax_ids': [], 'tax_line_id': False}, - {'account_id': self.tax_account_1.id, 'debit': 0.0, 'credit': 5.0, 'amount_currency': 0.0, 'tax_ids': [], 'tax_line_id': self.cash_basis_tax_a_third_amount.id}, - {'account_id': self.cash_basis_transfer_account.id, 'debit': 5.0, 'credit': 0.0, 'amount_currency': 0.0, 'tax_ids': [], 'tax_line_id': False}, - ]) + self.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, -20.0, -10.0), + (self.tax_account_1, -15.0, -10.0), ]) def test_reconcile_cash_basis_exchange_difference_transfer_account_check_entries_3(self): @@ -3079,17 +3142,12 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): ]) exchange_move = receivable_line.full_reconcile_id.exchange_move_id - self.assertRecordValues(exchange_move.line_ids, [ - {'account_id': self.cash_basis_base_account.id, 'debit': 0.0, 'credit': 50.0, 'amount_currency': 0.0, 'tax_ids': self.cash_basis_tax_a_third_amount.ids, 'tax_line_id': False}, - {'account_id': self.cash_basis_base_account.id, 'debit': 50.0, 'credit': 0.0, 'amount_currency': 0.0, 'tax_ids': [], 'tax_line_id': False}, - {'account_id': self.tax_account_1.id, 'debit': 0.0, 'credit': 5.0, 'amount_currency': 0.0, 'tax_ids': [], 'tax_line_id': self.cash_basis_tax_a_third_amount.id}, - {'account_id': self.cash_basis_transfer_account.id, 'debit': 5.0, 'credit': 0.0, 'amount_currency': 0.0, 'tax_ids': [], 'tax_line_id': False}, - ]) + self.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, -20.0, -10.0), + (self.tax_account_1, -15.0, -10.0), ]) def test_reconcile_cash_basis_exchange_difference_transfer_account_check_entries_4(self): @@ -3201,12 +3259,7 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): ]) # Exchange difference - # 66.67 amount residual on the payment line after reconciling receivable line of the cash basis move with the payment counterpart - # 50.00 difference of the cash_basis_move base line and the CABA entry created by the system - self.assertRecordValues(res['full_reconcile'].exchange_move_id.line_ids, [ - {'debit': 50.0, 'credit': 0.0, 'currency_id': currency_id, 'account_id': self.cash_basis_base_account.id}, - {'debit': 0.0, 'credit': 50.0, 'currency_id': currency_id, 'account_id': self.cash_basis_base_account.id}, - ]) + self.assertFalse(res['full_reconcile'].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 @@ -4015,6 +4068,163 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): {'balance': -1.0, 'tax_line_id': tax_b.id}, ]) + 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.