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/annuaire/* et
/api/workspace/annuaire/*. 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. |
| Enseigne | Brand | Marque commerciale fédérant plusieurs établissements (ex : « Carrefour Market »). |
| Segment | Segment | Classification commerciale org-scoped portée par l'établissement (ex : « GMS », « Horeca »). |
| Activité | Activity | Code NAF / NACE. |
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/annuaire/companies | DataTable (SIREN, nom, ville, segments, nb établissements) + filtres + enrichissement de masse. |
| Création entreprise | /workspace/annuaire/companies/new | Saisie SIREN → autocomplete Pappers (fallback INSEE) ; saisie manuelle possible. |
| Fiche entreprise | …/companies/[id] | Onglets : Synthèse KYB, Établissements, Contacts, Opportunités, Affaires LLD, Factures, Fichiers, Commentaires, Audit. |
| Liste établissements | /workspace/annuaire/establishments | DataTable + carte géo cluster (zoom-aware) + fiche détaillée (assets, maintenance, transport). |
| Liste personnes | /workspace/annuaire/people | DataTable + import Excel/CSV avec wizard de mapping colonnes. |
| Carte | /workspace/annuaire/map | MapLibre plein écran, marqueurs par segment, export KML/GeoJSON. |
| Segments | /workspace/annuaire/settings/segments | CRUD segments (label, couleur). |
| Fonctions | /workspace/annuaire/settings/functions | CRUD fonctions (label, catégorie : DG / DAF / DSI / ACHATS / OPERATIONS / AUTRE). |
| Doublons | /workspace/annuaire/duplicates | Groupes pré-calculés via trigrammes pg_trgm, résolution manuelle (merge). |
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("pappers"|"insee"|null),rawEnrichment(JSONB) surCompany.- Index PostgreSQL : FTS
gin(search),(organizationId, name),(organizationId, siren),(organizationId, postalCode).
API
Toutes les routes exigent une permission annuaire.* (vérifiée côté API et UI).
| Méthode | Route | Permission |
|---|---|---|
GET | /api/workspace/annuaire/companies | annuaire.company:read |
GET | /api/workspace/annuaire/companies/[id] | annuaire.company:read |
POST | /api/workspace/annuaire/companies | annuaire.company:create |
PATCH | /api/workspace/annuaire/companies/[id] | annuaire.company:update |
DELETE | /api/workspace/annuaire/companies/[id] | annuaire.company:delete |
POST | /api/workspace/annuaire/companies/[id]/enrich | annuaire.company:enrich |
POST | /api/workspace/annuaire/companies/merge | annuaire.company:delete |
GET | /api/workspace/annuaire/establishments | annuaire.establishment:read |
GET | /api/workspace/annuaire/establishments/[id] | annuaire.establishment:read |
POST | /api/workspace/annuaire/establishments | annuaire.establishment:create |
PATCH | /api/workspace/annuaire/establishments/[id] | annuaire.establishment:update |
DELETE | /api/workspace/annuaire/establishments/[id] | annuaire.establishment:delete |
GET | /api/workspace/annuaire/people | annuaire.person:read |
GET | /api/workspace/annuaire/people/[id] | annuaire.person:read |
POST | /api/workspace/annuaire/people | annuaire.person:create |
PATCH | /api/workspace/annuaire/people/[id] | annuaire.person:update |
POST | /api/workspace/annuaire/people/import | annuaire.person:create |
GET | /api/workspace/annuaire/segments | annuaire.company:read |
POST | /api/workspace/annuaire/segments | annuaire.company:update |
GET | /api/workspace/annuaire/functions | annuaire.person:read |
POST | /api/workspace/annuaire/functions | annuaire.person:update |
GET | /api/workspace/annuaire/pappers/lookup?siren= | annuaire.company:enrich |
GET | /api/workspace/annuaire/map | annuaire.establishment:read |
Exemple de référence d'endpoint :
/api/workspace/annuaire/companies/[id]/enrichAuth Déclenche l'enrichissement KYB d'une entreprise depuis Pappers (fallback INSEE). Ne jamais écraser des données existantes si le provider est indisponible.
Réponse
"pappers" | "insee" | null selon le provider utilisé.Requête
curl -s -X POST "$API/api/workspace/annuaire/companies/$ID/enrich" \
-H "Cookie: $SESSION"
Réponse
{ "id": "cmp_…", "enrichmentSource": "pappers", "enrichedAt": "2026-06-16T10:00:00.000Z" }
Workflows
Enrichissement KYB Pappers
Déclencheur (manuel / création avec SIREN / job hebdomadaire)
│
├─→ pappers.service.ts → GET /entreprise?siren=...
│ │ 200 OK → mapping Company + Establishment[] + Person[] (dirigeants)
│ │ 404 / timeout → fallback INSEE Sirene V3
│ └─ tout échoue → enrichmentSource=null, données existantes inchangées
│
└─→ enrichedAt = now() · rawEnrichment = payload · audit log company.enriched
Mapping Pappers → Company : name, legalName, legalForm, activityCode,
registrationDate, capitalAmount, websiteUrl, status.
Établissements : créer/mettre à jour via rapprochement SIRET.
Dirigeants : créer/mettre à jour Person via rapprochement nom complet.
Cache : si un appel Pappers pour ce SIREN date de moins de 7 jours → payload caché
renvoyé (sauf force-refresh).
Import multi-établissements
- Recherche SIREN (autocomplete Pappers ou saisie directe).
- Pappers renvoie la liste des
etablissements[]. - Wizard de sélection : l'utilisateur coche les établissements à importer.
- Pour chaque SIRET sélectionné :
upsert Establishment(rapprochement SIRET, pas de doublon). - Géocodage asynchrone (
job geocode-establishment) silat/lngabsents.
Géocodage adresse
À la création/modification d'un Establishment avec adresse complète et sans coordonnées →
job geocode-establishment via MapTiler (priorité, couverture FR) ou Nominatim (fallback
gratuit, throttling 1 req/s). Si échec : geocodeFailed = true, correction manuelle possible.
Détection et merge de doublons
- À la création : SIREN existant → erreur 409 avec CTA « Voir fiche existante » ; nom + ville > 80 % similarité → warning « Doublon possible ».
- Job nocturne
detect-duplicatesvia trigrammespg_trgm→ page/workspace/annuaire/duplicates. - Merge : choix du master, sélection champ par champ, redirection de toutes les FK,
slaves marqués
deletedAt+ commentaire « Mergé dans X », audit logcompany.merged.
Règles métier
- SIREN unique par organisation (
unique(organizationId, siren)). - Validation Zod :
SirenSchema(9 chiffres + Luhn),SiretSchema(14 chiffres + Luhn),VatNumberSchema(/^[A-Z]{2}\d{2,12}$/). TVA intra-UE vérifiable via VIES (async, optionnel). - Recherche full-text PostgreSQL (
tsvector GENERATED, index GIN) surCompany,Establishment,Person. Requête viawebsearch_to_tsquery('french', :q). - Listes paginées (max 100 par page), search avec debounce 300 ms 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 v2
KYB principal. Plan Business (5 000 appels/mois). Clé PAPPERS_API_TOKEN. Variable KYB_PROVIDER=pappers.
INSEE Sirene V3
Fallback gratuit. Throttling 30 req/min. Activé automatiquement si Pappers indisponible.
MapTiler / Nominatim
Géocodage adresse → latitude/longitude (Decimal 10,7). Nominatim = fallback, attribution requise.
VIES (UE)
Validation TVA intra-UE (SOAP ou REST). Optionnel, asynchrone.