Aller au contenu

Story CP-010 : Service Backend de Notifications + Templates

Module : control-plane Slice : Slice 10 from architecture Brand context : [BOTH] Papillon HR Suite + ALTARYS ENTERPRISE HR Suite Priority : 10 Depends on : CP-001, CP-002, CP-003 Estimated complexity : M


Objectif

Construire le service backend de notifications partagé utilisé par TOUS les modules : notifications in-app (via WebSocket STOMP), livraison d'emails et rendu de templates. Seeder les 13 templates email MVP. L'UI de notifications (cloche, dropdown, compteur non lus) est gérée dans CP-027.


Périmètre Backend

Entités & Records

Entité Notification (base tenant) :

Champ Type Contraintes
id UUID v7 PK
tenant_id UUID NOT NULL
recipient_user_id UUID NOT NULL
template_code String NOT NULL
channel NotificationChannel enum NOT NULL
priority NotificationPriority enum NOT NULL
title String NOT NULL
body String NOT NULL
is_read boolean NOT NULL, défaut false
metadata JSONB Nullable (contexte supplémentaire)
created_date Instant NOT NULL

NotificationChannel : IN_APP, EMAIL, BOTH NotificationPriority : LOW, NORMAL, HIGH, URGENT

Entité NotificationTemplate (base plateforme) :

Champ Type Contraintes
id UUID v7 PK
code String NOT NULL, unique
channel NotificationChannel NOT NULL
subject_template String NOT NULL (pour email)
body_template String NOT NULL (Mustache/Handlebars)
priority NotificationPriority NOT NULL

Templates MVP (depuis PRD FR-CP-091) :

Code Déclencheur Canal
ONBOARDING_WELCOME_A Vérification email Email
ONBOARDING_WELCOME_B_PASSWORD Canal B : lien mot de passe Email
ONBOARDING_WELCOME_B_PAYMENT Canal B : lien paiement Email
EMAIL_VERIFICATION Renvoi de vérification Email
PASSWORD_RESET Récupération de mot de passe Email
INVOICE_GENERATED Facture mensuelle Email
PAYMENT_RECEIVED Paiement confirmé Email + In-App
PAYMENT_FAILED Paiement échoué Email + In-App
SUBSCRIPTION_EXPIRING 30/15/7 jours avant expiration Email + In-App
ACCOUNT_SUSPENDED Tenant suspendu Email
ACCOUNT_REACTIVATED Tenant réactivé Email + In-App
USER_INVITATION Utilisateur invité Email
MISSING_LEGAL_INFO Rappel RCCM/NIF Email + In-App

Couche Repository

  • NotificationRepository.java — Tenant-aware : findByRecipientUserId(Pageable), countUnreadByRecipientUserId, markAsRead, markAllAsRead
  • NotificationTemplateRepository.java — Platform-scoped : findByCode

Couche Service

NotificationService.java (partagé — consommé par tous les modules) : - send(UUID tenantId, UUID recipientUserId, NotificationChannel channel, String templateCode, Map<String,Object> params, NotificationPriority priority) → void 1. Résoudre le template par code 2. Rendre le template avec les paramètres (Mustache/Handlebars) 3. SI le canal inclut IN_APP : persister Notification + diffuser via WebSocket 4. SI le canal inclut EMAIL : mettre l'email en file d'attente pour livraison asynchrone - getNotifications(UUID userId, UUID tenantId, Pageable) → Page<NotificationResponse> - getUnreadCount(UUID userId, UUID tenantId) → int - markAsRead(UUID notificationId) → void - markAllAsRead(UUID userId, UUID tenantId) → void

EmailService.java : - Livraison d'email asynchrone via fournisseur (Brevo/Mailjet/SES — pattern adaptateur fournisseur) - Templates email à l'image de Papillon (français, avec logo papillon) - Relance : 3 tentatives avec backoff exponentiel

TemplateRenderer.java : - Rendu de templates Mustache/Handlebars - Supporte : {{fullName}}, {{companyName}}, {{amount}}, {{date}}, {{link}}, etc.

Configuration WebSocket : - WebSocketConfig.java — Configuration STOMP over SockJS - Topics spécifiques à l'utilisateur : /user/queue/notifications - Tenant-scoped : les notifications sont uniquement livrées aux utilisateurs du même tenant - Fallback SockJS pour les réseaux proxy africains qui peuvent bloquer WebSocket

NotificationBroadcaster.java : - Écoute les nouvelles notifications in-app - Envoie aux clients WebSocket connectés - Met en file d'attente pour les utilisateurs hors ligne (livré à la prochaine connexion)

Endpoints API

Méthode Chemin Corps de requête Réponse Auth
GET /api/v1/notifications ?page=&size= Page Tout utilisateur authentifié
GET /api/v1/notifications/unread-count {count} Tout utilisateur authentifié
PATCH /api/v1/notifications/{id}/read 204 Tout utilisateur authentifié
PATCH /api/v1/notifications/read-all 204 Tout utilisateur authentifié
POST /api/v1/notifications API interne (service-to-service) 204 Internal

Considérations multi-tenant

  • Notifications dans la base tenant (tenant-scoped)
  • Templates dans la base plateforme (partagés entre les tenants)
  • WebSocket : la destination utilisateur STOMP garantit que les notifications sont envoyées au bon utilisateur/tenant
  • Aucune livraison de notification inter-tenant possible

Migrations Flyway

  • V005__create_notification_templates.sql — Base plateforme : table notification_templates + seed des 13 templates MVP
  • V009__create_notification.sql — Base tenant : table notifications

Périmètre Frontend

Aucun pour cette story. La cloche de notifications, le dropdown et le compteur non lus sont dans CP-027.


Critères d'acceptation

AC-107: Send in-app notification
Given user "Moussa" is logged in
When a notification is sent via NotificationService.send()
Then Moussa sees the notification in his bell dropdown in real-time (WebSocket)

AC-108: Send email notification
Given the PAYMENT_RECEIVED template exists
When a payment is confirmed
Then an email is sent to the tenant admin with correct template rendering

AC-112: Template rendering
Given the template "Bonjour {{fullName}}, votre paiement de {{amount}} FCFA a été confirmé."
When rendered with {fullName: "Moussa", amount: "15 000"}
Then the output is "Bonjour Moussa, votre paiement de 15 000 FCFA a été confirmé."

AC-113: Tenant-scoped notifications
Given user A belongs to tenant X and user B belongs to tenant Y
When a notification is sent to tenant X
Then only user A receives it (not user B)

Règles OHADA & Réglementaires

  • Aucune exigence réglementaire OHADA directe pour les notifications.
  • Les templates email doivent être en français (langue principale pour CI).
  • Protection des données : le contenu des notifications peut référencer des noms d'employés ou des données financières — s'assurer que les notifications sont tenant-scoped et user-scoped.

Standards & Conventions

  • docs/standards/java-spring-guidelines.md — §WebSocket STOMP, §Async processing, §Template rendering
  • docs/standards/api-guidelines.md — §Notification endpoints

Exigences de test

Tests unitaires

  • TemplateRenderer : les 13 templates se rendent correctement avec des paramètres exemples
  • NotificationService : routage par canal (IN_APP uniquement, EMAIL uniquement, BOTH)
  • NotificationService : isolation tenant — impossible d'envoyer au mauvais tenant

Tests d'intégration

  • WebSocket : connexion → réception de notification en temps réel
  • Email : envoi via fournisseur mocké → vérifier le contenu rendu
  • Isolation tenant : notification envoyée au tenant X non visible par le tenant Y

Ce que le QA validera

  • La livraison WebSocket fonctionne en temps réel
  • Les templates email se rendent correctement (vérification via outil de test d'email)
  • Les 13 templates MVP seedés et fonctionnels

Hors périmètre

  • UI de notifications (cloche, dropdown, compteur non lus) — CP-027
  • File d'attente de notifications hors ligne — CP-027
  • UI marquer comme lu / tout marquer comme lu — CP-027
  • Notifications SMS — V2
  • Notifications push (mobile) — V2
  • Préférences de notifications (désactiver certains types) — V2
  • Logo tenant dans les templates email — V2
  • Gestion des notifications en console admin — CP-012
  • Sélection du fournisseur email (Brevo vs Mailjet vs SES) — Décision d'infrastructure, abstrait derrière EmailService

Définition de Done

  • [ ] Tous les endpoints backend implémentés et retournant les réponses correctes
  • [ ] Tous les critères d'acceptation passent manuellement
  • [ ] Tests unitaires écrits et passants
  • [ ] Tests d'intégration écrits et passants
  • [ ] Isolation multi-tenant vérifiée (aucune fuite de données inter-tenant)
  • [ ] Pas de types Java bruts — tous les génériques explicites
  • [ ] Les réponses API respectent le format d'enveloppe standard
  • [ ] Les 13 templates MVP seedés via migration Flyway