[FIX] account: reconciliation models: properly match statement line fields when no partner is set

When no partner is set on a statement line, the reconciliation models try to find candidates using the payment reference or the partner name.

This was not working well when using reconciliation models configured to match on notes and/or reference. Only the default match on label was working.

As an example, consider the following case:

1) setup an invoice-matcing reconciliation model as such:
	- Partner Is Set and Matches = False
	- Match Invoice/bill with = Reference

2) Create an invoice with payment reference 123, for 100€

3) Create a statement line of 100€, with reference ('ref' field, inherited from account.move) = 123, label='test', and no partner set.

4) Try to reconcile the statement line

=> not match is found

OPW 2701729

X-original-commit: 74894b0da82f5f71cd22a3c9bb405696f908ef5c
Part-of: odoo/odoo#81730
This commit is contained in:
oco-odoo
2021-12-21 15:55:56 +00:00
parent c1c082543c
commit 840c93ddab
2 changed files with 80 additions and 23 deletions
@@ -634,30 +634,42 @@ class AccountReconcileModel(models.Model):
if partner:
st_line_subquery += r" AND aml.partner_id = %s" % partner.id
else:
st_line_subquery += r"""
AND
(
substring(REGEXP_REPLACE(st_line.payment_ref, '[^0-9\s]', '', 'g'), '\S(?:.*\S)*') != ''
AND
(
(""" + self._get_select_communication_flag() + """)
OR
(""" + self._get_select_payment_reference_flag() + """)
)
)
OR
(
/* We also match statement lines without partners with amls
whose partner's name's parts (splitting on space) are all present
within the payment_ref, in any order, with any characters between them. */
st_line_fields_consideration = [
(self.match_text_location_label, 'st_line.payment_ref'),
(self.match_text_location_note, 'st_line_move.narration'),
(self.match_text_location_reference, 'st_line_move.ref'),
]
aml_partner.name IS NOT NULL
AND """ + unaccent("st_line.payment_ref") + r""" ~* ('^' || (
SELECT string_agg(concat('(?=.*\m', chunk[1], '\M)'), '')
FROM regexp_matches(""" + unaccent("aml_partner.name") + r""", '\w{3,}', 'g') AS chunk
))
)
"""
no_partner_query = " OR ".join([
r"""
(
substring(REGEXP_REPLACE(""" + sql_field + r""", '[^0-9\s]', '', 'g'), '\S(?:.*\S)*') != ''
AND
(
(""" + self._get_select_communication_flag() + r""")
OR
(""" + self._get_select_payment_reference_flag() + r""")
)
)
OR
(
/* We also match statement lines without partners with amls
whose partner's name's parts (splitting on space) are all present
within the payment_ref, in any order, with any characters between them. */
aml_partner.name IS NOT NULL
AND """ + unaccent(sql_field) + r""" ~* ('^' || (
SELECT string_agg(concat('(?=.*\m', chunk[1], '\M)'), '')
FROM regexp_matches(""" + unaccent("aml_partner.name") + r""", '\w{3,}', 'g') AS chunk
))
)
"""
for consider_field, sql_field in st_line_fields_consideration
if consider_field
])
if no_partner_query:
st_line_subquery += " AND " + no_partner_query
st_lines_queries.append(r"st_line.id = %s AND (%s)" % (st_line.id, st_line_subquery))
@@ -298,6 +298,51 @@ class TestReconciliationMatchingRules(AccountTestInvoicingCommon):
self.bank_line_5.id: {'aml_ids': [self.invoice_line_6.id], 'model': self.rule_1, 'partner': self.bank_line_5.partner_id},
}, statements=self.bank_st_2)
def test_matching_fields_match_text_location_no_partner(self):
self.bank_line_2.unlink() # One line is enough for this test
self.bank_line_1.partner_id = None
self.partner_1.name = "Bernard Gagnant"
self.rule_1.write({
'match_partner': False,
'match_partner_ids': [(5, 0, 0)],
'line_ids': [(5, 0, 0)],
})
st_line_initial_vals = {'ref': None, 'payment_ref': 'nothing', 'narration': None}
recmod_initial_vals = {'match_text_location_label': False, 'match_text_location_note': False, 'match_text_location_reference': False}
rec_mod_options_to_fields = {
'match_text_location_label': 'payment_ref',
'match_text_location_note': 'narration',
'match_text_location_reference': 'ref',
}
for rec_mod_field, st_line_field in rec_mod_options_to_fields.items():
self.rule_1.write({**recmod_initial_vals, rec_mod_field: True})
# Fully reinitialize the statement line
self.bank_line_1.write(st_line_initial_vals)
# Nothing should match
self._check_statement_matching(self.rule_1, {
self.bank_line_1.id: {'aml_ids': []},
}, statements=self.bank_st)
# Test matching with the invoice ref
self.bank_line_1.write({st_line_field: self.invoice_line_1.move_id.payment_reference})
self._check_statement_matching(self.rule_1, {
self.bank_line_1.id: {'aml_ids': self.invoice_line_1.ids, 'model': self.rule_1, 'partner': self.env['res.partner']},
}, statements=self.bank_st)
# Test matching with the partner name (reinitializing the statement line first)
self.bank_line_1.write({**st_line_initial_vals, st_line_field: self.partner_1.name})
self._check_statement_matching(self.rule_1, {
self.bank_line_1.id: {'aml_ids': self.invoice_line_1.ids, 'model': self.rule_1, 'partner': self.env['res.partner']},
}, statements=self.bank_st)
def test_matching_fields_match_journal_ids(self):
self.rule_1.match_journal_ids |= self.cash_st.journal_id
self._check_statement_matching(self.rule_1, {