Aller au contenu

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_SERVICE role

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 auth
  • docs/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