[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:
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user