[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 ae848e9981
pull/819/head
Víctor Martínez 2025-03-31 09:23:10 +02:00
parent 0510276a36
commit 13f438d320
2 changed files with 114 additions and 50 deletions

View File

@ -380,15 +380,16 @@ class AccountReconcileModel(models.Model):
:param st_line: A statement line. :param st_line: A statement line.
:param partner: The partner associated to the 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" assert self.rule_type == "invoice_matching"
self.env["account.move"].flush_model() self.env["account.move"].flush_model()
self.env["account.move.line"].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) aml_domain = self._get_invoice_matching_amls_domain(st_line, partner)
query = self.env["account.move.line"]._where_calc(aml_domain) query = self.env["account.move.line"]._where_calc(aml_domain)
from_string, from_params = query.from_clause from_string, from_params = query.from_clause
@ -456,6 +457,7 @@ class AccountReconcileModel(models.Model):
all_params += where_params all_params += where_params
if sub_queries: if sub_queries:
order_by = get_order_by_clause(alias="sub")
self._cr.execute( self._cr.execute(
""" """
SELECT SELECT
@ -480,19 +482,48 @@ class AccountReconcileModel(models.Model):
"amls": self.env["account.move.line"].browse(candidate_ids), "amls": self.env["account.move.line"].browse(candidate_ids),
} }
# Search without any matching based on textual information. if not partner:
if partner: st_line_currency = (
if self.matching_order == "new_first": st_line.foreign_currency_id
order = "date_maturity DESC, date DESC, id DESC" 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: 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) order_by = get_order_by_clause(alias="account_move_line")
if amls: self._cr.execute(
return { f"""
"allow_auto_reconcile": False, SELECT account_move_line.id
"amls": amls, 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): def _get_invoice_matching_rules_map(self):
"""Get a mapping <priority_order, rule> that could be overridden in others """Get a mapping <priority_order, rule> that could be overridden in others

View File

@ -1095,40 +1095,6 @@ class TestReconciliationMatchingRules(AccountTestInvoicingCommon):
# Matching is back thanks to "coincoin". # Matching is back thanks to "coincoin".
self.assertEqual(st_line._retrieve_partner(), self.partner_1) 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): def test_match_multi_currencies(self):
"""Ensure the matching of candidates is made using the right statement line """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 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), rule._apply_rules(st_line, None),
{"amls": term_lines, "model": rule}, {"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,
},
},
)