Aller au contenu

signaletique-salarie

Interface synchrone (OpenAPI) — version 0.1.0. Producteur : entreprise. Consommateurs déclarés : back-office, traitement-dsn.

Le service coordonne deux domaines sans modèle partagé ni transaction distribuée : Entreprise détient le lien d’emploi et les données sociales, Épargnant détient l’identité. La réponse est asynchrone (202 et operationId) : l’accusé ne dépend pas de la disponibilité de l’Épargnant. Un lien d’emploi n’est jamais créé sans epargnantId confirmé ; un rapprochement ambigu suspend la création. Les parties personne, emploi et données sociales ont des résultats indépendants.

openapi: 3.1.0
info:
title: entreprise — la mise à jour de la signalétique salarié
version: 0.1.0
summary: >-
Enregistrer ou mettre à jour un salarié — unitairement ou par fichier —, suivre
l'opération, et corriger un mauvais rattachement.
description: >-
Le service coordonne deux domaines sans modèle partagé ni transaction distribuée :
Entreprise détient le lien d'emploi et les données sociales, Épargnant détient
l'identité. La réponse est asynchrone (202 et operationId) : l'accusé ne dépend pas
de la disponibilité de l'Épargnant. Un lien d'emploi n'est jamais créé sans
epargnantId confirmé ; un rapprochement ambigu suspend la création. Les parties
personne, emploi et données sociales ont des résultats indépendants.
x-producteurs:
- entreprise
x-consommateurs:
- composant: back-office
statut: pressenti — assistant signalétique et dialogue d'ajout d'un salarié
- composant: traitement-dsn
statut: pressenti — canal amont, hors plateforme
x-ruptures: [] # première version publiée — aucune rupture
paths:
/entreprise/v1/entreprises/{entrepriseId}/salaries:upsert:
post:
operationId: enregistrerOuMettreAJourUnSalarie
summary: >-
Enregistrer ou mettre à jour un salarié — accusé asynchrone, l'opération se
poursuit et se consulte.
description: >-
Au moins personneDeclaree ou emploi doit être présent. Pour créer un lien sans
epargnantId connu, les attributs minimaux de résolution sont exigés. Le tenant
vient du contexte sécurisé, jamais du corps.
security:
- authentification: [entreprise:signaletique]
parameters:
- $ref: '#/components/parameters/tenant'
- $ref: '#/components/parameters/correlation'
- $ref: '#/components/parameters/idempotence'
- name: entrepriseId
in: path
required: true
description: L'entreprise portant le lien d'emploi — résolue dans le tenant courant.
schema: { type: string, minLength: 1 }
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/DemandeDeSignaletique'
responses:
'202':
description: >-
La demande est validée et enregistrée ; le traitement se poursuit. Le rejeu
d'une même clé et d'un même corps retourne cette même réponse.
headers:
Location:
description: Le lien de suivi de l'opération.
schema: { type: string }
content:
application/json:
schema:
$ref: '#/components/schemas/AccuseDOperation'
'400': { $ref: '#/components/responses/demandeInvalide' }
'401': { $ref: '#/components/responses/nonAuthentifie' }
'403': { $ref: '#/components/responses/nonAutorise' }
'404': { $ref: '#/components/responses/introuvable' }
'409': { $ref: '#/components/responses/conflit' }
'422': { $ref: '#/components/responses/regleMetier' }
'429': { $ref: '#/components/responses/quota' }
'503': { $ref: '#/components/responses/indisponible' }
/entreprise/v1/operations/mises-a-jour-signaletique-salarie/{operationId}:
get:
operationId: consulterUneOperationDeSignaletique
summary: L'état de l'opération, ses résultats par partie et ses anomalies communicables.
description: >-
La réponse n'expose ni les candidats d'un rapprochement, ni une donnée
personnelle que l'appelant n'est pas autorisé à consulter.
security:
- authentification: [entreprise:consultation]
parameters:
- $ref: '#/components/parameters/tenant'
- $ref: '#/components/parameters/correlation'
- name: operationId
in: path
required: true
schema: { type: string, minLength: 1 }
responses:
'200':
description: L'état courant de l'opération.
content:
application/json:
schema:
$ref: '#/components/schemas/EtatDOperation'
'401': { $ref: '#/components/responses/nonAuthentifie' }
'403': { $ref: '#/components/responses/nonAutorise' }
'404': { $ref: '#/components/responses/introuvable' }
/entreprise/v1/liens-emploi/{lienEmploiId}:corriger-rattachement:
post:
operationId: corrigerLeRattachementDUnLienEmploi
summary: >-
Corriger le rattachement d'un lien d'emploi à une autre personne — droit
renforcé, historisé, sans déplacement silencieux des effets aval.
description: >-
Vérifie que le lien et les deux personnes appartiennent au même tenant,
verrouille la version courante du lien, conserve l'ancien et le nouveau
rattachement, publie un événement métier et déclenche l'analyse des conséquences
aval sans les corriger. Les comptes, adhésions, ordres et positions issus de
l'ancien rattachement ne sont jamais déplacés par cette seule commande.
security:
- authentification: [entreprise:correction-rattachement]
parameters:
- $ref: '#/components/parameters/tenant'
- $ref: '#/components/parameters/correlation'
- $ref: '#/components/parameters/idempotence'
- name: lienEmploiId
in: path
required: true
schema: { type: string, minLength: 1 }
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/DemandeDeCorrectionDeRattachement'
responses:
'200':
description: La correction est enregistrée et historisée.
content:
application/json:
schema:
$ref: '#/components/schemas/AccuseDOperation'
'400': { $ref: '#/components/responses/demandeInvalide' }
'401': { $ref: '#/components/responses/nonAuthentifie' }
'403': { $ref: '#/components/responses/nonAutoriseCorrection' }
'404': { $ref: '#/components/responses/introuvable' }
'409': { $ref: '#/components/responses/conflit' }
'422': { $ref: '#/components/responses/regleMetier' }
/entreprise/v1/imports/signaletiques-salaries:
post:
operationId: creerUnImportDeSignaletiques
summary: >-
Créer un import et annoncer son périmètre — une entreprise, ou un groupe dont
chaque ligne désignera son entreprise membre.
security:
- authentification: [entreprise:signaletique]
parameters:
- $ref: '#/components/parameters/tenant'
- $ref: '#/components/parameters/correlation'
- $ref: '#/components/parameters/idempotence'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/DemandeDImport'
responses:
'201':
description: >-
L'import est créé, en attente du fichier. Le rejeu d'une même clé de fichier
et d'une même empreinte retourne l'import initial (RM-026).
content:
application/json:
schema:
$ref: '#/components/schemas/EtatDImport'
'400': { $ref: '#/components/responses/demandeInvalide' }
'401': { $ref: '#/components/responses/nonAuthentifie' }
'403': { $ref: '#/components/responses/nonAutorise' }
'404': { $ref: '#/components/responses/introuvable' }
'409': { $ref: '#/components/responses/conflit' }
'413': { $ref: '#/components/responses/fichierTropGrand' }
'415': { $ref: '#/components/responses/formatNonSupporte' }
'422': { $ref: '#/components/responses/regleMetier' }
/entreprise/v1/imports/signaletiques-salaries/{importId}:sceller:
post:
operationId: scellerUnImport
summary: >-
Sceller l'import après l'envoi des octets — le contenu devient immuable ; un
nouvel envoi exige un nouvel import.
description: >-
Le scellement vérifie la taille observée, l'empreinte, le format, l'absence de
contenu dangereux et la cohérence avec les métadonnées annoncées. Une anomalie
fatale rejette l'import avant toute exécution métier.
security:
- authentification: [entreprise:signaletique]
parameters:
- $ref: '#/components/parameters/tenant'
- $ref: '#/components/parameters/correlation'
- $ref: '#/components/parameters/idempotence'
- name: importId
in: path
required: true
schema: { type: string, minLength: 1 }
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [empreinteSha256]
properties:
empreinteSha256:
type: string
description: L'empreinte des octets envoyés — l'identité du contenu.
responses:
'200':
description: L'import est scellé et validé, ou rejeté par un contrôle global.
content:
application/json:
schema:
$ref: '#/components/schemas/EtatDImport'
'400': { $ref: '#/components/responses/demandeInvalide' }
'401': { $ref: '#/components/responses/nonAuthentifie' }
'403': { $ref: '#/components/responses/nonAutorise' }
'404': { $ref: '#/components/responses/introuvable' }
'409': { $ref: '#/components/responses/conflit' }
'422': { $ref: '#/components/responses/regleMetier' }
/entreprise/v1/imports/signaletiques-salaries/{importId}:
get:
operationId: consulterUnImport
summary: >-
Les compteurs de l'import au moment de la requête — agrégés depuis les
partitions, jamais un rechargement des lignes.
security:
- authentification: [entreprise:consultation]
parameters:
- $ref: '#/components/parameters/tenant'
- $ref: '#/components/parameters/correlation'
- name: importId
in: path
required: true
schema: { type: string, minLength: 1 }
responses:
'200':
description: L'état et les compteurs de l'import.
content:
application/json:
schema:
$ref: '#/components/schemas/EtatDImport'
'401': { $ref: '#/components/responses/nonAuthentifie' }
'403': { $ref: '#/components/responses/nonAutorise' }
'404': { $ref: '#/components/responses/introuvable' }
/entreprise/v1/imports/signaletiques-salaries/{importId}/lignes:
get:
operationId: listerLesLignesDUnImport
summary: Les lignes de l'import, filtrables par statut et paginées par curseur.
security:
- authentification: [entreprise:consultation]
parameters:
- $ref: '#/components/parameters/tenant'
- $ref: '#/components/parameters/correlation'
- name: importId
in: path
required: true
schema: { type: string, minLength: 1 }
- name: statut
in: query
required: false
schema:
$ref: '#/components/schemas/StatutDeLigne'
- name: curseur
in: query
required: false
description: Le numéro de ligne après lequel poursuivre.
schema: { type: integer, format: int64 }
- name: limite
in: query
required: false
schema: { type: integer, default: 100, maximum: 1000 }
responses:
'200':
description: Une page de lignes — jamais la totalité.
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/LigneDImport'
'401': { $ref: '#/components/responses/nonAuthentifie' }
'403': { $ref: '#/components/responses/nonAutorise' }
'404': { $ref: '#/components/responses/introuvable' }
components:
parameters:
tenant:
name: X-Tenant-Id
in: header
required: true
description: >-
Le teneur de compte — injecté et signé par la passerelle, dérivé de l'identité
authentifiée. Il n'est jamais fourni comme valeur libre dans l'URL ou le corps.
schema: { type: string, minLength: 1 }
correlation:
name: X-Correlation-Id
in: header
required: true
description: La corrélation de bout en bout — propagée jusqu'aux événements.
schema: { type: string, minLength: 1 }
idempotence:
name: Idempotency-Key
in: header
required: true
description: >-
La clé d'idempotence, de portée tenant + appelant + route + clé. Même clé et
même corps : la réponse initiale. Même clé, corps différent : 409.
schema: { type: string, minLength: 1 }
securitySchemes:
authentification:
type: http
scheme: bearer
description: >-
Tout appel est authentifié (401) et autorisé par famille d'accès (403 hors famille),
déclarée en portée entreprise:famille. Trois familles ici : entreprise:signaletique (les
commandes et les imports),
entreprise:consultation (le suivi) et entreprise:correction-rattachement — un
droit renforcé, distinct des deux autres, comme le § 18 l'exige.
responses:
demandeInvalide:
description: JSON invalide ou champ obligatoire absent (INVALID_REQUEST).
content:
application/problem+json:
schema: { $ref: '#/components/schemas/Probleme' }
nonAuthentifie:
description: Identité absente ou invalide (UNAUTHENTICATED).
nonAutorise:
description: >-
Droit insuffisant (FORBIDDEN) — les 403 croisés entre familles sont prouvés par
les tests d'assemblage.
nonAutoriseCorrection:
description: >-
La correction de rattachement exige la famille entreprise:correction-rattachement
— ni entreprise:signaletique ni entreprise:consultation ne l'ouvrent.
introuvable:
description: >-
Ressource absente dans le tenant courant — y compris une ressource d'un autre
tenant, dont l'existence n'est jamais confirmée (404, jamais 403).
content:
application/problem+json:
schema: { $ref: '#/components/schemas/Probleme' }
conflit:
description: >-
IDEMPOTENCY_KEY_REUSED (même clé, corps différent), SOURCE_VERSION_CONFLICT
(version incompatible) ou conflit d'empreinte d'import.
content:
application/problem+json:
schema: { $ref: '#/components/schemas/Probleme' }
regleMetier:
description: >-
BUSINESS_RULE_VIOLATION ou IMPORT_SCOPE_VIOLATION — demande syntaxiquement
valide mais incohérente au regard du métier ou du périmètre annoncé.
content:
application/problem+json:
schema: { $ref: '#/components/schemas/Probleme' }
quota:
description: Capacité momentanément dépassée (RATE_LIMITED).
fichierTropGrand:
description: Taille de fichier ou de ligne supérieure à la limite (FILE_TOO_LARGE).
formatNonSupporte:
description: Format ou encodage non pris en charge (UNSUPPORTED_FILE_FORMAT).
indisponible:
description: >-
Demande non enregistrable ; le client peut rejouer (SERVICE_UNAVAILABLE). Une
indisponibilité survenant APRÈS l'enregistrement n'est jamais rendue ici : elle
conduit à ECHEC_REPRENABLE.
schemas:
DemandeDeSignaletique:
type: object
required: [source, modeMiseAJour, dateEffet]
description: >-
Au moins personneDeclaree ou emploi est présent. Les montants sont des chaînes
décimales ; les dates sont ISO 8601 et les instants portent leur décalage.
properties:
source:
$ref: '#/components/schemas/Source'
personneDeclaree:
$ref: '#/components/schemas/PersonneDeclaree'
emploi:
$ref: '#/components/schemas/Emploi'
donneesSociales:
$ref: '#/components/schemas/DonneesSociales'
modeMiseAJour:
$ref: '#/components/schemas/ModeMiseAJour'
dateEffet:
type: string
format: date
description: La date à laquelle le fait métier est valable.
Source:
type: object
required: [systeme, referenceSalarie, version]
properties:
systeme:
type: string
description: La source déclarée et autorisée — par exemple DSN, SIRH_GROUPE.
emetteurId:
type: string
referenceSalarie:
type: string
description: L'identifiant stable du salarié dans la source (matricule).
version:
type: string
description: La version ou séquence comparable selon la politique de la source.
dateObservation:
type: string
format: date-time
PersonneDeclaree:
type: object
description: >-
Les données transmises au domaine Épargnant pour la résolution. Elles ne
deviennent jamais des colonnes métier de personne dans Entreprise.
properties:
nomNaissance: { type: string }
nomUsage: { type: string }
prenoms:
type: array
items: { type: string }
dateNaissance: { type: string, format: date }
lieuNaissance:
type: object
properties:
codePays: { type: string }
codeCommune: { type: string }
identifiantsDeclares:
type: array
description: >-
Un identifiant sensible (NIR) est chiffré au repos et masqué dans les
interfaces et les journaux ; il n'apparaît jamais en clair.
items:
type: object
required: [type, valeur]
properties:
type: { type: string }
valeur: { type: string }
niveauVerification: { type: string }
Emploi:
type: object
properties:
etablissementId: { type: string }
matricule: { type: string }
natureLien: { type: string }
dateDebut: { type: string, format: date }
dateFin:
type: [string, 'null']
format: date
description: >-
Une date de fin ne supprime pas le lien : elle clôt sa période (RM-018).
statut: { type: string }
categorie: { type: string }
college: { type: string }
DonneesSociales:
type: object
properties:
periode:
type: string
description: La période de référence, par exemple 2026-07.
remunerationBrute:
type: object
properties:
montant:
type: string
description: Chaîne décimale — jamais un flottant, pour éviter la perte de précision.
devise: { type: string }
ModeMiseAJour:
type: string
enum: [DELTA, FULL_SNAPSHOT]
description: >-
En DELTA, un champ absent ne modifie rien et un null explicite efface si le
champ l'autorise. En FULL_SNAPSHOT, le contenu est l'état complet connu de la
source pour le périmètre annoncé. Dans les deux modes, une absence ne supprime
jamais implicitement une personne ni un lien d'emploi.
AccuseDOperation:
type: object
required: [operationId, statut, recuLe]
properties:
operationId: { type: string }
statut:
$ref: '#/components/schemas/StatutDOperation'
recuLe: { type: string, format: date-time }
liens:
type: object
properties:
statut: { type: string }
StatutDOperation:
type: string
enum:
- RECU
- VALIDE
- RESOLUTION_PERSONNE
- ARBITRAGE_REQUIS
- APPLICATION
- TERMINE
- TERMINE_PARTIELLEMENT
- REJETE
- ECHEC_REPRENABLE
- ECHEC_DEFINITIF
StatutDePartie:
type: string
enum: [ABSENTE, APPLIQUE, SANS_CHANGEMENT, EN_ATTENTE, REJETEE]
EtatDOperation:
type: object
required: [operationId, statut, recuLe]
properties:
operationId: { type: string }
statut:
$ref: '#/components/schemas/StatutDOperation'
source:
$ref: '#/components/schemas/Source'
resultats:
type: object
description: >-
Les trois parties ont des résultats indépendants : une modification d'emploi
valide n'est pas annulée parce qu'un changement d'identité attend un
justificatif.
properties:
personne:
type: object
properties:
statut: { $ref: '#/components/schemas/StatutDePartie' }
epargnantId: { type: string }
emploi:
type: object
properties:
statut: { $ref: '#/components/schemas/StatutDePartie' }
lienEmploiId: { type: string }
version: { type: integer }
donneesSociales:
type: object
properties:
statut: { $ref: '#/components/schemas/StatutDePartie' }
anomalies:
type: array
items:
type: object
properties:
code: { type: string }
message: { type: string }
actionAttendue: { type: string }
recuLe: { type: string, format: date-time }
misAJourLe: { type: string, format: date-time }
DemandeDeCorrectionDeRattachement:
type: object
required: [nouvelEpargnantId, dateEffet, motif]
properties:
nouvelEpargnantId: { type: string }
dateEffet: { type: string, format: date }
motif:
type: string
description: Le code de justification — par exemple ERREUR_DE_RAPPROCHEMENT.
justification: { type: string }
resolutionCaseId: { type: string }
DemandeDImport:
type: object
required: [source, perimetre, format, modeMiseAJour]
properties:
source:
$ref: '#/components/schemas/Source'
perimetre:
$ref: '#/components/schemas/PerimetreDImport'
format:
type: string
enum: [CSV_UTF8, NDJSON_UTF8]
description: >-
Des formats à lecture séquentielle seulement — CSV_UTF8 est obligatoire.
Le service ne construit jamais un arbre complet en mémoire.
modeMiseAJour:
$ref: '#/components/schemas/ModeMiseAJour'
nomFichier: { type: string }
tailleDeclaree: { type: integer, format: int64 }
empreinteSha256: { type: string }
PerimetreDImport:
type: object
required: [type]
description: >-
Un import a exactement un périmètre dans un seul tenant (RM-021). En GROUPE,
chaque ligne désigne son entreprise, vérifiée membre à la date de référence ;
une entreprise extérieure fait rejeter la ligne, sans jamais élargir le
périmètre.
properties:
type:
type: string
enum: [ENTREPRISE, GROUPE]
entrepriseId: { type: string }
groupeId: { type: string }
dateReference: { type: string, format: date }
StatutDImport:
type: string
enum:
- EN_ATTENTE_FICHIER
- RECU
- VALIDATION
- PRET_A_TRAITER
- TRAITEMENT
- TERMINE
- TERMINE_AVEC_ANOMALIES
- REJETE
StatutDeLigne:
type: string
enum:
- EN_ATTENTE
- RESERVEE
- TERMINE
- TERMINE_PARTIELLEMENT
- ARBITRAGE_REQUIS
- REJETE
- ECHEC_DEFINITIF
EtatDImport:
type: object
required: [importId, statut]
properties:
importId: { type: string }
statut:
$ref: '#/components/schemas/StatutDImport'
perimetre:
$ref: '#/components/schemas/PerimetreDImport'
empreinteSha256: { type: string }
nombreDePartitions: { type: integer }
compteurs:
type: object
description: >-
Les compteurs par statut de ligne, agrégés depuis les partitions — jamais
obtenus en rechargeant les lignes.
additionalProperties:
type: integer
format: int64
LigneDImport:
type: object
required: [numeroLigne, statut]
properties:
numeroLigne: { type: integer, format: int64 }
statut:
$ref: '#/components/schemas/StatutDeLigne'
entrepriseId: { type: string }
referenceSalarieSource: { type: string }
operationId: { type: string }
codesAnomalie:
type: array
items: { type: string }
Probleme:
type: object
description: Le format d'erreur du § 21.2 — application/problem+json.
required: [status, code]
properties:
type: { type: string }
title: { type: string }
status: { type: integer }
code: { type: string }
detail: { type: string }
instance: { type: string }
correlationId: { type: string }
errors:
type: array
items:
type: object
properties:
path: { type: string }
code: { type: string }