Location (LLD/LCD)

Référence technique du domaine Location — affaires, contrats, financement PMT, agréments bailleurs, cessions, assurance, maintenance, prélèvements et engagements bancaires.

Location (LLD/LCD)

Périmètre : cycle de vie complet du dossier de location longue durée (LLD) et courte durée (LCD) — qualification, équipements, devis fournisseurs, financement, propositions, agréments bailleurs, signature, mise en service, loyers, assurance, maintenance, suivi des prélèvements bailleur, engagements bancaires, cession de contrat à bailleur, restitution. C'est le cœur du métier Pulse.

Tous les écrans vivent sous /workspace/lease/*. Côté API, deux familles de préfixes coexistent : les ressources « affaire » et « contrat » sont des collections REST à part entière (/api/workspace/lease-affairs/* et /api/workspace/lease-contracts/*, avec tiret), tandis que les vues agrégées et le simulateur restent sous /api/workspace/lease/* (dashboard, pipeline, simulate-financing, billing/run…). Source : docs/11-domain-lease-satelease.md. Les contrats repris de Sat&Lease conservent leur référence historique (legacyExternalRef, ex. 201805167).

Vocabulaire & entités

TermeEntitéDéfinition
AffaireLeaseAffairDossier commercial pré-signature. Étapes QUALIFICATION → … → CEDED.
ContratLeaseContractEngagement signé. Numéro CLLD-YY-MM-NNN généré sous advisory lock PostgreSQL.
BailleurLenderOrganisme financier (LIXXBAIL, GRENKE, CM LEASING, LOCAM, SOFIBRA, BNP…).
Demande d'accordLenderApprovalRequestDossier soumis au bailleur pour validation (6 statuts).
CessionCessionTransfert du contrat de Pulse vers un bailleur ; irréversible une fois COMPLETED.
Type de refinancementRefinancingTypeMANAGEMENT_MANDATE / BACKED_LOAN / SELF_CARRIED.
Contrat miroirLeaseContract (isMirror)Jumeau créé lors d'une cession, relié via mirrorContractId.
PMTMensualité : PMT = C × i / (1 − (1+i)^(−n)).
Marge loueur% ajouté au taux bailleur pour rémunération Pulse.
Loyer intercalaireLeaseRentSchedule (isIntercalary)Pro-rata entre signature et 1er loyer ordinaire.
AssuranceLeaseInsuranceCouverture matériels loués ; facturation annuelle au prorata.
MaintenanceLeaseMaintenancePrestation périodique ; reversement au prestataire suivi.
Prélèvement bailleurLenderPaymentScheduleÉchéance prélevée par le bailleur, pointée sur relevé bancaire.
Engagement bancaireLeaseBankCommitmentEncours financier restant vis-à-vis d'un bailleur, par business unit.
AvenantLeaseAmendmentModification post-signature (durée, lignes, taux) ; numérotés 1..N.
PartenairePartnerApporteur d'affaires / courtier ; perçoit une commission.
Type d'annéeScheduleYearTypeFIRST / INTERMEDIATE / LAST — pilote le prorata temporis (assurance, maintenance).

Le modèle complet est dans docs/04-data-model.md §9.

Écrans

ÉcranRouteContenu
Dashboard Location/workspace/lease8 KPI dynamiques, funnel pipeline 10 statuts, exposition par bailleur, horizon 6 mois, alertes.
Liste affaires/workspace/lease-affairsDataTable 10 colonnes + 7 filtres URL-bound + vue Kanban 10 statuts.
Création affaire/workspace/lease-affairs/newWizard 8 champs (type, client/SIREN, établissement, owner, date, montant, bailleur pressenti, opportunité CRM).
Fiche affaire/workspace/lease-affairs/[id]Stepper 10 statuts + 8 onglets : Synthèse, Équipements, Enveloppe, Financement, Proposition, Agréments, Documents, Audit.
Simulateur financement(composant dans fiche affaire)Capital / durée / taux → PMT, tableau d'amortissement, comparaison scénarios.
Liste contrats/workspace/lease-contractsDataTable : N°, client, loyer, statut, bailleur ; filtres statut/période/montant.
Fiche contrat/workspace/lease-contracts/[id]Onglets : Synthèse, Équipements, Financement, Loyers, Assurance, Maintenance, Prélèvements bailleur, Engagements, Documents, Avenants, Cession, Audit.
Bailleurs/workspace/lendersCRUD + fiche 360° (conditions, agréments, contrats cédés, encours, performance).
Agréments/workspace/lease-approval-requestsListe cross-affaires LenderApprovalRequest + fiche (conditions, échanges, historique).
Cessions/workspace/lease-cessionsListe cross-contrats (statut, montant, date effective) + fiche.
Échéancier global/workspace/lease-rent-scheduleTous les loyers à venir ; vue liste + toggle calendaire CSS 4×3 ; KPI M+1/M+3/M+6.
Assurances/workspace/lease-insuranceLeaseInsurance avec alertes attestation manquante/expirée + échéancier par exercice.
Maintenance/workspace/lease-maintenanceLeaseMaintenance + échéancier + suivi reversement prestataire.
Engagements bancaires/workspace/lease-bank-commitmentsConsolidation encours par bailleur et BU + pointage LenderPaymentSchedule.
Partenaires/workspace/partnersCRUD + commissions dues/payées.

Modèle de données (points clés)

  • LeaseContract.number : séquence CLLD-YY-MM-NNN générée sous advisory lock PostgreSQL — immuable à la création.
  • LeaseRentSchedule : tableau d'amortissement figé après SIGNED (sauf via avenant). La régénération est refusée si un échéancier existe déjà.
  • LeaseFinancing : stocke capital, taux, PMT, VR, frais de dossier et la décomposition de marge (brute/nette/commissions partenaire/Pulse).
  • LeaseRentSchedule.isIntercalary : ligne intercalaire distincte dans la 1ère facture (PMT × jours / 30).
  • Cession : machine à états PROPOSED → ACCEPTED → COMPLETED, avec sorties REFUSED/CANCELED (PROPOSED peut aller vers ACCEPTED/REFUSED/CANCELED ; ACCEPTED vers COMPLETED/CANCELED). COMPLETED est irréversible. Si refinancingType = MANAGEMENT_MANDATE, un contrat miroir (isMirror = true, mirrorContractId croisé) est créé en transaction et le contrat d'origine passe CEDED.
  • LeaseInsuranceSchedule / LeaseMaintenanceSchedule : une ligne par exercice civil, prorata via prorataTemporis() (FIRST/INTERMEDIATE/LAST). Une ligne SENT ou SETTLED est immuable.
  • LeaseBankCommitment.outstandingHt : recalculé chaque 1er du mois (periodicAmount × remainingRentCount) par le job lease:bank-commitment-refresh.
  • LeaseContract.legacyExternalRef : référence Sat&Lease conservée pour les contrats repris. replacedByContractId trace la chaîne de remplacement.
  • LenderApprovalRequest soumise (SUBMITTED) ne peut pas être supprimée (audit).

API

Toutes les routes exigent une permission lease.* (vérifiée côté API et UI). Les ressources « affaire » / « contrat » sont des collections REST à tiret (lease-affairs, lease-contracts) ; les vues agrégées et le simulateur restent sous lease/.

MéthodeRoutePermission
GET/api/workspace/lease-affairslease.affair:read
POST/api/workspace/lease-affairslease.affair:create
GET / PATCH/api/workspace/lease-affairs/[id]lease.affair:read / :update
DELETE/api/workspace/lease-affairs/[id]lease.affair:delete
GET / POST/api/workspace/lease-affairs/[id]/financingslease.affair:read / :update
DELETE/api/workspace/lease-affairs/[id]/financings/[financingId]lease.affair:update
GET / POST/api/workspace/lease-affairs/[id]/approval-requestslease.affair:read / :update
POST/api/workspace/lease/simulate-financinglease.affair:read
POST/api/workspace/lease/simulate-financing/two-rateslease.affair:read
GET/api/workspace/lease-approval-requestslease.contract:read
PATCH/api/workspace/lease-approval-requests/[id]lease.affair:update
GET/api/workspace/lease-contractslease.contract:read
POST/api/workspace/lease-contractslease.contract:create
GET / PATCH/api/workspace/lease-contracts/[id]lease.contract:read / :update
POST/api/workspace/lease-contracts/[id]/signlease.contract:update
POST/api/workspace/lease-contracts/[id]/transitionlease.contract:update
POST/api/workspace/lease-contracts/[id]/locklease.contract:update
POST/api/workspace/lease-contracts/[id]/unlocklease.contract:unlock
GET / POST / DELETE/api/workspace/lease-contracts/[id]/amendments/*lease.contract:update
GET / POST / DELETE/api/workspace/lease-contracts/[id]/lines/*lease.contract:update
GET/api/workspace/lease-contracts/[id]/rent-schedulelease.contract:read
POST/api/workspace/lease-contracts/[id]/rent-schedulelease.contract:update
GET/api/workspace/lease-contracts/[id]/mirrorlease.contract:read
POST/api/workspace/lease-contracts/[id]/cessionlease.contract:update
GET / PATCH/api/workspace/lease-contracts/[id]/cessionlease.contract:read / :update
POST/api/workspace/lease/billing/runlease.contract:update
GET / POST/api/workspace/lease-contracts/[id]/insurancelease.contract:read / :update
GET / POST/api/workspace/lease-contracts/[id]/maintenancelease.contract:read / :update
GET / POST/api/workspace/lease-contracts/[id]/lender-paymentslease.contract:read / :update
GET / POST/api/workspace/lease-contracts/[id]/bank-commitmentslease.contract:read / :update
PATCH/api/workspace/lender-payments/[id]/reconcilelease.contract:update
PATCH/api/workspace/lender-payments/[id]/reactivatelease.contract:update
POST/api/workspace/lease-bank-commitments/refreshlease.lender:read
GET/api/workspace/lease-bank-commitmentslease.contract:read
GET/api/workspace/lenderslease.lender:read
POST/api/workspace/lenderslease.lender:manage
GET/api/workspace/lenders/[id]/performancelease.lender:read
GET/api/workspace/lease-rent-schedulelease.contract:read
GET/api/workspace/lease/exposure-by-lenderlease.affair:read
GET/api/workspace/lease/pipelinelease.affair:read
GET/api/workspace/lease/rent-horizonlease.affair:read
GET/api/workspace/lease/alertslease.affair:read
POST/api/webhooks/yousignsignature HMAC (prod)
Cession en deux temps. Il n'existe pas de routes propose-cession / complete ni de permissions lease.cession:*. La cession se pilote sur le contrat : POST …/lease-contracts/[id]/cession crée la proposition (PROPOSED), puis PATCH …/lease-contracts/[id]/cession transitionne le statut (jusqu'à COMPLETED). Les deux exigent lease.contract:update.Financement. La simulation est stateless (POST /api/workspace/lease/simulate-financing, lease.affair:read). La sauvegarde de scénarios se fait sur l'affaire via POST/GET/DELETE …/lease-affairs/[id]/financings[/financingId] (lease.affair:update en écriture, lease.affair:read en lecture).Agréments & transitions d'affaire. La demande d'agrément se crée via POST …/lease-affairs/[id]/approval-requests (lease.affair:update) ; la soumission au bailleur et sa réponse passent par le mêmePATCH /api/workspace/lease-approval-requests/[id]. Le statut d'une affaire évolue par le PATCH …/lease-affairs/[id] (champ status), pas par un endpoint transition dédié.

Exemple de référence d'endpoint :

POST/api/workspace/lease-contracts/[id]/unlockAuth

Déverrouille temporairement un contrat signé pour permettre des corrections. Réservé aux utilisateurs ADMIN ayant la permission lease.contract:unlock. Re-verrouillage automatique après 24 h.

Corps (JSON)

reason
string required
Raison du déverrouillage (minimum 20 caractères). Obligatoire.

Requête

curl -s -X POST "$API/api/workspace/lease-contracts/$ID/unlock" \
  -H "Content-Type: application/json" -H "Cookie: $SESSION" \
  -d '{"reason":"Erreur de saisie du taux bailleur validée par direction"}'

Réponse

{ "id": "ctr_…", "isLocked": false, "unlockedAt": "2026-06-16T10:00:00Z" }

Workflows

Cycle de vie d'une affaire LLD

QUALIFICATION
   │  (proposition financière prête)
   ▼
PROPOSAL
   │  (accord client sur principes)
   ▼
NEGOTIATION
   │  (lancement demande agrément bailleur)
   ▼
LENDER_REVIEW  ←──(refus → retour NEGOTIATION ou autre bailleur)
   │  (accord bailleur)
   ▼
CONTRACT_DRAFT
   │  (signature client + bailleur)
   ▼
SIGNED
   │  (mise en service)
   ▼
IN_USE
   ├──→ TERMINATED  (fin contrat normale ou anticipée + restitution)
   └──→ CEDED       (cédé à un bailleur)

Transitions contrôlées par la state machine lease.workflow.ts. Une affaire IN_USE ne peut plus être annulée, seulement résiliée.

Facturation des loyers

Job Nitro lease:billing (30 2 * * *) : pour chaque contrat ACTIVE ou CEDED (mandat de gestion inclus), génère 1 Invoice par LeaseRentSchedule dont dueDate ≤ today + 15j et status = PLANNED. Idempotent (invoiceId non-null → skip). 1ère facture = loyer intercalaire + frais de dossier + 1er loyer ordinaire.

Unlock & re-lock contrat

Réservé ADMIN. Raison obligatoire (≥ 20 chars) + confirmation 2-step + audit log renforcé + email automatique à la direction Pulse + re-verrouillage auto après 24 h. Task Nitro lease:contract-auto-relock schedulée 15 * * * *.

Avenant

Création avenant numéroté → PDF → signature (identique au contrat initial) → recalcul tableau d'amortissement si durée ou taux modifiés (nouveaux loyers à partir de la date effective). C'est le seul mécanisme permettant de modifier l'échéancier après SIGNED.

Cession à un bailleur

Proposition → acceptation bailleur (ACCEPTED) → émission facture de cession → création éventuelle contrat miroir → date effective → COMPLETED. Si MANAGEMENT_MANDATE : Pulse reste émetteur des facturations et responsable du suivi des prélèvements (LenderPaymentSchedule).

Facturation assurance & maintenance

Jobs lease-insurance-billing (0 3 * * *) et lease-maintenance-billing (0 4 * * *) : itèrent sur les échéances PLANNED dues → génèrent les factures → passent SENT puis SETTLED. Prorata temporis 1ère/dernière année géré par computeYearlySlices dans server/lib/finance.ts.

Pointage bancaire (3-voies)

PATCH /lender-payments/[id]/reconcile : transition ACTIVE → SETTLED ou REJECTED. En cas de rejet : statut REJECTED + notification gestionnaire. Job lease:bank-commitment-refresh (0 5 1 * *) : recalcule outstandingHt sur tous les engagements.

Signature électronique

Provider abstrait (FIXTURES / YOUSIGN / DOCUSEAL). Webhook HMAC Yousign → handleSignatureWebhook → transitions PENDING_CUSTOMER_SIGNATURE → PENDING_LENDER_SIGNATURE → ACTIVE. PDF signé + certificat eIDAS archivés dans bucket SIGNATURES_EVIDENCE (Object Lock COMPLIANCE 10 ans). Cron de rattrapage signature:archival-reconcile (45 * * * *) ré-essaie les enveloppes COMPLETED sans signedFileId.

Jalons d'affaire (AffairCheckpoint)

Les jalons matérialisent les étapes de validation d'une affaire (CE1, signature, etc.). Le mécanisme est partagé entre la Location et le CRM : un jalon (AffairCheckpoint) est polymorphe et pointe via affairModel sur une LeaseAffair ou une Opportunity (service server/services/commercial/affair-checkpoint.service.ts).

Côté API, les jalons vivent sous le préfixe commercial et non lease. Les endpoints d'instances sont gardés par sales.quote:read / sales.quote:update ; les templates sont gérés sous crm (crm.opportunity:read en lecture, crm.settings:update en écriture). Aucune permission lease.* n'est requise.

Modèle

  • AffairCheckpointTemplate : étape standard configurable par organisation (code unique par org, label, position, isActive). Ciblage double : affairTypes (SALE / LEASE_LLD / LEASE_LCD / SERVICE) et natureCodes (codes OpportunityNature). Vide = template « commun » posé sur toute affaire.
  • AffairCheckpoint : instance posée sur une affaire (@@unique([affairModel, affairId, templateId])), portant un status, doneAt / doneById, notes et resultRef (référence vers l'entité produite — Invoice.id, File.id…).

AffairCheckpointStatus : PENDING · IN_PROGRESS · DONE · NA · BLOCKED.

Logique

  • Bootstrap incrémental & idempotent : bootstrapCheckpoints ne pose que les templates isActive applicables au périmètre résolu de l'affaire (type d'affaire + natures), sans dupliquer ni écraser les jalons déjà posés. Le périmètre d'une LeaseAffair est dérivé de son LeaseType (LLD/LCD) via le mapping server/lib/commercial/affair-type-mapping.ts ; celui d'une Opportunity de l'union des natures portées par ses lignes.
  • Validation : markDone est le seul chemin vers DONE (horodate et trace le validateur) ; il refuse un jalon déjà DONE (409).
  • Réaffectation : setCheckpointStatus n'autorise que PENDING / NA / BLOCKED (DONE exclu) ; quitter DONE efface doneAt / doneById pour la cohérence des compteurs.
  • Toutes les mutations sont auditées (commercial.checkpoints.bootstrap, commercial.checkpoint.done, commercial.checkpoint.status).

API

MéthodeRoutePermission
GET/api/workspace/commercial/affair-checkpoints?affairModel=&affairId=sales.quote:read
POST/api/workspace/commercial/affair-checkpoints (bootstrap)sales.quote:update
POST/api/workspace/commercial/affair-checkpoints/[id]/donesales.quote:update
POST/api/workspace/commercial/affair-checkpoints/[id]/statussales.quote:update
GET / POST/api/workspace/crm/checkpoint-templatescrm.opportunity:read / crm.settings:update
GET / PATCH / DELETE/api/workspace/crm/checkpoint-templates/[id]crm.opportunity:read / crm.settings:update

Règles métier

Garde-fous non négociables
  • Tableau d'amortissement immuable après SIGNED : sauf via avenant (seul vecteur légal de recalcul).
  • lease.contract:unlock : permission spéciale réservée ADMIN, raison ≥ 20 chars, audit renforcé, email direction, re-lock automatique 24 h.
  • Cession COMPLETED irréversible : tout changement de bailleur post-cession est une novation complexe hors scope.
  • Numérotation contrat : séquence CLLD-YY-MM-NNN sous advisory lock PostgreSQL, immuable.
  • Fichiers contrats/signatures : bucket SIGNATURES_EVIDENCE Object Lock COMPLIANCE 10 ans (jamais supprimable).
  • Un contrat ne peut être cédé qu'une seule fois (mirrorContractId anti-double cession).
  • Demande d'agrément SUBMITTED non supprimable (audit trail).
  • Une échéance d'assurance ou maintenance SENT/SETTLED est immuable.
  • Affaire IN_USE : annulation interdite (résiliation uniquement).
  • Statut affaire et statut contrat sont distincts : une affaire peut être SIGNED (workflow) pendant que le contrat est ACTIVE (lifecycle technique).
  • Mandat de gestion : Pulse reste émetteur des facturations tant que ce type de refinancement est actif.
  • Contrats repris de Sat&Lease : legacyExternalRef conservé, chaîne de remplacement tracée via replacedByContractId.

KPIs

Nb affaires par statut · cycle moyen Qualification → Signature · taux d'aboutissement · taux d'accord par bailleur · délai moyen agrément · exposition (capital restant dû) par bailleur · encours engagements bancaires par bailleur et BU · ratio cession / auto-portage · marge brute/nette/cession par contrat · loyers attendus M+1/M+3/M+6 · taux attestations assurance reçues · taux de rejet des prélèvements.

Notifications

ÉvénementCanalDestinataire
Affaire crééeIn-appOwner + manager
Proposition acceptéeIn-app + emailOwner
Réponse bailleur reçueIn-app + emailOwner + FINANCE_OFFICER
Contrat prêt à signatureIn-app + email + SMSClient (signataires)
Contrat signéIn-app + emailClient + owner + accounting
Loyer en retardEmailClient + accounting
Restitution J-30EmailClient + owner
Cession finaliséeEmailClient (changement créancier)
Contrat unlockéEmailDirection Pulse
Prélèvement rejetéIn-appGestionnaire