Story CP-013 : Canal B — Le CRM Crée un Tenant + Emails + Authentification¶
Module : control-plane Slice : Slice 13a from architecture (split from original CP-013) Brand context : [BOTH] Papillon HR Suite + ALTARYS ENTERPRISE HR Suite Priority : 13 Depends on : CP-008 Estimated complexity : S
Objectif¶
Créer l'endpoint API interne permettant au module CRM (Sprint 3) de convertir des prospects en tenants. Il s'agit du parcours d'onboarding "Canal B" — où un commercial freelance inscrit directement un prospect depuis le CRM, en contournant le flux d'auto-inscription. Cette story établit le contrat d'interface, l'authentification service-à-service, la création de tenant depuis les données du prospect et les deux emails d'onboarding (création de mot de passe + lien de paiement). La gestion du désinscription, l'attribution des commissions et le provisionnement Canal B sont dans CP-030.
Périmètre Backend¶
Entités & Records¶
Événement :
public record ProspectConvertedToTenant(
UUID prospectId,
UUID tenantId,
String companyName,
String countryCode,
String employeeCountRange,
String adminEmail,
String adminFullName,
String adminPhone,
List<String> selectedModuleCodes,
UUID salespersonId,
Instant timestamp
) {}
DTOs :
public record CreateTenantFromProspectRequest(
@NotBlank String companyName,
@NotBlank String countryCode,
@NotBlank String employeeCountRange,
@NotBlank @Email String adminEmail,
@NotBlank String adminFullName, // Split at first space → User.first_name + User.last_name
@NotBlank String adminPhone,
@Nullable String rccm,
@NotEmpty List<String> selectedModuleCodes,
@NotNull UUID salespersonId,
@NotNull UUID prospectId
) {}
public record CreateTenantFromProspectResponse(
UUID tenantId,
String status // "PENDING"
) {}
Couche Service¶
CrmConversionListener.java :
- Écoute l'événement ProspectConvertedToTenant (si publié en interne)
- OU traite la requête API directement :
1. Créer un Tenant avec status=PENDING, onboarding_channel=CRM, salesperson_id défini
2. Envoyer Email #1 : lien de création de mot de passe (expire dans 7 jours) — template ONBOARDING_WELCOME_B_PASSWORD
3. Envoyer Email #2 : lien de paiement avec résumé des modules — template ONBOARDING_WELCOME_B_PAYMENT
4. L'email d'activation inclut un lien de désinscription (la gestion du désinscription est dans CP-030)
5. Retourner tenantId
Endpoints API¶
| Méthode | Chemin | Corps de requête | Réponse | Auth |
|---|---|---|---|---|
| POST | /api/v1/internal/tenants/from-prospect | CreateTenantFromProspectRequest | CreateTenantFromProspectResponse | JWT service-à-service |
Règles de validation¶
- Même validation que l'auto-inscription : email unique, téléphone unique, nom d'entreprise min 2
salespersonId: doit être un UUID valide (référençant une future entité utilisateur CRM)prospectId: stocké sur le tenant pour traçabilité- JWT service-à-service : client Keycloak spécial avec le rôle
CRM_SERVICE
Considérations multi-tenant¶
- L'endpoint interne n'est PAS tenant-scopé (il crée un NOUVEAU tenant)
- Le JWT service-à-service ne porte pas de tenant_id — il porte l'identité du service
Périmètre Frontend¶
Aucun pour cette story. La page de désinscription est dans CP-030.
Critères d'acceptation¶
AC-101: CRM creates tenant via internal API
Given the CRM module calls POST /api/v1/internal/tenants/from-prospect
When the request includes valid prospect data and salesperson_id
Then a tenant is created with status=PENDING and onboarding_channel=CRM
And salesperson_id is recorded on the tenant
AC-102: Channel B emails sent
Given a tenant is created via CRM
When the tenant creation succeeds
Then the tenant admin receives:
- Email #1: password creation link (expires 7 days)
- Email #2: payment link with module selection summary
AC-105: Service-to-service auth required
Given a regular tenant user JWT
When calling POST /api/v1/internal/tenants/from-prospect
Then HTTP 403 is returned (requires CRM_SERVICE role)
Règles OHADA & Réglementaires¶
- L'email d'activation DOIT inclure un lien de désinscription (le destinataire n'a pas demandé cette inscription). Cela est conforme aux bonnes pratiques de protection des données (Loi 2013-450). Le lien de désinscription est inclus dans le template email ; le gestionnaire de désinscription est implémenté dans CP-030.
- Commission du commercial : Suivie sur le paiement, pas sur l'inscription (PRD FR-CP-005). Le Control Plane stocke salesperson_id ; le calcul des commissions relève du module CRM.
Standards & Conventions¶
docs/standards/java-spring-guidelines.md— §Application Events, §Authentification service-à-servicedocs/standards/api-guidelines.md— §Endpoints internes, §JWT service
Exigences de test¶
Tests unitaires¶
- CrmConversionListener : création de tenant avec les champs corrects (status=PENDING, onboarding_channel=CRM, salesperson_id)
- Dispatch email : les deux templates déclenchés avec les données correctes
- Validation : email unique, téléphone unique, nom d'entreprise min 2
Tests d'intégration¶
- POST /api/v1/internal/tenants/from-prospect — flux complet : requête → tenant créé → emails mis en file d'attente
- Validation du JWT service-à-service (rôle CRM_SERVICE requis)
- JWT utilisateur ordinaire → 403
Ce que le QA validera¶
- Le contrat API correspond aux attentes du module CRM (Swagger)
- Les emails contiennent les liens corrects (vérification manuelle via outil de test d'email)
- Le tenant créé avec tous les champs correctement renseignés
Hors périmètre¶
- Gestion du désinscription — CP-030
- Suivi de l'attribution des commissions — CP-030
- Pipeline de provisionnement pour le Canal B — CP-030
- Module CRM lui-même — Sprint 3
- Calcul des commissions commerciales — Module CRM
- Porte d'approbation manager CRM — Explicitement NON inclus (FR-CP-005)
- Canal C (création back-office admin) — V2
- Rapport hebdomadaire d'inscriptions CRM — CP-012 (fonctionnalité de la console admin)
Définition de Done¶
- [ ] Tous les endpoints backend implémentés et retournant les réponses correctes
- [ ] Pas de pages frontend dans cette story
- [ ] Les micro-textes français dans les templates email correspondent exactement à la spec design
- [ ] Tous les critères d'acceptation passent manuellement
- [ ] Tests unitaires écrits et passants
- [ ] Tests d'intégration écrits et passants
- [ ] Isolation multi-tenant vérifiée (données du nouveau tenant isolées)
- [ ] Pas de types Java bruts — tous les génériques explicites
- [ ] Les réponses API respectent le format d'enveloppe standard
- [ ] Entrées d'audit créées pour chaque mutation de données