# Module — Notifications

> Phase 3. Notifications multi-canaux (in-app, e-mail, SMS) et alertes métier. **Module partiellement existant** : on réutilise l'infrastructure de notification/e-mail/SMS déjà en place et on ajoute les cas d'usage métier POS.
> Liens : [README modules](README.md) · [Vision](../README.md) · [Architecture](../01-architecture.md) · [Modèle de données](../02-modele-de-donnees.md) · [Sécurité](../03-securite.md) · [API](../04-api.md) · [Décisions](../notes/decisions.md).

## 0. État d'implémentation — incrément 1 (2026-07-25) 🚧

Premier cas d'usage métier **livré** : l'**alerte de stock bas** (in-app + e-mail au manager, dédupliquée, fail-soft — voir [stock-inventaire](stock-inventaire.md)) et le **centre de notifications exposé à la PWA**.

| Livré ✅ | Cible non encore faite ⬜ |
|---|---|
| Cas d'usage **stock bas** : `LowStockAlertService` (in-app `NotificationService` + e-mail `EmailService`, dédup `AlertDeduplicationService`), déclenché au franchissement descendant du seuil (`StockService`) | clôture de caisse (Z manquant), échéance de document, digest quotidien |
| **API PWA** `NotificationController` : `GET /api/v1/notifications` (paginé, `meta.unreadCount`, `?unread=1`), `GET /unread-count`, `POST /{id}/read`, `POST /read-all` — scopée à l'utilisateur | préférences par canal/catégorie côté API, push mobile |
| **Écran PWA Notifications** (`features/notifications/`) : liste, non lues surlignées, marquage lu au tap + navigation lien, « tout lire », pagination ; cloche+badge Journal, entrée Profil | filtres par type/catégorie, temps réel (SSE/WebSocket) |
| Canaux **in-app + e-mail** ; déduplication 15 min | canal **SMS** métier, planification |
| Test `tests/Functional/Api/NotificationApiTest.php` (6) + `LowStockAlertApiTest.php` (2) | tests SMS/échéances |

Fichiers : `src/Service/Pos/LowStockAlertService.php`, `src/Controller/Api/V1/NotificationController.php`, `templates/emails/company/low_stock_alert.html.twig` ; PWA `yima-shop/src/app/features/notifications/`, `core/services/notifications.service.ts`.

La suite de la fiche décrit la **cible complète**.

## 1. Objectif

Informer les bons utilisateurs, sur le bon canal, des événements importants de leur boutique — sans spam.

- **Employé (caisse)** : recevoir les alertes opérationnelles (stock bas, clôture de caisse à faire).
- **Manager** : suivre les alertes de sa boutique (stock, échéances de documents, sécurité) et configurer les canaux.
- **Admin** : superviser les alertes système/sécurité transverses.

## 2. Périmètre

**In**
- Trois canaux : **in-app**, **e-mail**, **SMS**.
- Cas d'usage métier : **stock bas**, **clôture de caisse (Z manquant)**, **échéance de document**, **alerte de sécurité**.
- **Déduplication obligatoire** de toute alerte e-mail (fenêtre 15 min) : aucune alerte ne part « en direct ».
- Centre de notifications in-app (liste, non-lus, marquer comme lu) — **existant**.

**Out (v1)**
- Push mobile natif (APNs/FCB) — la PWA relaie l'in-app pour l'instant.
- Préférences fines par utilisateur/par canal/par type (v2).
- Webhooks sortants tiers.

## 3. Entités

| État | Élément | Rôle |
|---|---|---|
| ✅ | **`NotificationApp`** (`user`, `type`, `category`, `title`, `message`, `link`, `icon`, `isRead`, `readAt`) | Notifications **in-app** |
| ✅ | **`SmtpConfiguration`** | Config e-mail (boutique/système) |
| ✅ | **`SmsConfiguration`**, `SmsLog`, `SmsCampaign` | Config et journal SMS |
| ✅ | **`EmailLog`** | Journal des e-mails envoyés |
| ⬜ | **`NotificationRule`** (optionnel v2) | Mappage `eventType → canaux/destinataires` par `Company` |

Rattachement `Company` via l'utilisateur destinataire / la boutique concernée.

## 4. Permissions (RBAC)

| Code | État | Rôle type | Usage |
|---|---|---|---|
| `notification.view` | ⬜ (à seeder) | Tous rôles connectés | Voir ses notifications in-app |
| `notification.manage` | ⬜ (à seeder) | Manager, Admin | Configurer canaux/règles, rejouer/tester |

Seeding idempotent via `RbacSeedCommand`.

## 5. API (`/api/v1`)

| Méthode | Route | Corps / params | Permission | État |
|---|---|---|---|---|
| `GET` | `/api/v1/notifications` | `?filter[unread]=1&page=&limit=` | `notification.view` | ⬜ (exposer l'existant) |
| `POST` | `/api/v1/notifications/{uuid}/read` | — | `notification.view` | ⬜ |
| `POST` | `/api/v1/notifications/read-all` | — | `notification.view` | ⬜ |
| `GET` | `/api/v1/notification-settings` | canaux configurés de la boutique | `notification.manage` | ⬜ |

Web/in-app déjà servis par `NotificationService` ; l'API `/api/v1` expose ces lectures/actions à la PWA. Enveloppe `ApiResponseFormatter`.

## 6. Écrans

**Web (Twig)** — ✅ existant
- Cloche + **centre de notifications** (non-lus, marquer comme lu / tout lire), via `NotificationService`.
- Admin : configuration **SMTP** (`AdminSmtpController`), **SMS** (`AdminSmsController`), journaux e-mail/SMS.

**PWA (Ionic)** — ⬜ à faire
- Liste des notifications (badge non-lus), pull-to-refresh, action « marquer comme lu » via l'API `/api/v1/notifications`.

## 7. Règles métier

- **Déduplication systématique (⚠️ règle centrale)** : toute alerte e-mail passe par **`AlertDeduplicationService::shouldSendAlert($signature)`** avant envoi (fenêtre **15 min**). On **n'envoie jamais** un e-mail d'alerte directement — on construit une **signature** (`buildSignature(...)` / `buildSignatureFromLog(...)`) et on respecte la décision (autorisé / `within_cooldown_window`).
- **Routage par canal** :
  - *in-app* → `NotificationService::createNotification(...)` (toujours, pas de dédup nécessaire).
  - *e-mail* → dédup puis `EmailService` (config `SmtpConfiguration`).
  - *SMS* → `SmsService` (config `SmsConfiguration`), réservé aux alertes critiques (coût).
- **Cas d'usage → canaux** (proposition) :
  - **Stock bas** : in-app + e-mail (manager). Signature = `stock_low:{company}:{product}`.
  - **Clôture de caisse (Z manquant)** : in-app (employé/manager) en fin de journée.
  - **Échéance de document** (`DocumentReminder`) : in-app + e-mail J-n.
  - **Alerte de sécurité** : réutilise `SecurityAlertService` → in-app + e-mail (déjà dédupliqué), SMS si critique.
- **Scoping** : un utilisateur ne reçoit que les notifications de **sa** boutique ; les destinataires d'une alerte métier = membres de la `Company` avec la permission concernée.
- Envoi e-mail/SMS via **Messenger** (`SendEmailMessage`, `MESSENGER_MODE` sync/async) ; échecs journalisés `FileLogger`, jamais bloquants pour l'action métier.

## 8. Projection e-commerce

- **Aucune projection** vers les bases clients. Les notifications sont des données de gestion internes au central ; le site e-commerce ne les consomme pas. (Les e-mails transactionnels d'un futur site sont hors de ce module.)

## 9. Tests

- **Unitaires** : `AlertDeduplicationService` (fenêtre 15 min, `within_cooldown_window`, unicité de signature) — couvrir les nouveaux cas métier ; construction des signatures stock/caisse/échéance.
- **Fonctionnels** : `GET /api/v1/notifications` (filtre non-lus, pagination), `read` / `read-all` ; envoi e-mail simulé passant par la dédup (2e alerte identique < 15 min = non renvoyée).
- **Accès** : `notification.view` limité à ses propres notifications ; `notification.manage` pour la config ; isolation `Company`.

## 10. Dépendances

- ✅ Existant : `NotificationApp` + `NotificationService`, `EmailService`/`SmtpConfiguration`, `SmsService`/`SmsConfiguration`, **`AlertDeduplicationService`**, `SecurityAlertService`, `DocumentReminder`, Messenger (`SendEmailMessage`).
- Métier : [Stock & inventaire](stock-inventaire.md) (seuils), [Caisse & paiements](caisse-paiements.md) (clôture), [Sécurité](../03-securite.md).
- Socle : RBAC, `FileLogger`, `ApiResponseFormatter`.

## 11. Notes / ADR liés

- Réutilisation maximale de l'infra existante ; **ne pas** dupliquer un envoyeur d'e-mail contournant la déduplication.
- ⬜ À faire : seeder `notification.view/manage`, exposer l'API `/api/v1/notifications`, brancher les déclencheurs métier (stock/caisse/échéance) sur la dédup, écrans PWA.
- v2 candidate : `NotificationRule` + préférences par utilisateur/canal.
