Files
Odoo-Modules/Fusion Accounting/models/account_reconcile_model_line.py
2026-02-22 01:22:18 -05:00

221 lines
8.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import re
from math import copysign
from odoo import _, models, Command
from odoo.exceptions import UserError
class ReconcileModelLine(models.Model):
"""Extends reconciliation model lines with methods for computing
journal item values across manual and bank reconciliation contexts."""
_inherit = 'account.reconcile.model.line'
# ------------------------------------------------------------------
# Core helpers
# ------------------------------------------------------------------
def _resolve_taxes_for_partner(self, partner):
"""Return the tax recordset that should be applied, taking fiscal
position mapping into account when a partner is provided."""
tax_records = self.tax_ids
if not tax_records or not partner:
return tax_records
fpos = self.env['account.fiscal.position']._get_fiscal_position(partner)
if fpos:
tax_records = fpos.map_tax(tax_records)
return tax_records
def _prepare_aml_vals(self, partner):
"""Build a base dictionary of account.move.line values derived from
this reconciliation model line.
Fiscal-position tax remapping is applied automatically when the
supplied *partner* record has a matching fiscal position.
Args:
partner: ``res.partner`` record to attach to the move line.
Returns:
``dict`` suitable for later account.move.line creation.
"""
self.ensure_one()
mapped_taxes = self._resolve_taxes_for_partner(partner)
result_values = {
'name': self.label,
'partner_id': partner.id,
'analytic_distribution': self.analytic_distribution,
'tax_ids': [Command.set(mapped_taxes.ids)],
'reconcile_model_id': self.model_id.id,
}
if self.account_id:
result_values['account_id'] = self.account_id.id
return result_values
# ------------------------------------------------------------------
# Manual reconciliation
# ------------------------------------------------------------------
def _compute_manual_amount(self, remaining_balance, currency):
"""Derive the line amount for manual reconciliation based on the
configured amount type (percentage or fixed).
Raises ``UserError`` for amount types that are only valid inside the
bank reconciliation widget (e.g. regex, percentage_st_line).
"""
if self.amount_type == 'percentage':
return currency.round(remaining_balance * self.amount / 100.0)
if self.amount_type == 'fixed':
direction = 1 if remaining_balance > 0.0 else -1
return currency.round(self.amount * direction)
raise UserError(
_("This reconciliation model cannot be applied in the manual "
"reconciliation widget because its amount type is not supported "
"in that context.")
)
def _apply_in_manual_widget(self, residual_amount_currency, partner, currency):
"""Produce move-line values for the manual reconciliation widget.
The ``journal_id`` field is deliberately included in the result even
though it is a related (read-only) field on the move line. The manual
reconciliation widget relies on its presence to group lines into a
single journal entry per journal.
Args:
residual_amount_currency: Open balance in the account's currency.
partner: ``res.partner`` record for the counterpart.
currency: ``res.currency`` record used by the account.
Returns:
``dict`` ready for account.move.line creation.
"""
self.ensure_one()
computed_amount = self._compute_manual_amount(residual_amount_currency, currency)
line_data = self._prepare_aml_vals(partner)
line_data.update({
'currency_id': currency.id,
'amount_currency': computed_amount,
'journal_id': self.journal_id.id,
})
return line_data
# ------------------------------------------------------------------
# Bank reconciliation
# ------------------------------------------------------------------
def _extract_regex_amount(self, payment_ref, residual_balance):
"""Try to extract a numeric amount from *payment_ref* using the
regex pattern stored on this line.
Returns the parsed amount with the correct sign, or ``0.0`` when
parsing fails or the pattern does not match.
"""
pattern_match = re.search(self.amount_string, payment_ref)
if not pattern_match:
return 0.0
separator = self.model_id.decimal_separator
direction = 1 if residual_balance > 0.0 else -1
try:
raw_group = pattern_match.group(1)
digits_only = re.sub(r'[^\d' + separator + ']', '', raw_group)
parsed_value = float(digits_only.replace(separator, '.'))
return copysign(parsed_value * direction, residual_balance)
except (ValueError, IndexError):
return 0.0
def _compute_percentage_st_line_amount(self, st_line, currency):
"""Calculate the move-line amount and currency when the amount type
is ``percentage_st_line``.
Depending on the model configuration the calculation uses either the
raw transaction figures or the journal-currency figures.
Returns a ``(computed_amount, target_currency)`` tuple.
"""
(
txn_amount, txn_currency,
jnl_amount, jnl_currency,
_comp_amount, _comp_currency,
) = st_line._get_accounting_amounts_and_currencies()
ratio = self.amount / 100.0
is_invoice_writeoff = (
self.model_id.rule_type == 'writeoff_button'
and self.model_id.counterpart_type in ('sale', 'purchase')
)
if is_invoice_writeoff:
# Invoice creation use the original transaction currency.
return currency.round(-txn_amount * ratio), txn_currency, ratio
# Standard write-off follow the journal currency.
return currency.round(-jnl_amount * ratio), jnl_currency, None
def _apply_in_bank_widget(self, residual_amount_currency, partner, st_line):
"""Produce move-line values for the bank reconciliation widget.
Handles three amount-type strategies:
* ``percentage_st_line`` percentage of the statement line amount
* ``regex`` amount extracted from the payment reference
* fallback delegates to :meth:`_apply_in_manual_widget`
Args:
residual_amount_currency: Open balance in the statement line currency.
partner: ``res.partner`` record for the counterpart.
st_line: ``account.bank.statement.line`` being reconciled.
Returns:
``dict`` ready for account.move.line creation.
"""
self.ensure_one()
line_currency = (
st_line.foreign_currency_id
or st_line.journal_id.currency_id
or st_line.company_currency_id
)
# -- percentage of statement line ---------------------------------
if self.amount_type == 'percentage_st_line':
computed_amount, target_cur, pct_ratio = (
self._compute_percentage_st_line_amount(st_line, line_currency)
)
entry_data = self._prepare_aml_vals(partner)
entry_data['currency_id'] = target_cur.id
entry_data['amount_currency'] = computed_amount
if pct_ratio is not None:
entry_data['percentage_st_line'] = pct_ratio
if not entry_data.get('name'):
entry_data['name'] = st_line.payment_ref
return entry_data
# -- regex extraction from payment reference ----------------------
if self.amount_type == 'regex':
extracted = self._extract_regex_amount(
st_line.payment_ref, residual_amount_currency,
)
entry_data = self._prepare_aml_vals(partner)
entry_data['currency_id'] = line_currency.id
entry_data['amount_currency'] = extracted
if not entry_data.get('name'):
entry_data['name'] = st_line.payment_ref
return entry_data
# -- percentage / fixed reuse manual widget logic ---------------
entry_data = self._apply_in_manual_widget(
residual_amount_currency, partner, line_currency,
)
if not entry_data.get('name'):
entry_data['name'] = st_line.payment_ref
return entry_data