My App
Données

Entités métier

Les concepts centraux de Bell et leurs relations — guest, room, booking, conversation, ticket, integration

Avant de regarder le schéma Drizzle, lis ici. Cette page décrit le quoi (les concepts), la page database-schema décrit le comment (les colonnes).

Vue d'ensemble

Bell est multi-tenant : une organization = un hôtel. Toutes les entités métier sont scopées par organizationId. Un user peut être membre de plusieurs orgs (staff multi-hôtel) mais un guest appartient à une org (on ne partage pas les profils guests entre hôtels).

Les 10 entités principales

1. organization

C'est : un hôtel.

Ce qui la définit : nom, slug (URL-friendly), metadata (adresse, logo, fuseau horaire, devise), timestamp de création.

Pourquoi multi-tenant : Bell vise à terme une gamme d'hôtels. Toutes les requêtes sont scopées par org, les joins cross-org sont impossibles via les procedures tRPC/Eden.

Géré par : Better Auth organization plugin.

2. user

C'est : un humain qui se connecte à Bell, côté staff ou côté guest.

Ce qui le définit : email (unique), nom, mot de passe hashé, timestamp création.

Rôles : via la table member ci-dessous — un user peut avoir différents rôles dans différentes orgs.

Géré par : Better Auth.

3. member (user × organization)

C'est : le lien entre un user et une org, avec un rôle.

Rôles :

RoleQuiPeut
ownerfondateur HOAIY, CEO hôteltout, y compris facturation et désactiver l'org
adminGM, head of stafftout sauf la facturation — configure les intégrations, invite des staff
managerchef de service, night auditorops quotidiennes : cardex, chats, analytics
staffréceptionniste, serveurcardex read + write sur son périmètre, chats
guestinvité de l'hôtelPWA guest : commandes, chat, voir son profil

Les 4 premiers sont "staff" côté API (voir staffProcedure dans le middleware).

4. guest

C'est : un profil d'invité dans le cardex d'un hôtel. Existe avant que le guest ne crée son compte user.

Flow :

Colonnes clés : organizationId, roomId?, userId? (null tant qu'il n'a pas signup), checkInStatus (pgEnum — cf. state machines), externalGuestId (ID côté PMS, null si hôtel sans PMS), externalSource (mews, opera, …), externalChannel (booking.com, expedia, direct, … — déduit de Mews).

5. guest_group

C'est : un groupe corporatif ou événementiel (mariage, conférence). Contient plusieurs guest.

Utile pour : bulk actions, contact person unique pour toute la réservation de groupe.

6. room

C'est : une chambre physique de l'hôtel.

Ce qui la définit : numéro, étage, type (single, double, suite, deluxe, penthouse), status (pgEnum : available, occupied, cleaning, maintenance, reserved), prix/nuit, amenities (JSONB : wifi, vue, jacuzzi…), externalRoomId (sync PMS).

Lien bidirectionnel PMS : quand Mews dit "room 302 = Dirty", on set status cleaning. Quand on termine un checkout, on push vers Mews setRoomStatus(302, dirty).

7. conversation + message

C'est : une conversation entre un guest et Bell. Un seul modèle unifié (vs ancien projet qui avait deux systèmes dupliqués — voir ADR futur merger).

Audiences (pgEnum) :

  • guest_ai : guest discute avec l'IA concierge (par défaut)
  • guest_staff : escalade à un humain après que l'IA a passé la main
  • staff_internal : note interne entre staff sur un guest (pas visible par le guest)

Messages :

  • role : user (le guest), assistant (AI), staff, system
  • content : texte (MD supporté)
  • metadata (JSONB typé) : tool calls si AI, AI cost, hotel tags

8. restaurant_booking / room_service_order / laundry_order / spa_booking

C'est : les 4 types de commandes/réservations qu'un guest peut faire.

Communs :

  • userId, organizationId, guestId
  • status (pgEnum propre à chaque type — voir state machines)
  • paymentIntentId (Stripe), paymentStatus
  • totalAmount, currency
  • Timestamps du cycle de vie (deliveredAt, completedAt, etc.)

Spécificités :

TypeChamps propres
restaurant_bookingpeopleCount, date, time, specialRequest
room_service_orderitems (JSONB array), deliveryFee, deliveryAddress
laundry_orderitems (JSONB array), specialRequest, collection/delivery times
spa_bookingserviceId, duration, date, time, therapistId?

9. ticket

C'est : une escalade créée par le staff depuis un chat.

Ce qui la définit : ticketNumber humain (TKT-00042), catégorie, priorité, status (openin_progresswaitingresolvedclosed), lien optionnel vers conversationId.

Utilité : un chat se résout en 90 % des cas par le staff directement. Les 10 % restants (plainte, demande spéciale, incident) deviennent des tickets que l'admin voit dans /admin/tickets.

Commentaires : ticket_comment — audit trail des actions staff.

10. integration

C'est : la config d'un PMS (ou OTA futur) connecté à une org.

Ce qui la définit :

  • provider (pgEnum : mews, opera, cloudbeds, booking, apaleo, …)
  • name (display : "Mews PMS - Oceania Paris")
  • credentials (JSONB chiffré : tokens, keys)
  • platformUrl (endpoint API du provider)
  • externalPropertyId (ID de l'hôtel chez le provider)
  • config (JSONB : options spécifiques)
  • isActive, lastSyncAt, syncStatus (idle / syncing / error)
  • syncError (message d'erreur si le dernier sync a échoué)
  • webhookSecret (pour valider les signatures HMAC des webhooks entrants)

Un hôtel = 0 ou 1 integration par provider. Peut avoir plusieurs providers simultanés si pertinent (ex: Mews pour PMS + Booking API directe pour du channel management avancé — rare).

Log : integration_sync_log trace chaque action sync pour debug.

Entités secondaires (résumé)

EntitéRôle
staff_notenotes internes du staff (cardex, today) avec priorité, assignation
note_commentaudit trail des notes
menu_category + menu_itemcatalogue des services proposés (restaurant menu, spa soins, laundry items)
activity_logjournal d'activité métier (booking_created, chat_started, payment_received…)
interaction_log (hypertable TimescaleDB future)logs analytics fine-grained
hotel_eventévénements internes hôtel (conférences, mariages) — affichage calendar
invitation (Better Auth)invitations staff pending
session, account, verificationtables Better Auth standard

Règles d'intégrité

Cascade deletes

  • organization supprimée → tout ce qui lui appartient supprimé (guest, room, bookings, …)
  • user supprimé → member cascade, mais les guests et bookings restent (pour compliance comptable/RGPD, droit à l'oubli géré séparément)
  • guest supprimé (soft delete) → bookings restent, guest.deletedAt posé

Contraintes

  • guest.email unique par organizationId (pas par global — un même email peut être guest de 2 hôtels)
  • room.roomNumber unique par organizationId
  • integration unique par (organizationId, provider) — 1 Mews par org max
  • user.email unique global (Better Auth)

Soft delete

Les entités métier critiques ont deletedAt: timestamp | null. Un soft delete preserve les FK et l'audit. Purge physique après X mois (configurable, voir RGPD).

Entités concernées : guest, booking, conversation, ticket, staff_note.

Entités sans soft delete (purge physique OK) : session, activity_log, integration_sync_log.

Canaux de réservation (channel tracking)

La feature différenciante Bell : détecter qu'un guest vient de Booking.com, Expedia, Airbnb, etc. sans intégration OTA directe, via le CommanderOrigin que Mews remonte dans chaque réservation.

Mapping dans packages/api/src/services/integrations/adapters/mews.ts, fonction inferChannel() :

Mews CommanderOrigin → externalChannel
  "Booking.com"         → "booking"
  "Expedia"             → "expedia"
  "Airbnb"              → "airbnb"
  "HRS"                 → "hrs"
  "Hotelbeds"           → "hotelbeds"
  "Agoda"               → "agoda"
  "Direct" / null       → "direct"
  (autre)               → "other"

Stocké sur guest.externalChannel. Permet :

  • Badge coloré dans le cardex (revenue manager voit instantanément la répartition)
  • Analytics (revenue par channel, conversion, upsells par canal)
  • Sans intégrer aucune OTA nous-mêmes — Mews agrège pour nous

Multi-devise, multi-langue (futur)

  • Multi-devise : currency sur chaque booking + organization.defaultCurrency. Pas de conversion multi-devise au MVP — on affiche dans la devise du booking.
  • Multi-langue : user.locale + templates email multi-langues. Fallback FR. Pas MVP.

Prochaine étape

On this page