Property Payment Schedule¶
Contrôleur HTTP de l'échéancier d'une property (création, simulation, prorogation, gestion des virements à assigner, séquestre d'intérêts). Source : property-payment-schedule.controller.ts.
Tous les endpoints requièrent une session admin BetterAuth valide (AdminAuthGuard). Les endpoints muteurs sont tracés via CreateAdminActionInterceptor.
Préfixe de routes : /administration/property.
POST /payment-schedule/simulation¶
Simule un échéancier sans le persister. Retourne les échéances calculées par PaymentScheduleComputing.generateSimulation.
Request body¶
{
"totalFundedAmount": 100000,
"initialCapitalLoaned": { "date": "2025-01-15", "amount": 100000 },
"durationInMonths": 12,
"annualInterestPercentage": 10,
"managementFeesPercentage": 1.5,
"spvCountry": "France",
"interestSequestre": 0,
"events": []
}
Validation via un sous-ensemble de EcheancierConfigModel.validator.props (totalFundedAmount, initialCapitalLoaned, durationInMonths, annualInterestPercentage, managementFeesPercentage, interestSequestre, events) (source), plus spvCountry (SpecialPurposeVehicule.countryValidator). paymentMode n'est pas validé ici.
Response¶
Echeance[] — tableau d'échéances simulées.
POST /payment-schedule/v2¶
Crée et persiste l'échéancier v2 d'une property.
Request body¶
{
"propertyId": "uuid",
"echeancierConfig": { "...": "EcheancierConfigModel.validator" },
"totalConstructionBudget": 100000
}
Validé via PropertyEcheancierModel.createEcheancierV2_Payload (source).
Response¶
Erreurs¶
| Code | Statut | Cause |
|---|---|---|
payment-schedule.already-exist |
400 | Un échéancier existe déjà pour cette property |
spv-not-found |
400 | SPV introuvable pour cette property |
initial-capital-loaned-is-greater-than-total-funded-amount |
400 | Validation config |
interest-sequestre-is-greater-than-total-funded-amount |
400 | Validation config |
sum-repayment-is-greater-than-sum-loaned |
400 | Validation config |
PATCH /:propertyId/payment-schedule/simulation/capital-event¶
Simule l'application d'un événement de capital (capitalLoaned ou capitalRepaymentUnplanned) sur l'échéancier existant. Ne mute pas la base.
Path param¶
propertyId— UUIDv4
Request body¶
Le type peut être capitalLoaned ou capitalRepaymentUnplanned (source).
Response¶
L'échéancier simulé avec l'événement appliqué.
Erreurs¶
| Code | Statut | Cause |
|---|---|---|
echeancier-not-found |
400 | Pas d'échéancier pour cette property |
echeance-not-found |
400 | Échéance correspondant à la date introuvable |
echeancePeriod-does-not-match-event-date |
400 | Date de l'événement hors période |
interest-sequestre-remaining-is-not-enough |
400 | Séquestre insuffisant pour le repayment |
event-date-is-out-of-bound |
400 | Date de l'événement hors bornes de l'échéancier |
echeancier-config-is-invalid |
400 | Config résultante incohérente |
PUT /:projectId/echeancier/payment-mode¶
Pose le mode de paiement mensuel de l'échéancier (wire ou direct-debit + mandat). Peut changer de mode ou seulement le lwMandateId en restant en prélèvement. Le jour du mois (dayOfMonth) est conservé. Passage en prélèvement : mandat SDD Lemonway obligatoire. Un SDD déjà programmé continue d'être synchro par echeancier-bank-debit-status-update même après un passage en virement. Trace une PROJECT_ECHEANCIER_SET_PAYMENT_MODE.
Path param¶
projectId— UUIDv4
Request body¶
ou
Validé via setMonthlyPaymentModeBodySchema (source).
Response¶
200 OK avec un corps vide.
GET /:propertyId/payment-schedule¶
Récupère l'échéancier persisté d'une property.
Path param¶
propertyId— UUIDv4
Response¶
Échéancier complet via PropertyPaymentScheduleRepository.getPaymentScheduleForProperty.
PUT /:propertyId/payment-schedule/unprogram-scheduled-mensualite-payment¶
Annule la programmation d'un prélèvement bancaire SDD (mensualité) prévu pour une échéance. Trace une PROPERTY_PAYMENT_SCHEDULE_UNPROGRAM_ONE_PAYMENT.
Path param¶
propertyId— UUIDv4
Request body¶
Erreurs¶
| Code | Statut | Cause |
|---|---|---|
echeance-not-found |
400 | Échéance introuvable |
echeance-mensualite-must-be-unpaid |
400 | La mensualité a déjà été payée |
echeance-mensualite-scheduled-payment-must-be-scheduled |
400 | Aucun prélèvement programmé à annuler |
failed-at-provider |
500 | Échec côté Lemonway |
PUT /:propertyId/payment-schedule/program-unscheduled-mensualite-payment¶
Programme un prélèvement bancaire pour une échéance pas encore planifiée. Trace une PROPERTY_PAYMENT_SCHEDULE_PROGRAM_ONE_PAYMENT.
Path param¶
propertyId— UUIDv4
Request body¶
Erreurs¶
| Code | Statut | Cause |
|---|---|---|
echeance-not-found |
400 | Échéance introuvable |
echeance-mensualite-must-be-unpaid |
400 | Mensualité déjà payée |
echeance-mensualite-must-be-not-scheduled |
400 | Mensualité déjà programmée |
echeance-nothing-to-pay |
400 | Rien à payer pour cette échéance |
echeancier-config-payment-mode-not-direct-debit |
400 | Mode de paiement n'est pas SDD |
failed-at-provider |
500 | Échec côté Lemonway |
GET /payment-schedule/payment/:period¶
Liste les paiements (avec statut financier projet) pour un mois donné.
Path param¶
period— au formatYYYY-MM(validé viayearMonthDate)
Response¶
Tableau de paiements avec leur statut financier projet.
GET /payment-schedule/wire-to-be-assigned¶
Liste les virements en attente d'assignation à une échéance.
Response¶
Vue des virements EcheancierWireToBeAssigned au statut waiting.
GET /payment-schedule/payment-to-be-assigned/history¶
Liste en lecture seule les paiements déjà assignés (assigned) ou archivés (archivedNotAssigned).
Response¶
Même forme enrichie que la liste waiting (projet, SPV, lien Lemonway) avec en plus, pour les assignés, le numéro et la période de l'échéance cible. Le champ JSON porte le nom paymentToBeAssigned.
POST /payment-schedule/wire-to-be-assigned/switch-project¶
Réassigne un virement reçu à un autre projet (changement de SPV destinataire). Trace une PROPERTY_PAYMENT_SCHEDULE_SWITCH_PROJECT_OF_WIRE_WAITING_TO_BE_ASSIGNED.
Request body¶
Erreurs¶
| Code | Statut | Cause |
|---|---|---|
wire-not-found |
400 | Virement introuvable |
wire-not-waiting |
400 | Virement n'est pas en statut waiting |
only-parent-wires-can-be-project-switched |
400 | Le virement est un enfant (split) |
only-wire-payments-can-be-project-switched |
400 | Le paiement n'est pas un virement entrant (p2p, other) |
spv-current-project-not-found |
400 | SPV courant introuvable |
new-project-not-found |
400 | Projet cible introuvable |
new-project-spv-not-found |
400 | SPV du projet cible introuvable |
p2p-failed-at-provider |
400 | Échec P2P Lemonway |
POST /payment-schedule/wire-to-be-assigned/assign¶
Assigne un virement à une échéance (mensualité ou capital repayment). Trace une PROPERTY_PAYMENT_SCHEDULE_ASSIGN_WIRE_TO_ECHEANCE.
Request body¶
{
"wireToBeAssignedId": "uuid-v4",
"paymentFor": "mensualite",
"echeanceId": "uuid-v4",
"comment": "optionnel",
"isSimulation": false
}
paymentFor ∈ 'capitalRepayment' | 'mensualite'.
Response¶
L'échéancier mis à jour.
Erreurs¶
| Code | Statut | Cause |
|---|---|---|
wire-not-found |
400 | Virement introuvable |
wire-not-waiting |
400 | Virement déjà assigné/archivé |
echeance-not-found |
400 | Échéance introuvable |
capital-repayment-no-payment-due |
400 | Pas de capital à rembourser sur l'échéance |
POST /payment-schedule/wire-to-be-assigned/split¶
Découpe un virement reçu en deux pour pouvoir l'assigner partiellement. Trace une PROPERTY_PAYMENT_SCHEDULE_SPLIT_WIRE_TO_ECHEANCE.
Request body¶
newAmount en cents (positif, doit être inférieur au montant initial).
Erreurs¶
| Code | Statut | Cause |
|---|---|---|
wire-not-found |
400 | Virement introuvable |
wire-not-waiting |
400 | Virement déjà traité |
new-amount-must-be-less-than-original-amount |
400 | Montant nouveau ≥ montant initial |
POST /payment-schedule/wire-to-be-assigned/archive¶
Archive un virement reçu sans l'assigner (cas erreur de saisie, virement non lié à un projet, etc.). Trace une PROPERTY_PAYMENT_SCHEDULE_ARCHIVE_WIRE.
Request body¶
comment requis (notEmptyString).
Erreurs¶
| Code | Statut | Cause |
|---|---|---|
wire-not-found |
400 | Virement introuvable |
wire-not-waiting |
400 | Virement déjà traité |
GET /payment-schedule/payment-to-be-assigned/eligible-projects¶
Liste les projets éligibles à la création manuelle d'un paiement à assigner (back-office). Source : property-payment-schedule.controller.ts L475.
Response¶
Liste de projets avec identifiants et métadonnées utiles au formulaire de création.
POST /payment-schedule/payment-to-be-assigned¶
Crée un paiement à assigner (virement entrant en attente d'affectation à une échéance). Trace une PROJECT_PAYMENT_SCHEDULE_CREATE_PAYMENT_TO_BE_ASSIGNED. Validator partagé : createPaymentToBeAssignedPayload (@bricks-common/api-communication-bricksoffice).
Request body¶
{
"projectId": "uuid-v4",
"amount": 100000,
"date": "2025-06-15",
"payment": {
"kind": "p2p",
"execution": "auto",
"debitAccountId": "compte-lemonway-debiteur",
"comment": "Paiement porteur juin"
}
}
amount en cents (positif). payment est une union discriminée sur kind :
| Variante | Champs |
|---|---|
p2p + auto |
debitAccountId, comment — déclenche un P2P Lemonway synchrone vers le wallet SPV du projet |
p2p + manual |
paymentId (entier Lemonway), comment — rattache un P2P Lemonway existant |
other |
comment — enregistrement sans mouvement Lemonway |
Comportement P2P auto en échec¶
Si le P2P Lemonway échoue après création de l'enregistrement : le virement reste en status: waiting sans wireId, une alerte Slack est envoyée (InternalAlertSlackService + PaymentScheduleSlackService), et la réponse HTTP reste un succès avec l'enregistrement créé. Le retry se fait via POST .../payment-to-be-assigned/retry-p2p (idempotent via attemptP2PAtLemonway).
Response¶
L'enregistrement waitingWire créé.
Erreurs¶
| Code | Statut | Cause |
|---|---|---|
project-not-eligible |
400 | Projet non éligible |
spv-not-found |
400 | SPV introuvable |
spv-lemonway-wallet-not-found |
400 | Wallet Lemonway SPV introuvable |
debit-account-not-found |
400 | Compte débiteur introuvable (P2P auto) |
debit-account-insufficient-balance |
400 | Solde débiteur insuffisant (P2P auto) |
lemonway-p2p-not-found |
400 | P2P Lemonway introuvable (P2P manual) |
lemonway-p2p-not-success |
400 | P2P Lemonway pas en succès (P2P manual) |
lemonway-p2p-amount-mismatch |
400 | Montant P2P ≠ montant demandé (P2P manual) |
lemonway-p2p-receiver-account-mismatch |
400 | Compte receveur P2P ≠ wallet SPV (P2P manual) |
lemonway-p2p-validation-failed |
500 | Échec technique de validation Lemonway |
POST /payment-schedule/payment-to-be-assigned/retry-p2p¶
Relance le P2P Lemonway pour un paiement à assigner créé en mode p2p + auto dont le P2P initial a échoué. Trace une PROJECT_PAYMENT_SCHEDULE_RETRY_PAYMENT_TO_BE_ASSIGNED_P2P.
Request body¶
Response¶
L'enregistrement waitingWire mis à jour.
Erreurs¶
| Code | Statut | Cause |
|---|---|---|
payment-to-be-assigned-not-found |
400 | Enregistrement introuvable |
payment-to-be-assigned-not-waiting |
400 | Déjà traité |
payment-to-be-assigned-not-p2p-auto-execution |
400 | Pas un paiement P2P auto |
payment-to-be-assigned-already-has-lemonway-wire-id |
400 | P2P déjà lié |
spv-not-found |
400 | SPV introuvable |
spv-lemonway-wallet-not-found |
400 | Wallet Lemonway SPV introuvable |
p2p-failed |
500 | Échec technique du P2P Lemonway |
POST /:propertyId/payment-schedule/prorogation¶
Proroge l'échéancier (prolonge la durée), avec optionnellement un nouveau taux d'intérêt sur la période de prorogation. Trace une PROPERTY_PAYMENT_SCHEDULE_PROROGATE_ECHEANCIER.
Path param¶
propertyId— UUIDv4
Request body¶
interestRateDuringProrogation est optionnel.
Response¶
L'échéancier prorogé.
Erreurs¶
| Code | Statut | Cause |
|---|---|---|
prorogation-duration-should-be-greater-than-nominal-duration |
400 | Durée demandée ≤ durée nominale |
prorogation-duration-should-be-greater-than-current-duration |
400 | Durée demandée ≤ durée courante |
POST /:propertyId/payment-schedule/recompute-from-echeance¶
Recalcule l'échéancier à partir d'une période donnée. Réservé à denis@bricks.co (beta dev — check email-only dans le contrôleur). Trace une PROPERTY_PAYMENT_SCHEDULE_RECOMPUTE_FROM_ECHEANCE.
Path param¶
propertyId— UUIDv4
Request body¶
Pré-conditions¶
- Email admin ∈
denis@bricks.co(sinon403 Forbidden)
Response¶
L'échéancier recalculé.
Erreurs¶
| Code | Statut | Cause |
|---|---|---|
| (forbidden) | 403 | Email admin non autorisé |
| (internal) | 500 | Échec du recalcul |
POST /echeancier/interest-sequestre/transfer-to-echeance-payment-to-be-assigned¶
Transfère du séquestre d'intérêts vers une échéance comme paiement à assigner. Trace une PROPERTY_ECHEANCIER_TRANSFER_SEQUESTRE_TO_ECHEANCE_PAYMENT_TO_BE_ASSIGNED.
Request body¶
{
"propertyId": "uuid",
"echeanceId": "uuid-v4",
"interestSequestreAmountToTransfer": 1500,
"paymentDate": "2025-06-15"
}
Erreurs¶
| Code | Statut | Cause |
|---|---|---|
| (internal) | 500 | Échec du transfert (séquestre insuffisant, échéance introuvable, etc.) |
GET /payment-schedule/capital-repayment/unpaid¶
Liste les remboursements de capital non payés à travers tous les échéanciers.
Response¶
Vue retournée par EcheancierViewService.getUnpaidCapitalRepayments.
POST /gestion-defauts/echeancier/penalties¶
Webhook server-to-server appelé par gestion-des-defauts pour synchroniser le statut financier projet (retard / défaut) et les pénalités d'échéance. Source : gestion-defauts.controller.ext.ts — préfixe route /administration.
Auth¶
Clé API gestion-defauts via ApiAuthService.assertApiKeyInHeaders(headers, 'gestion-defauts').
Request body¶
Tableau de projets (validator penaltiesWebhookPayload dans echeancier-penalty-sync.service.ts) :
[
{
"propertyId": "uuid",
"status": "delay | default | null",
"penalties": [
{
"echeanceId": "uuid",
"penalty": {
"total": 5000,
"forInvestors": 4000,
"forBricks": 1000
}
}
]
}
]
Montants en centimes. status: null = régularisation (retour à la normale). Seule la part forInvestors entre dans amountDue ; forBricks est réclamée au solde de tout compte.
Comportement¶
Traitement projet par projet dans EcheancierPenaltyService.updateProjectFinancialStatusAndPenalties :
- Ignore le projet si son
financialStatuscourant n'est pas dansprojectOwnerDebtStatuses(ex. funding encore en cours) — pas de flaghasBeenInDelayOrDefaultOnce, pas de changement de statut. - Met à jour
financialStatus(repayment-delay,repayment-default,repayment-ongoing…) et lèvehasBeenInDelayOrDefaultOncequandstatusest non null. - Applique les pénalités sur les échéances (recompute
amountDue/remainingToBePaid) — idempotent sipenalty.totalinchangé. - Purge le cache property (
PropertyFundingService.deletePropertyCache) pour chaque projet dont le statut a changé.
Erreurs tolérées (skip + Slack, le batch continue, HTTP 201) :
| Code | Cause |
|---|---|
echeance-not-found |
echeanceId absent de l'échéancier Bricks — le projet entier est ignoré (statut et pénalités non appliqués) |
echeancier-not-found |
Pas d'échéancier alors que penalties est non vide |
Erreurs bloquantes (HTTP 500, projets déjà traités dans le batch conservent leurs écritures) : spv-not-found, echeancier-computing-error.
Notifications Slack via SpecialPurposeVehiculeSlackService : erreurs skip, statut hors fenêtre de remboursement, pénalités effectivement appliquées.
Response¶
201 — { penaltiesApplied: PenaltyAppliedToEcheance[] } (pénalités réellement écrites, hors no-op).
Liens¶
- Service échéancier :
property-payment-schedule.service.ts - Service prélèvement bancaire :
property-payment-schedule.bank-debit-programming.service.ts - Service virements à assigner :
echeancier-wire-to-be-assigned.service.ts - Service séquestre :
echeancier.interest-sequestre.service.ts - Modèle config :
echeancier-config.model.ts - Modèle événement :
echeancier-event.model.ts - Modèle échéancier :
property-payment-schedule.model.ts - Computing :
property-payment-schedule.computing.ts