Aller au contenu principal

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

BesoinClientQuand
Import initial ou synchronisation récurrente de 10+ employésISyncClient.SyncQuotidien / 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 resteIEmployesClient.UpdateCourrielsCampagne de nettoyage, import de corrections mauvais-courriel
Membres créés côté MCM (formulaire public) et à transférer dans votre systèmeIConciliationClientClient 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 (ou IdExterne pour Employe). 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/Erreurs par 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 B2BUpdateEmployeDto contient IdentifiantExterneEmployeur et Matricule au niveau racine (legacy). Préférez ISyncClient.Sync pour 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.SearchSignaturesV2 avec filtres Apres/Avant pour un flux temporel,
  • ou une requête ciblée par GetEmployeById pour les synchronisations incrémentales (stocker le couple IdExterne → 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

CodeSignification
DemandeAdhesion.NotFoundLa demande n'existe pas ou n'appartient pas au client de la clé
DemandeAdhesion.StatutInvalidePourAccepterConfirmer appelé sur une demande qui n'est pas ATransferer (déjà traitée)
DemandeAdhesion.StatutInvalidePourRejetRejeter 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 IServiceScope par 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.DateDebut marque 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 LastSyncedUtc de votre côté et n'envoyez que le delta.
  • Alignement V1 / V2 : ne mélangez pas B2BUpdateEmployeDto (V1, fait un CRUD) avec ISyncClient.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ômeCause probable
Emploi rejeté, employeur absentIdentifiantExterneEmployeur 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 / validationDeux employés avec le même courriel — MCM impose l'unicité
Employe.Validation sur MatriculeMatricule vide ou non unique au sein de l'employeur
Écritures silencieusesRegardez B2BSyncResult.Erreurs par entité — les erreurs individuelles n'apparaissent pas dans result.IsError

Voir aussi