[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:
Lucas Perais (lpe)
2019-01-10 07:40:27 +00:00
parent 132717c7b1
commit 5e1b874cd8
2 changed files with 348 additions and 8 deletions
+11 -8
View File
@@ -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
+337
View File
@@ -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