Aller au contenu

Opérateur Console (OP) - PRD + Spécifications Fonctionnelles V1

Module : OP - operator-console
Plane : Application
Version cible : V1 (MVP)
Pays : CI (Côte d'Ivoire)
Préfixe story : OP-NNN
Date : 2026-05-19
Statut : Draft


V0 Envelope Scope (ajout 2026-05-21)

Lire docs/prd/prd-v0.md § 4 (ligne OP) et § 8 avant toute story V0. Ce PRD reste le contrat V1. Le V0 coupe FROM cette spec ; chaque sous-version V0 est additive et empile vers la cible V1.

OP dans le V0 (per prd-v0.md § 4 + § 8) :

Sous-version Périmètre vs V1 PRD Story IDs
[V0.0.0] UI création + envoi campagne (fire-and-forget). Liste contrats read-only (pas d'édition). Moteur campagne simplifié : renewal_window_days depuis settings tenant, tous les contrats matching sélectionnés, un envoi par création. Pas de scheduling, pas de segmentation, pas d'A/B, pas de feedback post-envoi. OP appelle NOTIF via API (Spring boundary) ; envoi async via domain events + outbox (D4). OP-001
[V0.0.1] Édition inline pré-envoi (admin cabinet uniquement). Vue de statut post-envoi (read-only). OP-002
[V0.0.2] Routing multi-agence : chaque opérateur d'agence voit uniquement les contrats de son agence ; les opérateurs branche peuvent éditer pré-envoi. OP-003
[V0.0.3] Actions post-envoi : view + revoke + cancel (admin cabinet uniquement). OP-004
[V0.1.0] Hooks d'observabilité (instrumentation pour ARTCI + ops). OP-005
[V1] Spec V1 complète telle qu'écrite ci-dessous (cohort selection avancée, multi-step scheduling, A/B testing, bulk approve, exceptions, approval workflow complet). -

Verrouillé V0.0.0 onwards (cf. prd-v0.md § 4 ligne OP) : desktop-first, online-first, UI FR-only, polling uniquement (pas de WebSocket, pas de service worker, pas d'offline sync en V0). OP couple LOOSELY à NOTIF + PAY via domain events + APIs (jamais inline send).

Note CAMP : le moteur de campagne reste plié dans OP pour tout V0 (pas de prefix CAMP-NNN). Si V2 fait émerger un vrai rules engine (segmentation, branching, journeys), CAMP redeviendra un module distinct ; aucun travail V0 ne doit baker une assumption qui bloque ce split.


Sommaire


Partie A - Exigences Produit

A.1 Résumé Exécutif

Problème résolu : Sans console opérateur, toute la valeur de la plateforme reste inaccessible. Le module OP est le point de commandement quotidien du cabinet et de ses agences : c'est ici qu'on importe les données, qu'on construit et valide les campagnes de relance, qu'on surveille les envois et les paiements, et qu'on traite les exceptions.

Valeur créée : - Pour le cabinet : visibilité consolidée sur l'ensemble du cycle de relance (import → campagne → envoi → paiement → anomalie) ; traçabilité auditée de chaque action opérateur ; productivité accrue (correction en lot, validation en un clic). - Pour l'opérateur d'agence : liste de travail claire par priorité (urgences ≤ 7 jours, anomalies bloquantes) ; interface simple, sans ERP, calée sur l'export bureau. - Pour le superviseur : dashboard de conversion en temps réel ; export CSV/XLSX pour rapports mensuels.

Position dans le flux V1 : OP est le pivot de l'application plane. Il consomme ING (données), déclenche NOTIF (envois), observe PAY (paiements), écrit dans AUDIT (traçabilité) et obtient son contexte de TENANT (cabinet / agence / profil pays). ING, PAY et NOTIF sont des services headless (API uniquement) - OP héberge l'intégralité de l'interface back-office.


A.2 Personas

Opérateur d'agence (agency_operator)

  • Exemple : Kouassi Brou, chargé de recouvrement - Agence Plateau, Cabinet Acme CI
  • Tâches quotidiennes : consulter la liste des campagnes, corriger les anomalies, valider et envoyer les campagnes, surveiller les statuts de notification/paiement, gérer les exceptions (hors-plateforme, renvoi de lien).
  • Portée de données : uniquement son agence (agency_id).
  • Ne peut pas : gérer les utilisateurs, modifier les paramètres cabinet, exporter des rapports, voir d'autres agences.

Administrateur cabinet (cabinet_admin)

  • Exemple : Marie Koné, directrice - Cabinet Acme CI
  • Tâches : provisionner les utilisateurs, configurer la fenêtre de détection, renseigner les informations légales CIMA, consulter toutes les agences.
  • Portée de données : toutes les agences du cabinet/tenant.
  • Peut faire : tout (CRUD, gestion utilisateurs, paramètres, export).

Superviseur (supervisor)

  • Exemple : Directeur commercial - Cabinet Acme CI
  • Tâches : consulter les dashboards de conversion, suivre les campagnes multi-agences, exporter les rapports mensuels.
  • Portée de données : toutes les agences (lecture seule).
  • Ne peut pas : modifier des données, créer/envoyer des campagnes, gérer des utilisateurs.

Rappel modèle : Cabinet = tenant = entité juridique (ex. Cabinet Acme CI). Agence = sous-unité opérationnelle du même cabinet (ex. Agence Plateau, Agence Cocody). Un opérateur est affecté à une agence ; un cabinet_admin ou supervisor a une vue transversale.


A.3 User Stories


US-OP-01 [V1][OPERATOR] - Importer un fichier de contrats

En tant qu' opérateur d'agence,
je veux uploader un fichier CSV/XLSX sur un écran dédié,
afin de ingérer les contrats exportés depuis notre outil de gestion, sans intervention technique.

  • AC1 : Étant donné que je suis sur l'écran « Importer un fichier », quand je glisse ou sélectionne un fichier, alors une barre de progression affiche l'avancement pendant que ING le traite.
  • AC2 : Quand ING termine le traitement, alors un écran de résultats affiche : X contrats valides, Y erreurs bloquantes, Z anomalies non-bloquantes, avec un lien vers chaque catégorie.
  • AC3 : Quand des erreurs bloquantes existent, si je clique sur une ligne d'erreur, alors je suis dirigé vers la fiche anomalie correspondante.
  • AC4 : Si le même fichier (même hash SHA-256) a déjà été importé, alors le système affiche un avertissement « Ce fichier a déjà été importé le {date}. Continuer quand même ? » avant de permettre l'import.

US-OP-02 [V1][OPERATOR] - Consulter la liste des campagnes

En tant qu' opérateur d'agence,
je veux voir la liste des campagnes de mon agence avec leurs statuts,
afin de savoir ce qui requiert mon action aujourd'hui.

  • AC1 : La liste est triée par date de création décroissante et affiche pour chaque campagne : nom auto-généré, date, agence, nombre de contrats, statut (pill coloré : Brouillon / Envoyée / Clôturée / Annulée).
  • AC2 : Je peux filtrer par statut, par date (période), et par branche produit.
  • AC3 : En tant que cabinet_admin ou supervisor, je vois les campagnes de toutes les agences avec une colonne « Agence », et je peux filtrer par agence.

US-OP-03 [V1][OPERATOR] - Créer une campagne ad hoc

En tant qu' opérateur d'agence,
je veux sélectionner des contrats éligibles et créer une campagne ad hoc,
afin de cibler un sous-ensemble spécifique en dehors de la cohort planifiée.

  • AC1 : Dans « Nouvelle campagne », je vois un sélecteur de contrats affichant les contrats éligibles (dans la fenêtre de détection, non actifs dans une autre campagne), filtrables par branche produit, plage de montant, plage de date d'échéance.
  • AC2 : Quand je sélectionne N contrats et clique « Créer la campagne », la campagne est créée avec le nom auto-généré « Campagne YYYY-MM-DD - {Agence} » et le statut Brouillon.
  • AC3 : Un contrat déjà actif dans une autre campagne est grisé et non sélectionnable, avec un tooltip : « Déjà dans la campagne {nom} ».

US-OP-04 [V1][OPERATOR] - Corriger et valider une campagne avant envoi

En tant qu' opérateur d'agence,
je veux réviser les contrats d'une campagne en Brouillon, corriger les anomalies, exclure des contrats, puis valider l'envoi,
afin d' assurer la qualité des données avant toute notification client.

  • AC1 : Le détail de campagne affiche pour chaque contrat : nom client (DOB masqué **/**/YYYY en liste), téléphone, branche produit, montant, date d'échéance, nombre d'anomalies (badge rouge si bloquante).
  • AC2 : Si un contrat a une anomalie bloquante non résolue (statut INCLUS), le bouton « Valider et Envoyer » est désactivé avec le message : « {N} contrat(s) avec des anomalies bloquantes. Corrigez ou excluez avant envoi. »
  • AC3 : En cliquant sur un contrat, je peux modifier uniquement : numéro de téléphone, montant, date d'échéance. Chaque modification est journalisée dans l'audit log (champ modifié, ancienne valeur, nouvelle valeur, opérateur, timestamp).
  • AC4 : Je peux sélectionner N contrats et cliquer « Exclure » pour les retirer de la campagne en une action (bulk exclude).
  • AC5 : Quand toutes les anomalies bloquantes sont résolues ou exclues et que je clique « Valider et Envoyer », une modale de confirmation s'affiche : « Envoyer à {N} client(s) ? [Confirmer / Annuler] ». Sur confirmation, la campagne passe à Envoyée et NOTIF est déclenché.

US-OP-05 [V1][OPERATOR] - Surveiller une campagne envoyée

En tant qu' opérateur d'agence,
je veux suivre en quasi-temps-réel les statuts de notification et de paiement de chaque contrat,
afin de détecter les échecs et agir rapidement.

  • AC1 : Le détail de campagne affiche des compteurs agrégés mis à jour via SSE/WS (latence < 5 s) : total envoyé, livré (NOTIF), payé, échoué, % conversion.
  • AC2 : Les contrats dont l'échéance est ≤ 7 jours et dont le paiement est non-terminal sont marqués d'un badge « URGENT » (rouge).
  • AC3 : En cliquant sur un contrat, je vois : statut NOTIF (livré / échoué / en attente), statut paiement (non initié / initié / payé / échoué / hors-plateforme), expiration du lien signé, historique des actions opérateur sur ce contrat.
  • AC4 : Je peux cliquer « Envoyer un rappel » sur un contrat non payé ; si le lien est expiré, un nouveau lien signé est généré (72h, ancien lien invalidé) et NOTIF envoie une nouvelle notification.

US-OP-06 [V1][OPERATOR] - Marquer un contrat comme réglé hors-plateforme

En tant qu' opérateur d'agence,
je veux enregistrer un paiement effectué en dehors de la plateforme,
afin que la réconciliation soit exacte même pour les paiements en cash ou par virement.

  • AC1 : Sur la fiche d'un contrat en campagne Envoyée (statut non terminal), le bouton « Régler hors-plateforme » ouvre une modale avec : méthode de paiement (select : mobile money / cash / chèque / virement), date de paiement, montant payé, référence (optionnel).
  • AC2 : À la validation, le statut du contrat passe à RÉGLÉ_HORS_PLATEFORME et un événement d'audit HORS_PLATEFORME_MARKED est créé avec l'identité de l'opérateur, la méthode, la date et le montant.
  • AC3 : Si le contrat est déjà PAYÉ via la plateforme, le bouton « Régler hors-plateforme » est désactivé.

US-OP-07 [V1][OPERATOR] - Annuler une campagne

En tant qu' opérateur d'agence,
je veux pouvoir annuler une campagne à tout moment (même après envoi),
afin de stopper immédiatement un lot envoyé par erreur.

  • AC1 : Pour toute campagne non terminale, le bouton « Annuler la campagne » affiche une modale : « {X} lien(s) seront révoqués. Annuler quand même ? [Oui / Non] »
  • AC2 : Sur confirmation : les envois NOTIF encore en queue sont annulés ; tous les liens signés des contrats non terminaux sont révoqués via AUTH ; la campagne passe à ANNULÉE ; les contrats non terminaux passent à LIEN_RÉVOQUÉ ; l'événement CAMPAIGN_CANCELLED est journalisé.
  • AC3 : Un client qui clique un lien révoqué voit : « Ce lien n'est plus valide. Veuillez contacter votre cabinet. » [CUSTOMER]

US-OP-08 [V1][OPERATOR] - Gérer les anomalies

En tant qu' opérateur d'agence,
je veux voir et résoudre les anomalies de données de mon agence,
afin qu' aucun contrat avec des données erronées ne parte en campagne sans correction préalable.

  • AC1 : La vue Anomalies affiche la queue des anomalies ouvertes de mon agence, filtrables par type (bloquante / non-bloquante / surveillance), par contrat, et par statut.
  • AC2 : En cliquant sur une anomalie, je vois la fiche contrat avec le champ défectueux mis en évidence et le contrôle d'édition activé pour téléphone / montant / date d'échéance uniquement.
  • AC3 : Quand le champ est corrigé, je peux cliquer « Marquer comme résolu » ; l'anomalie passe à RÉSOLUE et un événement d'audit ANOMALY_RESOLVED est enregistré.
  • AC4 : Pour une anomalie de surveillance (ex. paiement en attente prolongée), « Marquer comme résolu » exige un champ note obligatoire.

US-OP-09 [V1][OPERATOR][ADMIN] - Dashboard opérationnel

En tant que superviseur, opérateur ou cabinet_admin,
je veux un dashboard de démarrage qui affiche l'état de santé opérationnel en un coup d'œil,
afin de prioriser mes actions sans naviguer dans tous les écrans.

  • AC1 : Le dashboard affiche 4 widgets : (a) Contrats urgents - contrats à échéance ≤ 7 j non payés ; (b) Pipeline campagnes - compteurs par statut (Brouillon / Envoyée / Clôturée / Annulée) ; (c) Tunnel paiement - envoyé / payé / échoué + % conversion sur 30 j glissants ; (d) Anomalies bloquantes - nb non résolues + lien direct vers la vue anomalies.
  • AC2 : Un agency_operator voit uniquement les données de son agence.
  • AC3 : Un supervisor ou cabinet_admin voit les agrégats cross-agences avec un filtre par agence.

US-OP-10 [V1][OPERATOR] - Rechercher un contrat ou un client

En tant qu' opérateur d'agence,
je veux trouver rapidement un contrat par nom client, téléphone ou référence contrat,
afin de répondre à un appel entrant sans naviguer dans les campagnes.

  • AC1 : Une barre de recherche globale (accessible depuis n'importe quel écran) filtre en temps réel (debounced 300 ms) sur : nom client, numéro de téléphone (partiel), référence contrat - dans la portée agence/tenant.
  • AC2 : Un clic sur un résultat ouvre la fiche contrat.
  • AC3 : Seuls les contrats présents dans la base ingérée de la plateforme apparaissent dans les résultats (portée = données importées via ING pour ce tenant).

US-OP-11 [V1][ADMIN] - Exporter un rapport campagne / paiement

En tant que supervisor ou cabinet_admin,
je veux exporter les données d'une campagne ou de paiements filtrées en CSV/XLSX,
afin de produire les réconciliations et rapports mensuels.

  • AC1 : Sur la liste des campagnes ou la vue paiements, après avoir appliqué mes filtres (agence, période, statut), je peux cliquer « Exporter ».
  • AC2 : Si l'export dépasse 10 000 lignes, le système avertit et tronque le fichier avec une note en en-tête [TRONQUÉ : {N} lignes sur {total}].
  • AC3 : Un agency_operator ne voit pas le bouton « Exporter ».

US-OP-12 [V1][ADMIN] - Configurer les paramètres cabinet

En tant que cabinet_admin,
je veux configurer la fenêtre de détection et les champs légaux CIMA,
afin d' adapter la plateforme à mon cabinet sans dépendre du support technique.

  • AC1 : Le champ « Fenêtre de détection (jours) » accepte une valeur entière entre 7 et 90, avec une valeur par défaut de 30. Sur sauvegarde, le prochain batch de cohort utilise la nouvelle valeur et la modification est auditée (SETTINGS_CHANGED).
  • AC2 : La section « Informations légales CIMA » permet de renseigner : nom du cabinet, adresse, numéro d'agrément CIMA. Ces champs alimentent les mentions légales dans le portail client. La structure des paragraphes CIMA obligatoires est gérée par la plateforme - le cabinet ne peut pas les supprimer.

US-OP-13 [V1][ADMIN] - Gérer les utilisateurs du cabinet

En tant que cabinet_admin,
je veux créer, modifier, désactiver et réinitialiser les mots de passe de mes collaborateurs,
afin de maintenir des accès nominatifs, justes et révocables.

  • AC1 : Le formulaire « Nouvel utilisateur » requiert : prénom/nom, email, rôle (agency_operator / supervisor / cabinet_admin), agence (obligatoire pour agency_operator). Sur sauvegarde, Keycloak crée le compte et envoie un email d'invitation.
  • AC2 : Modifier le rôle ou l'agence d'un utilisateur prend effet immédiatement ; un événement d'audit OP_USER_ROLE_CHANGED est enregistré.
  • AC3 : Désactiver un utilisateur lui retire l'accès immédiatement ; son historique d'audit est conservé ; son statut affiche « Désactivé ».
  • AC4 : « Réinitialiser le mot de passe » déclenche un email de réinitialisation via Keycloak.

US-OP-14 [V1][OPERATOR] - Ajouter un contrat à une campagne envoyée

En tant qu' opérateur d'agence,
je veux ajouter un contrat manqué à une campagne déjà envoyée et le notifier immédiatement,
afin de ne pas créer une nouvelle campagne pour un cas isolé.

  • AC1 : Sur le détail d'une campagne Envoyée, un bouton « Ajouter un contrat » ouvre un sélecteur de contrats éligibles (non actifs dans une autre campagne active).
  • AC2 : Sur sélection d'un contrat, une modale de confirmation s'affiche : « Ajouter et notifier ce contrat ? [Confirmer / Annuler] ».
  • AC3 : Sur confirmation, le contrat est ajouté à la campagne et NOTIF envoie immédiatement une notification. Un événement d'audit CONTRACT_ADDED_TO_CAMPAIGN est enregistré.

A.4 Exigences Non-Fonctionnelles

Catégorie Exigence Cible V1
Interface Desktop-first Optimisé 1280 px, utilisable ≥ 1024 px
Interface Langue Français uniquement ; toutes les chaînes via i18n
Interface Dates DD/MM/YYYY ; heures HH:mm (24h) ; timezone UTC+0 (CI)
Interface Montants XOF entier, espace séparateur milliers, 0 décimale
Performance Chargement initial ≤ 2 s (réseau bureau)
Performance Transitions entre écrans ≤ 300 ms (navigation client-side)
Performance Actions d'écriture ≤ 3 s (validation campagne, exclusion, correction)
Performance Latence temps réel (SSE/WS) < 5 s pour mises à jour de statuts
Performance Pagination campagnes 25 par page
Performance Pagination contrats (détail) 50 par page
Performance Export max 10 000 lignes
Connectivité Mode offline Lecture listes chargées + notes localStorage
Sécurité Déconnexion auto 30 min d'inactivité (Keycloak)
Sécurité Isolation tenant Toute requête filtrée sur tenant_id
Sécurité Scoping agence Middleware ajoute agency_id pour agency_operator

A.5 Périmètre MVP V1

Inclus en V1 : - Interface back-office complète (tous les écrans §21 PRD V1) hébergée dans OP - Campaign engine : cohort planifiée (≥ 2×/semaine) + campagnes ad hoc - Correction workshop (téléphone, montant, date d'échéance uniquement) - Exclusion en lot (bulk exclude) - Surveillance post-envoi SSE/WebSocket (< 5 s) - Rappels manuels + renvoi de lien avec nouveau lien signé - Marquage hors-plateforme (méthode, date, montant, référence optionnelle) - Annulation de campagne avec révocation totale des liens signés - Dashboard 4 widgets - Recherche globale dans les données ingérées - Export CSV/XLSX (supervisor + cabinet_admin uniquement) - Gestion utilisateurs Keycloak (créer, modifier, désactiver, reset mot de passe) - Paramètres cabinet (fenêtre détection 7–90 j + identité légale CIMA) - Vue anomalies + résolution inline + marquage résolu - Offline minimum : lecture last-synced + notes localStorage synchronisées

Reporté :

Fonctionnalité Version
Rappels automatiques planifiés par campagne V2
Personnalisation templates notification par cabinet V3
Canal de notification configurable par tenant V3
Fenêtre de validité de lien configurable par tenant V3
Analytics avancées et dashboards segmentés V2
Gestion des quotas SMS/WhatsApp in-app V2
Workflow d'approbation Meta templates WhatsApp dans OP V2
Assignment et suivi d'anomalies par opérateur V2
Export multi-entités / consolidation groupe V2
API publique OP pour intégration ERP V3
Campagnes multi-step / scénarios branchés V3
Mode multilingue V2+

A.6 Questions Ouvertes

ID Question Module(s)
OQ-01 Le quota de messages SMS/WhatsApp inclus dans l'abonnement tenant doit-il déclencher un avertissement in-app dans OP (ex. « 80 % du quota mensuel utilisé ») ? OP + BILL
OQ-02 La validation des templates WhatsApp (pré-approbation Meta) est gérée out-of-band en V1. Faut-il afficher un statut de template (approuvé / en attente / rejeté) dans les paramètres cabinet ? OP + NOTIF
OQ-03 Les numéros de téléphone doivent-ils être partiellement masqués dans les vues liste (ex. +225 XX XX ** **) au titre de la minimisation ARTCI, en complément du masquage DOB ? OP
OQ-04 La génération de cohort planifiée : calendrier global ou per-tenant avec timezone ? En V1 CI (UTC+0) la question est mineure, mais doit être tranchée avant V2 multi-pays. OP + TENANT
OQ-05 Champ renewal_policy dans le schéma d'import ING : quel est le nom exact et le format ? (entier = mois, ou enum ?) À aligner avec le PRD ING. OP + ING
OQ-06 Un cabinet_admin peut-il se désactiver lui-même ou désactiver le seul cabinet_admin du tenant ? Ou doit-il toujours exister ≥ 1 cabinet_admin actif par tenant ? OP + TENANT

A.7 Hypothèses Temporairement Validées

ID Hypothèse Statut
TV-01 L'approche « textes CIMA gérés par la plateforme, cabinet renseigne ses données d'identité » est supposée suffisante pour les obligations d'information de CIMA 01-24. PRÉLIMINAIRE - [LEGAL-REVIEW] avant production
TV-02 La fenêtre de validité de lien signé à 72h est supposée compatible avec les délais d'offre acceptables en renouvellement d'assurance selon CIMA. PRÉLIMINAIRE - [LEGAL-REVIEW] avant production
TV-03 Les fonctionnalités utilisant WhatsApp et le traitement des numéros de téléphone sont développées sans restriction fonctionnelle en V1. La mise en conformité ARTCI (§B10, §B5 du mémo) est traitée hors code applicatif par l'équipe Papillon. PRÉLIMINAIRE - validé fondateur (2026-05-19)

Partie B - Spécifications Fonctionnelles

B.1 Règles Fonctionnelles

FR-OP-01 - Génération de cohort [V1][BACKEND]

SI contract.due_date ∈ [today, today + cabinet_settings.renewal_detection_window_days]
ET aucun campaign_contract actif n'existe pour ce contrat (pas de ligne avec campaign.status NOT IN ('CLÔTURÉE','ANNULÉE') ET campaign_contract.status NOT IN ('EXCLU','PAYÉ','RÉGLÉ_HORS_PLATEFORME','LIEN_EXPIRÉ','LIEN_RÉVOQUÉ'))
ET le contrat n'est pas marqué « déjà traité » dans la même période de renouvellement
ALORS le contrat est éligible pour la cohort.

  • Génération planifiée : au moins 2 fois par semaine (job backend, heure configurable au niveau plateforme, non exposée en V1).
  • Résultat : création d'une Campaign (type = SCHEDULED, statut = BROUILLON) avec les contrats éligibles en CampaignContract (statut = INCLUS).
  • Si aucun contrat éligible : aucune campagne n'est créée.

FR-OP-02 - Garde de validation de campagne [V1][OPERATOR]

SI un contrat campaign_contract.status = INCLUS a ≥ 1 anomalie de type BLOQUANTE à l'état OUVERTE ou EN_COURS
ALORS la campagne ne peut pas passer à ENVOYÉE.

Message bloquant : « {N} contrat(s) présentent des anomalies bloquantes. Corrigez les données ou excluez les contrats avant d'envoyer. »

Anomalies NON_BLOQUANTES et SURVEILLANCE : n'empêchent pas la validation, affichent un badge orange + compteur sur le bouton « Valider ».


FR-OP-03 - Nommage de campagne [V1][BACKEND]

  • Format : "Campagne {YYYY-MM-DD} - {nom_agence}" (date de création, timezone tenant = UTC+0 pour CI).
  • Calculé à la création, stocké, non modifiable.
  • Doublon même jour/même agence : "Campagne {YYYY-MM-DD} - {nom_agence} ({n})" (n incrémental démarrant à 2).

FR-OP-04 - Déduplication de cohort [V1][BACKEND]

Avant d'ajouter un contrat à une campagne (planifiée ou ad hoc) :

EXISTS (
  SELECT 1 FROM campaign_contracts cc
  JOIN campaigns c ON c.id = cc.campaign_id
  WHERE cc.contract_id = :contractId
    AND c.tenant_id    = :tenantId
    AND c.status       NOT IN ('CLÔTURÉE', 'ANNULÉE')
    AND cc.status      NOT IN ('EXCLU','PAYÉ','RÉGLÉ_HORS_PLATEFORME','LIEN_EXPIRÉ','LIEN_RÉVOQUÉ')
)

Si vrai → contrat inéligible. Dans le sélecteur ad hoc : grisé avec tooltip "Déjà dans la campagne {nom_campagne}".


FR-OP-05 - Annulation de campagne [V1][OPERATOR]

  1. Modale : "{X} lien(s) seront révoqués. Annuler quand même ? [Oui / Non]" (X = contrats non terminaux).
  2. Sur confirmation :
  3. campaign.status → ANNULÉE ; cancelled_at, cancelled_by enregistrés.
  4. Appel NOTIF : annuler les envois en queue pour cette campagne.
  5. Appel AUTH : révoquer les liens signés de tous les contrats non terminaux (PAYÉ, RÉGLÉ_HORS_PLATEFORME exclus).
  6. campaign_contract.status → LIEN_RÉVOQUÉ pour les contrats non terminaux.
  7. Audit event : CAMPAIGN_CANCELLED { campaignId, cancelledBy, linksRevokedCount, timestamp }.

FR-OP-06 - Masquage de la date de naissance [V1][OPERATOR]

Vue Affichage DOB
Vues liste / tableaux (campagne, anomalie, recherche) **/**/YYYY
Fiche contrat individuelle DD/MM/YYYY (clair)

Règle s'applique à tous les rôles dans les vues liste.


FR-OP-07 - Champs modifiables dans le correction workshop [V1][OPERATOR]

Éditables : phone_number, amount (XOF entier), due_date.
Lecture seule : tous les autres champs (branche produit, politique renouvellement, identifiants source).

  • amount : entier positif > 0, jamais float.
  • Chaque modification → audit event FIELD_CORRECTED { contractId, campaignId, fieldName, oldValue, newValue, operatorId, timestamp }.

FR-OP-08 - Marquage hors-plateforme [V1][OPERATOR]

Précondition : campaign.status = ENVOYÉE ET campaign_contract.status NOT IN ('PAYÉ','LIEN_RÉVOQUÉ','EXCLU').

Champs requis : payment_method (MOBILE_MONEY | CASH | CHEQUE | VIREMENT), payment_date (≤ today), amount_paid (XOF entier > 0).
Champ optionnel : reference (varchar 200).

Effet : campaign_contract.status → RÉGLÉ_HORS_PLATEFORME.
Audit event : HORS_PLATEFORME_MARKED { contractId, campaignId, method, amount, date, reference?, operatorId, timestamp }.


FR-OP-09 - Renvoi de lien / rappel [V1][OPERATOR]

SI campaign.status = ENVOYÉE
ET campaign_contract.status ∈ { NOTIF_LIVRÉ, NOTIF_ÉCHOUÉ, LIEN_EXPIRÉ, PAIEMENT_ÉCHOUÉ }
ALORS bouton « Envoyer un rappel » actif.

Mécanisme : 1. AUTH → nouveau lien signé 72h ; ancien lien invalidé. 2. NOTIF → nouvelle notification (WhatsApp d'abord, SMS fallback). 3. campaign_contract.status → NOTIF_EN_ATTENTE. 4. Audit event : NOTIFICATION_RESENT { contractId, campaignId, operatorId, newLinkExpiry, timestamp }.


FR-OP-10 - Politique de renouvellement (read-only) [V1][BACKEND]

  • renewal_policy importée avec le contrat via ING (champ optionnel dans le fichier d'import).
  • Si absent → valeur par défaut de product_branch_renewal_defaults (table seedée par la plateforme).
  • OP affiche en lecture seule sur la fiche contrat. Modification hors périmètre OP.

FR-OP-11 - Template de notification (géré plateforme) [V1][BACKEND]

Template générique unique par canal (WhatsApp, SMS), géré plateforme, non exposé en édition dans OP V1.

Champs de fusion rendus à l'envoi :

Champ Source
{{nom_client}} customer.full_name
{{montant}} montant en XOF (ex. 15 000 XOF)
{{date_echeance}} contract.due_date au format DD/MM/YYYY
{{lien}} URL lien signé (AUTH)
{{nom_cabinet}} cabinet_settings.cima_legal_name
{{type_contrat}} contract.product_branch
{{numero_contrat}} contract.source_contract_id

La pré-approbation des templates WhatsApp (Meta Business API) est gérée hors-bande par l'équipe Papillon en V1.


FR-OP-12 - Paramètres cabinet [V1][ADMIN]

  • renewal_detection_window_days : entier ∈ [7, 90], default 30. Chaque modification → audit event SETTINGS_CHANGED.
  • cima_legal_name (varchar 200), cima_legal_address (text), cima_license_number (varchar 50) : alimentent les mentions légales du portail client.
  • Constantes plateforme non modifiables en V1 : canal de notification (WhatsApp → SMS), validité du lien signé (72h).

FR-OP-13 - Gestion des utilisateurs [V1][ADMIN]

  • Un cabinet_admin ne gère que les utilisateurs de son propre tenant.
  • Hiérarchie : cabinet_admin > supervisor > agency_operator.
  • Il doit toujours exister ≥ 1 cabinet_admin actif par tenant (voir OQ-06).
  • Le premier cabinet_admin est provisionné par TENANT lors de l'onboarding.
  • OP appelle l'API Admin Keycloak pour créer / modifier / désactiver les utilisateurs.

FR-OP-14 - Export de données [V1][ADMIN]

  • Accessible aux rôles supervisor et cabinet_admin uniquement.
  • Portée : données filtrées, scoped tenant_id (+ agency_id si filtre par agence).
  • Formats : CSV (primaire), XLSX (secondaire).
  • Limite 10 000 lignes ; si dépassé : avertissement + note en-tête dans le fichier.
  • URLs de téléchargement : liens signés à usage unique, validité 10 min, générés côté serveur.
  • Audit event : DATA_EXPORTED { exportType, filters, rowCount, exportedBy, timestamp }.

FR-OP-15 - Notes locales offline [V1][OPERATOR]

  • Stockées en localStorage : { entityType, entityId, content, localTimestamp, synced: false }.
  • À la reconnexion : OP POST les notes non synchronisées vers POST /operator-notes.
  • En cas de conflit (note locale vs. note serveur sur le même entityId) : les deux sont conservées.

B.2 Machines à États

Campagne

stateDiagram-v2
    [*] --> BROUILLON : Cohort générée (planifiée ou ad hoc)
    BROUILLON --> ENVOYÉE : Opérateur valide & confirme\n(0 anomalie bloquante non résolue)
    ENVOYÉE --> CLÔTURÉE : Opérateur clôture\nOU tous contrats en état terminal
    BROUILLON --> ANNULÉE : Opérateur annule
    ENVOYÉE --> ANNULÉE : Opérateur annule (liens révoqués)
    CLÔTURÉE --> [*]
    ANNULÉE --> [*]

Contrat dans une campagne (CampaignContract)

stateDiagram-v2
    [*] --> INCLUS : Ajouté à la campagne
    INCLUS --> EXCLU : Opérateur exclut
    INCLUS --> NOTIF_EN_ATTENTE : Campagne validée - NOTIF en queue
    NOTIF_EN_ATTENTE --> NOTIF_LIVRÉ : Delivery confirmée
    NOTIF_EN_ATTENTE --> NOTIF_ÉCHOUÉ : Delivery échouée (retries épuisés)
    NOTIF_LIVRÉ --> LIEN_OUVERT : Client ouvre le lien (événement CP)
    LIEN_OUVERT --> PAYÉ : Paiement confirmé (événement PAY)
    LIEN_OUVERT --> PAIEMENT_ÉCHOUÉ : Paiement échoué (événement PAY)
    NOTIF_LIVRÉ --> LIEN_EXPIRÉ : 72h sans paiement
    LIEN_OUVERT --> LIEN_EXPIRÉ : 72h sans paiement
    PAIEMENT_ÉCHOUÉ --> NOTIF_EN_ATTENTE : Opérateur envoie rappel
    LIEN_EXPIRÉ --> NOTIF_EN_ATTENTE : Opérateur envoie rappel (nouveau lien)
    NOTIF_ÉCHOUÉ --> NOTIF_EN_ATTENTE : Opérateur envoie rappel
    NOTIF_LIVRÉ --> RÉGLÉ_HORS_PLATEFORME : Opérateur marque
    NOTIF_ÉCHOUÉ --> RÉGLÉ_HORS_PLATEFORME : Opérateur marque
    LIEN_EXPIRÉ --> RÉGLÉ_HORS_PLATEFORME : Opérateur marque
    INCLUS --> LIEN_RÉVOQUÉ : Campagne annulée
    NOTIF_EN_ATTENTE --> LIEN_RÉVOQUÉ : Campagne annulée
    NOTIF_LIVRÉ --> LIEN_RÉVOQUÉ : Campagne annulée
    NOTIF_ÉCHOUÉ --> LIEN_RÉVOQUÉ : Campagne annulée
    LIEN_OUVERT --> LIEN_RÉVOQUÉ : Campagne annulée
    LIEN_EXPIRÉ --> LIEN_RÉVOQUÉ : Campagne annulée
    PAIEMENT_ÉCHOUÉ --> LIEN_RÉVOQUÉ : Campagne annulée

États terminaux : PAYÉ, RÉGLÉ_HORS_PLATEFORME, EXCLU, LIEN_RÉVOQUÉ

Anomalie

stateDiagram-v2
    [*] --> OUVERTE : Détectée par ING (import) ou job de monitoring
    OUVERTE --> EN_COURS : Opérateur ouvre la fiche
    EN_COURS --> RÉSOLUE : Opérateur corrige + marque résolu
    EN_COURS --> IGNORÉE : Contrat exclu de la campagne
    OUVERTE --> IGNORÉE : Contrat exclu de la campagne
    RÉSOLUE --> [*]
    IGNORÉE --> [*]

B.3 Tables de Décision

TD-1 - Garde de validation campagne

Anomalies BLOQUANTES (contrats INCLUS) Anomalies NON_BLOQUANTES Envoi autorisé ? Affichage
0 N'importe OUI Bouton « Valider et Envoyer » actif
> 0 N'importe NON Badge rouge + message bloquant + lien vue anomalies

Anomalies NON_BLOQUANTES et SURVEILLANCE : badge orange sur le contrat + compteur non bloquant - n'empêchent pas l'envoi.

TD-2 - Badge d'urgence sur un contrat

Jours avant échéance Statut paiement Badge
≤ 7 jours Non-terminal 🔴 URGENT
> 7 jours Non-terminal (aucun)
N'importe Terminal (aucun)

TD-3 - Actions disponibles par statut CampaignContract

Statut Envoyer rappel Régler hors-plateforme Exclure Ajout autre campagne
INCLUS - -
NOTIF_EN_ATTENTE -
NOTIF_LIVRÉ -
NOTIF_ÉCHOUÉ -
LIEN_OUVERT -
LIEN_EXPIRÉ -
PAIEMENT_ÉCHOUÉ -
PAYÉ - - -
RÉGLÉ_HORS_PLATEFORME - - -
EXCLU - - -
LIEN_RÉVOQUÉ - - -

B.4 Modèle de Données

Entités appartenant à OP

CREATE TABLE campaigns (
  id                    UUID        PRIMARY KEY DEFAULT gen_random_uuid(),
  tenant_id             UUID        NOT NULL REFERENCES tenants(id),
  agency_id             UUID        NOT NULL REFERENCES agencies(id),
  name                  VARCHAR(200) NOT NULL,
  type                  VARCHAR(20) NOT NULL DEFAULT 'SCHEDULED'
                          CHECK (type IN ('SCHEDULED', 'AD_HOC')),
  status                VARCHAR(30) NOT NULL DEFAULT 'BROUILLON'
                          CHECK (status IN ('BROUILLON','ENVOYÉE','CLÔTURÉE','ANNULÉE')),
  created_by            UUID        NOT NULL REFERENCES users(id),
  created_at            TIMESTAMPTZ NOT NULL DEFAULT now(),
  sent_at               TIMESTAMPTZ,
  closed_at             TIMESTAMPTZ,
  cancelled_at          TIMESTAMPTZ,
  cancelled_by          UUID        REFERENCES users(id),
  cohort_params         JSONB
);
-- Index : (tenant_id, agency_id, status), (tenant_id, created_at DESC)

CREATE TABLE campaign_contracts (
  id                    UUID        PRIMARY KEY DEFAULT gen_random_uuid(),
  tenant_id             UUID        NOT NULL REFERENCES tenants(id),
  campaign_id           UUID        NOT NULL REFERENCES campaigns(id),
  contract_id           UUID        NOT NULL REFERENCES contracts(id),
  status                VARCHAR(40) NOT NULL DEFAULT 'INCLUS'
                          CHECK (status IN (
                            'INCLUS','EXCLU',
                            'NOTIF_EN_ATTENTE','NOTIF_LIVRÉ','NOTIF_ÉCHOUÉ',
                            'LIEN_OUVERT','PAYÉ','PAIEMENT_ÉCHOUÉ',
                            'RÉGLÉ_HORS_PLATEFORME','LIEN_EXPIRÉ','LIEN_RÉVOQUÉ'
                          )),
  excluded_by           UUID        REFERENCES users(id),
  excluded_at           TIMESTAMPTZ,
  exclusion_reason      VARCHAR(500),
  hp_method             VARCHAR(20)
                          CHECK (hp_method IN ('MOBILE_MONEY','CASH','CHEQUE','VIREMENT')),
  hp_date               DATE,
  hp_amount             BIGINT,
  hp_reference          VARCHAR(200),
  hp_marked_by          UUID        REFERENCES users(id),
  hp_marked_at          TIMESTAMPTZ,
  UNIQUE (campaign_id, contract_id)
);
-- Index : (tenant_id, campaign_id), (tenant_id, contract_id, status)

CREATE TABLE operator_notes (
  id                    UUID        PRIMARY KEY DEFAULT gen_random_uuid(),
  tenant_id             UUID        NOT NULL,
  agency_id             UUID        NOT NULL,
  entity_type           VARCHAR(20) NOT NULL CHECK (entity_type IN ('CONTRACT','CAMPAIGN')),
  entity_id             UUID        NOT NULL,
  content               TEXT        NOT NULL,
  created_by            UUID        NOT NULL REFERENCES users(id),
  created_at            TIMESTAMPTZ NOT NULL,
  synced_at             TIMESTAMPTZ,
  device_local_id       VARCHAR(100)
);

CREATE TABLE cabinet_settings (
  tenant_id                       UUID     PRIMARY KEY REFERENCES tenants(id),
  renewal_detection_window_days   SMALLINT NOT NULL DEFAULT 30
                                    CHECK (renewal_detection_window_days BETWEEN 7 AND 90),
  cima_legal_name                 VARCHAR(200),
  cima_legal_address              TEXT,
  cima_license_number             VARCHAR(50),
  updated_by                      UUID     REFERENCES users(id),
  updated_at                      TIMESTAMPTZ NOT NULL DEFAULT now()
);

Entités lues depuis d'autres modules

Entité Module propriétaire Accès OP
contracts, customers, deadlines ING Lecture via API REST
anomalies ING (détection) / OP (résolution) Lecture + PATCH statut résolution
notifications NOTIF Lecture statut ; POST send via API
payments PAY Lecture statut uniquement
tenants, agencies TENANT Lecture via API
users (Keycloak) AUTH CRUD via API Admin Keycloak
audit_events AUDIT POST uniquement

B.5 Implications Multi-Tenant

  • Isolation stricte : toute requête DB filtrée sur tenant_id. Aucune jointure cross-tenant possible.
  • Agency scoping : pour agency_operator, un middleware Spring ajoute automatiquement AND agency_id = :currentUserAgencyId à toutes les requêtes de lecture et d'écriture.
  • Profil BYO-DB : les 4 tables OP (campaigns, campaign_contracts, operator_notes, cabinet_settings) résident dans la base du tenant si profil BYO-DB. Le résolveur de datasource multitenante route automatiquement.
  • Cohort scheduling : s'exécute séquentiellement pour chaque tenant actif. En V1 CI-seul (UTC+0), pas d'ambiguïté de timezone. En V2+ multi-pays, le job devra tenir compte du country_code du tenant.
  • V2 readiness : aucune contrainte UNIQUE sur campaigns.name sans tenant_id. Schéma compatible N agences sans migration.

B.6 Dépendances Cross-Module

APIs consommées par OP

Module Endpoint Usage
ING GET /contracts?agencyId=&cohortWindow= Génération cohort + sélecteur ad hoc
ING GET /contracts/{id} Fiche contrat individuelle
ING POST /imports Déclenchement import
ING GET /imports/{id} Résultat import
ING GET /anomalies?agencyId=&status= Vue anomalies
ING PATCH /anomalies/{id} Résolution anomalie
NOTIF POST /notifications/batch Envoi campagne
NOTIF DELETE /notifications?campaignId= Annulation envois en queue
NOTIF POST /notifications/single Rappel individuel / renvoi lien
NOTIF GET /notifications/status?contractId= Statut notification
PAY GET /payments?contractId= Statut paiement
AUTH POST /links/generate Nouveau lien signé (rappel)
AUTH POST /links/revoke Révocation liens (annulation campagne)
AUTH Admin Keycloak API CRUD utilisateurs
AUDIT POST /events Journalisation toutes actions
TENANT GET /cabinets/{id} Profil cabinet + agences + country_code

Événements publiés par OP

Événement Déclencheur Consommateurs
OP_CAMPAIGN_VALIDATED Validation + confirmation AUDIT, NOTIF
CAMPAIGN_CANCELLED Annulation AUDIT, AUTH, NOTIF
CONTRACT_EXCLUDED Exclusion AUDIT
CONTRACT_ADDED_TO_CAMPAIGN Ajout post-envoi AUDIT, NOTIF
FIELD_CORRECTED Correction workshop AUDIT
HORS_PLATEFORME_MARKED Marquage hors-plateforme AUDIT, PAY
NOTIFICATION_RESENT Renvoi lien / rappel AUDIT
ANOMALY_RESOLVED Résolution anomalie AUDIT
OP_USER_CREATED Création utilisateur AUDIT
OP_USER_ROLE_CHANGED Modification rôle/agence AUDIT
OP_USER_DEACTIVATED Désactivation AUDIT
SETTINGS_CHANGED Modification paramètres cabinet AUDIT
DATA_EXPORTED Export CSV/XLSX AUDIT
IMPORT_TRIGGERED Déclenchement import AUDIT, ING

Événements reçus par OP (mise à jour statuts via SSE/WS)

Événement Source Effet dans OP
NOTIF_SENT NOTIF campaign_contract.status → NOTIF_LIVRÉ
NOTIF_FAILURE NOTIF campaign_contract.status → NOTIF_ÉCHOUÉ
CP_LINK_OPENED CP campaign_contract.status → LIEN_OUVERT
PAY_CONFIRMED PAY campaign_contract.status → PAYÉ
PAY_FAILED PAY campaign_contract.status → PAIEMENT_ÉCHOUÉ
AUTH_LINK_REJECTED_EXPIRED AUTH campaign_contract.status → LIEN_EXPIRÉ

Partie C - Contraintes et Frontières

C.1 Conformité Réglementaire

CIMA Régulation 01-24

  • La plateforme est positionnée comme prestataire de service technique agissant pour le compte de cabinets et compagnies d'assurance. Aucun écran OP ne présente Papillon comme un distributeur d'assurance indépendant. [VÉRIFIÉ]
  • Les informations légales affichées sur le portail client sont co-gérées : la plateforme fournit la structure des paragraphes obligatoires, le cabinet renseigne ses données d'identité (nom, adresse, numéro d'agrément). [TV-01 - PRÉLIMINAIRE - LEGAL-REVIEW avant production]
  • Le module OP génère une piste d'audit suffisante pour reconstituer la chronologie complète de tout cycle de relance (import → campagne → notification → authentification → paiement), conformément au §16.5 du PRD V1. [VÉRIFIÉ]

ARTCI / Loi n°2013-450

  • Minimisation des données : la date de naissance est masquée dans toutes les vues liste (**/**/YYYY). Seule la fiche contrat individuelle l'expose en clair.
  • Traçabilité des accès : tout accès à une fiche contrat est journalisé dans AUDIT (opérateur + timestamp).
  • Sous-traitance : le traitement des données clients par la plateforme pour le compte des cabinets est encadré par les clauses DPA des contrats B2B. OP lit uniquement les données ingérées par ING.
  • [PRÉLIMINAIRE - LEGAL-REVIEW] : L'utilisation de WhatsApp Cloud API (Meta) implique un transfert de données hors CI. La mise en conformité ARTCI (§B10 du mémo) est traitée hors code applicatif - cf. TV-03.

C.2 Localisation

  • Interface entièrement en français. Zéro chaîne en dur dans le code - toutes via i18n.
  • Dates : DD/MM/YYYY. Heures : HH:mm (24h). Timezone CI : UTC+0.
  • Montants : XOF uniquement en V1. Format : 15 000 XOF (espace séparateur milliers, 0 décimale). Stockage : entier XOF, jamais float.
  • Téléphones : format CI +225 XX XX XX XX.
  • Termes métier : prime, échéance, assuré, souscripteur, quittance, cabinet de courtage, agence.
  • Messages d'erreur : description claire + action recommandée. Jamais « Une erreur s'est produite » seul.

C.3 Sécurité

  • Isolation tenant : middleware Spring vérifie la correspondance entre le tenant_id du JWT et le tenant_id de chaque ressource. Aucune requête cross-tenant possible.
  • Agency scoping : pour agency_operator, le middleware ajoute agency_id automatiquement à toutes les requêtes.
  • Session Keycloak : access token 15 min, refresh 30 min d'inactivité. Déconnexion automatique après 30 min.
  • Audit log synchrone : les événements listés en B.6 sont écrits de façon synchrone - un échec d'écriture d'audit bloque l'action métier.
  • SSE/WS : connexions authentifiées par token Keycloak, scoped tenant_id + agency_id.
  • URLs d'export : liens signés à usage unique, validité 10 min, générés côté serveur.
  • Révocation de liens signés : lors d'une annulation de campagne, AUTH invalide tous les tokens des contrats non terminaux de façon synchrone avant que la campagne ne passe à ANNULÉE.

C.4 Connectivité

  • OP est desktop-first, online-first. Cible : connexion bureau stable (4G/fibre).
  • Bandeau offline : si la connexion est perdue, bandeau « Connexion perdue. Certaines fonctions sont indisponibles. » + blocage des actions d'écriture.
  • Données déjà chargées : listes déjà en mémoire React restent consultables hors-ligne (read-only), sans service worker.
  • Notes locales : écrites en localStorage, synchronisées à POST /operator-notes à la reconnexion (FR-OP-15).
  • Dégradation gracieuse : si ING, NOTIF ou PAY ne répondent pas, OP affiche un indicateur de chargement (timeout 10 s) + « Erreur : impossible de charger les données. [Réessayer] ». Aucune action silencieuse.
  • Fallback SSE/WS → polling : si le canal temps réel est indisponible, OP bascule sur un polling toutes les 30 s avec reconnexion SSE/WS en back-off exponentiel.

C.5 Hors Périmètre V1

Fonctionnalité Version cible Raison
Rappels automatiques planifiés par campagne V2 Moteur de règles requis ; V1 = humain-in-the-loop
Personnalisation templates notification par cabinet V3 Contrainte Meta pré-approbation + UX
Canal de notification configurable par tenant V3 Décision produit
Fenêtre de validité de lien configurable par tenant V3 72h fixe en V1
Analytics avancées et dashboards segmentés V2 Hors périmètre MVP
Gestion des quotas SMS/WhatsApp in-app V2 Dépend de BILL
Workflow d'approbation Meta templates WhatsApp dans OP V2 Out-of-band en V1
Assignment d'anomalies par opérateur V2 Queue partagée en V1
Export multi-entités / consolidation groupe V2 Architecture groupe = V2
API publique OP pour intégration ERP V3 API-first = V3
Campagnes multi-step / scénarios branchés V3 Orchestration V3
Mode multilingue V2+ V1 = français uniquement