Aller au contenu

Story CP-010: Notification Backend Service + 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


Objective

Build the shared notification backend service used by ALL modules: in-app notifications (via WebSocket STOMP), email delivery, and template rendering. Seed the 13 MVP email templates. Notification UI (bell, dropdown, unread count) is handled in CP-027.


Backend Scope

Entities & Records

Notification entity (tenant DB):

Field Type Constraints
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, default false
metadata JSONB Nullable (extra context)
created_date Instant NOT NULL

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

NotificationTemplate entity (platform DB):

Field Type Constraints
id UUID v7 PK
code String NOT NULL, unique
channel NotificationChannel NOT NULL
subject_template String NOT NULL (for email)
body_template String NOT NULL (Mustache/Handlebars)
priority NotificationPriority NOT NULL

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

Code Trigger Channel
ONBOARDING_WELCOME_A Email verification Email
ONBOARDING_WELCOME_B_PASSWORD Channel B: password link Email
ONBOARDING_WELCOME_B_PAYMENT Channel B: payment link Email
EMAIL_VERIFICATION Resend verification Email
PASSWORD_RESET Password recovery Email
INVOICE_GENERATED Monthly invoice Email
PAYMENT_RECEIVED Payment confirmed Email + In-App
PAYMENT_FAILED Payment failed Email + In-App
SUBSCRIPTION_EXPIRING 30/15/7 days before expiry Email + In-App
ACCOUNT_SUSPENDED Tenant suspended Email
ACCOUNT_REACTIVATED Tenant reactivated Email + In-App
USER_INVITATION User invited Email
MISSING_LEGAL_INFO RCCM/NIF reminder Email + In-App

Repository Layer

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

Service Layer

NotificationService.java (shared — consumed by all modules): - send(UUID tenantId, UUID recipientUserId, NotificationChannel channel, String templateCode, Map<String,Object> params, NotificationPriority priority) → void 1. Resolve template by code 2. Render template with params (Mustache/Handlebars) 3. IF channel includes IN_APP: persist Notification + broadcast via WebSocket 4. IF channel includes EMAIL: queue email for async delivery - 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: - Async email delivery via provider (Brevo/Mailjet/SES — provider adapter pattern) - Papillon-branded email templates (French, with butterfly logo) - Retry: 3 attempts with exponential backoff

TemplateRenderer.java: - Mustache/Handlebars template rendering - Supports: {{fullName}}, {{companyName}}, {{amount}}, {{date}}, {{link}}, etc.

WebSocket Configuration: - WebSocketConfig.java — STOMP over SockJS configuration - User-specific topics: /user/queue/notifications - Tenant-scoped: notifications only delivered to users of the same tenant - SockJS fallback for African proxy networks that may block WebSocket

NotificationBroadcaster.java: - Listens for new in-app notifications - Sends to connected WebSocket clients - Queues for offline users (delivered on next login)

API Endpoints

Method Path Request Body Response Auth
GET /api/v1/notifications ?page=&size= Page Any authenticated
GET /api/v1/notifications/unread-count {count} Any authenticated
PATCH /api/v1/notifications/{id}/read 204 Any authenticated
PATCH /api/v1/notifications/read-all 204 Any authenticated
POST /api/v1/notifications Internal API (service-to-service) 204 Internal

Multi-Tenant Considerations

  • Notifications in tenant DB (tenant-scoped)
  • Templates in platform DB (shared across tenants)
  • WebSocket: STOMP user destination ensures notifications go to the right user/tenant
  • No cross-tenant notification delivery possible

Flyway Migrations

  • V005__create_notification_templates.sql — Platform DB: notification_templates table + seed 13 MVP templates
  • V009__create_notification.sql — Tenant DB: notifications table

Frontend Scope

None for this story. Notification bell, dropdown, and unread count UI are in CP-027.


Acceptance Criteria

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)

OHADA & Regulatory Rules

  • No direct OHADA regulatory requirements for notifications.
  • Email templates must be in French (primary language for CI).
  • Data protection: notification content may reference employee names or financial data — ensure notifications are tenant-scoped and user-scoped.

Standards & Conventions

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

Testing Requirements

Unit Tests

  • TemplateRenderer: all 13 templates render correctly with sample params
  • NotificationService: channel routing (IN_APP only, EMAIL only, BOTH)
  • NotificationService: tenant isolation — cannot send to wrong tenant

Integration Tests

  • WebSocket: connect → receive notification in real-time
  • Email: send via mock provider → verify rendered content
  • Tenant isolation: notification sent to tenant X not visible to tenant Y

What QA Will Validate

  • WebSocket delivery works in real-time
  • Email templates render correctly (verify via email testing tool)
  • All 13 MVP templates seeded and functional

Out of Scope

  • Notification UI (bell, dropdown, unread count) — CP-027
  • Offline notification queuing — CP-027
  • Mark as read / mark all as read UI — CP-027
  • SMS notifications — V2
  • Push notifications (mobile) — V2
  • Notification preferences (opt out of specific types) — V2
  • Tenant logo in email templates — V2
  • Admin console notification management — CP-012
  • Email provider selection (Brevo vs Mailjet vs SES) — Infrastructure decision, abstract behind EmailService

Definition of Done

  • [ ] All backend endpoints implemented and returning correct responses
  • [ ] All acceptance criteria pass manually
  • [ ] Unit tests written and passing
  • [ ] Integration tests written and passing
  • [ ] Multi-tenant isolation verified (no cross-tenant data leaks)
  • [ ] No Java raw types — all generics explicit
  • [ ] API responses follow standard envelope format
  • [ ] All 13 MVP templates seeded via Flyway migration