[FIX] account: cash basis reconciliation on many amls
Make an expense report containing 2 expenses one of them has tax exigible on payment Validate the whole thing and make a payment Before this commit, the journal entries were wrong this was because: - there were 2 move lines in the payable account Each of them wanting to reconcile with the payment, and each of them trying to create an cash basis entry - the amounts were incorrect too, as the matched percentage was computed on move lines that were not in a payable/receivable, but in taxes accounts OPW 1912247 closes odoo/odoo#29878
This commit is contained in:
@@ -784,7 +784,7 @@ class AccountMoveLine(models.Model):
|
||||
(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([residual for aml, residual in to_balance.values()]):
|
||||
if to_balance and any([not float_is_zero(residual, precision_rounding=digits_rounding_precision) for aml, residual in to_balance.values()]):
|
||||
exchange_move = self.env['account.move'].create(
|
||||
self.env['account.full.reconcile']._prepare_exchange_diff_move(move_date=maxdate, company=amls[0].company_id))
|
||||
part_reconcile = self.env['account.partial.reconcile']
|
||||
@@ -813,7 +813,7 @@ class AccountMoveLine(models.Model):
|
||||
(debit_moves + credit_moves).read([field])
|
||||
to_create = []
|
||||
cash_basis = debit_moves and debit_moves[0].account_id.internal_type in ('receivable', 'payable') or False
|
||||
cash_basis_percentage_before_rec = []
|
||||
cash_basis_percentage_before_rec = {}
|
||||
while (debit_moves and credit_moves):
|
||||
debit_move = debit_moves[0]
|
||||
credit_move = credit_moves[0]
|
||||
@@ -848,7 +848,7 @@ class AccountMoveLine(models.Model):
|
||||
|
||||
if cash_basis:
|
||||
tmp_set = debit_move | credit_move
|
||||
cash_basis_percentage_before_rec.append(tmp_set._get_matched_percentage())
|
||||
cash_basis_percentage_before_rec.update(tmp_set._get_matched_percentage())
|
||||
|
||||
to_create.append({
|
||||
'debit_move_id': debit_move.id,
|
||||
@@ -859,14 +859,12 @@ class AccountMoveLine(models.Model):
|
||||
})
|
||||
|
||||
part_rec = self.env['account.partial.reconcile']
|
||||
index = 0
|
||||
with self.env.norecompute():
|
||||
for partial_rec_dict in to_create:
|
||||
new_rec = self.env['account.partial.reconcile'].create(partial_rec_dict)
|
||||
part_rec += new_rec
|
||||
if cash_basis:
|
||||
new_rec.create_tax_cash_basis_entry(cash_basis_percentage_before_rec[index])
|
||||
index += 1
|
||||
new_rec.create_tax_cash_basis_entry(cash_basis_percentage_before_rec)
|
||||
self.recompute()
|
||||
|
||||
return debit_moves+credit_moves
|
||||
@@ -1477,10 +1475,14 @@ class AccountPartialReconcile(models.Model):
|
||||
#move_date is the max of the 2 reconciled items
|
||||
if move_date < move.date:
|
||||
move_date = move.date
|
||||
percentage_before = percentage_before_rec[move.id]
|
||||
percentage_after = move.line_ids[0]._get_matched_percentage()[move.id]
|
||||
# update the percentage before as the move can be part of
|
||||
# multiple partial reconciliations
|
||||
percentage_before_rec[move.id] = percentage_after
|
||||
|
||||
for line in move.line_ids:
|
||||
if not line.tax_exigible:
|
||||
percentage_before = percentage_before_rec[move.id]
|
||||
percentage_after = line._get_matched_percentage()[move.id]
|
||||
#amount is the current cash_basis amount minus the one before the reconciliation
|
||||
amount = line.balance * percentage_after - line.balance * percentage_before
|
||||
rounded_amt = self._get_amount_tax_cash_basis(amount, line)
|
||||
@@ -1552,6 +1554,7 @@ class AccountPartialReconcile(models.Model):
|
||||
'amount_currency': self.amount_currency and line.currency_id.round(-line.amount_currency * amount / line.balance) or 0.0,
|
||||
'partner_id': line.partner_id.id,
|
||||
})
|
||||
|
||||
if newly_created_move:
|
||||
if move_date > (self.company_id.period_lock_date or date.min) and newly_created_move.date != move_date:
|
||||
# The move date should be the maximum date between payment and invoice (in case
|
||||
|
||||
@@ -49,6 +49,58 @@ class TestReconciliation(AccountingTestCase):
|
||||
'payment_type': 'inbound',
|
||||
})
|
||||
|
||||
self.expense_account = self.env['account.account'].create({
|
||||
'name': 'EXP',
|
||||
'code': 'EXP',
|
||||
'user_type_id': self.env.ref('account.data_account_type_expenses').id,
|
||||
'company_id': company.id,
|
||||
})
|
||||
# cash basis intermediary account
|
||||
self.tax_waiting_account = self.env['account.account'].create({
|
||||
'name': 'TAX_WAIT',
|
||||
'code': 'TWAIT',
|
||||
'user_type_id': self.env.ref('account.data_account_type_current_liabilities').id,
|
||||
'reconcile': True,
|
||||
'company_id': company.id,
|
||||
})
|
||||
# cash basis final account
|
||||
self.tax_final_account = self.env['account.account'].create({
|
||||
'name': 'TAX_TO_DEDUCT',
|
||||
'code': 'TDEDUCT',
|
||||
'user_type_id': self.env.ref('account.data_account_type_current_assets').id,
|
||||
'company_id': company.id,
|
||||
})
|
||||
self.tax_base_amount_account = self.env['account.account'].create({
|
||||
'name': 'TAX_BASE',
|
||||
'code': 'TBASE',
|
||||
'user_type_id': self.env.ref('account.data_account_type_current_assets').id,
|
||||
'company_id': company.id,
|
||||
})
|
||||
|
||||
# Journals
|
||||
self.purchase_journal = self.env['account.journal'].create({
|
||||
'name': 'purchase',
|
||||
'code': 'PURCH',
|
||||
'type': 'purchase',
|
||||
})
|
||||
self.cash_basis_journal = self.env['account.journal'].create({
|
||||
'name': 'CABA',
|
||||
'code': 'CABA',
|
||||
'type': 'general',
|
||||
})
|
||||
|
||||
# Tax Cash Basis
|
||||
self.tax_cash_basis = self.env['account.tax'].create({
|
||||
'name': 'cash basis 20%',
|
||||
'type_tax_use': 'purchase',
|
||||
'company_id': company.id,
|
||||
'amount': 20,
|
||||
'account_id': self.tax_waiting_account.id,
|
||||
'tax_exigibility': 'on_payment',
|
||||
'cash_basis_account_id': self.tax_final_account.id,
|
||||
'cash_basis_base_account_id': self.tax_base_amount_account.id,
|
||||
})
|
||||
|
||||
def create_invoice(self, type='out_invoice', invoice_amount=50, currency_id=None):
|
||||
#we create an invoice in given currency
|
||||
invoice = self.account_invoice_model.create({'partner_id': self.partner_agrolait_id,
|
||||
@@ -1107,3 +1159,288 @@ class TestReconciliation(AccountingTestCase):
|
||||
|
||||
receivable_lines = invoice.move_id.line_ids.filtered(lambda line: line.account_id == self.account_rcv).sorted('date_maturity')[0]
|
||||
self.assertTrue(receivable_lines.matched_credit_ids)
|
||||
|
||||
def test_reconciliation_cash_basis01(self):
|
||||
# Simulates an expense made up by 2 lines
|
||||
# one is subject to a cash basis tax
|
||||
# the other is not subject to tax
|
||||
|
||||
company = self.env.ref('base.main_company')
|
||||
company.tax_cash_basis_journal_id = self.cash_basis_journal
|
||||
|
||||
AccountMoveLine = self.env['account.move.line'].with_context(check_move_validity=False)
|
||||
|
||||
# Purchase
|
||||
purchase_move = self.env['account.move'].create({
|
||||
'name': 'purchase',
|
||||
'journal_id': self.purchase_journal.id,
|
||||
})
|
||||
|
||||
purchase_payable_line0 = AccountMoveLine.create({
|
||||
'account_id': self.account_rsa.id,
|
||||
'credit': 100,
|
||||
'move_id': purchase_move.id,
|
||||
})
|
||||
purchase_payable_line1 = AccountMoveLine.create({
|
||||
'account_id': self.account_rsa.id,
|
||||
'credit': 50,
|
||||
'move_id': purchase_move.id,
|
||||
})
|
||||
AccountMoveLine.create({
|
||||
'name': 'expensNoTax',
|
||||
'account_id': self.expense_account.id,
|
||||
'debit': 50,
|
||||
'move_id': purchase_move.id,
|
||||
})
|
||||
AccountMoveLine.create({
|
||||
'name': 'expenseTaxed',
|
||||
'account_id': self.expense_account.id,
|
||||
'debit': 83.33,
|
||||
'move_id': purchase_move.id,
|
||||
'tax_ids': [(4, self.tax_cash_basis.id, False)],
|
||||
})
|
||||
tax_line = AccountMoveLine.create({
|
||||
'name': 'TaxLine',
|
||||
'account_id': self.tax_waiting_account.id,
|
||||
'debit': 16.67,
|
||||
'move_id': purchase_move.id,
|
||||
'tax_line_id': self.tax_cash_basis.id,
|
||||
})
|
||||
purchase_move.post()
|
||||
|
||||
# Payment Move
|
||||
payment_move = self.env['account.move'].create({
|
||||
'name': 'payment',
|
||||
'journal_id': self.bank_journal_euro.id,
|
||||
})
|
||||
payment_payable_line = AccountMoveLine.create({
|
||||
'account_id': self.account_rsa.id,
|
||||
'debit': 150,
|
||||
'move_id': payment_move.id,
|
||||
})
|
||||
AccountMoveLine.create({
|
||||
'account_id': self.account_euro.id,
|
||||
'credit': 150,
|
||||
'move_id': payment_move.id,
|
||||
})
|
||||
payment_move.post()
|
||||
|
||||
to_reconcile = (purchase_move + payment_move).mapped('line_ids').filtered(lambda l: l.account_id.internal_type == 'payable')
|
||||
to_reconcile.reconcile()
|
||||
|
||||
cash_basis_moves = self.env['account.move'].search([('journal_id', '=', self.cash_basis_journal.id)])
|
||||
|
||||
self.assertEqual(len(cash_basis_moves), 2)
|
||||
self.assertTrue(cash_basis_moves.exists())
|
||||
|
||||
# check reconciliation in Payable account
|
||||
self.assertTrue(purchase_payable_line0.full_reconcile_id.exists())
|
||||
self.assertEqual(purchase_payable_line0.full_reconcile_id.reconciled_line_ids,
|
||||
purchase_payable_line0 + purchase_payable_line1 + payment_payable_line)
|
||||
|
||||
cash_basis_aml_ids = cash_basis_moves.mapped('line_ids')
|
||||
# check reconciliation in the tax waiting account
|
||||
self.assertTrue(tax_line.full_reconcile_id.exists())
|
||||
self.assertEqual(tax_line.full_reconcile_id.reconciled_line_ids,
|
||||
cash_basis_aml_ids.filtered(lambda l: l.account_id == self.tax_waiting_account) + tax_line)
|
||||
|
||||
self.assertEqual(len(cash_basis_aml_ids), 8)
|
||||
|
||||
# check amounts
|
||||
cash_basis_move1 = cash_basis_moves.filtered(lambda m: m.amount == 33.34)
|
||||
cash_basis_move2 = cash_basis_moves.filtered(lambda m: m.amount == 66.66)
|
||||
|
||||
self.assertTrue(cash_basis_move1.exists())
|
||||
self.assertTrue(cash_basis_move2.exists())
|
||||
|
||||
# For first move
|
||||
move_lines = cash_basis_move1.line_ids
|
||||
base_amount_tax_lines = move_lines.filtered(lambda l: l.account_id == self.tax_base_amount_account)
|
||||
self.assertEqual(len(base_amount_tax_lines), 2)
|
||||
self.assertAlmostEqual(sum(base_amount_tax_lines.mapped('credit')), 27.78)
|
||||
self.assertAlmostEqual(sum(base_amount_tax_lines.mapped('debit')), 27.78)
|
||||
|
||||
self.assertAlmostEqual((move_lines - base_amount_tax_lines).filtered(lambda l: l.account_id == self.tax_waiting_account).credit,
|
||||
5.56)
|
||||
self.assertAlmostEqual((move_lines - base_amount_tax_lines).filtered(lambda l: l.account_id == self.tax_final_account).debit,
|
||||
5.56)
|
||||
|
||||
# For second move
|
||||
move_lines = cash_basis_move2.line_ids
|
||||
base_amount_tax_lines = move_lines.filtered(lambda l: l.account_id == self.tax_base_amount_account)
|
||||
self.assertEqual(len(base_amount_tax_lines), 2)
|
||||
self.assertAlmostEqual(sum(base_amount_tax_lines.mapped('credit')), 55.55)
|
||||
self.assertAlmostEqual(sum(base_amount_tax_lines.mapped('debit')), 55.55)
|
||||
|
||||
self.assertAlmostEqual((move_lines - base_amount_tax_lines).filtered(lambda l: l.account_id == self.tax_waiting_account).credit,
|
||||
11.11)
|
||||
self.assertAlmostEqual((move_lines - base_amount_tax_lines).filtered(lambda l: l.account_id == self.tax_final_account).debit,
|
||||
11.11)
|
||||
|
||||
def test_reconciliation_cash_basis02(self):
|
||||
# Simulates an invoice made up by 2 lines
|
||||
# both subjected to cash basis taxes
|
||||
# with 2 payment terms
|
||||
# And partial payment not martching any payment term
|
||||
|
||||
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})
|
||||
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': 105,
|
||||
'move_id': purchase_move.id,
|
||||
})
|
||||
purchase_payable_line1 = AccountMoveLine.create({
|
||||
'account_id': self.account_rsa.id,
|
||||
'credit': 50,
|
||||
'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': 83.33,
|
||||
'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': 16.67,
|
||||
'move_id': purchase_move.id,
|
||||
'tax_line_id': self.tax_cash_basis.id,
|
||||
})
|
||||
purchase_move.post()
|
||||
|
||||
# Payment Move
|
||||
payment_move0 = self.env['account.move'].create({
|
||||
'name': 'payment',
|
||||
'journal_id': self.bank_journal_euro.id,
|
||||
})
|
||||
payment_payable_line0 = AccountMoveLine.create({
|
||||
'account_id': self.account_rsa.id,
|
||||
'debit': 40,
|
||||
'move_id': payment_move0.id,
|
||||
})
|
||||
AccountMoveLine.create({
|
||||
'account_id': self.account_euro.id,
|
||||
'credit': 40,
|
||||
'move_id': payment_move0.id,
|
||||
})
|
||||
payment_move0.post()
|
||||
|
||||
# Payment Move
|
||||
payment_move1 = self.env['account.move'].create({
|
||||
'name': 'payment',
|
||||
'journal_id': self.bank_journal_euro.id,
|
||||
})
|
||||
payment_payable_line1 = AccountMoveLine.create({
|
||||
'account_id': self.account_rsa.id,
|
||||
'debit': 115,
|
||||
'move_id': payment_move1.id,
|
||||
})
|
||||
AccountMoveLine.create({
|
||||
'account_id': self.account_euro.id,
|
||||
'credit': 115,
|
||||
'move_id': payment_move1.id,
|
||||
})
|
||||
payment_move1.post()
|
||||
|
||||
(purchase_move + payment_move0).mapped('line_ids').filtered(lambda l: l.account_id.internal_type == 'payable').reconcile()
|
||||
(purchase_move + payment_move1).mapped('line_ids').filtered(lambda l: l.account_id.internal_type == 'payable').reconcile()
|
||||
|
||||
cash_basis_moves = self.env['account.move'].search([('journal_id', '=', self.cash_basis_journal.id)])
|
||||
|
||||
self.assertEqual(len(cash_basis_moves), 3)
|
||||
self.assertTrue(cash_basis_moves.exists())
|
||||
|
||||
# check reconciliation in Payable account
|
||||
self.assertTrue(purchase_payable_line0.full_reconcile_id.exists())
|
||||
self.assertEqual(purchase_payable_line0.full_reconcile_id.reconciled_line_ids,
|
||||
purchase_payable_line0 + purchase_payable_line1 + payment_payable_line0 + payment_payable_line1)
|
||||
|
||||
cash_basis_aml_ids = cash_basis_moves.mapped('line_ids')
|
||||
|
||||
# check reconciliation in the tax waiting account
|
||||
self.assertTrue(tax_line0.full_reconcile_id.exists())
|
||||
self.assertEqual(tax_line0.full_reconcile_id.reconciled_line_ids,
|
||||
cash_basis_aml_ids.filtered(lambda l: l.account_id == tax_waiting_account10) + tax_line0)
|
||||
|
||||
self.assertTrue(tax_line1.full_reconcile_id.exists())
|
||||
self.assertEqual(tax_line1.full_reconcile_id.reconciled_line_ids,
|
||||
cash_basis_aml_ids.filtered(lambda l: l.account_id == self.tax_waiting_account) + tax_line1)
|
||||
|
||||
self.assertEqual(len(cash_basis_aml_ids), 24)
|
||||
|
||||
# check amounts
|
||||
expected_move_amounts = [
|
||||
{'base_20': 56.45, 'tax_20': 11.29, 'base_10': 33.87, 'tax_10': 3.39},
|
||||
{'base_20': 21.50, 'tax_20': 4.30, 'base_10': 12.90, 'tax_10': 1.29},
|
||||
{'base_20': 5.38, 'tax_20': 1.08, 'base_10': 3.23, 'tax_10': 0.32},
|
||||
]
|
||||
|
||||
index = 0
|
||||
for cb_move in cash_basis_moves.sorted('amount', reverse=True):
|
||||
expected = expected_move_amounts[index]
|
||||
move_lines = cb_move.line_ids
|
||||
base_amount_tax_lines20per = move_lines.filtered(lambda l: l.account_id == self.tax_base_amount_account and '20%' in l.name)
|
||||
base_amount_tax_lines10per = move_lines.filtered(lambda l: l.account_id == self.tax_base_amount_account and '10%' in l.name)
|
||||
self.assertEqual(len(base_amount_tax_lines20per), 2)
|
||||
|
||||
self.assertAlmostEqual(sum(base_amount_tax_lines20per.mapped('credit')), expected['base_20'])
|
||||
self.assertAlmostEqual(sum(base_amount_tax_lines20per.mapped('debit')), expected['base_20'])
|
||||
|
||||
self.assertEqual(len(base_amount_tax_lines10per), 2)
|
||||
self.assertAlmostEqual(sum(base_amount_tax_lines10per.mapped('credit')), expected['base_10'])
|
||||
self.assertAlmostEqual(sum(base_amount_tax_lines10per.mapped('debit')), expected['base_10'])
|
||||
|
||||
self.assertAlmostEqual(
|
||||
(move_lines - base_amount_tax_lines20per - base_amount_tax_lines10per)
|
||||
.filtered(lambda l: l.account_id == self.tax_waiting_account).credit,
|
||||
expected['tax_20']
|
||||
)
|
||||
self.assertAlmostEqual(
|
||||
(move_lines - base_amount_tax_lines20per - base_amount_tax_lines10per)
|
||||
.filtered(lambda l: 'TaxLine1' in l.name).debit,
|
||||
expected['tax_20']
|
||||
)
|
||||
|
||||
self.assertAlmostEqual(
|
||||
(move_lines - base_amount_tax_lines20per - base_amount_tax_lines10per)
|
||||
.filtered(lambda l: l.account_id == tax_waiting_account10).credit,
|
||||
expected['tax_10']
|
||||
)
|
||||
self.assertAlmostEqual(
|
||||
(move_lines - base_amount_tax_lines20per - base_amount_tax_lines10per)
|
||||
.filtered(lambda l: 'TaxLine0' in l.name).debit,
|
||||
expected['tax_10']
|
||||
)
|
||||
index += 1
|
||||
|
||||
Reference in New Issue
Block a user