Synchronisation des employés
Ce guide couvre l'import de membres dans MCM : synchronisation en masse (V2), mise à jour ciblée, suppression, flux avec système de mission, et bonnes pratiques multi-locataires.
Module requis : MaCarteDeMembre (voir le guide d'authentification).
Clients SDK impliqués : ISyncClient, IEmployesClient, IConciliationClient.
1. Choisir la bonne approche
| Besoin | Client | Quand |
|---|---|---|
| Import initial ou synchronisation récurrente de 10+ employés | ISyncClient.Sync | Quotidien / hebdomadaire, avec la source de vérité côté partenaire |
| CRUD unitaire (ajout, mise à jour ciblée) | IEmployesClient.Add/Update/Delete | Événements métiers isolés (création d'un membre, correction ponctuelle) |
| Corriger des courriels en masse sans toucher au reste | IEmployesClient.UpdateCourriels | Campagne de nettoyage, import de corrections mauvais-courriel |
| Membres créés côté MCM (formulaire public) et à transférer dans votre système | IConciliationClient | Client utilisant le système de mission |
2. Synchronisation en lot (ISyncClient.Sync)
Structure de l'appel V2
Un seul appel transmet une arborescence complète :
Sync(syndicats, employes, objetsConsentement)
└ Syndicats
└ Employeurs (nested)
└ Employes
├ Emplois (nested) → liens employé ↔ employeur
└ Adhesions (nested) → liens employé ↔ syndicat avec DateAdhesion
└ ObjetsConsentement
Ordre de traitement serveur :
1. Syndicats
2. Employeurs (résolus depuis Syndicats)
3. ObjetsConsentement
4. Employes
5. Emplois
6. Adhesions
Exemple complet
var result = await syncClient.Sync(
syndicatDtos:
[
new B2BUpdateSyndicatDtoV2
{
IdentifiantExterne = "SYND001",
Nom = "Syndicat des travailleurs",
Employeurs =
[
new() { IdentifiantExterne = "EMP001", Nom = "Entreprise ABC" },
new() { IdentifiantExterne = "EMP002", Nom = "Entreprise XYZ" }
]
}
],
employeDtos:
[
new B2BUpsertEmployeDtoV2
{
IdExterne = "PERSON001",
Prenom = "Jean",
Nom = "Dupont",
Courriel = "jean@example.com",
DateNaissance = new DateOnly(1985, 3, 12),
Emplois =
[
new()
{
IdentifiantExterne = "EMPLOI001",
IdentifiantExterneEmployeur = "EMP001",
Matricule = "M12345",
DateDebut = new DateTime(2020, 1, 15)
}
],
Adhesions =
[
new()
{
SyndicatIdExterne = "SYND001",
DateAdhesion = new DateTime(2020, 1, 15)
}
]
}
],
objetConsentementDtos: []);
if (result.IsError)
{
Console.WriteLine($"[{result.FirstError.Code}] {result.FirstError.Description}");
return;
}
var r = result.Value;
Console.WriteLine($"Syndicats: +{r.Syndicats.AddCount} ~{r.Syndicats.UpdateCount} (erreurs: {r.Syndicats.Erreurs.Count})");
Console.WriteLine($"Employeurs: +{r.Employeurs.AddCount} ~{r.Employeurs.UpdateCount}");
Console.WriteLine($"Employés: +{r.Employes.AddCount} ~{r.Employes.UpdateCount}");
Console.WriteLine($"Emplois: +{r.Emplois.AddCount} ~{r.Emplois.UpdateCount}");
Console.WriteLine($"Adhésions: +{r.Adhesions.AddCount} ~{r.Adhesions.UpdateCount}");
// Erreurs par enregistrement
foreach (var err in r.Employes.Erreurs)
logger.LogWarning("{IdExterne}: {Message}", err.IdUnique, err.Message);
Clés de déduplication
- Syndicat / Employeur / Emploi / Employé / ObjetConsentement :
IdentifiantExterne(ouIdExternepourEmploye). Doit être stable — changer cet identifiant côté source crée un doublon côté MCM. - Adhésion : clé composite
(EmployeIdExterne, SyndicatIdExterne). Une adhésion par couple employé-syndicat.
Idempotence et partialité
- Idempotent : renvoyer la même charge utile produit le même état final.
- Partiellement tolérant : une erreur sur un employé n'annule pas les autres. Les compteurs
AddCount/UpdateCount/Erreurspar entité reflètent les succès partiels. - Pas transactionnel global : si le réseau coupe au milieu, les syndicats et employeurs peuvent être écrits sans les employés. Relancer la même charge utile corrige l'état.
Taille conseillée
Testé avec succès jusqu'à ~10 000 employés par appel (avec emplois imbriqués). Au-delà, découpez en lots — la performance serveur chute sur les très grosses charges utiles (sérialisation, validation, transaction EF).
Stratégie pragmatique :
foreach (var batch in allEmployes.Chunk(5000))
{
await syncClient.Sync(
syndicatDtos: tousLesSyndicats, // maîtrise — petite liste
employeDtos: batch.ToList(),
objetConsentementDtos: []);
}
3. CRUD unitaire
Créer un employé
var result = await employesClient.AddEmploye(new B2BUpdateEmployeDto
{
IdExterne = "PERSON123",
Prenom = "Alice",
Nom = "Tremblay",
NoMembre = "NM-123",
Courriel = "alice@example.com",
IdentifiantExterneEmployeur = "EMP001", // requis en CRUD V1 — pas en V2 Sync
Matricule = "A123"
});
Le DTO V1
B2BUpdateEmployeDtocontientIdentifiantExterneEmployeuretMatriculeau niveau racine (legacy). PréférezISyncClient.Syncpour tout import structuré ; utilisez le CRUD V1 pour les cas unitaires ou une intégration existante.
Mettre à jour / supprimer
await employesClient.UpdateEmploye(dto); // même DTO — full replace
await employesClient.DeleteEmploye("PERSON123"); // suppression
La suppression est logique côté MCM : l'employé est désactivé. Ses adhésions historiques restent auditables.
Mise à jour ciblée des courriels
Pour ne modifier que les courriels d'une cohorte, sans toucher au reste :
var result = await employesClient.UpdateCourriels(new[]
{
new B2BUpdateCourrielDto { IdExterne = "PERSON001", CourrielPrincipal = "nouveau@example.com" },
new B2BUpdateCourrielDto { IdExterne = "PERSON002", CourrielAlternatif = "alt@example.com" },
new B2BUpdateCourrielDto { IdExterne = "PERSON003", CourrielPrincipal = "invalide" } // sera rejeté
});
if (result.IsError) return; // erreur globale (auth, serveur)
// Le Value est la liste des erreurs par entrée
foreach (var err in result.Value)
logger.LogWarning("{IdExterne}: {Msg}", err.IdUnique, err.Message);
Valeurs omises (null) = pas de changement. Passer un courriel invalide retourne une B2BError dans la liste, sans arrêter le traitement des autres.
4. Lecture des employés
Un par un
var result = await employesClient.GetEmployeById("PERSON001");
if (!result.IsError) { /* result.Value est un B2BEmployeItem */ }
Tous
var all = await employesClient.GetAllEmployes();
Pas de pagination sur cet endpoint — pour les gros volumes (>50 000), préférez :
ISignatureClient.SearchSignaturesV2avec filtresApres/Avantpour un flux temporel,- ou une requête ciblée par
GetEmployeByIdpour les synchronisations incrémentales (stocker le coupleIdExterne→ timestamp de dernière MAJ de votre côté).
5. Flux conciliation (système de mission)
Pour les clients configurés avec un système de mission externe, les demandes d'adhésion publiques (formulaires web remplis par des non-membres) ne créent pas directement un employé : elles sont marquées ATransferer et le système de mission doit les traiter.
Parcours type
Demande publique signée (côté MCM)
↓
Admin MCM accepte → demande passe à "ATransferer"
↓
Votre job quotidien appelle GetDemandesATransferer
↓
Pour chaque demande :
1. Créer le membre dans votre système (avec ou sans Matricule)
2. Confirmer(idUnique, data?) → MCM crée l'Employe local + IdExterne
3. (Optionnel) Rejeter(idUnique) si demande invalide
Exemple
var demandes = await conciliationClient.GetDemandesATransferer();
if (demandes.IsError) return;
foreach (var d in demandes.Value)
{
try
{
// 1. Créer dans votre système de mission
var noMembre = await missionSystem.CreerMembre(d);
// 2. Confirmer côté MCM avec le matricule attribué
var confirme = await conciliationClient.Confirmer(d.IdUnique,
new B2BConfirmerDemandeAdhesionDto
{
Matricule = noMembre,
// Autres champs optionnels : surchargent la demande
});
if (confirme.IsError)
{
logger.LogError("Confirmation échouée {Id}: {Err}",
d.IdUnique, confirme.FirstError.Description);
continue;
}
logger.LogInformation("Membre créé dans MCM: IdExterne={Id}", confirme.Value);
}
catch (Exception ex)
{
// Si votre système rejette la demande (doublon, liste noire) :
await conciliationClient.Rejeter(d.IdUnique);
logger.LogWarning(ex, "Demande rejetée {Id}", d.IdUnique);
}
}
Erreurs spécifiques
| Code | Signification |
|---|---|
DemandeAdhesion.NotFound | La demande n'existe pas ou n'appartient pas au client de la clé |
DemandeAdhesion.StatutInvalidePourAccepter | Confirmer appelé sur une demande qui n'est pas ATransferer (déjà traitée) |
DemandeAdhesion.StatutInvalidePourRejet | Rejeter sur une demande déjà acceptée ou fusionnée |
Webhook adhesion.signee
La création résultante déclenche adhesion.signee. Si vous écoutez ce webhook, vous recevrez automatiquement le B2BAdhesionItemV2 correspondant — pas besoin d'interroger périodiquement. Voir webhooks.md.
Les paiements déjà attachés à la demande sont automatiquement transférés vers la nouvelle adhésion.
6. Multi-locataires (plusieurs clients MCM)
Si votre service gère plusieurs clients MCM (par ex. un SaaS qui intègre plusieurs syndicats), utilisez l'override ApiKey sur IBaseClient :
public class MultiTenantSync(ISyncClient syncClient)
{
public async Task SyncPour(string tenantApiKey, List<B2BUpsertEmployeDtoV2> employes)
{
syncClient.ApiKey = tenantApiKey; // override
try
{
await syncClient.Sync(syndicats, employes, []);
}
finally
{
syncClient.ApiKey = null; // retour au défaut
}
}
}
Non thread-safe : séparez les scopes DI par job (un
IServiceScopepar tenant) plutôt que de partager un singleton.
7. Bonnes pratiques
- Stabilité des
IdentifiantExterne: traitez-les comme des clés primaires. Un renommage côté source = un doublon côté MCM. - Ordre des sections Sync : tous les syndicats/employeurs référencés par un emploi doivent être dans la même charge utile ou déjà présents côté MCM. Sinon, l'emploi est rejeté.
- DateAdhesion vs DateDebut :
Adhesion.DateAdhesion(V2) marque la signature au syndicat ;Emploi.DateDebutmarque l'embauche chez l'employeur. Deux dates distinctes, deux entités distinctes. - Tolérance aux erreurs partielles : ne faites pas échouer tout votre job sur une seule erreur d'employé. Journalisez les
Erreurs[]par catégorie et continuez. - Fréquence de sync : incrémental > complet. Stockez un timestamp
LastSyncedUtcde votre côté et n'envoyez que le delta. - Alignement V1 / V2 : ne mélangez pas
B2BUpdateEmployeDto(V1, fait un CRUD) avecISyncClient.Sync(V2, charge utile complète). Le second est la voie privilégiée. - Ne pas supprimer agressivement : un employé retiré de votre source n'est pas absent par erreur — confirmez un motif métier avant
DeleteEmploye.
8. Dépannage
| Symptôme | Cause probable |
|---|---|
| Emploi rejeté, employeur absent | IdentifiantExterneEmployeur ne correspond à aucun employeur existant ou dans la même charge utile |
| Adhésion dupliquée | (EmployeIdExterne, SyndicatIdExterne) ne correspond pas à la clé existante à cause d'une différence de casse/espaces |
Employe.CourrielDuplique / validation | Deux employés avec le même courriel — MCM impose l'unicité |
Employe.Validation sur Matricule | Matricule vide ou non unique au sein de l'employeur |
| Écritures silencieuses | Regardez B2BSyncResult.Erreurs par entité — les erreurs individuelles n'apparaissent pas dans result.IsError |