From 840c93ddabfe81d5e7ecbbd65619d37858c95589 Mon Sep 17 00:00:00 2001 From: oco-odoo Date: Tue, 21 Dec 2021 09:14:48 +0000 Subject: [PATCH] [FIX] account: reconciliation models: properly match statement line fields when no partner is set MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../account/models/account_reconcile_model.py | 58 +++++++++++-------- .../test_reconciliation_matching_rules.py | 45 ++++++++++++++ 2 files changed, 80 insertions(+), 23 deletions(-) diff --git a/addons/account/models/account_reconcile_model.py b/addons/account/models/account_reconcile_model.py index d0f705c32ae..2b5f60b724f 100644 --- a/addons/account/models/account_reconcile_model.py +++ b/addons/account/models/account_reconcile_model.py @@ -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)) diff --git a/addons/account/tests/test_reconciliation_matching_rules.py b/addons/account/tests/test_reconciliation_matching_rules.py index 4d32c31591b..d5c429b05b9 100644 --- a/addons/account/tests/test_reconciliation_matching_rules.py +++ b/addons/account/tests/test_reconciliation_matching_rules.py @@ -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, {