Files
odoo_source/addons/account/tests/test_reconciliation_matching_rules.py
T
Laurent Smet 7053ae7828 [IMP] account: management of reconciliation rules
Bank statement lines need to be matched with entries; We have to automate this operation as much as possible.
At the same time, create an algorithm that fit everyone needs is quite impossible.
For that reason we will let users choose the rules they want with the improvement of reconciliation models.

Note that we did it only for bank statement reconliation, not for manual reconciliations.

Was task: 1877375
Was PR #26669
2018-09-20 17:11:52 +02:00

285 lines
13 KiB
Python

# -*- coding: utf-8 -*-
from odoo.addons.account.tests.account_test_classes import AccountingTestCase
from odoo.tests.common import Form
from odoo.tests import tagged
@tagged('post_install', '-at_install')
class TestReconciliationMatchingRules(AccountingTestCase):
def _create_invoice_line(self, amount, partner, type):
''' Create an invoice on the fly.'''
self_ctx = self.env['account.invoice'].with_context(type=type)
journal_id = self_ctx._default_journal().id
self_ctx = self_ctx.with_context(journal_id=journal_id)
view = type in ('in_invoice', 'in_refund') and 'account.invoice_supplier_form' or 'account.invoice_form'
with Form(self_ctx, view=view) as invoice_form:
invoice_form.partner_id = partner
with invoice_form.invoice_line_ids.new() as invoice_line_form:
invoice_line_form.name = 'xxxx'
invoice_line_form.quantity = 1
invoice_line_form.price_unit = amount
invoice_line_form.invoice_line_tax_ids.clear()
invoice = invoice_form.save()
invoice.action_invoice_open()
lines = invoice.move_id.line_ids
return lines.filtered(lambda l: l.account_id == invoice.account_id)
def _check_statement_matching(self, rules, expected_values):
statement_lines = (self.bank_st + self.cash_st).mapped('line_ids')
matching_values = rules._apply_rules(statement_lines)
for st_line_id, values in matching_values.items():
values.pop('reconciled_lines', None)
self.assertDictEqual(values, expected_values[st_line_id])
def setUp(self):
super(AccountingTestCase, self).setUp()
self.partner_1 = self.env['res.partner'].create({'name': 'partner_1'})
self.partner_2 = self.env['res.partner'].create({'name': 'partner_2'})
self.invoice_line_1 = self._create_invoice_line(100, self.partner_1, 'out_invoice')
self.invoice_line_2 = self._create_invoice_line(200, self.partner_1, 'out_invoice')
self.invoice_line_3 = self._create_invoice_line(300, self.partner_1, 'in_refund')
self.invoice_line_4 = self._create_invoice_line(1000, self.partner_2, 'in_invoice')
current_assets_account = self.env['account.account'].search(
[('user_type_id', '=', self.env.ref('account.data_account_type_current_assets').id)], limit=1)
self.rule_1 = self.env.ref('account.reconciliation_model_default_rule')
self.rule_1.account_id = current_assets_account
self.rule_1.match_partner = True
self.rule_1.match_partner_ids |= self.partner_1 + self.partner_2
self.rule_2 = self.env['account.reconcile.model'].create({
'name': 'write-off model',
'rule_type': 'writeoff_suggestion',
'match_partner': True,
'match_partner_ids': [6, 0, (self.partner_1 + self.partner_2).ids],
'account_id': current_assets_account.id,
})
invoice_number = self.invoice_line_1.move_id.name
bank_journal = self.env['account.journal'].search([('type', '=', 'bank')], limit=1)
self.bank_st = self.env['account.bank.statement'].create({
'name': 'test bank journal', 'journal_id': bank_journal.id,
})
self.bank_line_1 = self.env['account.bank.statement.line'].create({
'statement_id': self.bank_st.id,
'name': 'invoice %s-%s' % (invoice_number.split('/')[1], invoice_number.split('/')[2]),
'partner_id': self.partner_1.id,
'amount': 100,
'sequence': 1,
})
self.bank_line_2 = self.env['account.bank.statement.line'].create({
'statement_id': self.bank_st.id,
'name': 'xxxxx',
'partner_id': self.partner_1.id,
'amount': 600,
'sequence': 2,
})
cash_journal = self.env['account.journal'].search([('type', '=', 'cash')], limit=1)
self.cash_st = self.env['account.bank.statement'].create({
'name': 'test cash journal', 'journal_id': cash_journal.id,
})
self.cash_line_1 = self.env['account.bank.statement.line'].create({
'statement_id': self.cash_st.id,
'name': 'yyyyy',
'partner_id': self.partner_2.id,
'amount': -1000,
'sequence': 1,
})
def test_matching_fields(self):
''' Test all fields used to restrict the rules's applicability.'''
# Check without restriction.
self._check_statement_matching(self.rule_1, {
self.bank_line_1.id: {'aml_ids': [self.invoice_line_1.id], 'model': self.rule_1},
self.bank_line_2.id: {'aml_ids': [
self.invoice_line_2.id,
self.invoice_line_3.id,
], 'model': self.rule_1},
self.cash_line_1.id: {'aml_ids': [self.invoice_line_4.id], 'model': self.rule_1},
})
# Check match_journal_ids.
self.rule_1.match_journal_ids |= self.cash_st.journal_id
self._check_statement_matching(self.rule_1, {
self.bank_line_1.id: {'aml_ids': []},
self.bank_line_2.id: {'aml_ids': []},
self.cash_line_1.id: {'aml_ids': [self.invoice_line_4.id], 'model': self.rule_1},
})
self.rule_1.match_journal_ids |= self.bank_st.journal_id + self.cash_st.journal_id
# Check match_nature.
self.rule_1.match_nature = 'amount_received'
self._check_statement_matching(self.rule_1, {
self.bank_line_1.id: {'aml_ids': [self.invoice_line_1.id], 'model': self.rule_1},
self.bank_line_2.id: {'aml_ids': [
self.invoice_line_2.id,
self.invoice_line_3.id,
], 'model': self.rule_1},
self.cash_line_1.id: {'aml_ids': []},
})
self.rule_1.match_nature = 'amount_paid'
self._check_statement_matching(self.rule_1, {
self.bank_line_1.id: {'aml_ids': []},
self.bank_line_2.id: {'aml_ids': []},
self.cash_line_1.id: {'aml_ids': [self.invoice_line_4.id], 'model': self.rule_1},
})
self.rule_1.match_nature = 'both'
# Check match_amount.
self.rule_1.match_amount = 'lower'
self.rule_1.match_amount_max = 150
self._check_statement_matching(self.rule_1, {
self.bank_line_1.id: {'aml_ids': [self.invoice_line_1.id], 'model': self.rule_1},
self.bank_line_2.id: {'aml_ids': []},
self.cash_line_1.id: {'aml_ids': []},
})
self.rule_1.match_amount = 'greater'
self.rule_1.match_amount_min = 200
self._check_statement_matching(self.rule_1, {
self.bank_line_1.id: {'aml_ids': []},
self.bank_line_2.id: {'aml_ids': [
self.invoice_line_1.id,
self.invoice_line_2.id,
self.invoice_line_3.id,
], 'model': self.rule_1},
self.cash_line_1.id: {'aml_ids': [self.invoice_line_4.id], 'model': self.rule_1},
})
self.rule_1.match_amount = 'between'
self.rule_1.match_amount_min = 200
self.rule_1.match_amount_max = 800
self._check_statement_matching(self.rule_1, {
self.bank_line_1.id: {'aml_ids': []},
self.bank_line_2.id: {'aml_ids': [
self.invoice_line_1.id,
self.invoice_line_2.id,
self.invoice_line_3.id,
], 'model': self.rule_1},
self.cash_line_1.id: {'aml_ids': []},
})
self.rule_1.match_amount = False
# Check match_label.
self.rule_1.match_label = 'contains'
self.rule_1.match_label_param = 'yyyyy'
self._check_statement_matching(self.rule_1, {
self.bank_line_1.id: {'aml_ids': []},
self.bank_line_2.id: {'aml_ids': []},
self.cash_line_1.id: {'aml_ids': [self.invoice_line_4.id], 'model': self.rule_1},
})
self.rule_1.match_label = 'not_contains'
self.rule_1.match_label_param = 'xxxxx'
self._check_statement_matching(self.rule_1, {
self.bank_line_1.id: {'aml_ids': [self.invoice_line_1.id], 'model': self.rule_1},
self.bank_line_2.id: {'aml_ids': []},
self.cash_line_1.id: {'aml_ids': [self.invoice_line_4.id], 'model': self.rule_1},
})
self.rule_1.match_label = 'match_regex'
self.rule_1.match_label_param = 'xxxxx|yyyyy'
self._check_statement_matching(self.rule_1, {
self.bank_line_1.id: {'aml_ids': []},
self.bank_line_2.id: {'aml_ids': [
self.invoice_line_1.id,
self.invoice_line_2.id,
self.invoice_line_3.id,
], 'model': self.rule_1},
self.cash_line_1.id: {'aml_ids': [self.invoice_line_4.id], 'model': self.rule_1},
})
self.rule_1.match_label = False
# Check match_total_amount.
self.rule_1.match_total_amount_param = 90.0
self.bank_line_1.amount += 5
self._check_statement_matching(self.rule_1, {
self.bank_line_1.id: {'aml_ids': [self.invoice_line_1.id], 'model': self.rule_1, 'status': 'write_off'},
self.bank_line_2.id: {'aml_ids': [
self.invoice_line_2.id,
self.invoice_line_3.id,
], 'model': self.rule_1},
self.cash_line_1.id: {'aml_ids': [self.invoice_line_4.id], 'model': self.rule_1},
})
self.rule_1.match_total_amount_param = 100.0
self.bank_line_1.amount -= 5
# Check match_partner_category_ids.
test_category = self.env.ref('base.res_partner_category_8')
self.partner_2.category_id = test_category
self.rule_1.match_partner_category_ids |= test_category
self._check_statement_matching(self.rule_1, {
self.bank_line_1.id: {'aml_ids': []},
self.bank_line_2.id: {'aml_ids': []},
self.cash_line_1.id: {'aml_ids': [self.invoice_line_4.id], 'model': self.rule_1},
})
self.rule_1.match_partner_category_ids = False
def test_mixin_rules(self):
''' Test usage of rules together.'''
# rule_1 is used before rule_2.
self.rule_1.sequence = 1
self.rule_2.sequence = 2
self._check_statement_matching(self.rule_1 + self.rule_2, {
self.bank_line_1.id: {'aml_ids': [self.invoice_line_1.id], 'model': self.rule_1},
self.bank_line_2.id: {'aml_ids': [
self.invoice_line_2.id,
self.invoice_line_3.id,
], 'model': self.rule_1},
self.cash_line_1.id: {'aml_ids': [self.invoice_line_4.id], 'model': self.rule_1},
})
# rule_2 is used before rule_1.
self.rule_1.sequence = 2
self.rule_2.sequence = 1
self._check_statement_matching(self.rule_1 + self.rule_2, {
self.bank_line_1.id: {'aml_ids': [], 'model': self.rule_2, 'status': 'write_off'},
self.bank_line_2.id: {'aml_ids': [], 'model': self.rule_2, 'status': 'write_off'},
self.cash_line_1.id: {'aml_ids': [], 'model': self.rule_2, 'status': 'write_off'},
})
# rule_2 is used before rule_1 but only on partner_1.
self.rule_2.match_partner_ids |= self.partner_1
self._check_statement_matching(self.rule_1 + self.rule_2, {
self.bank_line_1.id: {'aml_ids': [], 'model': self.rule_2, 'status': 'write_off'},
self.bank_line_2.id: {'aml_ids': [], 'model': self.rule_2, 'status': 'write_off'},
self.cash_line_1.id: {'aml_ids': [self.invoice_line_4.id], 'model': self.rule_1},
})
def test_auto_reconcile(self):
''' Test auto reconciliation.'''
self.rule_1.sequence = 2
self.rule_1.auto_reconcile = True
self.rule_1.match_total_amount_param = 90
self.rule_2.sequence = 1
self.rule_2.match_partner_ids |= self.partner_2
self.rule_2.auto_reconcile = True
self.bank_line_1.amount += 5
self._check_statement_matching(self.rule_1 + self.rule_2, {
self.bank_line_1.id: {'aml_ids': [self.invoice_line_1.id], 'model': self.rule_1, 'status': 'reconciled'},
self.bank_line_2.id: {'aml_ids': []},
self.cash_line_1.id: {'aml_ids': [], 'model': self.rule_2, 'status': 'reconciled'},
})
# Check first line has been well reconciled.
self.assertRecordValues(self.bank_line_1.journal_entry_ids, [
{'partner_id': self.partner_1.id, 'debit': 105.0, 'credit': 0.0},
{'partner_id': self.partner_1.id, 'debit': 0.0, 'credit': 100.0},
{'partner_id': self.partner_1.id, 'debit': 0.0, 'credit': 5.0},
])
# Check second line has been well reconciled.
self.assertRecordValues(self.cash_line_1.journal_entry_ids, [
{'partner_id': self.partner_2.id, 'debit': 0.0, 'credit': 1000.0},
{'partner_id': self.partner_2.id, 'debit': 1000.0, 'credit': 0.0},
])