[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 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,14 +482,43 @@ 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)
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,

View File

@ -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,
},
},
)