Aller au contenu

Night Audit

Le Night Audit est le processus de clôture comptable journalière de l’établissement. Il fait avancer la businessDate (date métier), pose les charges automatiques et prépare le reporting du lendemain.

Il s’exécute à 3h00 heure locale via un Temporal Schedule créé par établissement au démarrage du worker night-audit. Si un établissement est créé après le démarrage du worker, ensureAllSchedules recréé les schedules manquants.

La businessDate est distincte du temps système (new Date()). Elle représente la date commerciale de l’établissement :

  • Entre minuit et 3h00 (Night Audit), on est encore sur J — les opérations de la nuit appartiennent à J
  • Après le Night Audit, on passe à J+1

La businessDate est propriété de l’établissement, stockée dans pms.establishment_business_dates. Elle est avancée par le Night Audit (étape 6 ci-dessous) de manière idempotente avec advisory xact lock.

Le workflow Temporal night-audit exécute ces étapes séquentiellement :

ÉtapeActivityDescription
0verrouAdvisory xact lock par établissement (empêche le double run)
1markNoShowsMarque en no-show les réservations Confirmed dont checkInDate = businessDate courante
2postRoomChargesPoste les charges de séjour (revenue) pour toutes les Stay en cours
3postTourismTaxPoste la taxe de séjour (collected_tax, compte 4471) sur les folios
4postMealPlanChargesPoste les plans restauration du jour
5postDueInstallmentsPoste les échéances de contrats long-stay arrivées à échéance (ADR-0019)
6avanceBusinessDateAvance la businessDate de J à J+1 (idempotent, advisory xact lock)
6cancrage Merkle WORMCalcule la racine Merkle de la chaîne d’audit, écrit en stockage objet WORM
7snapshot reportingPublie l’event NightAuditCompleted → consumers reporting projettent les KPIs

La garde FOLIO_CHARGE_RETROACTIVE empêche de poster une charge sur une businessDate antérieure à la businessDate courante de l’établissement. Une charge sur une date passée fermée = incohérence comptable.

Seules les réservations dont checkInDate = businessDate_courante et état Confirmed sont marquées no-show. Les réservations futures restent Confirmed.

Si le depositNature = 'arrhes' (MandateFiscalProfile), les arrhes sont acquises au moment du no-show.

La taxe est calculée depuis le barème compliance-tax (commune, classement, période). Elle est postée comme collected_tax (hors TVA, hors recette). Le reporting lit NightAuditCompleted pour la restituer — la taxe de séjour n’est jamais recalculée dans le reporting (lecture from the event).

À l’étape 6c, la chaîne d’audit est ancrée de manière permanente :

  1. Calcul de la racine Merkle de toutes les entrées d’audit depuis le dernier ancrage
  2. Écriture de la racine dans MinIO avec Object Lock COMPLIANCE (durée de rétention : 10 ans)
  3. La clé d’objet est déterministe (audit/<tenantId>/<businessDate>) pour garantir l’idempotence en cas de retry
  4. En cas d’échec PUT WORM → le workflow Temporal relance l’étape (retryable)

La vérification de la chaîne est exposée via GET /admin/audit/verification (console admin, pms_control_plane).

L’event publié à la fin du Night Audit (NATS JetStream) :

NightAuditCompleted {
tenantId : string (UUID)
establishmentId : string (UUID)
businessDate : string (YYYY-MM-DD) // date qui VIENT D'ÊTRE CLÔTURÉE
roomsOccupied : int32
roomsAvailable : int32 // approché — sans soustraction OOO/OOS (résiduel A)
revenueCents : int64 // Σ charges revenue du jour
tourismTaxCents : int64 // Σ collected_tax du jour
occurredAt : timestamp
}

3 consumers NATS le consomment pour projeter les KPIs dans pms.reporting_*.

Si le Night Audit d’un établissement n’a pas tourné pour des raisons techniques, un script de réconciliation reconcile-night-audit.ts permet de le relancer manuellement sur une businessDate spécifique.

Le Night Audit a été exécuté en run réel (Programme A). Les tests d’intégration utilisent des services réels (Postgres + Temporal), pas de mocks.

Les fronts consomment la businessDate uniquement depuis l’API :

  • GET /v1/me retourne la businessDate de l’établissement courant
  • EstablishmentContextService initialise la businessDate au démarrage (APP_INITIALIZER)
  • null = établissement non initialisé → message d’avertissement, pas de fallback sur new Date()

Cela signifie qu’un front peut afficher une businessDate différente de la date système (entre minuit et 3h00 pendant le Night Audit).