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 | |
| ONBOARDING_WELCOME_B_PASSWORD | Canal B : lien mot de passe | |
| ONBOARDING_WELCOME_B_PAYMENT | Canal B : lien paiement | |
| EMAIL_VERIFICATION | Renvoi de vérification | |
| PASSWORD_RESET | Récupération de mot de passe | |
| INVOICE_GENERATED | Facture mensuelle | |
| 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 | |
| ACCOUNT_REACTIVATED | Tenant réactivé | Email + In-App |
| USER_INVITATION | Utilisateur invité | |
| MISSING_LEGAL_INFO | Rappel RCCM/NIF | Email + In-App |
Couche Repository¶
NotificationRepository.java— Tenant-aware : findByRecipientUserId(Pageable), countUnreadByRecipientUserId, markAsRead, markAllAsReadNotificationTemplateRepository.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 MVPV009__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 renderingdocs/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