Aller au contenu

Réservation — machine à états

Reservation et Stay sont deux agrégats distincts :

  • Reservation = contrat logique entre le client et l’établissement. Existe de la création jusqu’à la clôture/annulation.
  • Stay = occurrence physique d’hébergement. Créé au check-in, contient le folio de séjour.

Une réservation peut ne jamais générer de Stay (no-show, annulation) ou en générer plusieurs (modify + check-in du séjour modifié).

┌──────────────┐
create │ Tentative │
──────────► │ │──── confirm ────────────────────┐
└──────────────┘ ▼
┌──────────────┐
cancel ◄─────────────────────────── │ Confirmed │
└──────────────┘
┌──────────────┐ │ check-in
┌───► │ NoShow │ ▼
markNoShow │ └──────────────┘ ┌──────────────┐
└─────────────────────── │ CheckedIn │
└──────────────┘
┌──────────────┐ │ check-out
┌───► │ Walked │ ▼
│ └──────────────┘ ┌──────────────┐
└─────────────────────── │ CheckedOut │
└──────────────┘
│ close (Night Audit)
┌──────────────┐
│ Closed │
└──────────────┘

États terminaux : Closed, Cancelled, NoShow. Walked est un état intermédiaire (séjour débuté mais le client est parti sans payer — action UI absente en V1).

TransitionDéclencheurInvariants
createCreateReservationUseCasedispo ≥ quantité demandée (advisory xact lock)
confirmConfirmReservationOrchestratorSnapshotRate calculé et figé (immuable après) ; DepositPolicy snapshotée
check-inCheckInOrchestratorStay créé atomiquement (même TX que le Folio) ; unité physique assignée ; pre-assignment consommé si existant (consumedAt persisté)
check-outCheckOutOrchestratorStay clôturé ; unité libérée (I3)
cancelDirect ou via saga TemporalRate plan snapshoté à Confirmed utilisé pour calculer l’éventuelle pénalité
markNoShowNight Audit ou manuelArrhes acquises si depositNature = 'arrhes' (MandateFiscalProfile)
closeNight Audit (après check-out + solde folio)Grand total NF203 mis à jour

Chaque transition publie une entrée dans pms.reservation_versions (GRANT INSERT only) :

reservation_versions {
id, reservation_id, tenant_id,
version_number, -- séquence croissante
state, -- état après transition
actor, -- userId ou SYSTEM
occurred_at, -- timestamp serveur
payload, -- snapshot complet de l'agrégat (JSON)
hmac -- signature NF203
}

L’API expose la timeline via GET /v1/reservations/:id/versions (ADR-G5), avec cancellationPolicySnapshot inclus dans le payload.

À la confirmation, le tarif est calculé nuit par nuit et figé dans SnapshotRate :

  • Le tarif utilisé pour la facturation est toujours celui de la confirmation
  • Aucune variation de RatePlan postérieure ne modifie la réservation confirmée
  • Le modify d’une réservation confirmée recalcule et refige un nouveau snapshot

La pré-assignation est optionnelle et n’engage pas le stock par type (inventory_daily reste inchangé). Elle représente le souhait du réceptionniste d’affecter une unité physique spécifique sans verrouiller la dispo globale.

Règles :

  • EXCLUDE GiST [check_in_date, check_out_date) fait foi pour l’unicité (erreur Postgres 23P01 si doublon)
  • La garde mémoire de l’agrégat (preAssignments[]) est best-effort UX uniquement
  • Consommation atomique au check-in (même TX que la transition)
  • Cancel et no-show libèrent les pre-assignations actives

La CancellationPolicy est snapshotée depuis le RatePlan à la confirmation. Lors d’un cancel :

  1. La politique snapshotée est appliquée (jamais la politique actuelle du rate plan)
  2. Si pénalité : une ligne chargeNature = 'penalty' est postée au folio
  3. La ligne penalty est exclue du void-all (DEFAULT_EXCLUDE = ['penalty']) — trace de pénalité inaltérable

Une réservation peut inclure plusieurs types d’unités et quantités (units: [{ unitTypeId, quantity }]). La dispo est vérifiée pour chaque type indépendamment sous advisory xact lock.

Sous-module reservation/long-stay/ : contrats saisonniers avec échéancier de paiement. Les installments sont postés automatiquement par le Night Audit (postDueInstallments, ADR-0019).

La réservation camping porte un objet occupancy (adults, childrenAges, pets, vehicles, electricity) qui alimente le calcul tarifaire per_person des rate plans camping.