Story CP-013: Channel B — CRM Creates Tenant + Emails + Auth¶
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
Objective¶
Create the internal API endpoint that allows the CRM module (Sprint 3) to convert prospects into tenants. This is the "Channel B" onboarding path — where a freelance salesperson registers a prospect directly from the CRM, bypassing the self-registration flow. This story establishes the interface contract, service-to-service authentication, tenant creation from prospect data, and the two onboarding emails (password creation + payment link). Opt-out handling, commission attribution, and Channel B provisioning are handled in CP-030.
Backend Scope¶
Entities & Records¶
Event:
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"
) {}
Service Layer¶
CrmConversionListener.java:
- Listens for ProspectConvertedToTenant (if published internally)
- OR handles the API request directly:
1. Create Tenant with status=PENDING, onboarding_channel=CRM, salesperson_id set
2. Send Email #1: password creation link (expires 7 days) — template ONBOARDING_WELCOME_B_PASSWORD
3. Send Email #2: payment link with module summary — template ONBOARDING_WELCOME_B_PAYMENT
4. Activation email includes opt-out link (opt-out handling is in CP-030)
5. Return tenantId
API Endpoints¶
| Method | Path | Request Body | Response | Auth |
|---|---|---|---|---|
| POST | /api/v1/internal/tenants/from-prospect | CreateTenantFromProspectRequest | CreateTenantFromProspectResponse | Service-to-service JWT |
Validation Rules¶
- Same validation as self-registration: email unique, phone unique, company name min 2
salespersonId: must be a valid UUID (referencing a future CRM user entity)prospectId: stored on tenant for traceability- Service-to-service JWT: special Keycloak client with
CRM_SERVICErole
Multi-Tenant Considerations¶
- Internal endpoint is NOT tenant-scoped (it creates a NEW tenant)
- Service-to-service JWT does not carry tenant_id — it carries service identity
Frontend Scope¶
None for this story. The opt-out page is in CP-030.
Acceptance Criteria¶
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)
OHADA & Regulatory Rules¶
- The activation email MUST include an opt-out link (the recipient did not request this registration). This aligns with data protection best practices (Loi 2013-450). The opt-out link is included in the email template; the opt-out handler is implemented in CP-030.
- Salesperson commission: Tracked on payment, not registration (PRD FR-CP-005). Control Plane stores salesperson_id; commission calculation is a CRM module concern.
Standards & Conventions¶
docs/standards/java-spring-guidelines.md— §Application Events, §Service-to-service authdocs/standards/api-guidelines.md— §Internal endpoints, §Service JWT
Testing Requirements¶
Unit Tests¶
- CrmConversionListener: tenant creation with correct fields (status=PENDING, onboarding_channel=CRM, salesperson_id)
- Email dispatch: both templates triggered with correct data
- Validation: email unique, phone unique, company name min 2
Integration Tests¶
- POST /api/v1/internal/tenants/from-prospect — full flow: request → tenant created → emails queued
- Service-to-service JWT validation (CRM_SERVICE role required)
- Regular user JWT → 403
What QA Will Validate¶
- API contract matches CRM module expectations (Swagger)
- Emails contain correct links (manually verify via email testing)
- Tenant created with all fields populated correctly
Out of Scope¶
- Opt-out handling — CP-030
- Salesperson commission attribution tracking — CP-030
- Provisioning pipeline for Channel B — CP-030
- CRM module itself — Sprint 3
- Salesperson commission calculation — CRM module
- CRM manager approval gate — Explicitly NOT included (FR-CP-005)
- Channel C (admin back-office creation) — V2
- Weekly CRM registration report — CP-012 (admin console feature)
Definition of Done¶
- [ ] All backend endpoints implemented and returning correct responses
- [ ] No frontend pages in this story
- [ ] French micro-copy in email templates matches design spec exactly
- [ ] All acceptance criteria pass manually
- [ ] Unit tests written and passing
- [ ] Integration tests written and passing
- [ ] Multi-tenant isolation verified (new tenant data isolated)
- [ ] No Java raw types — all generics explicit
- [ ] API responses follow standard envelope format
- [ ] Audit trail entries created for every data mutation