[FIX] account: correct cash basis rounding in multicurrency

// CASE 1: payment with different rate

1) Setup a company in USD, with EUR activated as well

2) Set a rate of 1 EUR = 2 USD for date A, and 1 EUR = 3 USD for date B

3) Setup a Cash basis tax of 1/3, with a non-reconcilable cash basis transition account

4) At date A, create an invoice with just one line with base amount=99.99 EUR ; and using the tax setup in point 3).

5) Register a full payment for this invoice at date B

===> The exchange move created by the full reconciliation consists of cash basis rounding lines for the CABA base received, transition account and tax account. 99.99 USD for the base, and 33.33 USD for the tax. Those are introduced to bring back the total amount in domestic currency to the domestic amounts computed for the invoice at date A.

Impacting the tax account and base received account is totally wrong. When doing cash basis, we want to use the rate of the payment in the amounts that get reported, as it corresponds to the money that actually came in. So, none of the lines impacting those accounts should be there.

Only the line impacting the cash basis transition account is useful, to bring its value back to 0. But it actually corresponds to an exchange difference, and we need to balance it with an exchange gain/loss account. This account can already be set as reconcilable, and if so, will be auto-reconciled. We now choose to only rely on that behavior. The account will not be brought to 0 in domestic currency if it's not reoncilable. If it is, it will create a regular exchange difference entry to handle this case properly.

// CASE 2: multiple payments in foreign currency, at different rates

1) Setup a company in USD, with EUR activated as well

2) Set a rate of 1 EUR = 3 USD for date A, and 1 EUR = 2 USD for date B and 5 EUR = 1 USD for date C

3) Setup a Cash basis tax of 1/3, with a non-reconcilable cash basis transition account

4) At date A, create an invoice with just one line with base amount=99.99 EUR ; and using the tax setup in point 3).

5) Pay 66.66 EUR at date B

6) Make a second payment of 66.66 EUR at date C

===> The problem in this case is that the tax and base amounts in foreign currency (33.33 and 99.99 EUR) will need to be divided by 2 (because we're making 2 payments, hence 2 cash basis entries), and the currency rounding will round these values to 50.0 for the base (instead of 49.995) and 16.67 (instead of 16,665) for the tax. Summing the cash basis moves will hence give a total base of 100, and tax of 33,34, which is wrong.

To handle this, a cash basis rounding of 0.01 EUR needs to be done in the exchange difference entry. The corresponding amount in domestic currency must be computed using the rate of the most recent payment, so it will be 0.05 USD here.

Before this commit, this case behaved as the first one, and brought back the reported amount to the rate of the invoice. Moreover, since it never adjusted the amount in foreign currency, the transition account could not be fully reconciled.

// FIX

To handle both those cases, we now split the behavior of the CABA adjustment:

- For rounding errors: we now check the foreign currency amount, and adjust it if necessary, reusing the rate of the most recent payment. If there is no foreign currency, the adjustement is still done for domestic currency, of course.

- Exchange rate difference of the transition account is not handled there anymore: to do this, the transition account must be reconcilable. In case of rounding error to compensate for, the adjustment line added in the exchange difference entry will be reconciled with the one on the CABA entries and on the invoice, to reach full reconciliation.

In case a transition account is not reconcilable, we only create rounding adjustments, no exchange difference.

closes odoo/odoo#107334

X-original-commit: 01cb7e960f912c4758d30c04653b599937785799
Signed-off-by: Laurent Smet <las@odoo.com>
Signed-off-by: Olivier Colson (oco) <oco@odoo.com>
This commit is contained in:
oco-odoo
2022-12-07 18:29:15 +01:00
parent fa1abe7f2f
commit eaa882cb42
3 changed files with 358 additions and 92 deletions
+6
View File
@@ -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
+80 -30
View File
@@ -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()
@@ -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.