Organisations (Admin Système)
Endpoints de gestion des organisations. Réservés aux administrateurs plateforme (permission organization.manage).
Architecture multi-tenant
Chaque organisation est isolée dans son propre schéma PostgreSQL nommé tenant_{slug}. Le middleware Tenant positionne le search_path via une variable task_local avant chaque requête, garantissant une isolation totale des données.
flowchart LR
A[Requête API] --> B[Tenant middleware]
B --> C[Résoudre org depuis header/jwt]
C --> D[SET search_path = tenant_slug]
D --> E[Requête exécutée<br/>dans le bon schéma]
Structure de données
Une organisation contient les champs suivants dans la table public.organizations :
| Champ | Type | Description |
|---|---|---|
id | uuid | Identifiant unique |
slug | string | Identifiant URL unique (ex: cours-lumiere) |
short_code | string | Code court 2-5 caractères majuscules (ex: CL) |
name | string | Nom d'affichage (ex: Cours Lumière) |
legal_name | string? | Nom légal / raison sociale |
tax_id | string? | Numéro IFU / tax ID |
contact_email | string? | Email de contact |
contact_phone | string? | Téléphone de contact |
website | string? | Site web |
status | enum | active, suspended |
currency | string | Devise ISO 4217 (défaut: XOF) |
default_anomaly_threshold_cents | integer | Seuil anomalie solde faible en centimes (défaut: 30000) |
card_number_range_start | integer | Début plage numéros de carte (défaut: 1) |
card_number_range_end | integer | Fin plage numéros de carte (défaut: 99999) |
schema_name | string | Nom du schéma PostgreSQL (tenant_{slug}) |
settings | jsonb | Configuration typée de l'organisation |
notification_config | jsonb | Configuration des canaux de notification |
created_at | datetime | Date de création |
updated_at | datetime | Date de dernière modification |
GET /admin/system/organizations
Liste paginée des organisations.
Permissions : organization.manage
| Param | Type | Description |
|---|---|---|
limit | integer | Éléments par page (défaut: 20) |
offset | integer | Décalage (défaut: 0) |
Réponse :
{
"items": [
{
"id": "550e8400-e29b-41d4-a716-446655440001",
"slug": "cours-lumiere",
"short_code": "CL",
"name": "Cours Lumière",
"legal_name": null,
"tax_id": null,
"contact_email": "contact@courslumiere.com",
"contact_phone": "+22890123456",
"website": null,
"status": "active",
"currency": "XOF",
"default_anomaly_threshold_cents": 30000,
"card_number_range_start": 1,
"card_number_range_end": 99999,
"schema_name": "tenant_cours_lumiere",
"settings": {},
"notification_config": {
"channels": {
"email": { "enabled": true },
"sms": { "enabled": false },
"whatsapp": { "enabled": false }
},
"event_routing": {
"anomaly_detected": ["email"],
"recharge_completed": ["email"],
"low_balance": ["email"]
},
"email_default_from": "noreply@caffette.app"
},
"created_at": "2025-01-15T10:00:00Z",
"updated_at": "2025-06-01T14:30:00Z"
}
],
"total": 5
}
POST /admin/system/organizations
Créer une organisation. Cette opération provisionne automatiquement :
- Un enregistrement dans
public.organizations - Un schéma dédié
tenant_{slug}avec toutes les tables du tenant - La configuration de notification par défaut
- Les rôles système par défaut (administrateur, gestionnaire, caissier, etc.)
Permissions : organization.manage
Corps de la requête :
{
"slug": "nouvelle-ecole",
"short_code": "NE",
"name": "Nouvelle École",
"legal_name": "Nouvelle École SARL",
"contact_email": "dir@nouvelleecole.com",
"contact_phone": "+22890123456",
"card_range_start": 1,
"card_range_end": 99999
}
| Champ | Type | Requis | Description |
|---|---|---|---|
slug | string | Oui | Identifiant URL unique (sans espaces, sans majuscules) |
short_code | string | Oui | Code court (2-5 caractères, converti en majuscules) |
name | string | Oui | Nom d'affichage |
legal_name | string | Non | Nom légal / raison sociale |
contact_email | string | Non | Email de contact |
contact_phone | string | Non | Téléphone |
card_range_start | integer | Non | Début plage numéros de carte (défaut: 1) |
card_range_end | integer | Non | Fin plage numéros de carte (défaut: 99999) |
- Le
slugsert de base au nom du schéma :tenant_{slug}. Il doit être unique et valide comme nom de schéma PostgreSQL. - Le
short_codeest utilisé dans les codes d'identification publique (ex: cartesCL-001,NE-042). Il est automatiquement converti en majuscules. - Les plages de numéros de carte (
card_range_start/card_range_end) définissent l'espace de numérotation pour l'organisation.
Réponse (201) :
{
"message": "Organisation créée avec succès",
"organization": {
"id": "550e8400-e29b-41d4-a716-446655440099",
"slug": "nouvelle-ecole",
"short_code": "NE",
"name": "Nouvelle École",
"schema_name": "tenant_nouvelle_ecole",
"status": "active",
"currency": "XOF",
"default_anomaly_threshold_cents": 30000,
"card_number_range_start": 1,
"card_number_range_end": 99999,
"created_at": "2025-07-01T08:00:00Z"
}
}
GET /admin/system/organizations/{id}
Détails d'une organisation. Retourne l'objet Organization complet (mêmes champs que dans la liste).
Permissions : organization.manage
PUT /admin/system/organizations/{id}
Mettre à jour les métadonnées d'une organisation. Tous les champs sont optionnels — seuls les champs fournis sont modifiés.
Permissions : organization.manage
Corps de la requête :
{
"name": "Nouvelle École Internationale",
"short_code": "NEI",
"contact_email": "admin@nei.com",
"contact_phone": "+22890987654"
}
La modification du short_code a un impact sur les codes d'identification publique déjà émis (cartes, agents). Assurez-vous que les opérateurs sont informés du changement.
Réponse :
{
"message": "Organisation mise à jour",
"organization": {
"id": "uuid",
"name": "Nouvelle École Internationale",
"short_code": "NEI",
"updated_at": "2025-07-02T10:00:00Z"
}
}
POST /admin/system/organizations/{id}/suspend
Suspendre une organisation. Passe le statut à suspended et empêche l'accès à l'API pour tous les utilisateurs de cette organisation.
Permissions : organization.manage
Réponse :
{
"message": "Organisation suspendue"
}
POST /admin/system/organizations/{id}/activate
Réactiver une organisation précédemment suspendue. Passe le statut à active.
Permissions : organization.manage
Réponse :
{
"message": "Organisation activée"
}
Cycle de vie d'une organisation
stateDiagram-v2
[*] --> provisioning: POST /organizations
provisioning --> active: Schema créé<br/>Config initiale
active --> suspended: POST /suspend
suspended --> active: POST /activate
active --> [*]
Bonnes pratiques
- Plage de numéros de carte : Commencez avec une plage large (1-99999) et réduisez-la si nécessaire. Vous ne pouvez pas étendre la plage une fois des cartes émises sans risque de collision.
- Seuil d'anomalie : Le seuil par défaut (30000 centimes = 300 FCFA) est conservateur. Ajustez-le en fonction de votre panier moyen après quelques semaines d'exploitation.
- Slug : Une fois défini, le slug ne peut pas être changé (il est lié au nom du schéma PostgreSQL). Choisissez-le avec soin.
- Notification config : La configuration par défaut n'active que l'email. Pour activer SMS ou WhatsApp, modifiez
notification_configdans les settings de l'organisation.