Initial commit
This commit is contained in:
220
Fusion Accounting/models/account_reconcile_model_line.py
Normal file
220
Fusion Accounting/models/account_reconcile_model_line.py
Normal file
@@ -0,0 +1,220 @@
|
||||
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
|
||||
Reference in New Issue
Block a user