Merge branch 'OCA:17.0' into 17.0
commit
36cefd3ee6
|
@ -23,8 +23,8 @@ addon | version | maintainers | summary
|
||||||
--- | --- | --- | ---
|
--- | --- | --- | ---
|
||||||
[account_in_payment](account_in_payment/) | 17.0.1.0.0 | | This module enables in-payment mode for your accounting
|
[account_in_payment](account_in_payment/) | 17.0.1.0.0 | | This module enables in-payment mode for your accounting
|
||||||
[account_mass_reconcile](account_mass_reconcile/) | 17.0.1.0.1 | | Account Mass Reconcile
|
[account_mass_reconcile](account_mass_reconcile/) | 17.0.1.0.1 | | Account Mass Reconcile
|
||||||
[account_reconcile_model_oca](account_reconcile_model_oca/) | 17.0.1.0.2 | | This includes the logic moved from Odoo Community to Odoo Enterprise
|
[account_reconcile_model_oca](account_reconcile_model_oca/) | 17.0.1.0.4 | | This includes the logic moved from Odoo Community to Odoo Enterprise
|
||||||
[account_reconcile_oca](account_reconcile_oca/) | 17.0.1.5.5 | [](https://github.com/etobella) | Reconcile addons for Odoo CE accounting
|
[account_reconcile_oca](account_reconcile_oca/) | 17.0.1.5.8 | [](https://github.com/etobella) | Reconcile addons for Odoo CE accounting
|
||||||
[account_statement_base](account_statement_base/) | 17.0.1.5.0 | [](https://github.com/alexis-via) | Base module for Bank Statements
|
[account_statement_base](account_statement_base/) | 17.0.1.5.0 | [](https://github.com/alexis-via) | Base module for Bank Statements
|
||||||
|
|
||||||
[//]: # (end addons)
|
[//]: # (end addons)
|
||||||
|
|
|
@ -7,7 +7,7 @@ Account Reconcile Model Oca
|
||||||
!! This file is generated by oca-gen-addon-readme !!
|
!! This file is generated by oca-gen-addon-readme !!
|
||||||
!! changes will be overwritten. !!
|
!! changes will be overwritten. !!
|
||||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||||
!! source digest: sha256:80bb08dc3058116c364563a7014c16787db3ac0b12afadbde03716f7277fa298
|
!! source digest: sha256:f0554ce70e9ac90badf0a4082aecd5af7011cf1461fe0cc6678577d2b7f87e21
|
||||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||||
|
|
||||||
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
|
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
|
||||||
|
|
|
@ -5,11 +5,12 @@
|
||||||
"name": "Account Reconcile Model Oca",
|
"name": "Account Reconcile Model Oca",
|
||||||
"summary": """
|
"summary": """
|
||||||
This includes the logic moved from Odoo Community to Odoo Enterprise""",
|
This includes the logic moved from Odoo Community to Odoo Enterprise""",
|
||||||
"version": "17.0.1.0.2",
|
"version": "17.0.1.0.4",
|
||||||
"license": "LGPL-3",
|
"license": "LGPL-3",
|
||||||
"author": "Dixmit,Odoo,Odoo Community Association (OCA)",
|
"author": "Dixmit,Odoo,Odoo Community Association (OCA)",
|
||||||
"website": "https://github.com/OCA/account-reconcile",
|
"website": "https://github.com/OCA/account-reconcile",
|
||||||
"depends": ["account"],
|
"depends": ["account"],
|
||||||
|
"excludes": ["account_accountant"],
|
||||||
"data": [],
|
"data": [],
|
||||||
"demo": [],
|
"demo": [],
|
||||||
}
|
}
|
||||||
|
|
|
@ -109,23 +109,15 @@ class AccountBankStatementLine(models.Model):
|
||||||
"""
|
"""
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
|
|
||||||
def _get_text_value(field_name):
|
|
||||||
if self._fields[field_name].type == "html":
|
|
||||||
return self[field_name] and html2plaintext(self[field_name])
|
|
||||||
else:
|
|
||||||
return self[field_name]
|
|
||||||
|
|
||||||
st_line_text_values = []
|
st_line_text_values = []
|
||||||
if allowed_fields is None or "payment_ref" in allowed_fields:
|
if not allowed_fields or "payment_ref" in allowed_fields:
|
||||||
value = _get_text_value("payment_ref")
|
if self.payment_ref:
|
||||||
if value:
|
st_line_text_values.append(self.payment_ref)
|
||||||
st_line_text_values.append(value)
|
if not allowed_fields or "narration" in allowed_fields:
|
||||||
if allowed_fields is None or "narration" in allowed_fields:
|
value = html2plaintext(self.narration or "")
|
||||||
value = _get_text_value("narration")
|
|
||||||
if value:
|
|
||||||
st_line_text_values.append(value)
|
|
||||||
if allowed_fields is None or "ref" in allowed_fields:
|
|
||||||
value = _get_text_value("ref")
|
|
||||||
if value:
|
if value:
|
||||||
st_line_text_values.append(value)
|
st_line_text_values.append(value)
|
||||||
|
if not allowed_fields or "ref" in allowed_fields:
|
||||||
|
if self.ref:
|
||||||
|
st_line_text_values.append(self.ref)
|
||||||
return st_line_text_values
|
return st_line_text_values
|
||||||
|
|
|
@ -131,6 +131,8 @@ class AccountReconcileModel(models.Model):
|
||||||
balance = currency.round(
|
balance = currency.round(
|
||||||
line.amount * (1 if residual_balance > 0.0 else -1)
|
line.amount * (1 if residual_balance > 0.0 else -1)
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
balance = 0.0
|
||||||
|
|
||||||
if currency.is_zero(balance):
|
if currency.is_zero(balance):
|
||||||
continue
|
continue
|
||||||
|
@ -260,7 +262,7 @@ class AccountReconcileModel(models.Model):
|
||||||
or (
|
or (
|
||||||
self.match_partner
|
self.match_partner
|
||||||
and self.match_partner_category_ids
|
and self.match_partner_category_ids
|
||||||
and partner.category_id not in self.match_partner_category_ids
|
and not (partner.category_id & self.match_partner_category_ids)
|
||||||
)
|
)
|
||||||
):
|
):
|
||||||
return False
|
return False
|
||||||
|
@ -316,36 +318,63 @@ class AccountReconcileModel(models.Model):
|
||||||
|
|
||||||
return aml_domain
|
return aml_domain
|
||||||
|
|
||||||
|
def _get_st_line_text_values_for_matching(self, st_line):
|
||||||
|
"""Collect the strings that could be used on the statement line to perform
|
||||||
|
some matching.
|
||||||
|
|
||||||
|
:param st_line: The current statement line.
|
||||||
|
:return: A list of strings.
|
||||||
|
"""
|
||||||
|
self.ensure_one()
|
||||||
|
allowed_fields = []
|
||||||
|
if self.match_text_location_label:
|
||||||
|
allowed_fields.append("payment_ref")
|
||||||
|
if self.match_text_location_note:
|
||||||
|
allowed_fields.append("narration")
|
||||||
|
if self.match_text_location_reference:
|
||||||
|
allowed_fields.append("ref")
|
||||||
|
return st_line._get_st_line_strings_for_matching(allowed_fields=allowed_fields)
|
||||||
|
|
||||||
def _get_invoice_matching_st_line_tokens(self, st_line):
|
def _get_invoice_matching_st_line_tokens(self, st_line):
|
||||||
"""Parse the textual information from the statement line passed as parameter
|
"""Parse the textual information from the statement line passed as parameter
|
||||||
in order to extract from it the meaningful information in order to perform the
|
in order to extract from it the meaningful information in order to perform the
|
||||||
matching.
|
matching.
|
||||||
:param st_line: A statement line.
|
:param st_line: A statement line.
|
||||||
:return: A list of tokens, each one being a string.
|
:return: A tuple of list of tokens, each one being a string.
|
||||||
|
The first element is a list of tokens you may match on
|
||||||
|
numerical information.
|
||||||
|
The second element is a list of tokens you may match exactly.
|
||||||
"""
|
"""
|
||||||
st_line_text_values = st_line._get_st_line_strings_for_matching(
|
st_line_text_values = self._get_st_line_text_values_for_matching(st_line)
|
||||||
allowed_fields=(
|
|
||||||
"payment_ref" if self.match_text_location_label else None,
|
|
||||||
"narration" if self.match_text_location_note else None,
|
|
||||||
"ref" if self.match_text_location_reference else None,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
significant_token_size = 4
|
significant_token_size = 4
|
||||||
tokens = []
|
numerical_tokens = []
|
||||||
|
exact_tokens = []
|
||||||
|
text_tokens = []
|
||||||
for text_value in st_line_text_values:
|
for text_value in st_line_text_values:
|
||||||
for token in (text_value or "").split():
|
tokens = [
|
||||||
|
"".join(x for x in token if re.match(r"[0-9a-zA-Z\s]", x))
|
||||||
|
for token in (text_value or "").split()
|
||||||
|
]
|
||||||
|
|
||||||
|
# Numerical tokens
|
||||||
|
for token in tokens:
|
||||||
# The token is too short to be significant.
|
# The token is too short to be significant.
|
||||||
if len(token) < significant_token_size:
|
if len(token) < significant_token_size:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
text_tokens.append(token)
|
||||||
formatted_token = "".join(x for x in token if x.isdecimal())
|
formatted_token = "".join(x for x in token if x.isdecimal())
|
||||||
|
|
||||||
# The token is too short after formatting to be significant.
|
# The token is too short after formatting to be significant.
|
||||||
if len(formatted_token) < significant_token_size:
|
if len(formatted_token) < significant_token_size:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
tokens.append(formatted_token)
|
numerical_tokens.append(formatted_token)
|
||||||
return tokens
|
|
||||||
|
# Exact tokens.
|
||||||
|
if len(tokens) == 1:
|
||||||
|
exact_tokens.append(text_value)
|
||||||
|
return numerical_tokens, exact_tokens, text_tokens
|
||||||
|
|
||||||
def _get_invoice_matching_amls_candidates(self, st_line, partner):
|
def _get_invoice_matching_amls_candidates(self, st_line, partner):
|
||||||
"""Returns the match candidates for the 'invoice_matching' rule, with respect to
|
"""Returns the match candidates for the 'invoice_matching' rule, with respect to
|
||||||
|
@ -353,53 +382,97 @@ 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)
|
||||||
tables, where_clause, where_params = query.get_sql()
|
tables, where_clause, where_params = query.get_sql()
|
||||||
|
|
||||||
tokens = self._get_invoice_matching_st_line_tokens(st_line)
|
sub_queries = []
|
||||||
if tokens:
|
all_params = []
|
||||||
sub_queries = []
|
aml_cte = ""
|
||||||
for table_alias, field in (
|
(
|
||||||
("account_move_line", "name"),
|
numerical_tokens,
|
||||||
("account_move_line__move_id", "name"),
|
exact_tokens,
|
||||||
("account_move_line__move_id", "ref"),
|
_text_tokens,
|
||||||
):
|
) = self._get_invoice_matching_st_line_tokens(st_line)
|
||||||
|
if numerical_tokens or exact_tokens:
|
||||||
|
aml_cte = rf"""
|
||||||
|
WITH aml_cte AS (
|
||||||
|
SELECT
|
||||||
|
account_move_line.id as account_move_line_id,
|
||||||
|
account_move_line.date as account_move_line_date,
|
||||||
|
account_move_line.date_maturity as account_move_line_date_maturity,
|
||||||
|
account_move_line.name as account_move_line_name,
|
||||||
|
account_move_line__move_id.name as account_move_line__move_id_name,
|
||||||
|
account_move_line__move_id.ref as account_move_line__move_id_ref
|
||||||
|
FROM {tables}
|
||||||
|
JOIN account_move account_move_line__move_id
|
||||||
|
ON account_move_line__move_id.id = account_move_line.move_id
|
||||||
|
WHERE {where_clause}
|
||||||
|
)
|
||||||
|
""" # noqa: E501
|
||||||
|
all_params += where_params
|
||||||
|
|
||||||
|
enabled_matches = []
|
||||||
|
if self.match_text_location_label:
|
||||||
|
enabled_matches.append(("account_move_line", "name"))
|
||||||
|
if self.match_text_location_note:
|
||||||
|
enabled_matches.append(("account_move_line__move_id", "name"))
|
||||||
|
if self.match_text_location_reference:
|
||||||
|
enabled_matches.append(("account_move_line__move_id", "ref"))
|
||||||
|
|
||||||
|
if numerical_tokens:
|
||||||
|
for table_alias, field in enabled_matches:
|
||||||
sub_queries.append(
|
sub_queries.append(
|
||||||
rf"""
|
rf"""
|
||||||
SELECT
|
SELECT
|
||||||
account_move_line.id,
|
account_move_line_id as id,
|
||||||
account_move_line.date,
|
account_move_line_date as date,
|
||||||
account_move_line.date_maturity,
|
account_move_line_date_maturity as date_maturity,
|
||||||
UNNEST(
|
UNNEST(
|
||||||
REGEXP_SPLIT_TO_ARRAY(
|
REGEXP_SPLIT_TO_ARRAY(
|
||||||
SUBSTRING(
|
SUBSTRING(
|
||||||
REGEXP_REPLACE(
|
REGEXP_REPLACE(
|
||||||
{table_alias}.{field}, '[^0-9\s]', '', 'g'
|
{table_alias}_{field}, '[^0-9\s]', '', 'g'
|
||||||
),
|
),
|
||||||
'\S(?:.*\S)*'
|
'\S(?:.*\S)*'
|
||||||
),
|
),
|
||||||
'\s+'
|
'\s+'
|
||||||
)
|
)
|
||||||
) AS token
|
) AS token
|
||||||
FROM {tables}
|
FROM aml_cte
|
||||||
JOIN account_move account_move_line__move_id
|
WHERE {table_alias}_{field} IS NOT NULL
|
||||||
ON account_move_line__move_id.id = account_move_line.move_id
|
|
||||||
WHERE {where_clause} AND {table_alias}.{field} IS NOT NULL
|
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
self._cr.execute(
|
if exact_tokens:
|
||||||
|
for table_alias, field in enabled_matches:
|
||||||
|
sub_queries.append(
|
||||||
|
rf"""
|
||||||
|
SELECT
|
||||||
|
account_move_line_id as id,
|
||||||
|
account_move_line_date as date,
|
||||||
|
account_move_line_date_maturity as date_maturity,
|
||||||
|
{table_alias}_{field} AS token
|
||||||
|
FROM aml_cte
|
||||||
|
WHERE COALESCE({table_alias}_{field}, '') != ''
|
||||||
"""
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
if sub_queries:
|
||||||
|
order_by = get_order_by_clause(alias="sub")
|
||||||
|
self._cr.execute(
|
||||||
|
aml_cte
|
||||||
|
+ """
|
||||||
SELECT
|
SELECT
|
||||||
sub.id,
|
sub.id,
|
||||||
COUNT(*) AS nb_match
|
COUNT(*) AS nb_match
|
||||||
|
@ -413,7 +486,7 @@ class AccountReconcileModel(models.Model):
|
||||||
+ order_by
|
+ order_by
|
||||||
+ """
|
+ """
|
||||||
""",
|
""",
|
||||||
(where_params * 3) + [tuple(tokens)],
|
all_params + [tuple(numerical_tokens + exact_tokens)],
|
||||||
)
|
)
|
||||||
candidate_ids = [r[0] for r in self._cr.fetchall()]
|
candidate_ids = [r[0] for r in self._cr.fetchall()]
|
||||||
if candidate_ids:
|
if candidate_ids:
|
||||||
|
@ -421,20 +494,58 @@ class AccountReconcileModel(models.Model):
|
||||||
"allow_auto_reconcile": True,
|
"allow_auto_reconcile": True,
|
||||||
"amls": self.env["account.move.line"].browse(candidate_ids),
|
"amls": self.env["account.move.line"].browse(candidate_ids),
|
||||||
}
|
}
|
||||||
|
elif (
|
||||||
|
self.match_text_location_label
|
||||||
|
or self.match_text_location_note
|
||||||
|
or self.match_text_location_reference
|
||||||
|
):
|
||||||
|
# In the case any of the Label, Note or Reference matching rule has been
|
||||||
|
# toggled, and the query didn't return
|
||||||
|
# any candidates, the model should not try to mount another aml instead.
|
||||||
|
return
|
||||||
|
|
||||||
# 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 {tables}
|
||||||
}
|
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
|
||||||
|
@ -645,7 +756,9 @@ class AccountReconcileModel(models.Model):
|
||||||
for aml_values in amls_values_list
|
for aml_values in amls_values_list
|
||||||
)
|
)
|
||||||
sign = 1 if st_line_amount_curr > 0.0 else -1
|
sign = 1 if st_line_amount_curr > 0.0 else -1
|
||||||
amount_curr_after_rec = sign * (amls_amount_curr + st_line_amount_curr)
|
amount_curr_after_rec = st_line_currency.round(
|
||||||
|
sign * (amls_amount_curr + st_line_amount_curr)
|
||||||
|
)
|
||||||
|
|
||||||
# The statement line will be fully reconciled.
|
# The statement line will be fully reconciled.
|
||||||
if st_line_currency.is_zero(amount_curr_after_rec):
|
if st_line_currency.is_zero(amount_curr_after_rec):
|
||||||
|
@ -664,7 +777,10 @@ class AccountReconcileModel(models.Model):
|
||||||
# amount doesn't exceed the tolerance.
|
# amount doesn't exceed the tolerance.
|
||||||
if (
|
if (
|
||||||
self.payment_tolerance_type == "fixed_amount"
|
self.payment_tolerance_type == "fixed_amount"
|
||||||
and -amount_curr_after_rec <= self.payment_tolerance_param
|
and st_line_currency.compare_amounts(
|
||||||
|
-amount_curr_after_rec, self.payment_tolerance_param
|
||||||
|
)
|
||||||
|
<= 0
|
||||||
):
|
):
|
||||||
return {"allow_write_off", "allow_auto_reconcile"}
|
return {"allow_write_off", "allow_auto_reconcile"}
|
||||||
|
|
||||||
|
@ -674,7 +790,10 @@ class AccountReconcileModel(models.Model):
|
||||||
) * 100.0
|
) * 100.0
|
||||||
if (
|
if (
|
||||||
self.payment_tolerance_type == "percentage"
|
self.payment_tolerance_type == "percentage"
|
||||||
and reconciled_percentage_left <= self.payment_tolerance_param
|
and st_line_currency.compare_amounts(
|
||||||
|
reconciled_percentage_left, self.payment_tolerance_param
|
||||||
|
)
|
||||||
|
<= 0
|
||||||
):
|
):
|
||||||
return {"allow_write_off", "allow_auto_reconcile"}
|
return {"allow_write_off", "allow_auto_reconcile"}
|
||||||
|
|
||||||
|
|
|
@ -367,7 +367,7 @@ ul.auto-toc {
|
||||||
!! This file is generated by oca-gen-addon-readme !!
|
!! This file is generated by oca-gen-addon-readme !!
|
||||||
!! changes will be overwritten. !!
|
!! changes will be overwritten. !!
|
||||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||||
!! source digest: sha256:80bb08dc3058116c364563a7014c16787db3ac0b12afadbde03716f7277fa298
|
!! source digest: sha256:f0554ce70e9ac90badf0a4082aecd5af7011cf1461fe0cc6678577d2b7f87e21
|
||||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
|
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
|
||||||
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/lgpl-3.0-standalone.html"><img alt="License: LGPL-3" src="https://img.shields.io/badge/licence-LGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/account-reconcile/tree/17.0/account_reconcile_model_oca"><img alt="OCA/account-reconcile" src="https://img.shields.io/badge/github-OCA%2Faccount--reconcile-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/account-reconcile-17-0/account-reconcile-17-0-account_reconcile_model_oca"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/account-reconcile&target_branch=17.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
|
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/lgpl-3.0-standalone.html"><img alt="License: LGPL-3" src="https://img.shields.io/badge/licence-LGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/account-reconcile/tree/17.0/account_reconcile_model_oca"><img alt="OCA/account-reconcile" src="https://img.shields.io/badge/github-OCA%2Faccount--reconcile-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/account-reconcile-17-0/account-reconcile-17-0-account_reconcile_model_oca"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/account-reconcile&target_branch=17.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
|
||||||
<p>This module restores account reconciliation models functions moved from
|
<p>This module restores account reconciliation models functions moved from
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
from contextlib import contextmanager
|
||||||
|
|
||||||
from freezegun import freeze_time
|
from freezegun import freeze_time
|
||||||
|
|
||||||
from odoo import Command
|
from odoo import Command
|
||||||
|
@ -83,6 +85,8 @@ class TestReconciliationMatchingRules(AccountTestInvoicingCommon):
|
||||||
"match_nature": "both",
|
"match_nature": "both",
|
||||||
"match_same_currency": True,
|
"match_same_currency": True,
|
||||||
"allow_payment_tolerance": True,
|
"allow_payment_tolerance": True,
|
||||||
|
"match_text_location_note": True,
|
||||||
|
"match_text_location_reference": True,
|
||||||
"payment_tolerance_type": "percentage",
|
"payment_tolerance_type": "percentage",
|
||||||
"payment_tolerance_param": 0.0,
|
"payment_tolerance_param": 0.0,
|
||||||
"match_partner": True,
|
"match_partner": True,
|
||||||
|
@ -287,6 +291,7 @@ class TestReconciliationMatchingRules(AccountTestInvoicingCommon):
|
||||||
|
|
||||||
def test_matching_fields(self):
|
def test_matching_fields(self):
|
||||||
# Check without restriction.
|
# Check without restriction.
|
||||||
|
self.rule_1.match_text_location_label = False
|
||||||
self._check_statement_matching(
|
self._check_statement_matching(
|
||||||
self.rule_1,
|
self.rule_1,
|
||||||
{
|
{
|
||||||
|
@ -301,123 +306,8 @@ class TestReconciliationMatchingRules(AccountTestInvoicingCommon):
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@freeze_time("2020-01-01")
|
|
||||||
def test_matching_fields_match_text_location(self):
|
|
||||||
st_line = self._create_st_line(
|
|
||||||
payment_ref="1111", ref="2222 3333", narration="4444 5555 6666"
|
|
||||||
)
|
|
||||||
|
|
||||||
inv1 = self._create_invoice_line(
|
|
||||||
1000, self.partner_a, "out_invoice", pay_reference="bernard 1111 gagnant"
|
|
||||||
)
|
|
||||||
inv2 = self._create_invoice_line(
|
|
||||||
1000, self.partner_a, "out_invoice", pay_reference="2222 turlututu 3333"
|
|
||||||
)
|
|
||||||
inv3 = self._create_invoice_line(
|
|
||||||
1000,
|
|
||||||
self.partner_a,
|
|
||||||
"out_invoice",
|
|
||||||
pay_reference="4444 tsoin 5555 tsoin 6666",
|
|
||||||
)
|
|
||||||
|
|
||||||
rule = self._create_reconcile_model(
|
|
||||||
allow_payment_tolerance=False,
|
|
||||||
match_text_location_label=True,
|
|
||||||
match_text_location_reference=False,
|
|
||||||
match_text_location_note=False,
|
|
||||||
)
|
|
||||||
self.assertDictEqual(
|
|
||||||
rule._apply_rules(st_line, st_line._retrieve_partner()),
|
|
||||||
{"amls": inv1, "model": rule},
|
|
||||||
)
|
|
||||||
|
|
||||||
rule.match_text_location_reference = True
|
|
||||||
self.assertDictEqual(
|
|
||||||
rule._apply_rules(st_line, st_line._retrieve_partner()),
|
|
||||||
{"amls": inv2, "model": rule},
|
|
||||||
)
|
|
||||||
|
|
||||||
rule.match_text_location_note = True
|
|
||||||
self.assertDictEqual(
|
|
||||||
rule._apply_rules(st_line, st_line._retrieve_partner()),
|
|
||||||
{"amls": inv3, "model": rule},
|
|
||||||
)
|
|
||||||
|
|
||||||
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: {},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
# 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: {
|
|
||||||
"amls": self.invoice_line_1,
|
|
||||||
"model": self.rule_1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
# Test matching with the partner name (resetting 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: {
|
|
||||||
"amls": self.invoice_line_1,
|
|
||||||
"model": self.rule_1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_matching_fields_match_journal_ids(self):
|
def test_matching_fields_match_journal_ids(self):
|
||||||
|
self.rule_1.match_text_location_label = False
|
||||||
self.rule_1.match_journal_ids |= self.cash_line_1.journal_id
|
self.rule_1.match_journal_ids |= self.cash_line_1.journal_id
|
||||||
self._check_statement_matching(
|
self._check_statement_matching(
|
||||||
self.rule_1,
|
self.rule_1,
|
||||||
|
@ -429,6 +319,7 @@ class TestReconciliationMatchingRules(AccountTestInvoicingCommon):
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_matching_fields_match_nature(self):
|
def test_matching_fields_match_nature(self):
|
||||||
|
self.rule_1.match_text_location_label = False
|
||||||
self.rule_1.match_nature = "amount_received"
|
self.rule_1.match_nature = "amount_received"
|
||||||
self._check_statement_matching(
|
self._check_statement_matching(
|
||||||
self.rule_1,
|
self.rule_1,
|
||||||
|
@ -454,6 +345,7 @@ class TestReconciliationMatchingRules(AccountTestInvoicingCommon):
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_matching_fields_match_amount(self):
|
def test_matching_fields_match_amount(self):
|
||||||
|
self.rule_1.match_text_location_label = False
|
||||||
self.rule_1.match_amount = "lower"
|
self.rule_1.match_amount = "lower"
|
||||||
self.rule_1.match_amount_max = 150
|
self.rule_1.match_amount_max = 150
|
||||||
self._check_statement_matching(
|
self._check_statement_matching(
|
||||||
|
@ -497,6 +389,7 @@ class TestReconciliationMatchingRules(AccountTestInvoicingCommon):
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_matching_fields_match_label(self):
|
def test_matching_fields_match_label(self):
|
||||||
|
self.rule_1.match_text_location_label = False
|
||||||
self.rule_1.match_label = "contains"
|
self.rule_1.match_label = "contains"
|
||||||
self.rule_1.match_label_param = "yyyyy"
|
self.rule_1.match_label_param = "yyyyy"
|
||||||
self._check_statement_matching(
|
self._check_statement_matching(
|
||||||
|
@ -535,7 +428,11 @@ class TestReconciliationMatchingRules(AccountTestInvoicingCommon):
|
||||||
|
|
||||||
@freeze_time("2019-01-01")
|
@freeze_time("2019-01-01")
|
||||||
def test_zero_payment_tolerance(self):
|
def test_zero_payment_tolerance(self):
|
||||||
rule = self._create_reconcile_model(line_ids=[{}])
|
rule = self._create_reconcile_model(
|
||||||
|
line_ids=[{}],
|
||||||
|
match_text_location_reference=True,
|
||||||
|
match_text_location_note=True,
|
||||||
|
)
|
||||||
|
|
||||||
for inv_type, bsl_sign in (("out_invoice", 1), ("in_invoice", -1)):
|
for inv_type, bsl_sign in (("out_invoice", 1), ("in_invoice", -1)):
|
||||||
invl = self._create_invoice_line(
|
invl = self._create_invoice_line(
|
||||||
|
@ -543,21 +440,27 @@ class TestReconciliationMatchingRules(AccountTestInvoicingCommon):
|
||||||
)
|
)
|
||||||
|
|
||||||
# Exact matching.
|
# Exact matching.
|
||||||
st_line = self._create_st_line(amount=bsl_sign * 1000.0)
|
st_line = self._create_st_line(
|
||||||
|
amount=bsl_sign * 1000.0, payment_ref=invl.name
|
||||||
|
)
|
||||||
self._check_statement_matching(
|
self._check_statement_matching(
|
||||||
rule,
|
rule,
|
||||||
{st_line: {"amls": invl, "model": rule}},
|
{st_line: {"amls": invl, "model": rule}},
|
||||||
)
|
)
|
||||||
|
|
||||||
# No matching because there is no tolerance.
|
# No matching because there is no tolerance.
|
||||||
st_line = self._create_st_line(amount=bsl_sign * 990.0)
|
st_line = self._create_st_line(
|
||||||
|
amount=bsl_sign * 990.0, payment_ref=invl.name
|
||||||
|
)
|
||||||
self._check_statement_matching(
|
self._check_statement_matching(
|
||||||
rule,
|
rule,
|
||||||
{st_line: {}},
|
{st_line: {}},
|
||||||
)
|
)
|
||||||
|
|
||||||
# The payment amount is higher than the invoice one.
|
# The payment amount is higher than the invoice one.
|
||||||
st_line = self._create_st_line(amount=bsl_sign * 1010.0)
|
st_line = self._create_st_line(
|
||||||
|
amount=bsl_sign * 1010.0, payment_ref=invl.name
|
||||||
|
)
|
||||||
self._check_statement_matching(
|
self._check_statement_matching(
|
||||||
rule,
|
rule,
|
||||||
{st_line: {"amls": invl, "model": rule}},
|
{st_line: {"amls": invl, "model": rule}},
|
||||||
|
@ -580,7 +483,9 @@ class TestReconciliationMatchingRules(AccountTestInvoicingCommon):
|
||||||
)
|
)
|
||||||
|
|
||||||
# No matching because there is no tolerance.
|
# No matching because there is no tolerance.
|
||||||
st_line = self._create_st_line(amount=bsl_sign * 990.0)
|
st_line = self._create_st_line(
|
||||||
|
amount=bsl_sign * 990.0, payment_ref="123456"
|
||||||
|
)
|
||||||
self._check_statement_matching(
|
self._check_statement_matching(
|
||||||
rule,
|
rule,
|
||||||
{st_line: {}},
|
{st_line: {}},
|
||||||
|
@ -609,7 +514,9 @@ class TestReconciliationMatchingRules(AccountTestInvoicingCommon):
|
||||||
)
|
)
|
||||||
|
|
||||||
# No matching because there is no enough tolerance.
|
# No matching because there is no enough tolerance.
|
||||||
st_line = self._create_st_line(amount=bsl_sign * 990.0)
|
st_line = self._create_st_line(
|
||||||
|
amount=bsl_sign * 990.0, payment_ref=invl.name
|
||||||
|
)
|
||||||
self._check_statement_matching(
|
self._check_statement_matching(
|
||||||
rule,
|
rule,
|
||||||
{st_line: {}},
|
{st_line: {}},
|
||||||
|
@ -618,7 +525,9 @@ class TestReconciliationMatchingRules(AccountTestInvoicingCommon):
|
||||||
# The payment amount is higher than the invoice one.
|
# The payment amount is higher than the invoice one.
|
||||||
# However, since the invoice amount is lower than the payment amount,
|
# However, since the invoice amount is lower than the payment amount,
|
||||||
# the tolerance is not checked and the invoice line is matched.
|
# the tolerance is not checked and the invoice line is matched.
|
||||||
st_line = self._create_st_line(amount=bsl_sign * 1010.0)
|
st_line = self._create_st_line(
|
||||||
|
amount=bsl_sign * 1010.0, payment_ref=invl.name
|
||||||
|
)
|
||||||
self._check_statement_matching(
|
self._check_statement_matching(
|
||||||
rule,
|
rule,
|
||||||
{st_line: {"amls": invl, "model": rule}},
|
{st_line: {"amls": invl, "model": rule}},
|
||||||
|
@ -627,17 +536,19 @@ class TestReconciliationMatchingRules(AccountTestInvoicingCommon):
|
||||||
@freeze_time("2019-01-01")
|
@freeze_time("2019-01-01")
|
||||||
def test_enough_payment_tolerance(self):
|
def test_enough_payment_tolerance(self):
|
||||||
rule = self._create_reconcile_model(
|
rule = self._create_reconcile_model(
|
||||||
payment_tolerance_param=1.0,
|
payment_tolerance_param=2.0,
|
||||||
line_ids=[{}],
|
line_ids=[{}],
|
||||||
)
|
)
|
||||||
|
|
||||||
for inv_type, bsl_sign in (("out_invoice", 1), ("in_invoice", -1)):
|
for inv_type, bsl_sign in (("out_invoice", 1), ("in_invoice", -1)):
|
||||||
invl = self._create_invoice_line(
|
invl = self._create_invoice_line(
|
||||||
1000.0, self.partner_a, inv_type, inv_date="2019-01-01"
|
1210.0, self.partner_a, inv_type, inv_date="2019-01-01"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Enough tolerance to match the invoice line.
|
# Enough tolerance to match the invoice line.
|
||||||
st_line = self._create_st_line(amount=bsl_sign * 990.0)
|
st_line = self._create_st_line(
|
||||||
|
amount=bsl_sign * 1185.80, payment_ref=invl.name
|
||||||
|
)
|
||||||
self._check_statement_matching(
|
self._check_statement_matching(
|
||||||
rule,
|
rule,
|
||||||
{st_line: {"amls": invl, "model": rule, "status": "write_off"}},
|
{st_line: {"amls": invl, "model": rule, "status": "write_off"}},
|
||||||
|
@ -646,7 +557,9 @@ class TestReconciliationMatchingRules(AccountTestInvoicingCommon):
|
||||||
# The payment amount is higher than the invoice one.
|
# The payment amount is higher than the invoice one.
|
||||||
# However, since the invoice amount is lower than the payment amount,
|
# However, since the invoice amount is lower than the payment amount,
|
||||||
# the tolerance is not checked and the invoice line is matched.
|
# the tolerance is not checked and the invoice line is matched.
|
||||||
st_line = self._create_st_line(amount=bsl_sign * 1010.0)
|
st_line = self._create_st_line(
|
||||||
|
amount=bsl_sign * 1234.20, payment_ref=invl.name
|
||||||
|
)
|
||||||
self._check_statement_matching(
|
self._check_statement_matching(
|
||||||
rule,
|
rule,
|
||||||
{st_line: {"amls": invl, "model": rule}},
|
{st_line: {"amls": invl, "model": rule}},
|
||||||
|
@ -695,7 +608,9 @@ class TestReconciliationMatchingRules(AccountTestInvoicingCommon):
|
||||||
invl = self._create_invoice_line(
|
invl = self._create_invoice_line(
|
||||||
990.0, self.partner_a, inv_type, inv_date="2019-01-01"
|
990.0, self.partner_a, inv_type, inv_date="2019-01-01"
|
||||||
)
|
)
|
||||||
st_line = self._create_st_line(amount=bsl_sign * 1000)
|
st_line = self._create_st_line(
|
||||||
|
amount=bsl_sign * 1000, payment_ref=invl.name
|
||||||
|
)
|
||||||
|
|
||||||
# Partial reconciliation.
|
# Partial reconciliation.
|
||||||
self._check_statement_matching(
|
self._check_statement_matching(
|
||||||
|
@ -775,10 +690,14 @@ class TestReconciliationMatchingRules(AccountTestInvoicingCommon):
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_matching_fields_match_partner_category_ids(self):
|
def test_matching_fields_match_partner_category_ids(self):
|
||||||
|
self.rule_1.match_text_location_label = False
|
||||||
test_category = self.env["res.partner.category"].create(
|
test_category = self.env["res.partner.category"].create(
|
||||||
{"name": "Consulting Services"}
|
{"name": "Consulting Services"}
|
||||||
)
|
)
|
||||||
self.partner_2.category_id = test_category
|
test_category2 = self.env["res.partner.category"].create(
|
||||||
|
{"name": "Consulting Services2"}
|
||||||
|
)
|
||||||
|
self.partner_2.category_id = test_category + test_category2
|
||||||
self.rule_1.match_partner_category_ids |= test_category
|
self.rule_1.match_partner_category_ids |= test_category
|
||||||
self._check_statement_matching(
|
self._check_statement_matching(
|
||||||
self.rule_1,
|
self.rule_1,
|
||||||
|
@ -792,6 +711,7 @@ class TestReconciliationMatchingRules(AccountTestInvoicingCommon):
|
||||||
|
|
||||||
def test_mixin_rules(self):
|
def test_mixin_rules(self):
|
||||||
"""Test usage of rules together."""
|
"""Test usage of rules together."""
|
||||||
|
self.rule_1.match_text_location_label = False
|
||||||
# rule_1 is used before rule_2.
|
# rule_1 is used before rule_2.
|
||||||
self.rule_1.sequence = 1
|
self.rule_1.sequence = 1
|
||||||
self.rule_2.sequence = 2
|
self.rule_2.sequence = 2
|
||||||
|
@ -870,18 +790,24 @@ class TestReconciliationMatchingRules(AccountTestInvoicingCommon):
|
||||||
self.rule_2.auto_reconcile = True
|
self.rule_2.auto_reconcile = True
|
||||||
|
|
||||||
self._check_statement_matching(
|
self._check_statement_matching(
|
||||||
self.rule_1 + self.rule_2,
|
self.rule_1,
|
||||||
{
|
{
|
||||||
self.bank_line_1: {
|
self.bank_line_1: {
|
||||||
"amls": self.invoice_line_1,
|
"amls": self.invoice_line_1,
|
||||||
"model": self.rule_1,
|
"model": self.rule_1,
|
||||||
"auto_reconcile": True,
|
"auto_reconcile": True,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
rule_3 = self.rule_1.copy({"match_text_location_label": False})
|
||||||
|
self._check_statement_matching(
|
||||||
|
self.rule_2 + rule_3,
|
||||||
|
{
|
||||||
self.bank_line_2: {
|
self.bank_line_2: {
|
||||||
"amls": self.invoice_line_1
|
"amls": self.invoice_line_1
|
||||||
+ self.invoice_line_2
|
+ self.invoice_line_2
|
||||||
+ self.invoice_line_3,
|
+ self.invoice_line_3,
|
||||||
"model": self.rule_1,
|
"model": rule_3,
|
||||||
},
|
},
|
||||||
self.cash_line_1: {
|
self.cash_line_1: {
|
||||||
"model": self.rule_2,
|
"model": self.rule_2,
|
||||||
|
@ -910,11 +836,17 @@ class TestReconciliationMatchingRules(AccountTestInvoicingCommon):
|
||||||
"model": self.rule_1,
|
"model": self.rule_1,
|
||||||
"auto_reconcile": True,
|
"auto_reconcile": True,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
rule_3 = self.rule_1.copy({"match_text_location_label": False})
|
||||||
|
self._check_statement_matching(
|
||||||
|
rule_3,
|
||||||
|
{
|
||||||
self.bank_line_2: {
|
self.bank_line_2: {
|
||||||
"amls": self.invoice_line_1
|
"amls": self.invoice_line_1
|
||||||
+ self.invoice_line_2
|
+ self.invoice_line_2
|
||||||
+ self.invoice_line_3,
|
+ self.invoice_line_3,
|
||||||
"model": self.rule_1,
|
"model": rule_3,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -1069,6 +1001,7 @@ class TestReconciliationMatchingRules(AccountTestInvoicingCommon):
|
||||||
move_reversed = move._reverse_moves()
|
move_reversed = move._reverse_moves()
|
||||||
self.assertTrue(move_reversed.exists())
|
self.assertTrue(move_reversed.exists())
|
||||||
|
|
||||||
|
self.rule_1.match_text_location_label = False
|
||||||
self.bank_line_1.write(
|
self.bank_line_1.write(
|
||||||
{
|
{
|
||||||
"payment_ref": "8",
|
"payment_ref": "8",
|
||||||
|
@ -1110,7 +1043,7 @@ class TestReconciliationMatchingRules(AccountTestInvoicingCommon):
|
||||||
"partner_id": partner.id,
|
"partner_id": partner.id,
|
||||||
"foreign_currency_id": currency_statement.id,
|
"foreign_currency_id": currency_statement.id,
|
||||||
"amount_currency": 100,
|
"amount_currency": 100,
|
||||||
"payment_ref": "test",
|
"payment_ref": invoice_line.name,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
self._check_statement_matching(
|
self._check_statement_matching(
|
||||||
|
@ -1212,40 +1145,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
|
||||||
|
@ -1278,6 +1177,7 @@ class TestReconciliationMatchingRules(AccountTestInvoicingCommon):
|
||||||
"match_same_currency": False,
|
"match_same_currency": False,
|
||||||
"company_id": self.company_data["company"].id,
|
"company_id": self.company_data["company"].id,
|
||||||
"past_months_limit": False,
|
"past_months_limit": False,
|
||||||
|
"match_text_location_label": False,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1454,6 +1354,7 @@ class TestReconciliationMatchingRules(AccountTestInvoicingCommon):
|
||||||
200 should be proposed.
|
200 should be proposed.
|
||||||
"""
|
"""
|
||||||
self.rule_1.allow_payment_tolerance = False
|
self.rule_1.allow_payment_tolerance = False
|
||||||
|
self.rule_1.match_text_location_label = False
|
||||||
self.bank_line_2.amount = 250
|
self.bank_line_2.amount = 250
|
||||||
self.bank_line_1.partner_id = None
|
self.bank_line_1.partner_id = None
|
||||||
|
|
||||||
|
@ -1476,6 +1377,7 @@ class TestReconciliationMatchingRules(AccountTestInvoicingCommon):
|
||||||
other ones are disregarded.
|
other ones are disregarded.
|
||||||
"""
|
"""
|
||||||
self.rule_1.allow_payment_tolerance = False
|
self.rule_1.allow_payment_tolerance = False
|
||||||
|
self.rule_1.match_text_location_label = False
|
||||||
self.bank_line_2.amount = 300
|
self.bank_line_2.amount = 300
|
||||||
self.bank_line_1.partner_id = None
|
self.bank_line_1.partner_id = None
|
||||||
|
|
||||||
|
@ -1490,3 +1392,238 @@ class TestReconciliationMatchingRules(AccountTestInvoicingCommon):
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@freeze_time("2019-01-01")
|
||||||
|
def test_invoice_matching_using_match_text_location(self):
|
||||||
|
@contextmanager
|
||||||
|
def rollback():
|
||||||
|
savepoint = self.cr.savepoint()
|
||||||
|
yield
|
||||||
|
savepoint.rollback()
|
||||||
|
|
||||||
|
rule = self._create_reconcile_model(
|
||||||
|
match_partner=False,
|
||||||
|
allow_payment_tolerance=False,
|
||||||
|
match_text_location_reference=True,
|
||||||
|
match_text_location_note=True,
|
||||||
|
)
|
||||||
|
st_line = self._create_st_line(amount=1000, partner_id=False)
|
||||||
|
invoice = self.env["account.move"].create(
|
||||||
|
{
|
||||||
|
"move_type": "out_invoice",
|
||||||
|
"partner_id": self.partner_a.id,
|
||||||
|
"invoice_date": "2019-01-01",
|
||||||
|
"invoice_line_ids": [
|
||||||
|
Command.create(
|
||||||
|
{
|
||||||
|
"product_id": self.product_a.id,
|
||||||
|
"price_unit": 100,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
invoice.action_post()
|
||||||
|
term_line = invoice.line_ids.filtered(
|
||||||
|
lambda x: x.display_type == "payment_term"
|
||||||
|
)
|
||||||
|
|
||||||
|
# No match at all.
|
||||||
|
self.assertDictEqual(
|
||||||
|
rule._apply_rules(st_line, None),
|
||||||
|
{},
|
||||||
|
)
|
||||||
|
|
||||||
|
with rollback():
|
||||||
|
term_line.name = "1234"
|
||||||
|
st_line.payment_ref = "1234"
|
||||||
|
|
||||||
|
# Matching if no checkbox checked.
|
||||||
|
self.assertDictEqual(
|
||||||
|
rule._apply_rules(st_line, None),
|
||||||
|
{"amls": term_line, "model": rule},
|
||||||
|
)
|
||||||
|
|
||||||
|
# No matching if checkbox is unchecked.
|
||||||
|
rule.match_text_location_label = False
|
||||||
|
rule.match_text_location_reference = False
|
||||||
|
rule.match_text_location_note = False
|
||||||
|
self.assertDictEqual(
|
||||||
|
rule._apply_rules(st_line, None),
|
||||||
|
{},
|
||||||
|
)
|
||||||
|
|
||||||
|
with rollback():
|
||||||
|
# Test Matching on exact_token.
|
||||||
|
term_line.name = "PAY-123"
|
||||||
|
st_line.payment_ref = "PAY-123"
|
||||||
|
|
||||||
|
# Matching if no checkbox checked.
|
||||||
|
self.assertDictEqual(
|
||||||
|
rule._apply_rules(st_line, None),
|
||||||
|
{"amls": term_line, "model": rule},
|
||||||
|
)
|
||||||
|
|
||||||
|
with self.subTest(
|
||||||
|
rule_field="match_text_location_label", st_line_field="payment_ref"
|
||||||
|
):
|
||||||
|
with rollback():
|
||||||
|
term_line.name = ""
|
||||||
|
st_line.payment_ref = "/?"
|
||||||
|
|
||||||
|
# No exact matching when the term line name is an empty string
|
||||||
|
self.assertDictEqual(
|
||||||
|
rule._apply_rules(st_line, None),
|
||||||
|
{},
|
||||||
|
)
|
||||||
|
|
||||||
|
for rule_field, st_line_field in (
|
||||||
|
("match_text_location_label", "payment_ref"),
|
||||||
|
("match_text_location_reference", "ref"),
|
||||||
|
("match_text_location_note", "narration"),
|
||||||
|
):
|
||||||
|
with self.subTest(rule_field=rule_field, st_line_field=st_line_field):
|
||||||
|
with rollback():
|
||||||
|
rule[rule_field] = True
|
||||||
|
st_line[st_line_field] = "123456"
|
||||||
|
term_line.name = "123456"
|
||||||
|
|
||||||
|
# Matching if the corresponding flag is enabled.
|
||||||
|
self.assertDictEqual(
|
||||||
|
rule._apply_rules(st_line, None),
|
||||||
|
{"amls": term_line, "model": rule},
|
||||||
|
)
|
||||||
|
|
||||||
|
# It works also if the statement line contains the word.
|
||||||
|
st_line[st_line_field] = "payment for 123456 urgent!"
|
||||||
|
self.assertDictEqual(
|
||||||
|
rule._apply_rules(st_line, None),
|
||||||
|
{"amls": term_line, "model": rule},
|
||||||
|
)
|
||||||
|
|
||||||
|
# Not if the invoice has nothing in common even if numerical.
|
||||||
|
term_line.name = "78910"
|
||||||
|
self.assertDictEqual(
|
||||||
|
rule._apply_rules(st_line, None),
|
||||||
|
{},
|
||||||
|
)
|
||||||
|
|
||||||
|
# Exact matching on a single word.
|
||||||
|
st_line[st_line_field] = "TURLUTUTU21"
|
||||||
|
term_line.name = "TURLUTUTU21"
|
||||||
|
self.assertDictEqual(
|
||||||
|
rule._apply_rules(st_line, None),
|
||||||
|
{"amls": term_line, "model": rule},
|
||||||
|
)
|
||||||
|
|
||||||
|
# No matching if not enough numerical values.
|
||||||
|
st_line[st_line_field] = "12"
|
||||||
|
term_line.name = "selling 3 apples, 2 tomatoes and 12kg of potatoes"
|
||||||
|
self.assertDictEqual(
|
||||||
|
rule._apply_rules(st_line, None),
|
||||||
|
{},
|
||||||
|
)
|
||||||
|
|
||||||
|
invoice2 = self.env["account.move"].create(
|
||||||
|
{
|
||||||
|
"move_type": "out_invoice",
|
||||||
|
"partner_id": self.partner_a.id,
|
||||||
|
"invoice_date": "2019-01-01",
|
||||||
|
"invoice_line_ids": [
|
||||||
|
Command.create(
|
||||||
|
{
|
||||||
|
"product_id": self.product_a.id,
|
||||||
|
"price_unit": 100,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
invoice2.action_post()
|
||||||
|
term_lines = (invoice + invoice2).line_ids.filtered(
|
||||||
|
lambda x: x.display_type == "payment_term"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Matching multiple invoices.
|
||||||
|
rule.match_text_location_label = True
|
||||||
|
st_line.payment_ref = "paying invoices 1234 & 5678"
|
||||||
|
term_lines[0].name = "INV/1234"
|
||||||
|
term_lines[1].name = "INV/5678"
|
||||||
|
self.assertDictEqual(
|
||||||
|
rule._apply_rules(st_line, None),
|
||||||
|
{"amls": term_lines, "model": rule},
|
||||||
|
)
|
||||||
|
|
||||||
|
# Matching multiple invoices sharing the same reference.
|
||||||
|
term_lines[1].name = "INV/1234"
|
||||||
|
self.assertDictEqual(
|
||||||
|
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.currency_data_2["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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
|
@ -7,7 +7,7 @@ Account Reconcile Oca
|
||||||
!! This file is generated by oca-gen-addon-readme !!
|
!! This file is generated by oca-gen-addon-readme !!
|
||||||
!! changes will be overwritten. !!
|
!! changes will be overwritten. !!
|
||||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||||
!! source digest: sha256:cf0d778067ac722c5a6d7f65f8fa4b0766076829ba9c9c147cc3718782cc85b0
|
!! source digest: sha256:a1d19f7cb36b1b53954b505f964b766f7237615fc66c01368256cc9c5f390fd2
|
||||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||||
|
|
||||||
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
|
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
"name": "Account Reconcile Oca",
|
"name": "Account Reconcile Oca",
|
||||||
"summary": """
|
"summary": """
|
||||||
Reconcile addons for Odoo CE accounting""",
|
Reconcile addons for Odoo CE accounting""",
|
||||||
"version": "17.0.1.5.5",
|
"version": "17.0.1.5.8",
|
||||||
"license": "AGPL-3",
|
"license": "AGPL-3",
|
||||||
"author": "CreuBlanca,Dixmit,Odoo Community Association (OCA)",
|
"author": "CreuBlanca,Dixmit,Odoo Community Association (OCA)",
|
||||||
"maintainers": ["etobella"],
|
"maintainers": ["etobella"],
|
||||||
|
|
|
@ -613,7 +613,10 @@ class AccountBankStatementLine(models.Model):
|
||||||
self.env["res.partner"].browse(line["partner_id"]).display_name,
|
self.env["res.partner"].browse(line["partner_id"]).display_name,
|
||||||
)
|
)
|
||||||
elif self.partner_id:
|
elif self.partner_id:
|
||||||
new_line["partner_id"] = self.partner_id.name_get()[0]
|
new_line["partner_id"] = (
|
||||||
|
self.partner_id.id,
|
||||||
|
self.partner_id.display_name,
|
||||||
|
)
|
||||||
new_data.append(new_line)
|
new_data.append(new_line)
|
||||||
return new_data, reconcile_auxiliary_id
|
return new_data, reconcile_auxiliary_id
|
||||||
|
|
||||||
|
@ -681,13 +684,16 @@ class AccountBankStatementLine(models.Model):
|
||||||
reconciled_line.move_id.journal_id
|
reconciled_line.move_id.journal_id
|
||||||
== self.company_id.currency_exchange_journal_id
|
== self.company_id.currency_exchange_journal_id
|
||||||
):
|
):
|
||||||
reconcile_auxiliary_id, lines = self._get_reconcile_line(
|
for rl_item in (
|
||||||
reconciled_line.move_id.line_ids - reconciled_line,
|
reconciled_line.move_id.line_ids - reconciled_line
|
||||||
"other",
|
):
|
||||||
from_unreconcile=False,
|
reconcile_auxiliary_id, lines = self._get_reconcile_line(
|
||||||
move=True,
|
rl_item,
|
||||||
)
|
"other",
|
||||||
data += lines
|
from_unreconcile=False,
|
||||||
|
move=True,
|
||||||
|
)
|
||||||
|
data += lines
|
||||||
continue
|
continue
|
||||||
partial = partial_lines.filtered(
|
partial = partial_lines.filtered(
|
||||||
lambda r, line=reconciled_line: r.debit_move_id == line
|
lambda r, line=reconciled_line: r.debit_move_id == line
|
||||||
|
@ -1017,7 +1023,7 @@ class AccountBankStatementLine(models.Model):
|
||||||
suspense_lines,
|
suspense_lines,
|
||||||
_other_lines,
|
_other_lines,
|
||||||
) = st_line._seek_for_lines()
|
) = st_line._seek_for_lines()
|
||||||
line_vals = {"partner_id": st_line.partner_id}
|
line_vals = {"partner_id": st_line.partner_id.id}
|
||||||
line_ids_commands = [(1, liquidity_lines.id, line_vals)]
|
line_ids_commands = [(1, liquidity_lines.id, line_vals)]
|
||||||
if suspense_lines:
|
if suspense_lines:
|
||||||
line_ids_commands.append((1, suspense_lines.id, line_vals))
|
line_ids_commands.append((1, suspense_lines.id, line_vals))
|
||||||
|
@ -1270,3 +1276,11 @@ class AccountBankStatementLine(models.Model):
|
||||||
for line in lines:
|
for line in lines:
|
||||||
self._add_account_move_line(line, keep_current=True)
|
self._add_account_move_line(line, keep_current=True)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
def _retrieve_partner(self):
|
||||||
|
if self.env.context.get("skip_retrieve_partner"):
|
||||||
|
# This hook can be used, for example, when importing files.
|
||||||
|
# With large databases, we already have the information, moreover,
|
||||||
|
# the data might be preloaded, so it has no sense to import it again
|
||||||
|
return self.partner_id
|
||||||
|
return super()._retrieve_partner()
|
||||||
|
|
|
@ -367,7 +367,7 @@ ul.auto-toc {
|
||||||
!! This file is generated by oca-gen-addon-readme !!
|
!! This file is generated by oca-gen-addon-readme !!
|
||||||
!! changes will be overwritten. !!
|
!! changes will be overwritten. !!
|
||||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||||
!! source digest: sha256:cf0d778067ac722c5a6d7f65f8fa4b0766076829ba9c9c147cc3718782cc85b0
|
!! source digest: sha256:a1d19f7cb36b1b53954b505f964b766f7237615fc66c01368256cc9c5f390fd2
|
||||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
|
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
|
||||||
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/account-reconcile/tree/17.0/account_reconcile_oca"><img alt="OCA/account-reconcile" src="https://img.shields.io/badge/github-OCA%2Faccount--reconcile-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/account-reconcile-17-0/account-reconcile-17-0-account_reconcile_oca"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/account-reconcile&target_branch=17.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
|
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/account-reconcile/tree/17.0/account_reconcile_oca"><img alt="OCA/account-reconcile" src="https://img.shields.io/badge/github-OCA%2Faccount--reconcile-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/account-reconcile-17-0/account-reconcile-17-0-account_reconcile_oca"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/account-reconcile&target_branch=17.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
|
||||||
<p>This addon allows to reconcile bank statements and account marked as
|
<p>This addon allows to reconcile bank statements and account marked as
|
||||||
|
|
|
@ -6,15 +6,15 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: Odoo Server 16.0\n"
|
"Project-Id-Version: Odoo Server 16.0\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2023-07-29 12:10+0000\n"
|
"PO-Revision-Date: 2025-02-13 20:35+0000\n"
|
||||||
"Last-Translator: Ivorra78 <informatica@totmaterial.es>\n"
|
"Last-Translator: \"Pedro M. Baeza\" <pedro.baeza@tecnativa.com>\n"
|
||||||
"Language-Team: none\n"
|
"Language-Team: none\n"
|
||||||
"Language: es\n"
|
"Language: es\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: \n"
|
"Content-Transfer-Encoding: \n"
|
||||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||||
"X-Generator: Weblate 4.17\n"
|
"X-Generator: Weblate 5.6.2\n"
|
||||||
|
|
||||||
#. module: account_statement_base
|
#. module: account_statement_base
|
||||||
#: model_terms:ir.ui.view,arch_db:account_statement_base.view_bank_statement_form
|
#: model_terms:ir.ui.view,arch_db:account_statement_base.view_bank_statement_form
|
||||||
|
@ -63,7 +63,7 @@ msgstr "Etiqueta, referencia o notas"
|
||||||
#. module: account_statement_base
|
#. module: account_statement_base
|
||||||
#: model_terms:ir.ui.view,arch_db:account_statement_base.account_bank_statement_line_search
|
#: model_terms:ir.ui.view,arch_db:account_statement_base.account_bank_statement_line_search
|
||||||
msgid "Not Reconciled"
|
msgid "Not Reconciled"
|
||||||
msgstr "No reconciliado"
|
msgstr "No conciliado"
|
||||||
|
|
||||||
#. module: account_statement_base
|
#. module: account_statement_base
|
||||||
#: model_terms:ir.ui.view,arch_db:account_statement_base.account_bank_statement_line_form
|
#: model_terms:ir.ui.view,arch_db:account_statement_base.account_bank_statement_line_form
|
||||||
|
@ -89,7 +89,7 @@ msgstr "Socio"
|
||||||
#. module: account_statement_base
|
#. module: account_statement_base
|
||||||
#: model_terms:ir.ui.view,arch_db:account_statement_base.account_bank_statement_line_search
|
#: model_terms:ir.ui.view,arch_db:account_statement_base.account_bank_statement_line_search
|
||||||
msgid "Reconciled"
|
msgid "Reconciled"
|
||||||
msgstr "Reconciliado"
|
msgstr "Conciliado"
|
||||||
|
|
||||||
#. module: account_statement_base
|
#. module: account_statement_base
|
||||||
#: model_terms:ir.ui.view,arch_db:account_statement_base.account_bank_statement_line_tree
|
#: model_terms:ir.ui.view,arch_db:account_statement_base.account_bank_statement_line_tree
|
||||||
|
|
Loading…
Reference in New Issue