Annuaire
Annuaire
Périmètre : référentiel d'entreprises clientes/prospects, d'établissements (sites opérationnels), de personnes (contacts), enrichissement KYB via Pappers/INSEE, segmentation commerciale et géocodage.
/workspace/{companies,establishments,persons,enseignes}
et les routes API sont à plat sous /api/workspace/{companies,establishments,persons,segments}
(le préfixe /annuaire/ n'existe pas). Source de cette page : docs/07-domain-annuaire.md.Vocabulaire & entités
| Terme | Entité | Définition |
|---|---|---|
| Entreprise | Company | Entité légale identifiée par SIREN. Source de vérité légale. |
| Établissement | Establishment | Site physique identifié par SIRET — unité d'exploitation où les équipements sont installés. |
| Personne | Person | Contact humain rattaché à une Company et/ou un Establishment. Sa fonction est le champ libre Person.jobTitle (pas d'entité dédiée). |
| Enseigne | Enseigne | Marque commerciale fédérant plusieurs établissements (ex : « Carrefour Market »). Portée par Establishment.enseigneId. |
| Segment | Segment | Classification commerciale org-scoped portée par l'établissement (ex : « GMS », « Horeca »). Portée par Establishment.segmentId. |
| Code NAF / NACE | Company.activityCode | Champ de l'entreprise, pas une entité. |
L'ancienne entité
Activity(activité org-scoped rattachée à unEstablishment) n'existe plus : le référentiel métier est désormaisActivityScope, un arbre porté par une société du groupe (GroupCompany) et administré dans Paramètres → Scopes d'activité. Il ne relève plus de l'annuaire — cf.3.plateforme/2.modele-de-donnees.md.
Le modèle complet est dans
docs/04-data-model.md§4. L'identité légale (SIREN/SIRET/nom/adresse) est toujours lue viaCompany/Establishment; les modules CRM et Ventes ne font que référencer ces entités.
Écrans
| Écran | Route | Contenu |
|---|---|---|
| Liste entreprises | /workspace/companies | DataTable (SIREN, nom, ville, segments, nb établissements) + filtres. |
| Création entreprise | /workspace/companies/new | Saisie SIREN → autocomplete KYB (Pappers, fallback INSEE) ; saisie manuelle possible. |
| Import entreprises | /workspace/companies/import | Import CSV d'entreprises (création/mise à jour par SIREN). |
| Fiche entreprise | /workspace/companies/[id] | Onglets : Synthèse KYB, Établissements, Contacts, Opportunités, Affaires LLD, Factures, Fichiers, Commentaires, Audit. |
| Liste établissements | /workspace/establishments | DataTable + fiche détaillée (assets, maintenance, transport). |
| Création établissement | /workspace/establishments/new | Saisie d'un établissement (rattachement entreprise, adresse, segment, enseigne). |
| Liste personnes | /workspace/persons | DataTable des contacts (rattachement entreprise/établissement). |
| Création personne | /workspace/persons/new | Saisie d'un contact (identité, jobTitle, opt-in marketing). |
| Enseignes | /workspace/enseignes | CRUD enseignes (code, label) — partagé avec le CRM (cf. module CRM). |
Modèle de données (points clés)
Companypeut exister sansEstablishment(prospect étranger ou micro-entreprise), mais un siège ou établissement principal est requis à la première commande/contrat.Personest rattaché à 0 ou 1Companyet 0 ou 1Establishment.- Suppression d'une
Companyinterdite siCustomer/Order/Invoice/LeaseContractactifs — soft delete uniquement. - Suppression d'un
Establishmentinterdite si des assets y sont enCURRENT_LOCATIONactifs. Person.optInMarketingconditionne l'inclusion dans les exports Brevo (campagnes marketing).- Pas de données RGPD-sensitives sur les personnes morales ; RGPD strict sur
Person. enrichedAt,enrichmentSource(String?),rawEnrichment(JSONB) surCompany.- Unicité SIREN : index UNIQUE PARTIEL
WHERE "deletedAt" IS NULL(migrationrecyclebin_partial_unique), hors schéma Prisma. Index Prisma déclarés surCompany:(organizationId, name),(organizationId, deletedAt). Même schéma d'unicité partielle pourEstablishment(SIRET) etEnseigne/Segment(code/label).
API
Toutes les routes exigent une permission annuaire.* (vérifiée côté API et UI).
Les routes sont à plat : aucun préfixe /annuaire/.
Entreprises (companies)
| Méthode | Route | Permission |
|---|---|---|
GET | /api/workspace/companies | annuaire.company:read |
POST | /api/workspace/companies | annuaire.company:create |
POST | /api/workspace/companies/with-enrichment | annuaire.company:create |
POST | /api/workspace/companies/import | annuaire.company:create |
GET | /api/workspace/companies/[id] | annuaire.company:read |
PATCH | /api/workspace/companies/[id] | annuaire.company:update |
DELETE | /api/workspace/companies/[id] | annuaire.company:delete |
GET | /api/workspace/companies/[id]/delete-preview | annuaire.company:delete |
GET | /api/workspace/companies/[id]/dirigeants | annuaire.company:read |
Établissements (establishments)
| Méthode | Route | Permission |
|---|---|---|
GET | /api/workspace/establishments | annuaire.establishment:read |
POST | /api/workspace/establishments | annuaire.establishment:create |
GET | /api/workspace/establishments/[id] | annuaire.establishment:read |
PATCH | /api/workspace/establishments/[id] | annuaire.establishment:update |
DELETE | /api/workspace/establishments/[id] | annuaire.establishment:delete |
GET | /api/workspace/establishments/[id]/delete-preview | annuaire.establishment:delete |
POST | /api/workspace/establishments/[id]/set-headquarters | annuaire.establishment:update |
Personnes (persons)
| Méthode | Route | Permission |
|---|---|---|
GET | /api/workspace/persons | annuaire.person:read |
POST | /api/workspace/persons | annuaire.person:create |
POST | /api/workspace/persons/ensure | annuaire.person:create |
GET | /api/workspace/persons/[id] | annuaire.person:read |
PATCH | /api/workspace/persons/[id] | annuaire.person:update |
Segments (segments)
| Méthode | Route | Permission |
|---|---|---|
GET | /api/workspace/segments | annuaire.establishment:read |
POST | /api/workspace/segments | annuaire.establishment:create |
PATCH | /api/workspace/segments/[id] | annuaire.establishment:update |
DELETE | /api/workspace/segments/[id] | annuaire.establishment:update |
KYB (Pappers / INSEE)
| Méthode | Route | Permission |
|---|---|---|
GET | /api/workspace/kyb/lookup?siren= | annuaire.company:create |
GET | /api/workspace/kyb/search | annuaire.company:create |
GET | /api/workspace/kyb/enrich?siren= | annuaire.company:create |
GET | /api/workspace/kyb/establishments | annuaire.establishment:read |
POST | /api/workspace/kyb/import-establishment | annuaire.establishment:create |
Enseigne) sont un référentiel partagé avec le CRM : leur CRUD
est exposé sous /api/workspace/crm/enseignes (lecture crm.opportunity:read,
écriture crm.settings:update), pas sous l'annuaire. Voir le module CRM.Exemple de référence d'endpoint :
/api/workspace/kyb/enrichAuth Enrichissement KYB complet d'un SIREN : entreprise + établissements + dirigeants,
pour alimenter la sélection « au cas par cas » à la création d'entreprise.
company est null si le SIREN est introuvable. Permission annuaire.company:create.
Query
Requête
curl -s "$API/api/workspace/kyb/enrich?siren=$SIREN" \
-H "Cookie: $SESSION"
Réponse
{
"company": { "name": "…", "siren": "…", "activityCode": "…" },
"establishments": [{ "siret": "…", "city": "…" }],
"dirigeants": [{ "fullName": "…", "role": "…" }]
}
Workflows
Enrichissement KYB
Déclencheur manuel (création d'entreprise avec SIREN)
│
├─→ kyb.service.ts → getKybProvider() (sélection via KYB_PROVIDER, défaut `mock`)
│ ├─ lookupBySiren(siren) → KybCompany | null (route /kyb/lookup)
│ ├─ enrichBySiren(siren) → company + … (route /kyb/enrich)
│ ├─ listEstablishments(siren)→ établissements[] (route /kyb/establishments)
│ └─ getDirigeants(siren) → dirigeants[]
│
└─→ persistance à la création via /companies/with-enrichment
enrichedAt = now() · enrichmentSource · rawEnrichment = payload
Le fournisseur KYB est abstrait derrière getKybProvider() et sélectionné par
KYB_PROVIDER (défaut mock ; pappers en KYB_PROVIDER_MODE=live avec
PAPPERS_API_TOKEN). Champs persistés sur Company : enrichedAt,
enrichmentSource, rawEnrichment (JSONB), activityCode, etc.
Import multi-établissements
- Recherche SIREN (autocomplete KYB ou saisie directe).
- Le fournisseur renvoie la liste des établissements (
/kyb/establishments). - Wizard de sélection : l'utilisateur coche les établissements à importer.
POST /kyb/import-establishment{ siren, sirets? }: crée/réutilise laCompanypuis, par SIRET,findOrCreateEstablishmentBySiret(rapprochement SIRET, idempotent, pas de doublon). Sanssirets, seul le siège est importé.
Géocodage adresse
Establishment porte latitude/longitude (Decimal(10,7), nullables), alimentés
lors de la sélection/import KYB.
geocodeFailed dans le schéma actuel.Détection et merge de doublons
pg_trgm, écran de résolution et fusion (merge) de fiches entreprises. Aucune
de ces fonctions n'existe en code aujourd'hui (pas de route companies/merge,
pas de job detect-duplicates, pas d'écran « doublons »). À la création, la seule
garde active est l'unicité unique(organizationId, siren).Règles métier
- SIREN unique par organisation via index unique partiel
WHERE "deletedAt" IS NULL(les fiches soft-deleted ne bloquent pas une recréation du même SIREN). - Validation SIREN/SIRET (Luhn) via
shared/utils/siret.ts(exemption La Poste).vatNumberest une chaîne libre (≤ 20 car.), sans regex ni vérification VIES. - Listes paginées (max 100 par page), recherche serveur avec debounce côté UI.
- Suppression : soft delete uniquement si entité référencée (voir contraintes modèle ci-dessus).
Person.optInMarketing: seuls les contacts opt-in sont exportés vers Brevo.
Intégrations
Pappers
Fournisseur KYB. Clé PAPPERS_API_TOKEN, activé via KYB_PROVIDER=pappers +
KYB_PROVIDER_MODE=live. Défaut mock (fixtures), sélection centralisée dans
server/lib/kyb/.