Initial commit

This commit is contained in:
gsinghpal
2026-02-22 01:22:18 -05:00
commit 5200d5baf0
2394 changed files with 386834 additions and 0 deletions

View 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