Control Plane — PRD+FSD Hybride¶
Module : Control Plane (CP) Produit : [LES DEUX] (Papillon HR Suite + ALTARYS ENTERPRISE HR Suite) Périmètre pays : CI (Côte d'Ivoire) — MVP Version : 1.0 Date : 26/02/2026 Auteur : ANALYST (Claude Code) Documents source : - docs/specs/control-plane.md (spec écrite par humain, Tier 2) - docs/specs/platform-vision.md (spec vision plateforme, Tier 1) - docs/prd/platform-vision-prd.md (Platform Vision PRD v1.0) - docs/specs/tarifs_papillon_hr_suite.md (grille tarifaire) - docs/research/3_validated/control-plane-legal-validated.md (recherche juridique) - docs/research/2_audit/control-plane-legal-audit.md (lacunes de l'audit juridique)
PARTIE A — Exigences Produit (PRD)¶
A.1 Résumé Exécutif¶
Le Control Plane est le socle de la plateforme SaaS multi-tenant d'ALTARYS ENTERPRISE. Chaque module HR/Finance vendable en dépend pour l'authentification, la résolution de tenant, l'identité utilisateur, la facturation et les données de référence.
Le Problème¶
Les entreprises de la zone OHADA — de la startup de 2 personnes à la PME de 350 employés — ont besoin d'une plateforme cloud qui : - Les embarque rapidement (pas de processus d'installation de plusieurs semaines) - Gère le paiement via Mobile Money (le moyen de paiement dominant en Afrique de l'Ouest, où le taux de pénétration des cartes bancaires est < 5 %) - Applique l'isolation des données par tenant (une fuite entre tenants est un risque existentiel pour un SaaS RH qui stocke des données salariales) - Fournit la structure organisationnelle (départements, postes, catégories) dont chaque module RH a besoin - Est conforme aux Actes Uniformes OHADA, à la loi ivoirienne sur la protection des données (Loi 2013-450) et aux exigences de facturation électronique FNE
Ce que fait le Control Plane¶
Le Control Plane est la couche opérateur SaaS (inspirée de l'architecture AWS SaaS Builder Toolkit). Ce n'est PAS un module HR/Finance orienté tenant. Il gère :
| Responsabilité | Description |
|---|---|
| Gestion des tenants | Onboarding, cycle de vie, provisionnement, suspension, suppression |
| Facturation et abonnements | Offres, factures (prêtes FNE), paiements (agnostique fournisseur), métrologie |
| Gestion des utilisateurs | Utilisateurs, invitations, accès multi-tenant, intégration Keycloak |
| Authentification | Auth basée JWT, résolution de tenant, gestion des tokens |
| RBAC | Rôles, permissions, contrôle d'accès conditionné aux modules |
| Organisation et données de référence | Unités org., postes, catégories, types de contrat, hiérarchie managériale |
| Entité Employé (Baseline) | Fiche employé minimale consommée par tous les modules |
| Moteur de configuration pays | Tables fiscales, taux de cotisations sociales, types de congés, jours fériés |
| Contrôle d'accès aux modules | Suivi des modules souscrits, application des accès, affichage upsell |
| Journal d'audit | Journalisation immuable de toutes les mutations de données sur tous les modules |
| Service de notifications | Pipeline de notifications in-app + email partagé entre tous les modules |
| Console d'administration | Outils opérateur ALTARYS (application séparée) |
Ce que le Control Plane ne fait PAS¶
- Il ne traite PAS les demandes d'absence (module Gestion des Absences)
- Il ne calcule PAS la paie (module Paie)
- Il ne gère PAS le dossier employé complet (module Core RH, post-MVP)
- Il ne fournit PAS la gestion des processus administratifs (module Gestion des Employés, post-MVP)
- Il n'applique PAS le verrouillage de période AUDCIF Art. 22 ni les chaînes de hachage (module Comptabilité)
- Il ne fournit PAS le versionnage, la rétention ou la recherche de documents (Gestion Avancée des Documents, post-MVP)
A.2 User Stories¶
US-CP-001 : Auto-inscription tenant [LES DEUX] [CI]¶
En tant que prospect visitant papillon.ci, Je veux inscrire mon entreprise et commencer à utiliser la plateforme immédiatement, Afin de ne pas perdre de temps en configuration administrative avant de voir la valeur.
Critères d'acceptation : - Étant donné que je suis sur la page d'inscription, Quand je remplis : nom de l'entreprise, pays (CI), effectif déclaré, email admin, nom admin, téléphone, RCCM (facultatif), Et je passe le hCaptcha, Et je soumets le formulaire, Alors je reçois un email de vérification dans les 60 secondes. - Étant donné que j'ai cliqué sur le lien de vérification d'email, Quand j'ai choisi l'essai gratuit de 14 jours, Alors mon statut tenant est TRIAL, tous les modules sélectionnés sont actifs, et je peux commencer à utiliser la plateforme immédiatement. - Étant donné que j'ai cliqué sur le lien de vérification d'email, Quand j'ai choisi de NE PAS utiliser l'essai gratuit, Alors mon statut tenant est PAYMENT_REQUIRED, je peux me connecter mais tous les modules sont en lecture seule, et j'ai 14 jours pour payer. - Étant donné que mon tenant est au statut PAYMENT_REQUIRED, Quand je n'ai pas encore demandé d'essai, Alors je peux demander mon essai gratuit unique de 14 jours à tout moment. - Étant donné que mon email admin existe déjà dans le système, Quand j'essaie de m'inscrire, Alors le système affiche "Un compte existe déjà avec cet email."
US-CP-002 : Commercial CRM crée un tenant [LES DEUX]¶
En tant que commercial freelance ALTARYS, Je veux inscrire un prospect en tant que tenant directement depuis mon CRM, Afin que le prospect puisse commencer à payer et à utiliser la plateforme sans délais.
Critères d'acceptation : - Étant donné que j'ai les informations du prospect dans le CRM, Quand je déclenche "Convertir en client", Alors le système crée un tenant au statut PENDING. - Étant donné qu'un tenant est créé depuis le CRM, Quand le système traite la création, Alors l'admin tenant reçoit l'email n°1 (lien de création de mot de passe) et l'email n°2 (lien de paiement). - Étant donné l'email d'activation, Quand le destinataire n'a pas demandé cette inscription, Alors il peut cliquer sur "Annuler cette inscription" (lien d'opt-out) pour supprimer le tenant PENDING. - Étant donné que l'admin tenant a défini son mot de passe et payé, Quand le paiement est confirmé, Alors le statut tenant passe à ACTIVE et tous les modules souscrits sont disponibles. - Étant donné l'identifiant du commercial, Quand le tenant paie, Alors l'identifiant du commercial est enregistré sur le tenant pour l'attribution de commission (commission comptabilisée au paiement, pas à l'inscription).
US-CP-003 : L'admin tenant gère les utilisateurs [LES DEUX]¶
En tant qu' administrateur tenant, Je veux inviter des utilisateurs sur le compte plateforme de mon entreprise, Afin que mes employés et collaborateurs externes puissent accéder aux modules dont ils ont besoin.
Critères d'acceptation : - Étant donné que je suis admin tenant, Quand j'invite un utilisateur par email, Alors l'utilisateur reçoit un email d'invitation avec un lien de création de mot de passe, et son statut est INVITED. - Étant donné qu'un utilisateur invité clique sur le lien et définit son mot de passe, Quand il termine l'inscription, Alors son statut passe à ACTIVE et il peut se connecter avec les rôles que j'ai attribués. - Étant donné que j'attribue un rôle à un utilisateur, Quand le rôle inclut des permissions pour un module inactif, Alors ces permissions sont ignorées silencieusement (le contrôle d'accès aux modules prime sur les permissions de rôle).
US-CP-004 : Utilisateur multi-tenant (Cabinet Comptable) [LES DEUX]¶
En tant que comptable gérant 5 entreprises clientes, Je veux me connecter une seule fois et basculer entre les tenants de mes clients, Afin de ne pas avoir besoin de comptes séparés avec des emails différents pour chaque entreprise.
Critères d'acceptation : - Étant donné que j'appartiens à plusieurs tenants, Quand je me connecte, Alors j'arrive sur le tableau de bord de mon tenant principal avec une section "Mes entreprises" affichant les vignettes/logos de tous mes tenants clients. - Étant donné que je clique sur la vignette d'un tenant client, Quand le changement de tenant s'initie, Alors un nouveau JWT est émis pour le tenant sélectionné (échange de token, sans ré-authentification), et j'opère dans le contexte de ce tenant. - Étant donné que j'opère dans le contexte d'un tenant client, Quand je veux basculer vers un autre client ou revenir à l'accueil, Alors je peux changer via un sélecteur de tenant persistant sans me reconnecter.
US-CP-005 : L'admin ALTARYS gère la plateforme [LES DEUX]¶
En tant qu' administrateur plateforme ALTARYS, Je veux consulter tous les tenants, confirmer les paiements et gérer les abonnements, Afin de pouvoir opérer la plateforme et accompagner les clients.
Critères d'acceptation : - Étant donné que j'accède à la console d'administration (application séparée, URL non publique), Quand je consulte la liste des tenants, Alors je vois tous les tenants avec leur statut, leurs détails d'abonnement, leur effectif et leur statut de paiement. - Étant donné qu'un tenant a payé par virement bancaire, Quand je confirme le paiement manuellement, Alors le statut tenant passe de ACTIVE_PAYMENT_PENDING à ACTIVE. - Étant donné que je dois suspendre un tenant, Quand je déclenche la suspension manuelle, Alors le statut tenant devient SUSPENDED et tous les modules passent en lecture seule pour ce tenant.
US-CP-006 : Gestion des employés (Baseline) [LES DEUX]¶
En tant qu' administrateur tenant, Je veux créer et gérer les fiches employés, Afin que les modules comme Absences, Présence et Paie puissent référencer mes employés.
Critères d'acceptation :
- Étant donné que je crée un employé en ligne,
Quand je soumets le formulaire,
Alors l'employé est créé avec un identifiant UUID v7 (généré côté client) et un matricule attribué par le serveur (format : {PREFIX}-{YEAR}-{MM}-{SEQ}, ex. ALT-2026-03-001).
- Étant donné que je crée un employé hors ligne,
Quand je soumets le formulaire,
Alors l'employé est sauvegardé dans IndexedDB avec UUID v7, le matricule affiche "Numéro en attente", et il se synchronise à la reconnexion.
- Étant donné que j'attribue un poste à un employé,
Quand le poste n'existe pas dans le catalogue,
Alors je peux saisir un intitulé de poste personnalisé qui s'ajoute automatiquement au catalogue pour une réutilisation future.
- Étant donné qu'un employé n'a pas de manager attribué,
Quand un module achemine une demande d'approbation pour cet employé,
Alors la demande est acheminée vers l'Approbateur par défaut du tenant.
US-CP-007 : Affichage upsell des modules [PAPILLON UNIQUEMENT]¶
En tant qu' employé utilisant Papillon, Je veux voir quels autres modules mon entreprise pourrait souscrire, Afin de pouvoir recommander des outils utiles à mon admin.
Critères d'acceptation : - Étant donné que mon tenant a Absences mais pas Paie, Quand je consulte le menu de navigation, Alors Paie apparaît grisé/verrouillé avec une brève description et un bouton "Souscrire". - Étant donné que je clique sur "Souscrire", Quand je suis un employé ordinaire (non admin), Alors je vois "Contactez votre administrateur pour activer ce module." - Étant donné que je clique sur "Souscrire" en tant qu'admin tenant, Quand je clique, Alors je suis redirigé vers la page de gestion de facturation/abonnement.
A.3 Personas Utilisateurs¶
P-CP-01 : Propriétaire / DG (CEO-Owner) — PME [PAPILLON]¶
- Contexte : Propriétaire d'une entreprise de négoce de 20 employés à Abidjan. Gère tout actuellement via WhatsApp + Excel.
- Objectif : S'inscrire rapidement (< 5 minutes), commencer à suivre les absences, ajouter la paie ensuite.
- Point de douleur : "Je veux que ça marche tout de suite. Pas de formation compliquée."
- Appareil : Téléphone Android (Samsung série A, 3G/4G).
- Interaction avec CP : Auto-inscription, invitations utilisateurs, paramètres entreprise, facturation.
P-CP-02 : Responsable RH — PME de taille moyenne [PAPILLON]¶
- Contexte : Responsable RH dans une entreprise industrielle de 150 employés à San Pedro.
- Objectif : Gérer les fiches employés de façon centralisée, assurer la conformité au code du travail.
- Point de douleur : "J'ai 3 cahiers et 5 fichiers Excel. Je perds des informations."
- Appareil : Bureau (principal), téléphone pour les approbations.
- Interaction avec CP : Création d'employés, configuration de la structure org., gestion des rôles.
P-CP-03 : Comptable Externe — Cabinet Comptable [LES DEUX]¶
- Contexte : Comptable dans un cabinet comptable gérant 12 clients PME.
- Objectif : Accéder à tous les clients depuis un seul compte. Consulter les données de paie, valider les budgets.
- Point de douleur : "12 mots de passe pour 12 clients, c'est ingérable."
- Appareil : Bureau (double écran).
- Interaction avec CP : Connexion multi-tenant, changement de tenant (tableau de bord vignettes), rôle-par-tenant.
P-CP-04 : Directeur Financier (DAF) — Grande Entreprise [ENTERPRISE UNIQUEMENT]¶
- Contexte : DAF dans une entreprise de 500 employés avec plusieurs sites et départements.
- Objectif : Vues de données denses, opérations par lots, reporting avancé.
- Interaction avec CP : Paramètres entreprise, structure org. (multi-sites), suivi de facturation.
- Note : Persona Enterprise. Le frontend V2 sert cet utilisateur avec une interface plus dense.
P-CP-05 : Administrateur Plateforme ALTARYS [LES DEUX]¶
- Contexte : Personnel des opérations ALTARYS gérant la plateforme.
- Objectif : Surveiller la santé des tenants, résoudre les problèmes de paiement, inscrire les tenants manuellement.
- Appareil : Bureau (console d'administration — application séparée).
- Interaction avec CP : Console d'administration pour la gestion des tenants, confirmation des paiements, suspension/réactivation, métriques plateforme.
P-CP-06 : Commercial Freelance [LES DEUX]¶
- Contexte : Commercial freelance ALTARYS prospectant des entreprises en CI.
- Objectif : Inscrire rapidement des prospects depuis le CRM, suivre les commissions à la conversion.
- Appareil : Téléphone (toujours en déplacement).
- Interaction avec CP : Onboarding Canal B (CRM → création de tenant).
A.4 Exigences Non Fonctionnelles¶
| Exigence | Cible | Notes |
|---|---|---|
| Temps de chargement de page | < 2 secondes en 3G | Pages Control Plane (paramètres, gestion utilisateurs, structure org.) |
| Temps de complétion de l'onboarding | < 5 minutes | Du premier champ du formulaire au statut TRIAL ou PAYMENT_REQUIRED |
| Temps de provisionnement tenant | < 30 secondes (Profil A), < 2 minutes (Profil C) | Inclut provisionnement DB, configuration Keycloak, données de référence |
| Temps de réponse API | < 500ms (p95) pour les endpoints d'auth, < 1s pour le CRUD | L'auth est le chemin critique — chaque requête le traverse |
| Capacité hors ligne | Création d'employés, navigation dans la structure org. | Les formulaires employés sont mis en file d'attente hors ligne. Paramètres/facturation nécessitent une connexion. |
| Disponibilité | 99,5 % (endpoints d'auth : 99,9 %) | L'auth est critique pour tous les modules |
| Tenants simultanés | 100 000+ dans 5 ans | La conception DB doit supporter cette échelle |
| Utilisateurs simultanés par tenant | 500 au lancement, évolutif à 5 000 | Le dimensionnement Keycloak doit le supporter |
| Isolation des données | Zéro fuite de données entre tenants — auditable et testable | Appliqué au niveau de la couche repository (TenantAwareRepository). Toutes les requêtes sont scopées. |
| Rétention d'audit | 10 ans minimum | Conformité AUDCIF Art. 24 |
| Sauvegarde | Quotidienne automatisée, rétention 30 jours | Sauvegarde par tenant pour le Profil C |
| Support navigateur | Chrome 90+, Firefox 90+, Safari 15+ | 95 %+ des utilisateurs ouest-africains |
| Support appareils | Téléphones Android (modèles jusqu'à 8 ans), tablettes, bureaux | Principal : mobile 360px |
A.5 Périmètre MVP¶
Dans le MVP¶
- Gestion des tenants : Cycle de vie complet (10 statuts), 2 canaux actifs (A + B), pipeline de provisionnement
- Profils DB : Les 4 profils — A (DB partagée), B (Schéma par tenant), C (DB par tenant), D (BYO-DB). Sélection automatique selon l'effectif.
- Facturation : Adaptateur de paiement agnostique fournisseur, solution de repli manuelle, modèle de facturation prêt FNE, facturation mensuelle, facturation annuelle (14 mois pour le prix de 12), tarification par segment (S1-S6), packs comme règles de remise, tableau de bord des paiements en attente
- Gestion des utilisateurs : Invitation, RBAC, rôles par défaut (DG, RH Manager, Comptable, Employé, Manager), utilisateurs multi-tenant avec home_tenant_id et tableau de bord vignettes
- Authentification : Keycloak single realm, JWT (user_id, tenant_id, country_code, roles[]), permissions hybrides (rôles JWT + matrice de permissions en cache), échange de token pour le changement de tenant
- Organisation et données de référence : Arbre flexible à 3 niveaux (Entité → Site optionnel → Département), catalogue de postes avec trappe de secours, 4 catégories employés (CI) + 5 grilles salariales sectorielles, types de contrat (CDI, CDD, Stage, Apprentissage, Journalier, CTT), hiérarchie managériale (N/N+1), Approbateur par défaut
- Entité Employé : Baseline avec statuts (PRE_HIRE, ACTIVE, SUSPENDED, TERMINATED), UUID v7 côté client, matricule configurable attribué par le serveur
- Moteur de configuration pays : CI uniquement — tranches ITS (provisoirement validées), taux CNPS (provisoirement validés), types de congés, jours fériés, plan comptable SYSCOHADA, catégories + 5 grilles sectorielles, types de contrat, règle CDD 2 ans (alertes 90/30/7)
- Contrôle d'accès aux modules : Suivi du statut par module, application API + frontend, affichage upsell (visible + verrouillé + CTA)
- Journal d'audit : Journalisation immuable pour tous les modules, interrogeable par les admins tenant, rétention 10 ans
- Service de notifications : Service partagé pour tous les modules, in-app (cloche de notification) + email, ~10 templates Papillon-brandés stockés en DB
- Console d'administration : Application légère séparée (bureau uniquement, sans hors ligne) — liste des tenants, gestion des paiements, suspension/réactivation, override de module, métriques de base
- Stub Canal B : Contrat d'interface défini + endpoint stub pour l'intégration CRM
- Sécurité : hCaptcha sur les endpoints publics, rate limiting, détection de doublons (email + téléphone + nom approximatif), alertes de révision manuelle pour les patterns suspects
- Essai gratuit 14 jours : Facultatif, Canal A uniquement, unique par tenant
- Français uniquement, XOF uniquement, CI uniquement
Hors MVP (V2+)¶
- Suspension par module : Modèle de données prêt dans le MVP, logique d'application en V2
- Canal C : Création de tenant via le back-office admin
- Rôles personnalisés : Rôles créés par le tenant (MVP : rôles par défaut fixes)
- SSO / OAuth / realm-per-enterprise-tenant : V2 Enterprise
- Notifications SMS : V2 (important pour l'Afrique de l'Ouest)
- Facturation à l'usage / tableaux de bord de métrologie : V2
- Proratisation en cours de période : V2 (MVP : changements effectifs au prochain cycle de facturation)
- Périodes d'essai par module : V2 (MVP : essai uniquement au niveau tenant)
- Outil d'export de données tenant : Export self-service avant suppression (MVP : manuel via admin)
- Intégration API DGI FNE : MVP stocke les données prêtes FNE, V2 transmet au DGI
- CinetPay / Stripe : Intégrations spécifiques fournisseurs (quand les partenariats sont confirmés)
- Configs multi-pays : BEN + CMR au mois 9, RDC au mois 15
- Support devise XAF : V2
- Anglais / Portugais : V2+
- Module Core RH : Dossier employé complet (module vendable post-MVP)
- Module Gestion des Employés : Couche processus administratifs (post-MVP)
- Gestion Avancée des Documents : Versionnage, rétention, recherche (post-MVP)
- Centres de coûts : V2 (§ documenté dans Platform Vision)
- Structures multi-entités / groupes : V2
- Organigramme graphique : V2
- Grades, classifications, familles de postes : V2-V3
- Organisations matricielles : V3 Enterprise
A.6 Questions Ouvertes¶
| ID | Question | Priorité | Impact |
|---|---|---|---|
| OQ-CP-01 | Seuil délégués du personnel : Le seuil >10 travailleurs est-il par établissement (site) ou par entreprise ? Affecte le suivi des effectifs par unité org. | ÉLEVÉE | Config. pays, entité Employé |
| OQ-CP-02 | Formule RICF : Réduction d'Impôt pour Charges de Famille — formule de calcul spécifique et montants de déduction par nombre de parts. Nécessaire pour le Moteur de Configuration Pays. | ÉLEVÉE | Config. pays, Paie |
| OQ-CP-03 | Déclaration préalable ARTCI : Dépôt non initié. Requis avant le traitement des données personnelles de résidents CI. Autorisation de transfert transfrontalier pour Hetzner (Allemagne) également requise. BLOQUANT JURIDIQUE PRÉ-LANCEMENT. | CRITIQUE | Conformité légale |
| OQ-CP-04 | API DGI FNE : Endpoint, processus d'onboarding et exigences techniques pour transmettre les factures FNE-conformes au DGI. Nécessaire pour l'intégration V2. | MOYENNE | Facturation V2 |
| OQ-CP-05 | Limites agrégateur BCEAO : Limites réelles par transaction et mensuelles pour les canaux marchands Flutterwave/Orange Money/Wave. Affecte la conception du flux de paiement. | MOYENNE | Facturation |
| OQ-CP-06 | API jours fériés nationaux : Trouver une API ou une méthode pour fournir les jours fériés nationaux pour les 17 pays OHADA de façon programmatique. ÉLÉMENT DE BACKLOG. | MOYENNE | Config. pays |
| OQ-CP-07 | Source confidentialité des bulletins de paie : Source juridique de l'exigence de confidentialité reconnue comme SOURCE INCONNUE dans la recherche. | BASSE | Notification, Contrôle d'accès |
| OQ-CP-08 | Délai de production de documents à l'inspecteur du travail : Aucune source juridique trouvée pour ce délai. | BASSE | Gestion documentaire |
| OQ-CP-09 | Seuil de sélection automatique DB-par-tenant : À quel effectif déclaré la sélection automatique doit-elle recommander le Profil C plutôt que le Profil A ? | ÉLEVÉE | Provisionnement tenant |
A.7 Provisoirement Validés¶
| ID | Élément | Valeur | Statut | Notes |
|---|---|---|---|---|
| TV-CP-01 | Taux CNPS Prestations Familiales | 5 % (employeur uniquement, pas de plafond) | Provisoirement validé — source : Guide Employeur CNPS, décret non vérifié | Implémenter dans Config. Pays, signaler en DB comme non vérifié. Mettre à jour quand décret identifié. |
| TV-CP-02 | Taux CNPS Maternité | 0,75 % (employeur uniquement, pas de plafond) | Provisoirement validé | Idem ci-dessus |
| TV-CP-03 | Taux CNPS AT/MP | 2–5 % (employeur uniquement, variable selon classe de risque) | Provisoirement validé | Correspondance classe de risque → taux pas entièrement définie |
| TV-CP-04 | Taux CNPS Assurance Vieillesse | 14 % total (7,7 % employeur + 6,3 % employé), plafond 3 375 000 FCFA/mois | Provisoirement validé — source : Communiqué CNPS 2023 | Critique pour la Paie. Plafond et taux à vérifier. |
| TV-CP-05 | Tranches ITS (CI) | 0–75K : 0 %, 75K–240K : 16 %, 240K–800K : 21 %, 800K–2,4M : 24 %, 2,4M–8M : 28 %, >8M : 32 % | Provisoirement validé — source : Ordonnance n° 2023-719 | Vérifier qu'aucune Loi de Finances 2025/2026 ne les a modifiés. |
| TV-CP-06 | Durée max. CDD | 2 ans (renouvelable une fois) — Art. 15.10 Code du Travail CI | Provisoirement validé | Seuils d'alerte : 90/30/7 jours avant limite. |
PARTIE B — Spécifications Fonctionnelles (FSD)¶
B.1 Règles Fonctionnelles — Gestion des Tenants¶
B.1.1 Onboarding Tenant¶
FR-CP-001 [LES DEUX] [CI] : Données d'auto-inscription tenant - Le formulaire d'auto-inscription (Canal A) collecte : - Nom de l'entreprise (obligatoire, min 2 cars, max 200 cars) - Code pays (obligatoire, ISO 3166-1 alpha-2 ; MVP : "CI" uniquement, menu pré-sélectionné) - Tranche d'effectif déclaré (obligatoire, une parmi : "2-12", "13-29", "30-49", "50-99", "100-199", "200-350") - Email admin (obligatoire, format email valide, unique sur tous les tenants) - Nom complet admin (obligatoire, min 2 cars) - Numéro de téléphone admin (obligatoire, format CI : +225 XX XX XX XX XX, unique sur tous les tenants) - Numéro RCCM (facultatif — présent dans le formulaire, NON obligatoire. Ne bloque jamais l'onboarding ni le paiement.) - Sélection des modules (obligatoire, au moins 1 module) - Option essai gratuit (facultatif, case à cocher : "Démarrer un essai gratuit de 14 jours") - SI l'email admin OU le téléphone existe déjà dans le système ALORS afficher "Un compte existe déjà avec ces informations" et bloquer l'inscription - SI une correspondance approximative de nom d'entreprise est détectée (distance de Levenshtein < 3 OU correspondance normalisée) ALORS signaler pour révision par l'ops ALTARYS (NE PAS bloquer l'inscription) - Validation hCaptcha requise à la soumission - Limite de débit : max 3 tentatives d'inscription par IP par heure
FR-CP-002 [LES DEUX] : Vérification d'email - SI le formulaire d'auto-inscription est soumis avec succès ALORS envoyer un email de vérification avec un lien à usage unique (expire en 24 heures) - Email de vérification envoyé dans les 60 secondes après soumission du formulaire - Statut tenant = PENDING_VERIFICATION jusqu'au clic sur le lien - SI le lien a expiré ALORS le tenant peut demander un nouvel email de vérification (max 3 renvois par 24h)
FR-CP-003 [LES DEUX] : Routage post-vérification - SI le tenant a coché l'essai gratuit lors de l'inscription ALORS le statut passe à TRIAL (voir FR-CP-010) - SI le tenant n'a PAS coché l'essai gratuit ALORS le statut passe à PAYMENT_REQUIRED (voir FR-CP-011) - Dans les deux cas, déclencher le pipeline de provisionnement tenant (FR-CP-008)
FR-CP-004 [LES DEUX] : Canal B — Onboarding via Commercial CRM - SI le commercial déclenche "Convertir le prospect en tenant" depuis le CRM ALORS créer le tenant avec statut = PENDING ET enregistrer le salesperson_id sur l'entité tenant ET envoyer deux emails à l'admin tenant : - Email n°1 : lien de création de mot de passe (expire 7 jours) - Email n°2 : lien de paiement (avec résumé de la sélection de modules et tarification) ET l'email d'activation inclut un lien d'opt-out : "Si cette inscription est une erreur, cliquez ici pour annuler" - SI le lien d'opt-out est cliqué ALORS supprimer en soft delete le tenant PENDING et toutes ses données associées - L'ops ALTARYS reçoit un rapport hebdomadaire de tous les tenants créés via CRM pour vérification ponctuelle
FR-CP-005 [LES DEUX] : Canal B — Pas de validation manager - Le Canal B ne nécessite PAS l'approbation du manager CRM avant la création du tenant - La commission du commercial n'est attribuée QUE lorsque le paiement du tenant est confirmé (pas à l'inscription) - C'est une politique commerciale documentée dans le module CRM ; le Control Plane enregistre le salesperson_id sur le tenant
FR-CP-006 [LES DEUX] : Canal C — Back-Office Admin (Post-MVP) - NON dans le périmètre MVP - Contrat d'interface : la console d'administration prendra en charge la création de tenant avec tous les champs (y compris les surcharges) - Implémentation V2
FR-CP-007 [LES DEUX] : Détection de doublons tenant - À chaque inscription tenant (Canal A, B ou C), vérifier les doublons : - Correspondance exacte sur l'email admin → BLOQUER l'inscription, afficher l'erreur - Correspondance exacte sur le téléphone admin → BLOQUER l'inscription, afficher l'erreur - Correspondance approximative sur le nom d'entreprise (distance de Levenshtein < 3 après normalisation : minuscules, supprimer "SARL", "SA", "SAS", "SUARL", supprimer les espaces) → SIGNALER pour révision ops, NE PAS bloquer - SI un doublon est détecté et signalé ALORS créer une alerte dans la console d'administration pour l'ops ALTARYS
B.1.2 Pipeline de Provisionnement Tenant¶
FR-CP-008 [LES DEUX] : Étapes de provisionnement - Déclenché quand le tenant passe au statut TRIAL, PAYMENT_REQUIRED ou PENDING (Canal B) - Le pipeline exécute les étapes suivantes dans l'ordre : 1. Sélectionner le profil DB : Sélection automatique basée sur l'effectif déclaré (voir FR-CP-009). Les 4 profils sont disponibles : A (Partagé), B (Schéma-par-tenant), C (DB-par-tenant), D (BYO-DB). 2. Provisionner les ressources de base de données : - Profil A : Pas de création de base de données nécessaire. Données de référence insérées avec tenant_id. - Profil C : Créer une nouvelle base de données PostgreSQL pour le tenant. Exécuter les migrations. Insérer les données de référence. 3. Initialiser les données de référence spécifiques au pays (depuis le Moteur de Configuration Pays) : - Tranches ITS (CI) - Taux et plafonds CNPS (CI) - Types de congés par défaut selon le code du travail CI - Jours fériés pour CI (année en cours + année suivante) - Plan comptable SYSCOHADA - Catégories d'employés par défaut (CI) : Cadre, Agent de maîtrise, Employé, Ouvrier - Types de contrat par défaut : CDI, CDD, Stage, Apprentissage, Journalier, CTT - 5 meilleures grilles salariales sectorielles (CI) 4. Initialiser la structure organisationnelle par défaut : - Créer l'Entité racine (type : ENTITY, nom : nom de l'entreprise du tenant) - Initialiser 6 départements par défaut sous l'Entité racine : Direction Générale, Ressources Humaines, Comptabilité, Commercial, Production, Informatique - Les départements sont modifiables/supprimables par l'admin tenant 5. Configurer Keycloak : - Créer le client Keycloak pour le tenant (dans le single realm) - Créer l'utilisateur admin avec le rôle DG - Initialiser les rôles par défaut : DG, RH Manager, Comptable, Employé, Manager 6. Initialiser le contrôle d'accès aux modules : Activer uniquement les modules auxquels le tenant a souscrit 7. Initialiser le bucket MinIO : Créer un bucket de stockage scopé au tenant pour les pièces jointes 8. Envoyer la communication d'activation : Le contenu varie par canal (voir FR-CP-002, FR-CP-004) 9. Mettre à jour le statut tenant : Selon le canal et le statut de paiement (voir machine d'état) - SI une étape de provisionnement échoue ALORS : - Marquer le tenant comme PROVISIONING_FAILED (statut interne, non visible par le tenant) - Alerter l'ops ALTARYS via la console d'administration - Réessayer automatiquement jusqu'à 3 fois avec backoff exponentiel - Après 3 échecs, nécessiter une intervention manuelle
FR-CP-009 [LES DEUX] : Sélection automatique du profil DB (MVP) - SI l'effectif déclaré <= [seuil À DÉFINIR — voir OQ-CP-09] ALORS Profil A (Partagé) - SI l'effectif déclaré > [seuil À DÉFINIR] ALORS Profil C (DB-par-tenant) - Le tenant peut surcharger le profil auto-sélectionné lors de l'onboarding - Défaut MVP : commencer avec seuil = 200 (provisoire, à confirmer) - Les 4 profils (A, B, C, D) sont disponibles dès le MVP. Affiner les seuils selon les données opérationnelles.
B.1.3 Machine d'État du Cycle de Vie Tenant¶
FR-CP-010 [LES DEUX] : Statut TRIAL - SI le tenant a sélectionné l'essai gratuit ET n'a pas encore utilisé son essai unique ALORS statut = TRIAL - Durée de l'essai : 14 jours calendaires à compter de l'activation - Pendant TRIAL : tous les modules souscrits sont pleinement actifs (lecture + écriture) - SI l'essai expire sans paiement ALORS statut → SUSPENDED - SI paiement reçu pendant l'essai ALORS statut → ACTIVE (ou ACTIVE_INCOMPLETE si informations légales manquantes) - SI virement bancaire choisi pendant l'essai ALORS statut → ACTIVE_PAYMENT_PENDING - L'essai est unique par tenant. Il ne peut pas être demandé une seconde fois.
FR-CP-011 [LES DEUX] : Statut PAYMENT_REQUIRED - SI le tenant n'a pas sélectionné l'essai (Canal A) OU si le paiement via API a échoué ALORS statut = PAYMENT_REQUIRED - Pendant PAYMENT_REQUIRED : - Le tenant PEUT se connecter - Tous les modules sont en lecture seule - Le tenant PEUT inviter des utilisateurs (les utilisateurs voient aussi en lecture seule) - Le tenant PEUT encore demander son essai unique s'il ne l'a pas encore utilisé - Fenêtre de 14 jours pour finaliser le paiement - SI paiement reçu ALORS statut → ACTIVE (ou ACTIVE_INCOMPLETE si RCCM/NIF manquants) - SI virement bancaire choisi ALORS statut → ACTIVE_PAYMENT_PENDING - SI 14 jours écoulés sans paiement ET pas d'essai demandé ALORS statut → SUSPENDED
FR-CP-012 [LES DEUX] : Statut ACTIVE_PAYMENT_PENDING - SI le tenant a choisi le virement bancaire comme moyen de paiement ALORS statut = ACTIVE_PAYMENT_PENDING - Pendant ACTIVE_PAYMENT_PENDING : - Tous les modules souscrits sont pleinement actifs (lecture + écriture) pendant 10 jours calendaires - Le tenant peut choisir de payer via méthode API à tout moment (remplace l'attente du virement) - SI 10 jours écoulés sans confirmation de paiement ALORS statut → SUSPENDED - SI paiement confirmé (manuellement par l'admin OU via webhook si disponible) ALORS statut → ACTIVE (ou ACTIVE_INCOMPLETE)
FR-CP-013 [LES DEUX] : Statut ACTIVE_INCOMPLETE - SI le tenant a payé MAIS n'a pas fourni RCCM et/ou NIF ALORS statut = ACTIVE_INCOMPLETE - Pendant ACTIVE_INCOMPLETE : - Tous les modules souscrits sont pleinement actifs (lecture + écriture) - Le système affiche une bannière persistante et masquable : "Veuillez compléter vos informations légales (RCCM, NIF) dans les paramètres de votre entreprise." - Les factures émises pendant cette période sont des factures proforma (non conformes FNE, car le NIF acheteur est manquant) - SI RCCM et NIF fournis ALORS statut → ACTIVE - SI 3 mois écoulés sans RCCM/NIF ALORS : - Le statut passe à SUSPENDED (accès en lecture seule) - La bannière devient non masquable : "Accès en lecture seule. Complétez vos informations légales pour restaurer l'accès complet." - Un événement TenantStatusChanged est publié - Le tenant reçoit une notification expliquant le mode lecture seule et comment restaurer l'accès complet
FR-CP-014 [LES DEUX] : Statut ACTIVE - Statut = ACTIVE quand : - Paiement confirmé ET - Informations légales complètes (RCCM + NIF fournis) - Tous les modules souscrits pleinement actifs - Période d'abonnement suivie. Le système envoie des alertes de renouvellement à 30, 15 et 7 jours avant expiration.
FR-CP-015 [LES DEUX] : Statut SUSPENDED
- SI l'abonnement a expiré ET la période de grâce de 10 jours est écoulée (à compter de la date d'expiration)
OU SI l'admin suspend manuellement le tenant
ALORS statut = SUSPENDED
- Pendant SUSPENDED (MVP — lecture seule stricte) :
- TOUS les modules sont en lecture seule pour TOUS les utilisateurs (aucune création de données d'aucune sorte)
- Aucun nouvel employé ne peut être créé
- Aucune transaction (demandes de congé, traitements de paie, engagements) ne peut être initiée
- Les données existantes sont consultables
- L'admin tenant peut toujours accéder à la facturation/paiement pour réactiver
- SI paiement reçu ALORS statut → ACTIVE
- SI 30 jours écoulés sans paiement ALORS statut → DELETED (soft)
- V2 : Suspension par module — tenant_modules.status prend en charge SUSPENDED dès le premier jour (modèle de données prêt), logique d'application activée en V2
FR-CP-016 [LES DEUX] : Statut DELETED (Soft Delete) - DELETED est une suppression douce. L'enregistrement tenant et les métadonnées sont conservés. - Traitement des données à la suppression : 1. Données comptables/paie/financières : conservées 10 ans (AUDCIF Art. 24, obligatoire) 2. Données personnelles non financières (noms, photos, coordonnées) : anonymisées dans les 90 jours 3. Données archivées en stockage froid (MinIO) après anonymisation 4. Le stockage d'archivage est gratuit pendant la période légale de 10 ans (coût absorbé par ALTARYS) - Avant suppression : - Le tenant peut exporter TOUTES ses données (droit à la portabilité) - MVP : export via demande au support admin. V2 : outil d'export self-service. - SI l'admin demande manuellement la restauration dans les 30 jours suivant le soft delete ALORS le tenant peut être réactivé au statut SUSPENDED (paiement en attente)
FR-CP-017 [LES DEUX] : Suspension manuelle par l'admin - L'admin ALTARYS peut suspendre tout tenant à tout moment depuis la console d'administration - La raison de la suspension doit être enregistrée dans le journal d'audit - Email de suspension envoyé à l'admin tenant avec la raison - La réactivation nécessite également une action admin (ou un paiement, selon la raison)
B.1.4 Diagramme de la Machine d'État Tenant¶
stateDiagram-v2
[*] --> PENDING_VERIFICATION : Channel A (self-register)
[*] --> PENDING : Channel B (CRM)
PENDING_VERIFICATION --> TRIAL : Email verified + trial selected
PENDING_VERIFICATION --> PAYMENT_REQUIRED : Email verified + no trial
PENDING --> PAYMENT_REQUIRED : Password set, no payment yet
PENDING --> ACTIVE_PAYMENT_PENDING : Password set + bank transfer
PENDING --> ACTIVE : Password set + API payment collected
TRIAL --> ACTIVE : Payment received
TRIAL --> ACTIVE_INCOMPLETE : Payment received, legal info missing
TRIAL --> ACTIVE_PAYMENT_PENDING : Bank transfer chosen
TRIAL --> SUSPENDED : 14 days expired, no payment
PAYMENT_REQUIRED --> TRIAL : One-time trial requested
PAYMENT_REQUIRED --> ACTIVE : Payment received
PAYMENT_REQUIRED --> ACTIVE_INCOMPLETE : Payment received, legal info missing
PAYMENT_REQUIRED --> ACTIVE_PAYMENT_PENDING : Bank transfer chosen
PAYMENT_REQUIRED --> SUSPENDED : 14 days expired
ACTIVE_PAYMENT_PENDING --> ACTIVE : Payment confirmed
ACTIVE_PAYMENT_PENDING --> ACTIVE_INCOMPLETE : Payment confirmed, legal info missing
ACTIVE_PAYMENT_PENDING --> SUSPENDED : 10 days expired
ACTIVE_INCOMPLETE --> ACTIVE : Legal info completed
note right of ACTIVE_INCOMPLETE : After 3 months → read-only sub-state
ACTIVE --> SUSPENDED : Subscription expired + grace
ACTIVE --> SUSPENDED : Admin manual suspension
SUSPENDED --> ACTIVE : Payment received / admin reactivation
SUSPENDED --> DELETED : 30 days no action
DELETED --> [*]
note right of DELETED : Soft delete. 10-year archive for financial data.
B.2 Règles Fonctionnelles — Facturation et Abonnements¶
FR-CP-020 [LES DEUX] [CI] : Segments tarifaires - Les tenants sont affectés à un segment tarifaire selon l'effectif actif (employés avec statut ACTIVE ou PRE_HIRE) :
| Segment | Effectif | Modèle tarifaire |
|---|---|---|
| S1 | 2 – 12 | Forfait / mois |
| S2 | 13 – 29 | Forfait / mois |
| S3 | 30 – 49 | Forfait / mois |
| S4 | 50 – 99 | Par utilisateur/mois + Frais de plateforme |
| S5 | 100 – 199 | Par utilisateur/mois + Frais de plateforme |
| S6 | 200 – 350 | Par utilisateur/mois + Frais de plateforme |
- Segment déterminé à la facturation par instantané de l'effectif actif
- SI l'effectif franchit une limite de segment en cours de mois ALORS le nouveau segment s'applique au prochain cycle de facturation (pas de proratisation en cours de période dans le MVP)
FR-CP-021 [LES DEUX] [CI] : Calcul des Frais de Plateforme (S4-S6)
- Formule des Frais de Plateforme : FP = [1 + (x - 1) × 0,15] × frais_base
où x = nombre de modules actifs au moment de la facturation
- Frais de base par segment par an (depuis la grille tarifaire) :
| Segment | Frais de base (annuel) |
|---|---|
| S4 (50-99) | 360 000 FCFA |
| S5 (100-199) | 540 000 FCFA |
| S6 (200-350) | 660 000 FCFA |
- Les Frais de Plateforme sont une charge mensuelle : base annuelle / 12 × multiplicateur
FR-CP-022 [LES DEUX] : Tarification par Pack (Règles de Remise) - Les packs ne sont PAS des entités d'abonnement de premier ordre - Les packs sont des règles tarifaires : SI les modules souscrits d'un tenant correspondent à une combinaison de pack ALORS appliquer le prix pack remisé au lieu de la somme des prix individuels - La détection du pack s'exécute au moment de la facturation - SI plusieurs packs peuvent s'appliquer (modules qui se chevauchent) ALORS appliquer le pack qui offre la plus grande remise au tenant - Définitions des packs :
| Pack | Modules Inclus | Remise vs. Individuel |
|---|---|---|
| Pack Contrôle | Absence + Présence QR | ~12-30 % selon le segment |
| Pack Finance | NDF + Engagement/Budget | ~5-15 % |
| Pack Essentiel | Core RH + Absences | ~17-20 % |
| Pack Standard | Core RH + Absences + Présence QR | Remise incrémentale |
| Pack Complet | Core RH + Absences + Présence QR + NDF + Eval. Performance | Remise la plus élevée |
- Détails tarifaires des packs codés en dur dans le code applicatif pour le MVP (piloté par DB en V2)
FR-CP-023 [LES DEUX] : Génération de factures
- Facture mensuelle générée à la date de facturation du tenant (anniversaire du premier paiement)
- L'entité facture inclut les champs prêts FNE dès le premier jour :
- Numéro de facture : séquentiel par exercice fiscal, format FAC-{YYYY}-{numéro_zéro_padded} (ex., FAC-2026-00001)
- Date de facture
- Vendeur : coordonnées d'ALTARYS ENTERPRISE (nom, RCCM, NIF, adresse)
- Acheteur : coordonnées de l'entreprise tenant (nom, RCCM si disponible, NIF si disponible)
- Lignes : par module souscrit (nom, quantité, prix unitaire, total)
- Sous-total HT
- Montant TVA (18 % pour CI)
- Total TTC
- Champ QR code (vide jusqu'à l'intégration FNE DGI en V2)
- Champ numéro fiscal FNE (vide jusqu'en V2)
- Moyen de paiement
- Statut de paiement : DRAFT | SENT | PAID | OVERDUE | CANCELLED
- SI le tenant est ACTIVE_INCOMPLETE (pas de NIF/RCCM) ALORS la facture est marquée "Facture proforma" avec le champ NIF acheteur vide
- Tous les montants en XOF — zéro décimale (ex., 15 000 FCFA, jamais 15 000,00)
- Facture stockée de façon immuable. Corrections via avoirs (nouvelle facture), jamais en modifiant une facture existante.
FR-CP-024 [LES DEUX] : Traitement des paiements (agnostique fournisseur)
- Le moteur de facturation utilise une interface abstraite PaymentProvider :
interface PaymentProvider {
initiatePayment(invoiceId, amount, currency, returnUrl) → PaymentSession
handleWebhook(webhookPayload) → PaymentConfirmation
getPaymentStatus(paymentSessionId) → PaymentStatus
}
initiatePayment()
2. Le fournisseur retourne l'URL de redirection ou le widget → le tenant finalise le paiement
3. Le fournisseur envoie le webhook → le système appelle handleWebhook()
4. Paiement confirmé → mettre à jour le statut facture à PAID → mettre à jour le statut tenant selon la machine d'état
- SI le webhook confirme le paiement ALORS la mise à jour s'effectue automatiquement
- SI virement bancaire ALORS l'admin confirme manuellement via la console d'administration → paiement enregistré avec le user_id de l'admin dans le journal d'audit
- Solution de repli manuelle TOUJOURS disponible quelle que soit l'intégration fournisseur active
FR-CP-025 [LES DEUX] : Tableau de bord des paiements en attente (Console d'administration) - La console d'administration affiche un tableau de bord "Paiements en attente" avec : - Tous les tenants au statut ACTIVE_PAYMENT_PENDING (attente virement bancaire) - Tous les tenants au statut PAYMENT_REQUIRED - Tous les tenants avec des factures OVERDUE - Jours restants avant suspension pour chacun - Bouton d'action "Confirmer le paiement" par tenant - Le tableau de bord se rafraîchit automatiquement (push WebSocket ou polling)
FR-CP-026 [LES DEUX] : Métrologie - Suivre par tenant par mois : - Effectif actif (statut = ACTIVE ou PRE_HIRE) - Liste des modules actifs (codes modules avec statut = ACTIVE) - Stockage utilisé (MinIO — pertinent pour la future tarification ADVDOC) - Données de métrologie utilisées pour : calcul de facturation, détermination du segment, métriques plateforme
B.3 Règles Fonctionnelles — Gestion des Utilisateurs et Authentification¶
FR-CP-030 [LES DEUX] : Entité Utilisateur
- Attributs utilisateur :
- id (UUID v7, généré côté serveur pour les utilisateurs — les utilisateurs sont toujours créés en ligne)
- email (unique globalement, identifiant de connexion)
- first_name
- last_name
- phone
- status : ACTIVE | DISABLED | INVITED
- home_tenant_id (FK vers Tenant — le tenant principal/d'accueil de l'utilisateur)
- created_date, last_login_date
- Un Utilisateur n'est PAS nécessairement un Employé. Exemple : un comptable externe est un Utilisateur mais pas dans le registre Employés.
- Un Utilisateur peut appartenir à plusieurs tenants (via la table user_tenant_memberships)
- Chaque membership a : user_id, tenant_id, role_ids[], joined_date
FR-CP-031 [LES DEUX] : Invitation utilisateur - L'admin tenant invite un utilisateur par email - SI l'email n'existe pas dans le système ALORS : - Créer l'utilisateur avec statut = INVITED - Envoyer un email d'invitation avec un lien de création de mot de passe (expire 7 jours) - Ajouter un membership au tenant actuel avec les rôles spécifiés - SI l'email existe déjà (utilisateur dans un autre tenant) ALORS : - Ajouter un nouveau membership au tenant actuel avec les rôles spécifiés - Envoyer la notification : "Vous avez été ajouté à l'entreprise {companyName}" - L'utilisateur peut désormais accéder aux deux tenants via le sélecteur de tenant - SI l'utilisateur invité ne finalise pas l'inscription dans les 7 jours ALORS le lien expire ; l'admin peut ré-inviter
FR-CP-032 [LES DEUX] : Intégration Keycloak
- Single realm Keycloak pour tous les tenants (MVP)
- Flux de connexion :
1. L'utilisateur soumet email + mot de passe → Keycloak authentifie
2. SI l'utilisateur appartient à 1 tenant → JWT émis avec ce tenant_id
3. SI l'utilisateur appartient à plusieurs tenants → le système retourne la liste des memberships tenant avec noms + logos. L'utilisateur atterrit sur home_tenant_id par défaut.
4. Claims JWT : user_id (UUID), tenant_id (UUID), country_code (ISO), roles[] (noms de rôles), iat, exp
5. Expiration du token d'accès : 15 minutes
6. Expiration du token de rafraîchissement : 7 jours
- Échange de token pour le changement de tenant :
- L'utilisateur demande à basculer vers le tenant B
- Le système valide que l'utilisateur a un membership dans le tenant B
- Nouveau JWT émis pour le tenant B (sans ré-authentification)
- L'ancien JWT pour le tenant A est invalidé
- Récupération de mot de passe : via le flux email Keycloak
FR-CP-033 [LES DEUX] : Modèle de permissions hybride
- Le JWT contient : user_id, tenant_id, country_code, roles[] (noms de rôles uniquement, PAS les permissions individuelles)
- À la première connexion (ou changement de rôle), le frontend récupère la matrice de permissions complète pour les rôles de l'utilisateur :
GET /api/v1/auth/my-permissions → retourne { permissions: ["absence.leave_request.create", "absence.leave_request.approve", ...] }
- Le frontend met en cache cette matrice de permissions dans IndexedDB (fonctionne hors ligne)
- Le frontend vérifie les permissions en cache pour le rendu UI (afficher/masquer boutons, éléments de navigation)
- Le backend valide TOUJOURS les permissions côté serveur sur chaque requête (ne fait jamais confiance au frontend)
- Invalidation du cache : au changement de rôle (notifié via WebSocket), au rechargement de l'app, au changement de tenant
- Format des permissions : {module_code}.{entity}.{action}
- Exemples : absence.leave_request.approve, payroll.payslip.read, cp.employee.create, cp.tenant.manage
- Actions : create, read, update, delete, approve, export
FR-CP-034 [LES DEUX] : Rôles par défaut (initialisés par tenant) | Rôle | Permissions principales | Périmètre | |---|---|---| | DG (Directeur Général) | Accès complet à tous les modules actifs au sein du tenant | Tenant entier | | RH Manager (Responsable RH) | Gestion des employés, absences, présence, supervision paie | Tenant entier | | Comptable | Budget, engagements, notes de frais, paie (lecture), comptabilité | Tenant entier | | Employé | Self-service uniquement : ses propres congés, bulletins de paie, profil (lecture), pointage | Ses propres données | | Manager (Chef de Service) | Approbations limitées à l'équipe : valider les congés des N-1, voir les données d'équipe | N-1 directs |
- Les rôles par défaut sont immuables (ne peuvent être supprimés ni renommés par l'admin tenant)
- V2 : L'admin tenant peut créer des rôles personnalisés avec des ensembles de permissions sur mesure
FR-CP-035 [LES DEUX] : Permissions conditionnées aux modules - Une permission est effective UNIQUEMENT SI : 1. Le rôle de l'utilisateur l'inclut, ET 2. Le module correspondant est ACTIVE pour le tenant (via la table tenant_modules), ET 3. Le statut tenant autorise les opérations d'écriture (pas SUSPENDED, pas en mode lecture seule) - SI le module est INACTIVE pour le tenant ALORS toutes les permissions de ce module sont refusées silencieusement (pas d'erreur — juste masquées dans l'UI, 403 en API) - SI le tenant est SUSPENDED ALORS toutes les permissions d'écriture sont refusées ; les permissions de lecture restent actives
B.4 Règles Fonctionnelles — Organisation et Données de Référence¶
FR-CP-040 [LES DEUX] : Arbre des unités organisationnelles - Chaque tenant dispose d'un arbre hiérarchique d'unités organisationnelles - Types d'unités : ENTITY (racine), SITE (optionnel, intermédiaire), DEPARTMENT (feuille) - Modèle de données par unité : - id (UUID v7), tenant_id, name, type (ENTITY | SITE | DEPARTMENT), parent_id (FK vers soi-même), status (ACTIVE | INACTIVE), created_date, deactivated_date - Règles de hiérarchie : - L'Entité est toujours la racine (parent_id = null) - MVP : une Entité par tenant. V2 : multi-entité pour les groupes d'entreprises. - Le parent d'un Site doit être une Entité - Le parent d'un Département peut être une Entité (sans sites) OU un Site - Pas de suppressions définitives — désactivation uniquement (intégrité des données historiques) - Unités désactivées : les employés existants restent affectés mais les nouvelles affectations sont bloquées - Initialisé à l'onboarding : Entité racine (depuis le nom de l'entreprise) + 6 départements par défaut (Direction Générale, RH, Comptabilité, Commercial, Production, Informatique) - Les départements par défaut peuvent être renommés, désactivés, ou complétés par de nouveaux départements par l'admin tenant
FR-CP-041 [LES DEUX] : Catalogue de postes - Chaque tenant dispose d'un catalogue de postes - Modèle de données par poste : - id (UUID v7), tenant_id, title, description (optionnel), category_id (FK vers EmployeeCategory), department_id (FK optionnel vers OrgUnit — affectation par défaut), status (ACTIVE | INACTIVE), is_custom (booléen) - Structuré avec trappe de secours : - L'admin tenant peut créer des postes via l'écran de gestion du catalogue - Lors de l'affectation d'un poste à un employé, SI le poste souhaité n'existe pas, l'utilisateur peut saisir un intitulé personnalisé → le système crée automatiquement un nouveau poste dans le catalogue (is_custom = true) - Les postes personnalisés sont disponibles pour réutilisation (apparaissent dans la liste déroulante pour les futures affectations) - Chaque poste est lié à une EmployeeCategory (obligatoire). Ce lien est critique : les taux CNPS dépendent de la catégorie.
FR-CP-042 [LES DEUX] [CI] : Catégories d'employés - Les catégories sont des données de référence spécifiques au pays - Catégories CI (initialisées à l'onboarding) : - Cadre (code : CAD) - Agent de maîtrise (code : AM) - Employé (code : EMP) - Ouvrier (code : OUV) - Modèle de données par catégorie : - id (UUID v7), tenant_id, country_code, name, code, description (optionnel), sort_order, is_system (booléen) - Les catégories initialisées par le système (is_system = true) ne peuvent pas être supprimées. Peuvent être désactivées. - L'admin tenant peut ajouter des catégories personnalisées (is_system = false) - 5 meilleures grilles salariales sectorielles pour CI (à renseigner dans Config. Pays) : - Commerce, BTP (Bâtiment et Travaux Publics), Industrie, Services, Agriculture - Chaque secteur a un salaire minimum par catégorie - Grille stockée dans la table Config. Pays (voir FR-CP-070)
FR-CP-043 [LES DEUX] : Référentiel des types de contrat - Les types de contrat sont des données de référence par pays - Types de contrat CI (initialisés à l'onboarding) : - CDI — Contrat à Durée Indéterminée (max_duration_months : null) - CDD — Contrat à Durée Déterminée (max_duration_months : 24, renewable_once : true) - Stage — Stage (max_duration_months : 24) - Apprentissage — Apprentissage (max_duration_months : 36) - Journalier — Travailleur journalier (max_duration_months : null) - CTT — Contrat de Travail Temporaire (max_duration_months : 24) - Modèle de données : id (UUID v7), tenant_id, country_code, name, code, max_duration_months (nullable), description, is_system, status - Règle de requalification CDD : SI la durée cumulée des CDD pour un employé atteint 2 ans ALORS le système génère des alertes à 90, 30 et 7 jours avant la limite (consommées par le module Gestion des Employés quand il sera construit ; règle stockée dans Config. Pays maintenant)
FR-CP-044 [LES DEUX] : Hiérarchie managériale (N/N+1)
- Chaque employé a 0 ou 1 manager direct (N+1)
- Le manager est une FK de Employé vers Employé (auto-référencement)
- Hiérarchie plate (N/N+1 uniquement) dans le MVP. Pas de traversée de chaîne multi-niveau.
- L'affectation du manager est historisée :
- Table manager_history : employee_id, manager_id, start_date, end_date
- Au changement de manager : l'enregistrement précédent reçoit end_date = aujourd'hui, nouvel enregistrement créé avec start_date = aujourd'hui
- Historique interrogeable : "Qui était le manager de X à la date Y ?"
- Manager NULL autorisé (voir FR-CP-045 Approbateur par défaut)
- Événement publié : ManagerChanged (employeeId, tenantId, oldManagerId, newManagerId, effectiveDate)
FR-CP-045 [LES DEUX] : Approbateur par défaut
- Chaque tenant a un Approbateur par défaut (Approbateur par défaut)
- L'Approbateur par défaut est défini lors de l'onboarding = l'utilisateur admin tenant
- Configurable dans les paramètres de l'entreprise (peut être changé pour le RH Manager ou tout autre utilisateur)
- L'Approbateur par défaut reçoit les demandes d'approbation quand la chaîne N+1 normale est rompue :
- L'employé n'a pas de manager attribué (manager_id = null)
- L'employé EST le DG/PDG (pas de N+1 existant) — SI le DG EST AUSSI l'Approbateur par défaut ALORS ses demandes sont auto-approuvées
- Le manager de l'employé a été licencié — les demandes sont acheminées vers l'Approbateur par défaut jusqu'à ce que les RH réaffectent
- Lors du routage vers l'Approbateur par défaut, le système génère une alerte au RH Manager : "L'employé {name} n'a pas de responsable assigné. Veuillez assigner un N+1."
- Modèle de données : tenant_settings.default_approver_user_id (FK vers User)
B.5 Règles Fonctionnelles — Entité Employé (Baseline)¶
FR-CP-050 [LES DEUX] : Entité Employé — Baseline Control Plane
- Le Control Plane possède une entité Employé minimale. C'est la baseline consommée par TOUS les modules.
- Attributs :
- id : UUID v7 (généré côté client — fonctionne hors ligne)
- tenant_id : UUID (FK vers Tenant, obligatoire)
- matricule : String - Identifiant séquentiel unique d'un employé au sein d'un tenant. Format : {PREFIX}-{YEAR}-{MM}-{SEQ}.
- first_name : String (obligatoire, min 2 cars)
- last_name : String (obligatoire, min 2 cars)
- email : String (optionnel — tous les employés n'ont pas d'email, notamment les ouvriers)
- phone : String (optionnel)
- contract_type_id : UUID (FK vers référentiel ContractType — PAS du texte libre)
- org_unit_id : UUID (FK vers OrganisationalUnit — affectation département/site)
- position_id : UUID (FK vers catalogue Position — PAS du texte libre)
- category_id : UUID (dérivé de poste → catégorie, OU affecté directement)
- hire_date : Date (obligatoire)
- manager_id : UUID (FK vers Employé, auto-référencement — nullable, voir FR-CP-045)
- status : Enum (PRE_HIRE | ACTIVE | SUSPENDED | TERMINATED)
- status_effective_date : Date (quand le statut actuel a pris effet)
- created_date, updated_date
- ON_LEAVE N'EST PAS un statut. Le statut de congé est dérivé : SI un employé ACTIVE a un enregistrement de congé approuvé chevauchant la date du jour ALORS l'UI affiche le badge "En congé" mais le statut sous-jacent reste ACTIVE.
FR-CP-051 [LES DEUX] : Transitions de statut employé
stateDiagram-v2
[*] --> PRE_HIRE : Employee created with future hire date
[*] --> ACTIVE : Employee created with today or past hire date
PRE_HIRE --> ACTIVE : Hire date reached
ACTIVE --> SUSPENDED : Administrative suspension (disciplinary, etc.)
SUSPENDED --> ACTIVE : Suspension lifted
ACTIVE --> TERMINATED : Employment ended
SUSPENDED --> TERMINATED : Employment ended during suspension
TERMINATED --> [*]
note right of TERMINATED : Soft delete. Data retained for compliance.
- PRE_HIRE → ACTIVE : Transition automatique à la date d'embauche (traitement batch quotidien à 00h01 heure locale du tenant)
- Les employés TERMINATED ne sont jamais supprimés définitivement. Données conservées selon les exigences OHADA.
- Les changements de statut produisent l'événement
EmployeeUpdatedavec changedFields incluant "status"
FR-CP-052 [LES DEUX] : Schéma UUID v7 à l'échelle de la plateforme - TOUTES les entités créées par les utilisateurs (employés, unités org., postes, demandes de congé, enregistrements de présence, etc.) utilisent UUID v7 comme clé primaire - UUID v7 est généré CÔTÉ CLIENT au moment de la création (y compris hors ligne) - Avantages : - Pas de conflits d'ID entre clients hors ligne - Tri par temps (performance des index de base de données) - Permanent dès la naissance — pas de remappage temp-ID vers real-ID à la synchronisation - Le serveur ne génère JAMAIS d'IDs pour les entités créées par l'utilisateur. Il valide le format UUID v7 à la réception. - Les entités générées par le serveur (entrées d'audit, événements système, enregistrements de facturation) utilisent aussi UUID v7 mais généré côté serveur.
FR-CP-053 [LES DEUX] : Matricule employé (attribué par le serveur)
- Le matricule est un numéro d'employé lisible par l'humain, distinct de la clé primaire UUID
- Attribué par le serveur à la première synchronisation (ou immédiatement si créé en ligne)
- Format : {PREFIX}-{YEAR}-{MM}-{SEQ}.
- Préfixe : {PREFIX} est un trigramme ou un mot de 4 à 6 lettres dérivé du nom de l'entreprise
- Année : {YEAR} année courante d'inscription de l'employé
- Mois : {MM} mois courant d'inscription de l'employé
- Séquence : {SEQ} auto-incrémentale par tenant, mensuelle, avec zéros non significatifs sur 3 chiffres minimum, recommence à 001 chaque mois
- L'admin tenant peut configurer : préfixe, longueur du padding
- Hors ligne (avant synchronisation) : l'UI affiche "Numéro en attente" à la place du matricule
- Le matricule est unique au sein du tenant. Pas unique globalement.
- Le matricule est immuable une fois attribué (ne change jamais, même si l'employé est licencié)
FR-CP-054 [LES DEUX] : Création d'employé hors ligne - Le formulaire employé fonctionne hors ligne (PWA + IndexedDB) - Flux de création hors ligne : 1. L'utilisateur remplit le formulaire → le client génère UUID v7 → sauvegarde dans IndexedDB 2. Le champ matricule affiche "Numéro en attente" 3. L'employé est immédiatement disponible pour d'autres opérations hors ligne (ex., une demande de congé référence l'UUID) 4. À la reconnexion : la file de synchronisation envoie l'employé au serveur 5. Le serveur valide, attribue le matricule, persiste dans PostgreSQL 6. Le serveur retourne le matricule → le client met à jour l'enregistrement IndexedDB 7. Toutes les entités référençant l'UUID de cet employé restent valides (UUID était permanent dès l'étape 1) - Détection de conflits à la synchronisation : - Email en doublon au sein du tenant → signaler pour résolution par l'admin - Téléphone en doublon au sein du tenant → signaler pour résolution par l'admin - Pas de conflits UUID possibles (UUID v7 généré côté client)
B.6 Règles Fonctionnelles — Contrôle d'Accès aux Modules¶
FR-CP-060 [LES DEUX] : Suivi du statut des modules
- Table tenant_modules : tenant_id, module_code, status, activated_date, deactivated_date
- Valeurs de statut de module (MVP) : ACTIVE | INACTIVE | TRIAL
- Valeurs de statut de module (V2) : + SUSPENDED (suspension par module)
- Codes modules vendables : HRCORE, EMPMGT, ABSMGT, QRCONTR, TIMACT, EXPMGT, COMMIT, PAYROL, HSEMGT, PERFOB, GPEC, LTRAIN, ATSMGT, ADVDOC, CRMMGT
- Non vendable : BUDMGT (auto-inclus — voir FR-CP-061)
- Le Control Plane et l'Organisation et Données de Référence sont toujours actifs (socle plateforme, non soumis au contrôle d'accès)
FR-CP-061 [LES DEUX] : Auto-inclusion des Lignes Budgétaires - SI EXPMGT OU COMMIT est ACTIVE ALORS BUDMGT est automatiquement mis à ACTIVE - SI NI EXPMGT NI COMMIT n'est ACTIVE ALORS BUDMGT est automatiquement mis à INACTIVE - BUDMGT n'apparaît jamais dans l'UI de sélection des modules (non vendable) - Les changements de statut BUDMGT sont enregistrés dans le journal d'audit avec la raison "Règle d'auto-inclusion"
FR-CP-062 [LES DEUX] : Contrôle d'accès aux modules au niveau API
- Chaque endpoint API pour un module soumis au contrôle vérifie : tenant_modules.status = ACTIVE pour le module concerné
- SI le module est INACTIVE pour le tenant demandeur ALORS retourner HTTP 403 avec le corps :
{
"error": "MODULE_NOT_SUBSCRIBED",
"module": "PAYROL",
"message": "Module non souscrit. Contactez votre administrateur pour activer ce module."
}
FR-CP-063 [PAPILLON UNIQUEMENT] : Affichage upsell frontend - Les modules non souscrits apparaissent dans le menu de navigation, grisés et verrouillés - Chaque module verrouillé affiche : - Icône du module (grisée) - Nom du module - Brève description d'une ligne - Bouton CTA "Souscrire" - SI l'utilisateur est admin tenant ET clique sur "Souscrire" ALORS rediriger vers la gestion de facturation/abonnement - SI l'utilisateur n'est PAS admin ET clique sur "Souscrire" ALORS afficher : "Contactez votre administrateur pour activer ce module." - [ENTERPRISE UNIQUEMENT] : Le frontend Enterprise peut utiliser un schéma upsell différent (décision de conception V2)
FR-CP-064 [LES DEUX] : Désactivation de module (Rétrogradation) - Quand un tenant désactive un module : 1. Le système vérifie les dépendances en aval et avertit : - "Le module Paie utilise les données d'Absences. La désactivation d'Absences rendra ces données inaccessibles à la Paie." 2. SI l'admin confirme ALORS statut module → INACTIVE 3. Les données du module désactivé restent : - Consultables par le tenant (accès en lecture seule aux données historiques) - Exportables par le tenant (portabilité des données) - Consultables par les autres modules actifs (ex., la Paie peut toujours lire les données d'absence historiques) - NON modifiables — aucune nouvelle donnée ne peut être créée pour un module inactif 4. Le module peut être réactivé à tout moment → accès complet restauré
B.7 Règles Fonctionnelles — Moteur de Configuration Pays¶
FR-CP-070 [LES DEUX] [CI] : Modèle de données de Configuration Pays - La configuration pays est stockée dans des tables DB (pas en dur dans le code). Modifiable par l'admin ALTARYS sans redéploiement. - La configuration est versionnée par exercice fiscal (un pays peut modifier les taux en cours d'année) - Tous les modules de la couche applicative lisent la configuration pays depuis ce moteur via API interne
FR-CP-071 [LES DEUX] [CI] : Configuration fiscale — Tranches ITS
- [PROVISOIREMENT VALIDÉ — TV-CP-05]
- Stocké comme : table country_tax_brackets
- country_code, fiscal_year, bracket_lower, bracket_upper, rate_percent, effective_date
- Tranches ITS CI (Ordonnance n° 2023-719 — non vérifiée) :
| Inférieur (FCFA/mois) | Supérieur (FCFA/mois) | Taux |
|---|---|---|
| 0 | 75 000 | 0 % |
| 75 001 | 240 000 | 16 % |
| 240 001 | 800 000 | 21 % |
| 800 001 | 2 400 000 | 24 % |
| 2 400 001 | 8 000 000 | 28 % |
| 8 000 001 | ∞ | 32 % |
- RICF (Réduction d'Impôt pour Charges de Famille) : [OUVERT — OQ-CP-02] — formule et montants de déduction pas encore définis. Doit être recherché avant l'implémentation du module Paie.
- Indicateur
is_verifiedsur chaque enregistrement de taux. Le tableau de bord admin ALTARYS affiche les taux non vérifiés en orange.
FR-CP-072 [LES DEUX] [CI] : Configuration des cotisations sociales — Taux CNPS
- [PROVISOIREMENT VALIDÉ — TV-CP-01 à TV-CP-04]
- Stocké comme : table country_social_contributions
- country_code, fiscal_year, branch_code, branch_name, employer_rate, employee_rate, ceiling_monthly (nullable), risk_class (nullable), effective_date, is_verified
| Branche | Code | Employeur | Employé | Plafond (FCFA/mois) | Notes |
|---|---|---|---|---|---|
| Prestations Familiales | PF | 5,00 % | 0 % | Sans plafond | |
| Maternité | MAT | 0,75 % | 0 % | Sans plafond | |
| Accidents du Travail / Maladies Prof. | ATMP | 2,00–5,00 % | 0 % | Sans plafond | Taux variable selon classe de risque |
| Assurance Vieillesse | AV | 7,70 % | 6,30 % | 3 375 000 | Source plafond : Communiqué CNPS 2023 (non vérifié) |
- Classe de risque AT/MP : stockée dans une table de référence séparée
atmp_risk_classes(class_code, rate, description). Le tenant sélectionne sa classe de risque dans les paramètres de l'entreprise. - Tous les taux marqués
is_verified = falsejusqu'à ce que les numéros de décret soient confirmés.
FR-CP-073 [LES DEUX] [CI] : Grilles salariales par catégorie d'employé
- 5 meilleures grilles salariales sectorielles stockées dans : country_sector_salary_grids
- country_code, sector_code, sector_name, category_code, minimum_salary_monthly, effective_date
- Secteurs : Commerce (COM), BTP (BTP), Industrie (IND), Services (SVC), Agriculture (AGR)
- Valeurs des grilles à renseigner lors de la recherche juridique pour le module Paie
- Utilisées par la Paie pour la validation du salaire minimum par catégorie et par secteur
FR-CP-074 [LES DEUX] [CI] : Types de congés, Jours fériés et SYSCOHADA - Types de congés initialisés par pays : à détailler dans le PRD Gestion des Absences. Le Control Plane stocke les données de référence. - Jours fériés pour CI initialisés par année. [BACKLOG : OQ-CP-06] — Trouver une API ou une méthode pour fournir les jours fériés nationaux pour les 17 pays OHADA de façon programmatique. - Plan comptable SYSCOHADA : plan comptable OHADA standard (identique pour les 17 pays), initialisé à l'onboarding tenant pour que le module Comptabilité puisse le consommer.
B.8 Règles Fonctionnelles — Journal d'Audit¶
FR-CP-080 [LES DEUX] : Structure d'une entrée d'audit - Chaque opération de création/modification/suppression sur TOUS les modules produit une entrée d'audit immuable - Attributs d'une entrée d'audit : - id : UUID v7 (généré côté serveur) - tenant_id : UUID - user_id : UUID (qui a effectué l'action) - timestamp : Instant (UTC) - entity_type : String (ex., "Employee", "LeaveRequest", "Tenant") - entity_id : UUID - module_code : String (ex., "CP", "ABSMGT", "PAYROL") - action : Enum (CREATE | UPDATE | DELETE) - before_value : JSONB (instantané de l'entité avant mutation — null pour CREATE) - after_value : JSONB (instantané de l'entité après mutation — null pour DELETE) - metadata : JSONB (optionnel — contexte supplémentaire comme "raison de suspension") - Les entrées d'audit sont : - Immuables : pas de suppression, pas de modification, jamais - Scopées au tenant : l'admin tenant voit uniquement le journal d'audit de son tenant - Scopées à la plateforme pour l'admin : l'admin ALTARYS peut interroger tous les tenants - Conservées 10 ans minimum (AUDCIF Art. 24)
FR-CP-081 [LES DEUX] : API de consultation du journal d'audit
- GET /api/v1/audit?entity_type=Employee&date_from=2026-01-01&date_to=2026-02-28
- Filtrable par : entity_type, entity_id, user_id, action, module_code, plage de dates
- Paginé (50 par page par défaut)
- Scopé automatiquement au tenant via TenantContext
- Export CSV (V2)
FR-CP-082 [LES DEUX] : AUDCIF Art. 22 — PAS dans le Control Plane - Les fonctionnalités spécifiques AUDCIF (verrouillage mensuel de période, clôture trimestrielle, numérotation séquentielle avec chaînes de hachage) sont de la responsabilité du module Comptabilité - Le journal d'audit du Control Plane fournit une journalisation immuable et horodatée — cela satisfait les exigences générales de traçabilité - Le module Comptabilité implémentera des mécanismes d'intégrité supplémentaires (chaînes de hachage, verrous de période) sur ses propres entrées de journal
B.9 Règles Fonctionnelles — Service de Notifications¶
FR-CP-090 [LES DEUX] : Service de notifications partagé - Le Control Plane fournit un service de notifications centralisé utilisé par TOUS les modules - Canaux de notification (MVP) : in-app (icône cloche) + email - Tout module publie une notification via API interne :
NotificationService.send(
tenantId, recipientUserId, channel (IN_APP | EMAIL | BOTH),
templateCode, templateParams, priority (LOW | NORMAL | HIGH | URGENT)
)
FR-CP-091 [LES DEUX] [CI] : Templates email MVP (~10)
| Code Template | Déclencheur | Canal |
|---|---|---|
| ONBOARDING_WELCOME_A | Canal A : vérification email | Email |
| ONBOARDING_WELCOME_B_PASSWORD | Canal B : lien de création de mot de passe | Email |
| ONBOARDING_WELCOME_B_PAYMENT | Canal B : lien de paiement | Email |
| EMAIL_VERIFICATION | Vérification d'adresse email (renvoi) | Email |
| PASSWORD_RESET | Demande de récupération de mot de passe | Email |
| INVOICE_GENERATED | Facture mensuelle créée | Email |
| PAYMENT_RECEIVED | Paiement confirmé | Email + In-App |
| PAYMENT_FAILED | Tentative de paiement API échouée | Email + In-App |
| SUBSCRIPTION_EXPIRING | 30/15/7 jours avant expiration de l'abonnement | Email + In-App |
| ACCOUNT_SUSPENDED | Tenant suspendu | Email |
| ACCOUNT_REACTIVATED | Tenant réactivé après paiement | Email + In-App |
| USER_INVITATION | Utilisateur invité dans un tenant | Email |
| MISSING_LEGAL_INFO | Rappel pour compléter RCCM/NIF (ACTIVE_INCOMPLETE) | Email + In-App |
B.10 Règles Fonctionnelles — Console d'Administration (Opérateurs ALTARYS)¶
FR-CP-100 [LES DEUX] : Console d'administration — Application séparée
- La console d'administration est une application React séparée :
- URL : non publique (ex., admin.altarys.internal ou restreinte par IP)
- Bureau uniquement, pas d'exigence mobile-first
- Pas de support hors ligne (le personnel ALTARYS a une connectivité fiable)
- Pas de PWA, pas de service workers
- Authentification Keycloak avec le rôle Platform Admin (distinct des rôles tenant)
- Accès restreint au personnel ALTARYS avec le rôle Platform Admin
FR-CP-101 [LES DEUX] : Console d'administration — Fonctionnalités MVP | Fonctionnalité | Description | |---|---| | Liste des tenants | Tous les tenants avec : nom, statut, segment, effectif, modules actifs, date de création, statut de paiement. Filtrable, triable, consultable. | | Détail tenant | Informations complètes tenant : coordonnées entreprise, abonnement, liste modules, utilisateur admin, historique facturation, journal d'audit. | | Gestion des paiements | Tableau de bord des paiements en attente (FR-CP-025). Confirmation manuelle de paiement. Historique des factures. | | Suspension / Réactivation | Suspendre ou réactiver manuellement un tenant. Raison obligatoire (enregistrée dans l'audit). | | Override de module | Accorder à un tenant l'accès à un module (ex., module d'essai, accès promotionnel). Override enregistré dans l'audit. | | Révision des inscriptions CRM | Rapport hebdomadaire des tenants créés via CRM. Signaler les inscriptions suspectes. | | Métriques de base | Total des tenants par statut, total des employés actifs, revenus (MRR), top modules par nombre de souscriptions. | | Gestion de la configuration pays | Consulter/modifier les taux fiscaux, taux CNPS, types de congés, jours fériés. Signaler les taux non vérifiés. |
B.11 Exigences de données — Résumé des entités clés¶
| Entité | Champs clés | Scopée au tenant ? | Module |
|---|---|---|---|
| Tenant | id, name, country_code, status, db_profile, segment, onboarding_channel, salesperson_id, default_approver_user_id | Non (niveau plateforme) | CP |
| TenantModule | tenant_id, module_code, status, activated_date, deactivated_date | Oui | CP |
| User | id, email, first_name, last_name, phone, status, home_tenant_id | Non (cross-tenant) | CP |
| UserTenantMembership | user_id, tenant_id, role_ids[], joined_date | Oui | CP |
| Role | id, name, code, permissions[], is_system | Non (global plateforme) | CP — Les rôles sont au niveau plateforme (SUPER_ADMIN, TENANT_ADMIN, MANAGER, EMPLOYEE, etc.). Les tenants sélectionnent les rôles à utiliser via UserTenantMembership mais ne peuvent pas en créer. |
| OrganisationalUnit | id, tenant_id, name, type, parent_id, status | Oui | CP (Org) |
| Position | id, tenant_id, title, category_id, department_id, is_custom, status | Oui | CP (Org) |
| EmployeeCategory | id, tenant_id, country_code, name, code, is_system | Oui | CP (Org) |
| ContractType | id, tenant_id, country_code, name, code, max_duration_months | Oui | CP (Org) |
| Employee | id, tenant_id, matricule, first_name, last_name, email, phone, contract_type_id, org_unit_id, position_id, category_id, hire_date, manager_id, status | Oui | CP |
| ManagerHistory | employee_id, manager_id, start_date, end_date | Oui | CP |
| Invoice | id, tenant_id, invoice_number, date, seller_, buyer_, line_items[], subtotal_ht, tva_amount, total_ttc, qr_code, fne_fiscal_number, payment_status | Oui | CP (Facturation) |
| Payment | id, tenant_id, invoice_id, provider, amount, currency, status, provider_reference, confirmed_by_user_id | Oui | CP (Facturation) |
| AuditEntry | id, tenant_id, user_id, timestamp, entity_type, entity_id, module_code, action, before_value, after_value | Oui | CP |
| Notification | id, tenant_id, recipient_user_id, template_code, channel, priority, read, created_date | Oui | CP |
| CountryTaxBracket | country_code, fiscal_year, bracket_lower, bracket_upper, rate, is_verified | Non (référence) | CP (Config) |
| CountrySocialContribution | country_code, fiscal_year, branch_code, employer_rate, employee_rate, ceiling, is_verified | Non (référence) | CP (Config) |
| CountrySectorSalaryGrid | country_code, sector_code, category_code, minimum_salary, effective_date | Non (référence) | CP (Config) |
| PublicHoliday | country_code, year, date, name | Non (référence) | CP (Config) |
| TenantSettings | tenant_id, fiscal_year_start_month, working_days_per_week, working_hours_per_day, currency, logo_url, matricule_prefix, matricule_padding, default_approver_user_id, atmp_risk_class | Oui | CP |
B.12 Implications Multi-Tenant¶
Profil A — Base de données partagée (MVP)¶
- Tous les tenants dans une seule base de données PostgreSQL
- Chaque table a une colonne
tenant_id(sauf les tables de données de référence) - TenantAwareRepository obligatoire : Tous les repositories de module étendent la classe de base
TenantAwareRepository. Impossible d'exécuter une requête sans scopage tenant. Appliqué au niveau de la couche repository — conception "pit of success". - Le filtre Spring Security extrait le
tenant_iddu JWT → définit leTenantContext(ThreadLocal/ScopedValue) → chaque requête en aval l'utilise - Index : tous les patterns de requête primaires incluent tenant_id (index composites)
Profil C — DB-par-Tenant (MVP)¶
- Chaque tenant obtient sa propre base de données PostgreSQL
- Le provisionnement tenant crée une nouvelle base de données, exécute toutes les migrations, initialise les données de référence
- Routage de connexion : DataSource résolu dynamiquement depuis le mapping tenant_id → connexion base de données
TenantAwareRepositoryapplique toujours le scopage tenant (défense en profondeur — même si la DB elle-même est isolée)- Les migrations doivent s'exécuter contre chaque base de données tenant individuellement (orchestrateur de migrations)
- La sauvegarde/restauration est par tenant
Profil B (Schéma-par-Tenant) et Profil D (BYO-DB)¶
- Le modèle de données est conçu pour supporter les 4 profils dès le premier jour
- Profil B : mapping tenant_id → nom de schéma. Même DB, schémas différents.
- Profil D : le tenant fournit sa propre chaîne de connexion DB. ALTARYS ne gère aucune infrastructure pour les données de ce tenant.
B.13 Dépendances Inter-Modules¶
Événements publiés par le Control Plane¶
| Événement | Déclencheur | Consommé par | Payload |
|---|---|---|---|
TenantProvisioned |
Pipeline de provisionnement terminé | Tous les modules (initialisation) | tenantId, countryCode, dbProfile, subscribedModules[] |
TenantStatusChanged |
Toute transition de statut tenant | Tous les modules (appliquer lecture seule, suspension) | tenantId, oldStatus, newStatus, reason |
TenantModuleActivated |
Module activé pour le tenant | Module cible | tenantId, moduleCode, activatedDate |
TenantModuleDeactivated |
Module désactivé pour le tenant | Module cible | tenantId, moduleCode, deactivatedDate |
EmployeeCreated |
Nouvel employé créé | Absences, Présence, Paie | tenantId, employeeId, matricule, fullName, contractTypeCode, hireDate, categoryCode, orgUnitId |
EmployeeUpdated |
Données employé modifiées | Modules mettant en cache les données employé | employeeId, tenantId, changedFields[] |
EmployeeStatusChanged |
Transition de statut employé | Absences, Présence, Paie | employeeId, tenantId, oldStatus, newStatus, effectiveDate |
OrganisationUnitCreated |
Nouveau département/site créé | Lignes budgétaires, Reporting | unitId, tenantId, type, name, parentId |
OrganisationUnitDeactivated |
Unité désactivée | Lignes budgétaires, Reporting | unitId, tenantId, deactivatedDate |
PositionCreated |
Nouveau poste ajouté | GPEC (V2) | positionId, tenantId, title, categoryId |
ManagerChanged |
N+1 d'un employé mis à jour | Absences, Engagement, Gestion Employés | employeeId, tenantId, oldManagerId, newManagerId, effectiveDate |
UserRoleChanged |
Rôles d'un utilisateur mis à jour | Frontend (invalidation du cache) | userId, tenantId, newRoles[] |
ProspectConvertedToTenant |
CRM convertit un prospect (Canal B) | Control Plane (provisionnement) | prospectId, tenantData, salespersonId |
APIs exposées par le Control Plane (consommées par les autres modules)¶
| Endpoint | Méthode | Consommé par | Objectif |
|---|---|---|---|
/api/v1/cp/employees/{id} |
GET | Paie, Absences, Présence, tous | Obtenir les données d'un employé |
/api/v1/cp/employees?org_unit_id={id} |
GET | Absences (vue équipe), Paie | Obtenir les employés par département |
/api/v1/cp/employees/{id}/manager |
GET | Absences, Engagement | Obtenir la chaîne d'approbation |
/api/v1/cp/org-units/{id} |
GET | Budget, Engagement, Reporting | Obtenir les détails d'une unité org. |
/api/v1/cp/org-units/{id}/tree |
GET | Reporting, Core RH | Obtenir le sous-arbre org. |
/api/v1/cp/country-config/{countryCode}/tax-brackets |
GET | Paie | Obtenir les tranches ITS |
/api/v1/cp/country-config/{countryCode}/social-contributions |
GET | Paie | Obtenir les taux CNPS |
/api/v1/cp/country-config/{countryCode}/leave-types |
GET | Absences | Obtenir le référentiel des types de congés |
/api/v1/cp/country-config/{countryCode}/holidays/{year} |
GET | Absences, Paie | Obtenir les jours fériés |
/api/v1/auth/my-permissions |
GET | Frontend (tous modules) | Obtenir la matrice de permissions pour les rôles de l'utilisateur courant |
/api/v1/cp/tenant/modules |
GET | Frontend (navigation) | Obtenir les modules actifs/inactifs pour le tenant courant |
/api/v1/cp/tenant/settings |
GET | Tous les modules | Obtenir la configuration tenant (exercice fiscal, jours ouvrés, etc.) |
/api/v1/cp/tenant/default-approver |
GET | Absences, Engagement, Notes de frais | Obtenir l'approbateur de repli |
/api/v1/cp/notifications |
POST | Tous les modules | Publier une notification via le service partagé |
Endpoint Stub Canal B (Intégration CRM)¶
| Endpoint | Méthode | Objectif |
|---|---|---|
/api/v1/internal/tenants/from-prospect |
POST | Créer un tenant depuis les données d'un prospect CRM. Accepte : nom entreprise, pays, effectif, email admin, nom admin, téléphone, sélection modules, salesperson_id. Retourne : tenantId, status. |
- Cet endpoint est "interne" — non exposé publiquement. Authentifié avec un JWT service-à-service (module CRM → Control Plane).
- Pour les tests MVP : peut être déclenché manuellement via la console d'administration ou Swagger.
PARTIE C — Contraintes et Limites¶
C.1 Conformité Réglementaire [CI]¶
C.1.1 Protection des données — Loi n° 2013-450 du 19 juin 2013¶
| Exigence | Implémentation | Statut |
|---|---|---|
| Déclaration préalable à l'ARTCI | ALTARYS doit déposer la déclaration avant de traiter les données personnelles des résidents CI | NON INITIÉ — BLOQUANT PRÉ-LANCEMENT (OQ-CP-03) |
| Autorisation de transfert transfrontalier | Requise pour l'hébergement Hetzner (Allemagne) — pays non CEDEAO | NON INITIÉ — BLOQUANT PRÉ-LANCEMENT (OQ-CP-03) |
| Minimisation des données (Art. 36) | Collecter uniquement les données nécessaires à la finalité déclarée | Appliqué par la conception de l'entité baseline CP (champs minimaux) |
| Consentement | L'admin tenant consent au nom de son entreprise lors de l'inscription | Les CGV/CGU doivent inclure une clause de consentement au traitement des données |
| Droits des personnes (accès, rectification, suppression) | Les employés peuvent consulter leurs données (self-service) ; la suppression est remplacée par la règle de rétention AUDCIF 10 ans pour les données financières | Implémenté dans l'entité Employé + journal d'audit |
| Exigence DPO | ALTARYS doit nommer un DPO étant donné le volume et la sensibilité des données | Action juridique/organisationnelle requise |
| Notification de violation | L'ARTCI doit être notifiée dans les 48 heures d'une violation de données | Procédure de réponse aux incidents requise (opérationnelle, pas de code CP) |
C.1.2 Facturation Électronique — FNE (Facture Normalisée Électronique)¶
| Exigence | Implémentation | Statut |
|---|---|---|
| FNE obligatoire depuis juin 2025 (Arrêté n° 0337 du 9 mai 2025) | Les factures d'abonnement ALTARYS doivent être conformes FNE | MVP : modèle de données prêt FNE. V2 : intégration API DGI. |
| Numérotation séquentielle des factures | FAC-{YYYY}-{numéro_zéro_padded} |
Implémenté dès le premier jour |
| NIF acheteur sur la facture | Requis pour la pleine conformité FNE | Les tenants ACTIVE_INCOMPLETE reçoivent des factures proforma jusqu'à la fourniture du NIF |
| QR code + numéro fiscal | Requis quand l'intégration DGI est active | Champs présents dans l'entité facture (vides jusqu'en V2) |
| Intégration API DGI | Requise pour transmettre les factures électroniquement | V2 — voir OQ-CP-04 |
C.1.3 AUDCIF (Acte Uniforme relatif au Droit Comptable et à l'Information Financière)¶
| Exigence | Implémentation | Statut |
|---|---|---|
| Art. 24 : Rétention 10 ans | Tous les enregistrements financiers/comptables conservés 10 ans minimum | Appliqué pour : entrées d'audit, factures, données de paie (quand le module Paie sera livré) |
| Art. 22 : Enregistrements immuables | Les écritures comptables ne peuvent pas être modifiées ni supprimées | Le journal d'audit du Control Plane est immuable. Verrouillage de période AUDCIF → module Comptabilité |
| Art. 20 : Correction d'erreur | Les erreurs sont corrigées par écriture de contrepassation, pas par modification | Responsabilité du module Comptabilité |
| Suppression tenant vs. rétention | L'AUDCIF Art. 24 prime sur les droits à l'effacement de la protection des données | Soft delete + archive 10 ans pour les données financières + anonymisation des données personnelles non financières |
C.1.4 Code du Travail OHADA — Données Employé¶
| Exigence | Implémentation | Statut |
|---|---|---|
| Code du Travail Art. 92.3 : Registre d'employeur | L'employeur doit tenir un registre du personnel avec les champs obligatoires | L'entité Employé CP stocke les champs baseline. Registre complet → Core RH / Gestion des Employés. |
| Catégories employés (CCI) | La Convention Collective Interprofessionnelle de CI définit 4 catégories | Initialisées dans Config. Pays. Affecte les taux CNPS. |
| Limite CDD 2 ans (Art. 15.10) | Un CDD dépassant 2 ans cumulatifs devient automatiquement CDI | Règle stockée dans Config. Pays. Alertes à 90/30/7 jours. Application → module Gestion des Employés. |
| Délégués du personnel (Décret 2024-901) | Les entreprises de >10 travailleurs doivent élire des représentants du personnel | Seuil suivi. Par établissement vs. par entreprise : OUVERT (OQ-CP-01) |
C.1.5 Bloquants Juridiques Pré-Lancement¶
| Bloquant | Action Requise | Responsable |
|---|---|---|
| Déclaration préalable ARTCI | Déposer la déclaration auprès de l'ARTCI pour le traitement des données personnelles | Avocat + Direction ALTARYS |
| Autorisation transfrontalière ARTCI | Déposer la demande d'autorisation pour l'hébergement Hetzner (Allemagne) | Avocat + Direction ALTARYS |
| Rédaction CGV/DPA | Les CGV doivent inclure des clauses de responsabilité AUDCIF, conditions de traitement des données | Avocat |
| Nomination DPO | Nommer un Délégué à la Protection des Données | Direction ALTARYS |
C.2 Exigences de Localisation [CI]¶
| Aspect | Exigence |
|---|---|
| Langue | Français uniquement (MVP). Toutes les chaînes UI externalisées dans des fichiers de traduction. |
| Devise | XOF — zéro décimale. Format : 15 000 FCFA (espace comme séparateur de milliers) |
| Format de date | JJ/MM/AAAA (ex., 26/02/2026) |
| Format numérique | Espace comme séparateur de milliers : 1 000 000 |
| Format téléphone | CI : +225 XX XX XX XX XX (10 chiffres après l'indicatif pays) |
| Heures de travail | Par défaut : Lun-Ven, 08h00-17h00 (configurable par tenant) |
| Fuseau horaire | GMT+0 (Abidjan — pas d'heure d'été en CI) |
C.3 Exigences de Sécurité¶
| Exigence | Implémentation |
|---|---|
| Isolation des données tenant | Schéma TenantAwareRepository obligatoire. Toutes les requêtes scopées par tenant_id. Zéro fuite de données entre tenants. |
| Authentification | Keycloak OIDC/OAuth2. Basé JWT. Pas de cookies de session. |
| Autorisation | Hybride : le JWT porte les rôles, le backend valide les permissions côté serveur sur chaque requête. |
| Chiffrement en transit | TLS 1.2+ sur tous les endpoints. HSTS appliqué. |
| Chiffrement au repos | Chiffrement disque PostgreSQL. Chiffrement des buckets MinIO. |
| Politique de mot de passe | Min 8 chars, doit inclure majuscule + minuscule + chiffre. Pas de mots de passe courants. |
| Protection force brute | Keycloak : verrouillage de compte après 5 tentatives échouées (cooldown 15 minutes). |
| CAPTCHA | hCaptcha sur les endpoints publics (auto-inscription, réinitialisation de mot de passe). |
| Limitation de débit | 3 inscriptions/IP/heure. Limitation API par tenant À DÉFINIR (V2). |
| Journal d'audit | Toute mutation de données journalisée de façon immuable. Rétention 10 ans. |
| Isolation de la console d'administration | Application séparée, URL non publique, rôle Platform Admin uniquement. |
| Validation des entrées | Tous les endpoints API valident les entrées (type, longueur, format, plage). |
| Prévention injection SQL | Requêtes paramétrées via Spring Data JDBC. Pas de concaténation SQL brute. |
| Prévention XSS | Échappement par défaut de React. En-têtes Content-Security-Policy. |
C.4 Hors Périmètre¶
| Élément | Raison | Quand |
|---|---|---|
| Module Core RH (dossier employé complet) | Module vendable séparé. CP fournit uniquement l'entité baseline. | Post-MVP (Ordre 7) |
| Module Gestion des Employés (processus administratifs) | Module vendable séparé. CP stocke les statuts employés mais ne gère pas le flux onboarding/offboarding. | Post-MVP (Ordre 8) |
| Gestion Avancée des Documents (versionnage, rétention, recherche) | Module vendable séparé. CP fournit uniquement l'upload/download de base via MinIO. | Post-MVP (Ordre 10) |
| Calculs de paie | Responsabilité du module Paie. CP stocke les taux dans Config. Pays mais ne calcule pas les salaires. | MVP Ordre 5 |
| Traitement des demandes de congé | Module Gestion des Absences. CP stocke les types de congés dans Config. Pays. | MVP Ordre 2a |
| Enregistrement des présences | Module Présence QR. | MVP Ordre 2b |
| Verrouillage de période AUDCIF + chaînes de hachage | Module Comptabilité. Le journal d'audit CP est immuable mais pas conforme AUDCIF Art. 22 pour la comptabilité spécifiquement. | Post-MVP (Ordre 9) |
| Profil DB B (Schéma-par-tenant) | MVP — les 4 profils sont livrés dans le MVP. | MVP |
| Profil DB D (BYO-DB) | MVP — les 4 profils sont livrés dans le MVP. | MVP |
| SSO / OAuth (externe) | Keycloak email+mot de passe pour le MVP. | V2 Enterprise |
| Notifications SMS | Important pour l'Afrique de l'Ouest mais pas dans le MVP. Email + in-app en premier. | V2 |
| Rôles personnalisés | Rôles créés par le tenant. MVP : 5 rôles par défaut fixes. | V2 |
| Suspension par module | Le modèle de données le supporte dès le premier jour. Application en V2. | V2 (dans 6 mois) |
| Proratisation en cours de période | MVP : changements de segment au prochain cycle de facturation. | V2 |
| Export de données self-service | MVP : export via support admin. V2 : outil d'export self-service. | V2 |
| Intégration API DGI FNE | MVP stocke les données prêtes FNE. V2 transmet au DGI. | V2 |
| Configs multi-pays | CI uniquement pour le MVP. BEN + CMR au mois 9. | Mois 9 |
| Devise XAF | V2 (zone CEMAC). | V2 |
| Anglais / Portugais | V2+ (Cameroun bilingue, Guinée Bissau). | V2+ |
| Centres de coûts | Extension du modèle de données V2. | V2 |
| Structures multi-entités / groupes | V2 (groupes d'entreprises avec filiales). | V2 |
| Organigramme graphique | V2 (rendu visuel de l'organigramme). | V2 |
| Outil de facturation orienté tenant | La plateforme générant des factures FNE-conformes POUR les propres clients du tenant. | V2+ (nouveau module) |