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​