Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FIX] account: revert move subjected to cash basis #32023

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
22 changes: 19 additions & 3 deletions addons/account/models/account_move.py
Expand Up @@ -770,7 +770,9 @@ def check_full_reconcile(self):
total_amount_currency = 0
maxdate = date.min
to_balance = {}
cash_basis_partial = self.env['account.partial.reconcile']
for aml in amls:
cash_basis_partial |= aml.move_id.tax_cash_basis_rec_id
total_debit += aml.debit
total_credit += aml.credit
maxdate = max(aml.date, maxdate)
Expand All @@ -785,15 +787,28 @@ def check_full_reconcile(self):
to_balance[aml.currency_id] = [self.env['account.move.line'], 0]
to_balance[aml.currency_id][0] += aml
to_balance[aml.currency_id][1] += aml.amount_residual != 0 and aml.amount_residual or aml.amount_residual_currency

# Check if reconciliation is total
# To check if reconciliation is total we have 3 differents use case:
# 1) There are multiple currency different than company currency, in that case we check using debit-credit
# 2) We only have one currency which is different than company currency, in that case we check using amount_currency
# 3) We have only one currency and some entries that don't have a secundary currency, in that case we check debit-credit
# or amount_currency.
# 4) Cash basis full reconciliation
# - either none of the moves are cash basis reconciled, and we proceed
# - or some moves are cash basis reconciled and we make sure they are all fully reconciled

digits_rounding_precision = amls[0].company_id.currency_id.rounding
if (currency and float_is_zero(total_amount_currency, precision_rounding=currency.rounding)) or \
(multiple_currency and float_compare(total_debit, total_credit, precision_rounding=digits_rounding_precision) == 0):
if (
(
not cash_basis_partial or (cash_basis_partial and all([p >= 1.0 for p in amls._get_matched_percentage().values()]))
) and
(
currency and float_is_zero(total_amount_currency, precision_rounding=currency.rounding) or
multiple_currency and float_compare(total_debit, total_credit, precision_rounding=digits_rounding_precision) == 0
)
):

exchange_move_id = False
# Eventually create a journal entry to book the difference due to foreign currency's exchange rate that fluctuates
if to_balance and any([not float_is_zero(residual, precision_rounding=digits_rounding_precision) for aml, residual in to_balance.values()]):
Expand Down Expand Up @@ -887,7 +902,8 @@ def _reconcile_lines(self, debit_moves, credit_moves, field):

for after_rec_dict in cash_basis_subjected:
new_rec = part_rec.create(after_rec_dict)
if cash_basis:
# if the pair belongs to move being reverted, do not create CABA entry
if cash_basis and not (new_rec.debit_move_id + new_rec.credit_move_id).mapped('move_id').mapped('reverse_entry_id'):
new_rec.create_tax_cash_basis_entry(cash_basis_percentage_before_rec)
self.recompute()

Expand Down
125 changes: 124 additions & 1 deletion addons/account/tests/test_reconciliation.py
Expand Up @@ -5,7 +5,8 @@
import unittest


@tagged('post_install', '-at_install')
# TODO in master
# The name of this class should be TestReconciliationHelpers
class TestReconciliation(AccountingTestCase):

"""Tests for reconciliation (account.tax)
Expand Down Expand Up @@ -166,6 +167,10 @@ def make_customer_and_supplier_flows(self, invoice_currency_id, invoice_amount,
supplier_move_lines = bank_stmt.move_line_ids
return customer_move_lines, supplier_move_lines


@tagged('post_install', '-at_install')
class TestReconciliationExec(TestReconciliation):

def test_statement_usd_invoice_eur_transaction_eur(self):
customer_move_lines, supplier_move_lines = self.make_customer_and_supplier_flows(self.currency_euro_id, 30, self.bank_journal_usd, 42, 30, self.currency_euro_id)
self.assertRecordValues(customer_move_lines, [
Expand Down Expand Up @@ -1876,3 +1881,121 @@ def test_reconciliation_cash_basis_fx_02(self):
(move_lines - base_amount_tax_lines)
.filtered(lambda l: l.account_id == self.tax_final_account)
.debit, 17094.66)

def test_reconciliation_cash_basis_revert(self):
company = self.env.ref('base.main_company')
company.tax_cash_basis_journal_id = self.cash_basis_journal
tax_cash_basis10percent = self.tax_cash_basis.copy({'amount': 10})
self.tax_waiting_account.reconcile = True
tax_waiting_account10 = self.tax_waiting_account.copy({
'name': 'TAX WAIT 10',
'code': 'TWAIT1',
})

AccountMoveLine = self.env['account.move.line'].with_context(check_move_validity=False)

# Purchase
purchase_move = self.env['account.move'].create({
'name': 'invoice',
'journal_id': self.purchase_journal.id,
})

purchase_payable_line0 = AccountMoveLine.create({
'account_id': self.account_rsa.id,
'credit': 175,
'move_id': purchase_move.id,
})

AccountMoveLine.create({
'name': 'expenseTaxed 10%',
'account_id': self.expense_account.id,
'debit': 50,
'move_id': purchase_move.id,
'tax_ids': [(4, tax_cash_basis10percent.id, False)],
})
tax_line0 = AccountMoveLine.create({
'name': 'TaxLine0',
'account_id': tax_waiting_account10.id,
'debit': 5,
'move_id': purchase_move.id,
'tax_line_id': tax_cash_basis10percent.id,
})
AccountMoveLine.create({
'name': 'expenseTaxed 20%',
'account_id': self.expense_account.id,
'debit': 100,
'move_id': purchase_move.id,
'tax_ids': [(4, self.tax_cash_basis.id, False)],
})
tax_line1 = AccountMoveLine.create({
'name': 'TaxLine1',
'account_id': self.tax_waiting_account.id,
'debit': 20,
'move_id': purchase_move.id,
'tax_line_id': self.tax_cash_basis.id,
})
purchase_move.post()

reverted = self.env['account.move'].browse(purchase_move.reverse_moves())
self.assertTrue(reverted.exists())

for inv_line in [purchase_payable_line0, tax_line0, tax_line1]:
self.assertTrue(inv_line.full_reconcile_id.exists())
reverted_expected = reverted.line_ids.filtered(lambda l: l.account_id == inv_line.account_id)
self.assertEqual(len(reverted_expected), 1)
self.assertEqual(reverted_expected.full_reconcile_id, inv_line.full_reconcile_id)

def test_reconciliation_cash_basis_foreign_currency_low_values(self):
journal = self.env['account.journal'].create({
'name': 'Bank', 'type': 'bank', 'code': 'THE',
'currency_id': self.currency_usd_id,
})
usd = self.env['res.currency'].browse(self.currency_usd_id)
usd.rate_ids.unlink()
self.env['res.currency.rate'].create({
'name': time.strftime('%Y-01-01'),
'rate': 1/17.0,
'currency_id': self.currency_usd_id,
'company_id': self.env.ref('base.main_company').id,
})
invoice = self.create_invoice(
type='out_invoice', invoice_amount=50,
currency_id=self.currency_usd_id)
invoice.journal_id.update_posted = True
invoice.action_cancel()
invoice.state = 'draft'
invoice.invoice_line_ids.write({
'invoice_line_tax_ids': [(6, 0, [self.tax_cash_basis.id])]})
invoice.compute_taxes()
invoice.action_invoice_open()

self.assertTrue(invoice.currency_id != self.env.user.company_id.currency_id)

# First Payment
payment0 = self.make_payment(invoice, journal, invoice.amount_total - 0.01)
self.assertEqual(invoice.residual, 0.01)

tax_waiting_line = invoice.move_id.line_ids.filtered(lambda l: l.account_id == self.tax_waiting_account)
self.assertFalse(tax_waiting_line.reconciled)

move_caba0 = tax_waiting_line.matched_debit_ids.debit_move_id.move_id
self.assertTrue(move_caba0.exists())
self.assertEqual(move_caba0.journal_id, self.env.user.company_id.tax_cash_basis_journal_id)

pay_receivable_line0 = payment0.move_line_ids.filtered(lambda l: l.account_id == self.account_rcv)
self.assertTrue(pay_receivable_line0.reconciled)
self.assertEqual(pay_receivable_line0.matched_debit_ids, move_caba0.tax_cash_basis_rec_id)

# Second Payment
payment1 = self.make_payment(invoice, journal, 0.01)
self.assertEqual(invoice.residual, 0)
self.assertEqual(invoice.state, 'paid')

self.assertTrue(tax_waiting_line.reconciled)
move_caba1 = tax_waiting_line.matched_debit_ids.mapped('debit_move_id').mapped('move_id').filtered(lambda m: m != move_caba0)
self.assertEqual(len(move_caba1.exists()), 1)
self.assertEqual(move_caba1.journal_id, self.env.user.company_id.tax_cash_basis_journal_id)

pay_receivable_line1 = payment1.move_line_ids.filtered(lambda l: l.account_id == self.account_rcv)
self.assertTrue(pay_receivable_line1.reconciled)
self.assertEqual(pay_receivable_line1.matched_debit_ids, move_caba1.tax_cash_basis_rec_id)