Aller au contenu principal

Système de Réservation Publique (Public Booking System)

Vue d'ensemble

Le système de réservation publique permet aux clients et prospects de réserver des créneaux de disponibilité directement via une interface publique, sans authentification. Les réservations créent des événements Google Calendar avec le préfixe [MEETING] et envoient des notifications par email.

Architecture

Backend

Endpoints publics

  • GET /api/public/booking/:userId/info - Informations publiques du User (nom, logo, entreprise)
  • GET /api/public/booking/:userId/availability - Liste des créneaux disponibles pour un User
  • POST /api/public/booking/:userId/book - Créer une réservation
  • POST /api/public/booking/cancel/:token - Annuler une réservation via token

Services

  • BookingController (backend/src/booking/booking.controller.ts)

    • Gère les réservations publiques
    • Crée des événements Google Calendar (pas de sessions)
    • Gère les tokens d'annulation
    • Envoie des emails de confirmation et de notification
  • BookingCancelTokensService (backend/src/booking/booking-cancel-tokens.service.ts)

    • Génère et valide les tokens d'annulation
    • Stocke les tokens avec expiration (30 jours par défaut)
    • Utilise event_id pour lier les tokens aux événements

Base de données

  • Table booking_cancel_tokens :
    • id (UUID)
    • event_id (TEXT) - ID de l'événement Google Calendar
    • token (TEXT) - Token encrypté
    • expires_at (TIMESTAMPTZ)
    • used_at (TIMESTAMPTZ, nullable)
    • created_at, updated_at

Frontend

Pages publiques

  • BookingPage (frontend/src/pages/booking/BookingPage/BookingPage.tsx)

    • Page publique de réservation
    • Affiche le calendrier des créneaux disponibles
    • Formulaire de réservation avec validation
    • Page de confirmation après réservation
  • BookingCancelPage (frontend/src/pages/booking/BookingCancelPage/BookingCancelPage.tsx)

    • Page publique d'annulation
    • Utilise un token sécurisé pour annuler une réservation

Composants

  • BookingCalendar - Affiche les créneaux disponibles par semaine
  • BookingForm - Formulaire de réservation (nom, email, téléphone, type de rendez-vous, notes)
  • BookingConfirmation - Page de confirmation après réservation

Hooks React Query

  • useBookingAvailability - Récupère les créneaux disponibles
  • useBookingUserInfo - Récupère les informations publiques du User
  • useCreateBooking - Crée une réservation (invalide automatiquement les queries d'availability)
  • useCancelBooking - Annule une réservation

Flux de données

Création d'une réservation

graph TD
A[Client sélectionne un créneau] --> B[Client remplit le formulaire]
B --> C[POST /api/public/booking/:userId/book]
C --> D{Vérification disponibilité}
D -->|Non disponible| E[Erreur: Créneau déjà réservé]
D -->|Disponible| F[Création/Recherche Contact]
F --> G[Création événement Google Calendar avec préfixe MEETING]
G --> H[Création token d'annulation]
H --> I[Email confirmation au client]
H --> J[Email notification au User]
I --> K[Page de confirmation]
J --> L[Événement visible dans calendrier User]

Annulation d'une réservation

graph TD
A[Client clique sur lien d'annulation] --> B[POST /api/public/booking/cancel/:token]
B --> C[Validation token]
C -->|Token invalide| D[Erreur]
C -->|Token valide| E[Recherche événement dans calendrier User]
E --> F[Suppression événement Google Calendar]
F --> G[Marquage token comme utilisé]
G --> H[Email notification au User]
H --> I[Confirmation d'annulation]

Fonctionnalités

Filtrage des créneaux disponibles

  • Les créneaux récurrents sont expansés en occurrences individuelles
  • Les créneaux qui chevauchent avec des sessions existantes sont exclus
  • Les créneaux qui chevauchent avec des événements de booking (avec bookingType: "public") sont exclus
  • Seuls les créneaux de type AVAILABLE sont retournés

Cache temporaire pour réservations récentes

Pour gérer le délai de propagation de l'API Google Calendar, un cache temporaire en mémoire est utilisé :

  • Cache en mémoire : recentBookingsCache dans BookingController
  • Durée de vie : 5 minutes par entrée
  • Utilisation : Les réservations récentes sont ajoutées au cache immédiatement après création
  • Filtrage : Le cache est consulté lors de getAvailability pour exclure immédiatement les créneaux réservés
  • Nettoyage automatique : Les entrées de plus de 5 minutes sont supprimées automatiquement

Cela garantit que même si l'API Google Calendar n'a pas encore propagé l'événement, le créneau réservé n'apparaît plus dans la liste des disponibilités.

Gestion des événements

  • Les réservations créent uniquement des événements Google Calendar (pas de sessions)
  • Les événements sont préfixés avec [MEETING] pour identification
  • Les événements contiennent userId et bookingType: "public" dans extendedProperties
  • Les événements peuvent être édités et supprimés depuis le calendrier User

Emails

  • Email au client : Confirmation avec lien d'annulation
  • Email au User : Notification de nouvelle réservation (avec détails du client)
  • Email au User : Notification d'annulation (avec détails du rendez-vous annulé)

Notifications

Le système envoie des notifications in-app et push au User lors des événements de booking :

  • Notification in-app : Créée via NotificationsService.createForUser()

    • Type BOOKING_CREATED pour les nouvelles réservations
    • Type BOOKING_CANCELLED pour les annulations
    • Inclut les métadonnées (eventId, type) pour navigation
    • Émise via WebSocket en temps réel
  • Notification push : Envoyée via PushNotificationsService.sendNotification()

    • Notification navigateur même si l'application est fermée
    • Inclut les données pour navigation (URL, eventId)
    • Tag unique pour éviter les doublons

Les notifications sont envoyées de manière non-bloquante avec gestion d'erreurs appropriée.

Sécurité

  • Tokens d'annulation encryptés
  • Tokens avec expiration (30 jours par défaut)
  • Tokens marqués comme utilisés après annulation
  • Validation de disponibilité avant création de réservation

Patterns et conventions

Préfixe des événements

Tous les événements créés via le système de réservation publique sont préfixés avec [MEETING] :

const eventTitle = `[MEETING] ${
data.title || `Rendez-vous avec ${data.client_name}`
}`;

ExtendedProperties

Les événements de booking contiennent dans extendedProperties.private :

{
userId: string; // ID du User propriétaire
bookingType: "public"; // Identifie les réservations publiques
}

Invalidation des queries

Après une réservation réussie, les queries d'availability sont automatiquement invalidées pour rafraîchir l'affichage :

onSuccess: (_data, variables) => {
// Invalidate availability queries for this user to refresh the available slots
queryClient.invalidateQueries({
exact: false,
queryKey: [BOOKING_QUERY_KEY, variables.userId, "availability"],
});
// Also remove the query from cache to force a fresh fetch
queryClient.removeQueries({
exact: false,
queryKey: [BOOKING_QUERY_KEY, variables.userId, "availability"],
});
};

Cette double invalidation (invalidate + remove) garantit un refetch complet des données, même si le cache React Query contient des données obsolètes.

Interface de gestion des bookings

Backend

Endpoints protégés

  • GET /api/bookings - Liste les bookings de l'utilisateur authentifié

    • Query params: timeMin, timeMax (optionnels)
    • Retourne un tableau de bookings avec détails (eventId, dates, client, location, etc.)
  • GET /api/bookings/stats - Statistiques des bookings

    • Query params: timeMin, timeMax (optionnels)
    • Retourne: { totalBookings, upcomingBookings, pastBookings }
  • PATCH /api/bookings/:eventId - Met à jour un booking

    • Body: { title?, startDate?, endDate?, location?, description?, addGoogleMeet?, attendeeEmails? }
    • Retourne: { message: string, success: boolean }
    • Vérifie que l'événement existe et est un booking public
    • Utilise GoogleCalendarService.updateEvent() pour la mise à jour
  • DELETE /api/bookings/:eventId - Supprime un booking

    • Retourne: { message: string }
    • Vérifie que l'événement existe et est un booking public
    • Supprime l'événement Google Calendar

Services

  • BookingsController (backend/src/booking/bookings.controller.ts)

    • Controller protégé (nécessite authentification)
    • Utilise GoogleCalendarService.getBookingEvents() pour récupérer les événements avec bookingType: "public"
  • GoogleCalendarService.getBookingEvents()

    • Filtre les événements Google Calendar par bookingType: "public" dans extendedProperties
    • Supporte le filtrage par plage de dates

Frontend

Composants

  • BookingsPanel (frontend/src/pages/calendar/CalendarPage/components/BookingsPanel.tsx)

    • Affiche la liste des bookings du mois en cours
    • Affiche les statistiques (total, upcoming, past)
    • Permet de cliquer sur un booking pour voir les détails
    • Tri automatique par date (upcoming first)
  • BookingDetailsDialog (frontend/src/pages/calendar/CalendarPage/components/BookingDetailsDialog.tsx)

    • Dialog modal avec détails complets d'un booking
    • Affiche: date/heure, client (email), location (téléphone ou Google Meet), notes
    • Mode édition : Formulaire pour modifier le booking (titre, dates, participants, Google Meet, notes)
    • Actions :
      • Contact : Si le contact existe dans la base, navigation vers /contacts/:id, sinon ouverture du client email
      • Join Call : Ouvre le lien Google Meet dans un nouvel onglet
      • Edit : Active le mode édition avec formulaire complet
      • Delete : Supprime le booking avec confirmation

Hooks React Query

  • useBookings(options) - Récupère la liste des bookings

    • Options: timeMin, timeMax, enabled
    • Invalide automatiquement après mutations
  • useBookingStats(options) - Récupère les statistiques

    • Options: timeMin, timeMax, enabled
  • useUpdateBooking(options) - Met à jour un booking

    • Options: onSuccess, onError
    • Invalide automatiquement les queries de bookings et d'événements après mise à jour
  • useDeleteBooking(options) - Supprime un booking

    • Options: onSuccess, onError
    • Invalide automatiquement les queries de bookings et d'événements après suppression

Intégration dans CalendarPage

  • Onglets pour basculer entre "Availability" et "Bookings"
  • Le panel Bookings s'affiche dans la colonne latérale droite
  • Filtrage automatique par mois en cours

Limitations connues

  1. Gestion des conflits : Résolu avec verrouillage distribué Redis. Voir BOOKING_IMPROVEMENTS.md.
  2. Cache en mémoire : Le cache temporaire des réservations récentes est en mémoire et ne persiste pas entre redémarrages du serveur. C'est acceptable car le cache sert uniquement à gérer le délai de propagation de Google Calendar (5 minutes).
  3. Statistiques : Complété avec graphiques, export (CSV, Excel, JSON), et filtres avancés. Voir BOOKING_IMPROVEMENTS.md.
  4. Filtrage limité : Filtrage uniquement par plage de dates (pas de filtres avancés par client, statut, etc.). Le panel Bookings affiche les bookings de 3 mois passés à 1 an futur.
  5. Gestion des timezones : Résolu avec détection automatique et conversion. Voir BOOKING_IMPROVEMENTS.md.

Améliorations futures

Voir BOOKING_IMPROVEMENTS.md pour la liste complète des améliorations prévues avec leur état d'avancement.

Résumé rapide

Fonctionnalités complétées : 10/10 (100%) ✅

  • ✅ Système de réservation publique
  • ✅ Documentation OpenAPI
  • ✅ Tests unitaires (142 tests)
  • ✅ Interface de gestion des bookings
  • ✅ Notifications push
  • ✅ Gestion des conflits (verrouillage Redis)
  • ✅ Statistiques et analytics (avec export)
  • ✅ Personnalisation de la page de booking
  • ✅ Gestion des timezones
  • ✅ Améliorations UX

Améliorations techniques complétées :

  • ✅ Tests unitaires pour ConflictDetectionHelper (26 tests)
  • ✅ Tests unitaires pour CacheService (17 tests)
  • ✅ Tests unitaires pour RecurringSlotExpander (14 tests)
  • ✅ Tests unitaires pour SlotFilteringHelper (18 tests)
  • ✅ Tests unitaires pour RecentBookingsCacheService (20 tests)

Prochaines étapes optionnelles :

  • Tests d'intégration pour les conflits de réservation
  • Tests de scénarios concurrents