Folio NF203
Définition
Section intitulée « Définition »Le folio est le compte courant de séjour du client — l’équivalent de la note d’hôtel. C’est un agrégat append-only : on n’y efface rien, on n’y modifie rien.
Il existe trois kind de folio :
stay— folio standard associé à unStaymaster_event— folio d’événement MICE (masterFolio = folio ordinaire, ADR-D24)reservation_deposit— folio d’acompte/arrhes préalable à l’arrivée
Entrées de folio (FolioEntry)
Section intitulée « Entrées de folio (FolioEntry) »Chaque opération produit une ligne immuable dans pms.folio_entries :
chargeNature | Compte | Description |
|---|---|---|
revenue | 706xxx | Recette d’hébergement ou prestation |
disbursement | 625xxx | Débours refacturé |
collected_tax | 4471 | Taxe de séjour (hors recette, hors TVA) |
penalty | 706xxx | Pénalité d’annulation (exclue du void-all) |
Chaque chargeType porte en plus : amountHt, taxRate, taxAmount, description, businessDate, idempotencyKey.
Règle d’or : void = contre-écriture (Invariant I1)
Section intitulée « Règle d’or : void = contre-écriture (Invariant I1) »Il n’existe pas de DELETE sur un folio entry. Pour annuler une ligne, on poste une contre-écriture de signe opposé :
Ligne originale : +12 500 centimes HT (charge mini-bar)Contre-écriture : -12 500 centimes HT (void de la charge)Solde net visible : 0 (mais les deux lignes restent dans le journal)Le void-all annule toutes les lignes sauf celles de chargeNature = 'penalty' (DEFAULT_EXCLUDE = ['penalty']).
businessDate serveur-autoritaire (ADR-00D78)
Section intitulée « businessDate serveur-autoritaire (ADR-00D78) »La businessDate d’une entrée de folio est toujours résolue côté serveur :
operatingMandateId → MandateEstablishmentReader → BusinessDateProviderLe serveur connaît la businessDate courante de l’établissement (avancée par le Night Audit). Entre minuit et le Night Audit, on est encore sur J.
Les fronts ne doivent jamais envoyer new Date() comme businessDate — la valeur null signifie “NA non initialisé” et affiche un message d’avertissement, jamais un fallback silencieux.
La garde FOLIO_BUSINESS_DATE_UNRESOLVABLE fait échouer (Result.fail) si la businessDate ne peut pas être résolue.
Taxe de séjour
Section intitulée « Taxe de séjour »La taxe de séjour est une ligne chargeNature = 'collected_tax' (compte 4471). Elle est :
- Hors recette (ne rentre pas dans le CA)
- Hors base de calcul TVA (elle n’est pas soumise à TVA)
- Postée par le Night Audit via
postTourismTax - Restitution dans le reporting (
NightAuditCompleted→ consumer reporting)
Settlement (règlement)
Section intitulée « Settlement (règlement) »Un règlement poste une entrée de type payment :
method: cash/card/transfer/onlineamount:Moneybigint centimespaymentReference: référence PSP opaque (jamais de numéro de carte — PCI SAQ A)
Le solde du folio = Σ charges (HT) - Σ règlements. Un folio est solde-nul avant de pouvoir être clos.
Fermeture du folio
Section intitulée « Fermeture du folio »Un folio clos (status = 'closed') :
- Ne peut plus recevoir de nouvelles charges ou règlements
- Le
void-allest désactivé en UI et en store (double couche, NF203) - Les lignes voidées restent visibles (barrées dans l’UI)
Facture d’acompte (ADR-00D73)
Section intitulée « Facture d’acompte (ADR-00D73) »Deux natures fiscales distinctes selon MandateFiscalProfile.depositNature :
| Nature | Base légale | TVA | Comptabilité |
|---|---|---|---|
acompte | CGI 269-2-c | TVA à l’encaissement | CRÉDIT 419 + 4457x (jamais 706 immédiat) |
arrhes | Art. 1590 Cc | Hors champ | Aucune TVA encaissée |
La facture finale déduit l’acompte (anti-double TVA). MandateFiscalProfile est fail-closed zéro-défaut : si le profil est absent, les opérations d’encaissement échouent.
Périmètre fiscal = operating_mandate_id
Section intitulée « Périmètre fiscal = operating_mandate_id »Le grand total NF203 (numérotation séquentielle des factures, chaîne HMAC) est par mandat fiscal (operating_mandate_id), pas par établissement. Si un changement de gestionnaire intervient, le grand total et la chaîne repartent de zéro sur le nouveau mandat.
Cette règle n’est pas vérifiée par une fitness function. Elle doit être contrôlée manuellement sur tout nouveau use-case impliquant un folio ou une facture.
Money — bigint centimes
Section intitulée « Money — bigint centimes »Tous les montants sont des bigint centimes côté backend. Côté API et fronts :
- L’API expose
amountCents: string(jamais unnumber— les int64 JavaScript ne peuvent pas représenter tous les centimes d’une grande facture fidèlement) - Les fronts utilisent
eurosToCents(str)BigInt-exact (pas deparseFloat) - L’affichage utilise
Intl.NumberFormat— jamais de division flottante
// Correctconst cents = BigInt(response.amountCents); // string → bigint
// Faux (perte de précision possible sur grands montants)const cents = Number(response.amountCents);Tables inaltérables (GRANT)
Section intitulée « Tables inaltérables (GRANT) »pms_app n’a que SELECT + INSERT sur :
pms.folio_entriespms.invoice*
UPDATE(status) uniquement pour la clôture du folio (status : open → closed). Aucun DELETE.