Sélectionner une révision Git
financial_contract_guarantee.py

Hugo Trentesaux authored
financial_contract_guarantee.py 16,27 Kio
# © 2024 Le Filament (<http://www.le-filament.com>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import Command, _, api, fields, models
from odoo.exceptions import ValidationError
class FinancialContractGuarantee(models.Model):
_name = "financial.contract.guarantee"
_inherit = [
"financial.contract",
"mail.thread",
"mail.activity.mixin",
]
_description = "Contrat de garantie"
_check_company_auto = True
_rec_names_search = [
"name",
"number",
"partner_id.name",
"partner_id.member_number",
]
_order = "create_date desc"
# chaque contrat de garantie est associée à un identifiant titulaire
# plusieurs contrats de garantie peuvent être associés au même identifiant
titular_number = fields.Char("Identifiant Titulaire")
person_number = fields.Char(
"Identifiant Personne", related="partner_id.person_number"
)
product_id = fields.Many2one(
comodel_name="financial.product.template.guarantee",
string="Gamme",
check_company=True,
required=True,
)
suspensive_condition_ids = fields.One2many(
comodel_name="financial.contract.guarantee.suspensive.condition",
inverse_name="contract_id",
compute="_compute_suspensive_condition_ids",
store=True,
readonly=False,
)
# TODO: voir pour la gestion de ce champ avec le module contract external
external_loan_id = fields.Many2one(
comodel_name="financial.contract.external",
string="Prêt lié",
domain="[('partner_id', '=', partner_id), ('tool', '=', 'loan')]",
)
loan_contract_number = fields.Char(
string="Numéro de contrat de prêt",
related="external_loan_id.number",
store=True,
)
loan_bank = fields.Many2one(
comodel_name="financial.contract.external.partner",
related="external_loan_id.external_partner_id",
)
# 123.45 % → 1.2345 → 5 digits, 4 after decimal
guarantee_rate = fields.Float(
"Quotité garantie", tracking=1, aggregator="avg", digits=(5, 4)
)
initial_guarantee_amount = fields.Monetary(
string="Montant initial garanti",
compute="_compute_initial_guarantee_amount",
store=True,
)
guarantee_amount = fields.Monetary(
compute="_compute_guarantee_amount",
string="En cours de garantie",
store=True,
tracking=1,
help="Calculé comme le produit du CRDU par la quotité garantie.",
)
is_counter_guarantee = fields.Boolean("Contre Garantie", default=False)
counter_guarantee_rate = fields.Float("Quotité contre garantie")
counter_guarantee_partner_id = fields.Many2one(
comodel_name="financial.contract.guarantee.partner", string="Partenaire"
)
final_risk_guarantee = fields.Monetary(
string="Garantie risque final",
compute="_compute_final_risk_guarantee",
store=True,
tracking=1,
help="Le risque final est l'encours de garantie moins les FMG "
"et les parts sociales. Dans le cas d'une co-garantie, "
"on retire également son montant.",
)
# --- FMG ---
fmg_amount = fields.Monetary(
string="FMG calculé",
compute="_compute_fmg_amount",
store=True,
help="Calculé à partir du montant garanti initial et du taux de FMG.",
)
fmg_paid = fields.Monetary(
string="FMG payé",
compute="_compute_fmg_paid",
store=True,
help="Calculé sur la somme des lignes de FMG.",
)
fmg_ids = fields.One2many(
comodel_name="mutual.guarantee.fund.line",
inverse_name="guarantee_id",
string="FMG",
)
# --- Social share ---
social_share_number = fields.Integer(
string="Nbre parts sociales",
compute="_compute_social_share_number",
store=True,
help="Nombre de parts sociales calculé à partir "
"du montant total et du prix unitaire.",
)
social_share_amount = fields.Monetary(
string="Montant parts sociales décidé",
)
social_share_amount_computed = fields.Monetary(
string="Montant parts sociales calculé",
compute="_compute_social_share",
store=True,
help="Montant des parts sociales calculé à partir du taux "
"et de la quantité maximale de parts sociales.",
)
social_share_ids = fields.One2many(
comodel_name="company.share.line",
inverse_name="guarantee_id",
string="Parts sociales",
)
social_share_paid = fields.Monetary(
string="Montant parts sociales payé",
compute="_compute_social_share_paid",
store=True,
help="Montant calculé sur la somme des lignes de mouvements de parts sociales.",
)
# --- Commission ---
commission_ids = fields.One2many(
comodel_name="financial.guarantee.commission.line",
inverse_name="guarantee_id",
string="Commissions",
)
is_old_associate = fields.Boolean(
string="Nouveau sociétaire",
compute="_compute_is_new_associate",
store=True,
default=False,
)
line_ids = fields.One2many(
comodel_name="financial.contract.guarantee.line",
inverse_name="guarantee_id",
string="Lignes de solde",
)
date_folder_received = fields.Date("Date de réception dossier")
date_approval_president = fields.Date("Date Validation Président")
# --- Loan Data ---
payment_date = fields.Date("Date de versement")
payment_date_computed = fields.Date(
string="Date de paiement mise à jour",
help="Calculée sur la dernière valeur connue dans les lignes CRESERFI",
store=True,
compute="_compute_guarantee_data",
)
amount_initial = fields.Monetary(
string="Montant initial",
compute="_compute_amount_initial",
store=True,
readonly=False,
help="Montant du prêt."
)
amount_received = fields.Monetary("Montant reçu")
expiration_date_computed = fields.Date(
string="Date d'expiration mise à jour",
help="Calculée sur la dernière valeur connue dans les lignes CRESERFI",
store=True,
compute="_compute_guarantee_data",
)
remaining_capital = fields.Monetary(
"Capital restant dû",
tracking=1,
store=True,
compute="_compute_guarantee_data",
)
loan_duration = fields.Integer(
string="Durée du crédit (mois)",
compute="_compute_loan_duration",
store=True,
readonly=False,
)
loan_duration_computed = fields.Integer(
string="Durée du crédit (mois) mise à jour",
help="Calculée sur la dernière valeur connue dans les lignes CRESERFI",
store=True,
compute="_compute_guarantee_data",
)
# --- Company Data ---
segment_code = fields.Char("Code Segment")
bdf_scoring = fields.Char(
string="Cotation BDF",
related="partner_id.bdf_scoring",
)
bdf_date = fields.Date(
string="Date Cotation BDF",
related="partner_id.bdf_date",
)
mcdo_scoring = fields.Char(
string="Cotation MacDonough",
related="partner_id.mcdo_scoring",
)
mcdo_date = fields.Date(
string="Date Cotation MacDonough",
related="partner_id.mcdo_date",
)
branch_code = fields.Char("Code branche")
# Invoice Data
commission_amount = fields.Monetary(
string="Montant de commission",
compute="_compute_commission_amount",
store=True,
)
# --- counters ----
# TODO is there a less verbose way to do this?
commission_ids_count = fields.Integer(
compute="_compute_commission_ids_count", store=True
)
@api.depends("commission_ids")
def _compute_commission_ids_count(self):
for g in self:
g.commission_ids_count = len(g.commission_ids)
line_ids_count = fields.Integer(compute="_compute_line_ids_count", store=True)
@api.depends("line_ids")
def _compute_line_ids_count(self):
for g in self:
g.line_ids_count = len(g.line_ids)
fmg_ids_count = fields.Integer(compute="_compute_fmg_ids_count", store=True)
@api.depends("fmg_ids")
def _compute_fmg_ids_count(self):
for g in self:
g.fmg_ids_count = len(g.fmg_ids)
social_share_ids_count = fields.Integer(
compute="_compute_social_share_ids_count", store=True
)
@api.depends("social_share_ids")
def _compute_social_share_ids_count(self):
for g in self:
g.social_share_ids_count = len(g.social_share_ids)
# ------------------------------------------------------
# Constrains functions
# ------------------------------------------------------
# ------------------------------------------------------
# Computed fields / Search Fields
# ------------------------------------------------------
@api.depends("external_loan_id", "external_loan_id.amount")
def _compute_amount_initial(self):
for guarantee in self:
if guarantee.external_loan_id:
guarantee.amount_initial = guarantee.external_loan_id.amount
@api.depends("external_loan_id", "external_loan_id.duration")
def _compute_loan_duration(self):
for guarantee in self:
if guarantee.external_loan_id:
guarantee.loan_duration = guarantee.external_loan_id.duration
@api.depends(
"line_ids",
"line_ids.end_date",
"line_ids.payment_date",
"line_ids.loan_duration",
"line_ids.remaining_capital",
"line_ids.amount_received",
)
def _compute_guarantee_data(self):
for guarantee in self:
with_date = guarantee.line_ids.filtered(lambda x: not not x.line_date)
ordered = with_date.sorted("line_date", reverse=True)
if ordered:
last_line = ordered[0]
guarantee.update(
{
"expiration_date_computed": last_line.end_date,
"payment_date_computed": last_line.payment_date,
"loan_duration_computed": last_line.loan_duration,
"remaining_capital": last_line.remaining_capital,
"amount_received": last_line.amount_received,
}
)
if last_line.remaining_capital == 0:
guarantee.state = "done"
guarantee.external_loan_id.state = "done"
@api.depends("line_ids", "line_ids.commission_amount")
def _compute_commission_amount(self):
for guarantee in self:
guarantee.commission_amount = sum(
guarantee.line_ids.mapped("commission_amount")
)
@api.depends("guarantee_amount", "is_counter_guarantee", "counter_guarantee_rate")
def _compute_final_risk_guarantee(self):
"""encours garanti - FMG - PS
si contre garantie, on retire également son montant
"""
for guarantee in self:
# FIXME doit-on vraiment retrancher le montant des parts sociales ?
frg = (
guarantee.guarantee_amount
- guarantee.fmg_amount
- guarantee.social_share_amount
)
# la contre garantie diminue d'autant le risque final
if guarantee.is_counter_guarantee:
frg *= 1 - guarantee.counter_guarantee_rate
guarantee.final_risk_guarantee = frg
@api.depends("remaining_capital", "guarantee_rate")
def _compute_guarantee_amount(self):
"CRDU × quotité"
for guarantee in self:
guarantee.guarantee_amount = (
guarantee.remaining_capital * guarantee.guarantee_rate
)
@api.depends("amount_initial", "guarantee_rate")
def _compute_initial_guarantee_amount(self):
for guarantee in self:
guarantee.initial_guarantee_amount = (
guarantee.amount_initial * guarantee.guarantee_rate
)
@api.depends("initial_guarantee_amount")
def _compute_fmg_amount(self):
fmg_rate = self.company_id.fmg_rate
if not fmg_rate or fmg_rate == 0.0:
raise ValidationError(_("Le taux de FMG n'est pas configuré."))
for guarantee in self:
guarantee.fmg_amount = fmg_rate * guarantee.initial_guarantee_amount
@api.depends("fmg_ids", "fmg_ids.amount")
def _compute_fmg_paid(self):
for guarantee in self:
guarantee.fmg_paid = sum(guarantee.fmg_ids.mapped("amount"))
@api.depends("initial_guarantee_amount", "partner_id")
def _compute_social_share(self):
"""
Calcule le montant des parts sociales en fonction :
- du taux défini sur la société
- du montant max défini sur la société
- du prix unitaire de la part défini sur la société
- des prises de parts existantes sur la société
:return: affecte les valeurs de montant et nombre de parts
Sert au moment de la décision pour automatiser le calcul
"""
social_share_rate = self.company_id.social_share_rate
social_share_amount_max = self.company_id.social_share_amount_max
for guarantee in self:
max_amount = (
social_share_amount_max - guarantee.partner_id.company_share_total
)
rate_amount = social_share_rate * guarantee.initial_guarantee_amount
final_amount = rate_amount if rate_amount <= max_amount else max_amount
guarantee.social_share_amount_computed = final_amount
@api.depends("social_share_amount")
def _compute_social_share_number(self):
share_unit_price = self.company_id.share_unit_price
for guarantee in self:
guarantee.social_share_number = (
guarantee.social_share_amount // share_unit_price
)
@api.depends("social_share_ids", "social_share_ids.share_total_amount")
def _compute_social_share_paid(self):
for guarantee in self:
guarantee.social_share_paid = sum(
guarantee.social_share_ids.mapped("share_total_amount")
)
@api.depends("product_id")
def _compute_suspensive_condition_ids(self):
for contract in self:
condition_to_create = []
if contract.product_id:
for condition in self.product_id.suspensive_condition_ids:
condition_to_create.append(
Command.create(
{
"condition_id": condition.id,
"condition_comment": condition.condition_comment,
},
)
)
if condition_to_create:
contract.suspensive_condition_ids.unlink()
contract.suspensive_condition_ids = condition_to_create
@api.depends("partner_id", "partner_id.company_share_line_ids")
def _compute_is_new_associate(self):
for guarantee in self:
if guarantee.partner_id and guarantee.partner_id.company_share_line_ids:
guarantee.is_old_associate = True
# ------------------------------------------------------
# Onchange
# ------------------------------------------------------
@api.onchange("product_id")
def _onchange_product_id(self):
self._set_product_values()
@api.onchange("is_counter_guarantee")
def _onchange_is_counter_guarantee(self):
if not self.is_counter_guarantee:
self.counter_guarantee_rate = 0.0
self.counter_guarantee_partner_id = False
# ------------------------------------------------------
# Actions
# ------------------------------------------------------
def action_set_social_share_amount(self):
self.write({"social_share_amount": self.social_share_amount_computed})
# ------------------------------------------------------
# CRUD (Override ORM)
# ------------------------------------------------------
# ------------------------------------------------------
# Business functions
# ------------------------------------------------------
def _set_product_values(self):
if self.product_id:
self.guarantee_rate = self.product_id.guarantee_rate