========================== Tenant — Invitations ========================== Le système d'invitations permet d'inviter des utilisateurs à rejoindre un tenant par email avec un token d'acceptation. Module : ``src.module.tenant.domain.invitation_service`` .. module:: src.module.tenant.domain.invitation_service :synopsis: Service domaine pour les invitations tenant send_invitation =============== .. function:: send_invitation(tenant, email, roles, invited_by, membership_queryset) -> Invitation Crée une invitation et envoie un email. 1. Vérifie qu'aucune invitation en attente n'existe pour cet email/tenant 2. Crée l'invitation avec un token UUID unique 3. Calcule la date d'expiration (``INVITATION_EXPIRATION_HOURS``) 4. Envoie l'email (différent selon que l'utilisateur est déjà inscrit ou non) 5. Émet ``InvitationSent`` :param tenant: Tenant émetteur :param email: Email du destinataire :param roles: Liste de rôles pré-assignés :param invited_by: Utilisateur émetteur :param membership_queryset: QuerySet pour vérifier les doublons :returns: Instance Invitation créée .. code-block:: python from src.module.tenant.domain.invitation_service import send_invitation invitation = send_invitation( tenant=acme_tenant, email="new.user@example.com", roles=["member", "billing"], invited_by=admin_user, membership_queryset=TenantMembership.objects.all(), ) accept_invitation_by_token ============================ .. function:: accept_invitation_by_token(token, user) -> TenantMembership Accepte une invitation par son token. 1. Recherche l'invitation par token 2. Vérifie que l'invitation est en attente et non expirée 3. Vérifie que l'email correspond à l'utilisateur 4. Crée la membership avec les rôles pré-assignés 5. Émet ``InvitationAccepted`` :param token: UUID du token d'invitation :param user: Utilisateur acceptant l'invitation :returns: Instance TenantMembership créée :raises: ``ValueError`` si le token est invalide, expiré, ou l'email ne correspond pas Flux d'invitation ================= .. code-block:: text Admin Système Invité │ │ │ ├── POST /invitations/ ──────►│ │ │ {email, roles} │ │ │ ├── Crée Invitation ──────────│ │ ├── Envoie email ─────────────►│ │ │ (lien avec token) │ │ │ │ │ │◄── GET /invitations/accept/ ─┤ │ │ {token} │ │ │ │ │ ├── Valide token │ │ ├── Crée TenantMembership │ │ ├── Émet InvitationAccepted │ │ │ │ │ │── 200 OK ───────────────────►│ │ │ │ Configuration ============= .. code-block:: python MULTITENANT = { 'INVITATION_ENABLED': True, 'INVITATION_EXPIRATION_HOURS': 168, # 7 jours 'INVITATION_FROM_EMAIL': 'noreply@example.com', }