========================== Tenant — Modèles ========================== Les modèles du Tenant Context gèrent les organisations (tenants), les memberships utilisateur-tenant, les rôles, départements et invitations. Module : ``src.module.tenant.infrastructure.models`` .. module:: src.module.tenant.infrastructure.models :synopsis: Modèles Django pour le Tenant Context Tenant ====== .. class:: Tenant(models.Model) Agrégat racine représentant une organisation/tenant. .. attribute:: id :type: UUIDField Clé primaire UUID4. .. attribute:: name :type: CharField(max_length=255) Nom de l'organisation. .. attribute:: slug :type: SlugField(unique=True) Identifiant URL-safe unique (ex: ``acme-corp``). .. attribute:: schema_name :type: CharField(max_length=63, unique=True) Nom du schéma PostgreSQL (auto-généré : ``tenant_``). .. attribute:: status :type: CharField Statut du tenant : ``active``, ``suspended``, ``archived``. Utilise l'enum ``TenantStatus``. .. attribute:: metadata :type: JSONField Métadonnées libres (plan, config custom, etc.). **Méthodes de cycle de vie :** .. method:: suspend() -> None Passe le tenant en statut ``suspended``. Émet ``TenantSuspended``. .. method:: activate() -> None Réactive un tenant suspendu. Émet ``TenantActivated``. .. method:: archive() -> None Archive définitivement un tenant. Émet ``TenantArchived``. .. code-block:: python tenant = Tenant.objects.create( name="Acme Corp", slug="acme-corp", ) # schema_name = "tenant_acme_corp" (auto-généré) TenantMembership ================= .. class:: TenantMembership(models.Model) Pivot utilisateur-tenant avec rôles et attributs ABAC. .. attribute:: user :type: ForeignKey → User .. attribute:: tenant :type: ForeignKey → Tenant .. attribute:: roles :type: JSONField Liste de rôles (ex: ``["admin", "billing"]``). .. attribute:: abac_attributes :type: JSONField Attributs ABAC personnalisés (ex: ``{"department": "finance", "level": 3}``). .. attribute:: department :type: ForeignKey → Department (nullable) .. attribute:: status :type: CharField Statut : ``active``, ``invited``, ``suspended``. **Méthodes :** .. method:: update_roles(new_roles: list[str]) -> None Met à jour les rôles. Émet ``MemberRolesUpdated``. Role ==== .. class:: Role(TranslatableSharedModel) Définitions de rôles par tenant pour le système ABAC. Traduisible via django-parler. .. attribute:: tenant :type: ForeignKey → Tenant .. attribute:: name :type: CharField Identifiant programmatique du rôle (non traduit, utilisé dans les conditions ABAC et les listes JSON de rôles). .. attribute:: display_name :type: CharField (traduit) Nom d'affichage traduisible. .. attribute:: description :type: TextField (traduit) Description traduisible du rôle. .. attribute:: is_active :type: BooleanField :Table: ``mt_tenant_role`` / ``mt_tenant_role_translation`` Department ========== .. class:: Department(TranslatableSharedModel) Départements par tenant. Traduisible via django-parler. .. attribute:: tenant :type: ForeignKey → Tenant .. attribute:: name :type: CharField Identifiant programmatique (non traduit). .. attribute:: display_name :type: CharField (traduit) Nom d'affichage traduisible. .. attribute:: description :type: TextField (traduit) Description traduisible du département. .. attribute:: is_active :type: BooleanField :Table: ``mt_department`` / ``mt_department_translation`` Invitation ========== .. class:: Invitation(models.Model) Invitation à rejoindre un tenant par email. .. attribute:: tenant :type: ForeignKey → Tenant .. attribute:: email :type: EmailField .. attribute:: token :type: UUIDField(unique=True) Token d'acceptation (envoyé par email). .. attribute:: roles :type: JSONField Rôles pré-assignés à l'invité. .. attribute:: status :type: CharField ``pending``, ``accepted``, ``declined``, ``expired``, ``cancelled``. .. attribute:: invited_by :type: ForeignKey → User .. attribute:: expires_at :type: DateTimeField **Méthodes :** .. method:: accept(user) -> None Accepte l'invitation. Crée la membership. Émet ``InvitationAccepted``. .. method:: decline() -> None Décline l'invitation. .. method:: cancel() -> None Annule l'invitation (par l'émetteur). .. method:: is_expired() -> bool Vérifie si l'invitation a expiré.