Annuaire

Référence technique du domaine Annuaire — entreprises, établissements, contacts, KYB Pappers/INSEE, segmentation, géocodage.

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.

Tous les écrans et routes vivent sous /workspace/annuaire/* et /api/workspace/annuaire/*. Source de cette page : docs/07-domain-annuaire.md.

Vocabulaire & entités

TermeEntitéDéfinition
EntrepriseCompanyEntité légale identifiée par SIREN. Source de vérité légale.
ÉtablissementEstablishmentSite physique identifié par SIRET — unité d'exploitation où les équipements sont installés.
PersonnePersonContact humain rattaché à une Company et/ou un Establishment.
EnseigneBrandMarque commerciale fédérant plusieurs établissements (ex : « Carrefour Market »).
SegmentSegmentClassification commerciale org-scoped portée par l'établissement (ex : « GMS », « Horeca »).
ActivitéActivityCode 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 via Company/Establishment ; les modules CRM et Ventes ne font que référencer ces entités.

Écrans

ÉcranRouteContenu
Liste entreprises/workspace/annuaire/companiesDataTable (SIREN, nom, ville, segments, nb établissements) + filtres + enrichissement de masse.
Création entreprise/workspace/annuaire/companies/newSaisie 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/establishmentsDataTable + carte géo cluster (zoom-aware) + fiche détaillée (assets, maintenance, transport).
Liste personnes/workspace/annuaire/peopleDataTable + import Excel/CSV avec wizard de mapping colonnes.
Carte/workspace/annuaire/mapMapLibre plein écran, marqueurs par segment, export KML/GeoJSON.
Segments/workspace/annuaire/settings/segmentsCRUD segments (label, couleur).
Fonctions/workspace/annuaire/settings/functionsCRUD fonctions (label, catégorie : DG / DAF / DSI / ACHATS / OPERATIONS / AUTRE).
Doublons/workspace/annuaire/duplicatesGroupes pré-calculés via trigrammes pg_trgm, résolution manuelle (merge).

Modèle de données (points clés)

  • Company peut exister sans Establishment (prospect étranger ou micro-entreprise), mais un siège ou établissement principal est requis à la première commande/contrat.
  • Person est rattaché à 0 ou 1 Company et 0 ou 1 Establishment.
  • Suppression d'une Company interdite si Customer/Order/Invoice/LeaseContract actifs — soft delete uniquement.
  • Suppression d'un Establishment interdite si des assets y sont en CURRENT_LOCATION actifs.
  • Person.optInMarketing conditionne 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) sur Company.
  • 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éthodeRoutePermission
GET/api/workspace/annuaire/companiesannuaire.company:read
GET/api/workspace/annuaire/companies/[id]annuaire.company:read
POST/api/workspace/annuaire/companiesannuaire.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]/enrichannuaire.company:enrich
POST/api/workspace/annuaire/companies/mergeannuaire.company:delete
GET/api/workspace/annuaire/establishmentsannuaire.establishment:read
GET/api/workspace/annuaire/establishments/[id]annuaire.establishment:read
POST/api/workspace/annuaire/establishmentsannuaire.establishment:create
PATCH/api/workspace/annuaire/establishments/[id]annuaire.establishment:update
DELETE/api/workspace/annuaire/establishments/[id]annuaire.establishment:delete
GET/api/workspace/annuaire/peopleannuaire.person:read
GET/api/workspace/annuaire/people/[id]annuaire.person:read
POST/api/workspace/annuaire/peopleannuaire.person:create
PATCH/api/workspace/annuaire/people/[id]annuaire.person:update
POST/api/workspace/annuaire/people/importannuaire.person:create
GET/api/workspace/annuaire/segmentsannuaire.company:read
POST/api/workspace/annuaire/segmentsannuaire.company:update
GET/api/workspace/annuaire/functionsannuaire.person:read
POST/api/workspace/annuaire/functionsannuaire.person:update
GET/api/workspace/annuaire/pappers/lookup?siren=annuaire.company:enrich
GET/api/workspace/annuaire/mapannuaire.establishment:read

Exemple de référence d'endpoint :

POST/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

enrichmentSource
string
"pappers" | "insee" | null selon le provider utilisé.
enrichedAt
string
Timestamp ISO 8601 de l'enrichissement.

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

  1. Recherche SIREN (autocomplete Pappers ou saisie directe).
  2. Pappers renvoie la liste des etablissements[].
  3. Wizard de sélection : l'utilisateur coche les établissements à importer.
  4. Pour chaque SIRET sélectionné : upsert Establishment (rapprochement SIRET, pas de doublon).
  5. Géocodage asynchrone (job geocode-establishment) si lat/lng absents.

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-duplicates via trigrammes pg_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 log company.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) sur Company, Establishment, Person. Requête via websearch_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.