From 13f438d3209f79973a26e4a41b953ea79f9a19b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Mart=C3=ADnez?= Date: Mon, 31 Mar 2025 09:23:10 +0200 Subject: [PATCH] [FIX] account_reconcile_model_oca: Fix matching rules with no partner The orm doesn't match monetary amounts using the related currency. It means, -208.73 != 208.730000000000002. Let's match the amount using an sql query instead. Related to https://github.com/odoo/odoo/commit/ae848e9981a7f359995f48dbd909c5764abd6a9c --- .../models/account_reconcile_model.py | 63 ++++++++--- .../tests/test_reconciliation_match.py | 101 ++++++++++++------ 2 files changed, 114 insertions(+), 50 deletions(-) diff --git a/account_reconcile_model_oca/models/account_reconcile_model.py b/account_reconcile_model_oca/models/account_reconcile_model.py index 43f67e82..c7c1c6e1 100644 --- a/account_reconcile_model_oca/models/account_reconcile_model.py +++ b/account_reconcile_model_oca/models/account_reconcile_model.py @@ -380,15 +380,16 @@ class AccountReconcileModel(models.Model): :param st_line: A statement line. :param partner: The partner associated to the statement line. """ + + def get_order_by_clause(alias=None): + direction = "DESC" if self.matching_order == "new_first" else "ASC" + dotted_alias = f"{alias}." if alias else "" + return f"{dotted_alias}date_maturity {direction}, {dotted_alias}date {direction}, {dotted_alias}id {direction}" # noqa: E501 + assert self.rule_type == "invoice_matching" self.env["account.move"].flush_model() self.env["account.move.line"].flush_model() - if self.matching_order == "new_first": - order_by = "sub.date_maturity DESC, sub.date DESC, sub.id DESC" - else: - order_by = "sub.date_maturity ASC, sub.date ASC, sub.id ASC" - aml_domain = self._get_invoice_matching_amls_domain(st_line, partner) query = self.env["account.move.line"]._where_calc(aml_domain) from_string, from_params = query.from_clause @@ -456,6 +457,7 @@ class AccountReconcileModel(models.Model): all_params += where_params if sub_queries: + order_by = get_order_by_clause(alias="sub") self._cr.execute( """ SELECT @@ -480,19 +482,48 @@ class AccountReconcileModel(models.Model): "amls": self.env["account.move.line"].browse(candidate_ids), } - # Search without any matching based on textual information. - if partner: - if self.matching_order == "new_first": - order = "date_maturity DESC, date DESC, id DESC" + if not partner: + st_line_currency = ( + st_line.foreign_currency_id + or st_line.journal_id.currency_id + or st_line.company_currency_id + ) + if st_line_currency == self.company_id.currency_id: + aml_amount_field = "amount_residual" else: - order = "date_maturity ASC, date ASC, id ASC" + aml_amount_field = "amount_residual_currency" - amls = self.env["account.move.line"].search(aml_domain, order=order) - if amls: - return { - "allow_auto_reconcile": False, - "amls": amls, - } + order_by = get_order_by_clause(alias="account_move_line") + self._cr.execute( + f""" + SELECT account_move_line.id + FROM {from_clause} + WHERE + {where_clause} + AND account_move_line.currency_id = %s + AND ROUND(account_move_line.{aml_amount_field}, %s) = ROUND(%s, %s) + ORDER BY {order_by} + """, # noqa: E501 + where_params + + [ + st_line_currency.id, + st_line_currency.decimal_places, + -st_line.amount_residual, + st_line_currency.decimal_places, + ], + ) + amls = self.env["account.move.line"].browse( + [row[0] for row in self._cr.fetchall()] + ) + else: + amls = self.env["account.move.line"].search( + aml_domain, order=get_order_by_clause() + ) + if amls: + return { + "allow_auto_reconcile": False, + "amls": amls, + } def _get_invoice_matching_rules_map(self): """Get a mapping that could be overridden in others diff --git a/account_reconcile_model_oca/tests/test_reconciliation_match.py b/account_reconcile_model_oca/tests/test_reconciliation_match.py index d48ecea9..05af3aa4 100644 --- a/account_reconcile_model_oca/tests/test_reconciliation_match.py +++ b/account_reconcile_model_oca/tests/test_reconciliation_match.py @@ -1095,40 +1095,6 @@ class TestReconciliationMatchingRules(AccountTestInvoicingCommon): # Matching is back thanks to "coincoin". self.assertEqual(st_line._retrieve_partner(), self.partner_1) - def test_partner_name_in_communication(self): - self.invoice_line_1.partner_id.write({"name": "Archibald Haddock"}) - self.bank_line_1.write( - {"partner_id": None, "payment_ref": "1234//HADDOCK-Archibald"} - ) - self.bank_line_2.write({"partner_id": None}) - self.rule_1.write({"match_partner": False}) - - # bank_line_1 should match, as its communic. contains the invoice's partner name - self._check_statement_matching( - self.rule_1, - { - self.bank_line_1: {"amls": self.invoice_line_1, "model": self.rule_1}, - self.bank_line_2: {}, - }, - ) - - def test_partner_name_with_regexp_chars(self): - self.invoice_line_1.partner_id.write({"name": "Archibald + Haddock"}) - self.bank_line_1.write( - {"partner_id": None, "payment_ref": "1234//HADDOCK+Archibald"} - ) - self.bank_line_2.write({"partner_id": None}) - self.rule_1.write({"match_partner": False}) - - # The query should still work - self._check_statement_matching( - self.rule_1, - { - self.bank_line_1: {"amls": self.invoice_line_1, "model": self.rule_1}, - self.bank_line_2: {}, - }, - ) - def test_match_multi_currencies(self): """Ensure the matching of candidates is made using the right statement line currency. In this test, the value of the statement line is 100 USD = 300 @@ -1540,3 +1506,70 @@ class TestReconciliationMatchingRules(AccountTestInvoicingCommon): rule._apply_rules(st_line, None), {"amls": term_lines, "model": rule}, ) + + @freeze_time("2019-01-01") + def test_matching_exact_amount_no_partner(self): + """In case the reconciliation model can't match via text or partner matching + we do a last check to find amls with the exact amount. + """ + self.rule_1.write( + { + "match_text_location_label": False, + "match_partner": False, + "match_partner_ids": [Command.clear()], + } + ) + self.bank_line_1.partner_id = None + self.bank_line_1.payment_ref = False + + with self.subTest(test="single_currency"): + st_line = self._create_st_line( + amount=100, payment_ref=None, partner_id=None + ) + invl = self._create_invoice_line(100, self.partner_1, "out_invoice") + self._check_statement_matching( + self.rule_1, + { + st_line: { + "amls": invl, + "model": self.rule_1, + }, + }, + ) + + with self.subTest(test="rounding"): + st_line = self._create_st_line( + amount=-208.73, payment_ref=None, partner_id=None + ) + invl = self._create_invoice_line(208.73, self.partner_1, "in_invoice") + self._check_statement_matching( + self.rule_1, + { + st_line: { + "amls": invl, + "model": self.rule_1, + }, + }, + ) + + with self.subTest(test="multi_currencies"): + foreign_curr = self.other_currency + invl = self._create_invoice_line( + 300, self.partner_1, "out_invoice", currency=foreign_curr + ) + st_line = self._create_st_line( + amount=15.0, + foreign_currency_id=foreign_curr.id, + amount_currency=300.0, + payment_ref=None, + partner_id=None, + ) + self._check_statement_matching( + self.rule_1, + { + st_line: { + "amls": invl, + "model": self.rule_1, + }, + }, + )