-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore: ajout d'un écran de visualisation & suppression de duplicats d…
…'effectifs (#3116) Co-authored-by: Maxime Dréau <[email protected]>
- Loading branch information
Showing
19 changed files
with
1,165 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,7 @@ | |
"axiosist", | ||
"coordonnees", | ||
"DECA", | ||
"duplicats", | ||
"etablissement", | ||
"gesti", | ||
"groupby", | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import { ObjectId } from "mongodb"; | ||
|
||
import { effectifsDb } from "@/common/model/collections"; | ||
|
||
import { getAnneesScolaireListFromDate } from "../utils/anneeScolaireUtils"; | ||
|
||
/** | ||
* Construction du pipeline d'aggregation du clean des noms / prenom pour identification des doublons | ||
* @returns | ||
*/ | ||
const getSanitizedNomPrenomPipeline = ( | ||
nomApprenantField = "$apprenant.nom", | ||
prenomApprenantField = "$apprenant.prenom" | ||
) => [ | ||
{ | ||
$addFields: { | ||
sanitizedNom: { $regexFindAll: { input: { $toLower: nomApprenantField }, regex: /[A-Za-zÀ-ÖØ-öø-ÿ]/ } }, | ||
}, | ||
}, | ||
{ | ||
$addFields: { | ||
sanitizedPrenom: { $regexFindAll: { input: { $toLower: prenomApprenantField }, regex: /[A-Za-zÀ-ÖØ-öø-ÿ]/ } }, | ||
}, | ||
}, | ||
{ | ||
$addFields: { | ||
sanitizedNom: { | ||
$reduce: { input: "$sanitizedNom.match", initialValue: "", in: { $concat: ["$$value", "$$this"] } }, | ||
}, | ||
}, | ||
}, | ||
{ | ||
$addFields: { | ||
sanitizedPrenom: { | ||
$reduce: { input: "$sanitizedPrenom.match", initialValue: "", in: { $concat: ["$$value", "$$this"] } }, | ||
}, | ||
}, | ||
}, | ||
]; | ||
|
||
/** | ||
* Méthode de récupération de la liste des doublons au sein d'un organisme | ||
* @param organisme_id | ||
*/ | ||
export const getDuplicatesEffectifsForOrganismeId = async (organisme_id: ObjectId) => { | ||
return await effectifsDb() | ||
.aggregate([ | ||
{ $match: { organisme_id, annee_scolaire: { $in: getAnneesScolaireListFromDate(new Date()) } } }, | ||
...getSanitizedNomPrenomPipeline(), | ||
{ | ||
$group: { | ||
_id: { | ||
nom_apprenant: "$sanitizedNom", | ||
prenom_apprenant: "$sanitizedPrenom", | ||
date_de_naissance_apprenant: "$apprenant.date_de_naissance", | ||
annee_scolaire: "$annee_scolaire", | ||
formation_cfd: "$formation.cfd", | ||
}, | ||
count: { $sum: 1 }, | ||
duplicates: { $addToSet: { id: "$_id", created_at: "$created_at", source: "$source" } }, | ||
}, | ||
}, | ||
{ $match: { count: { $gt: 1 } } }, | ||
]) | ||
.toArray(); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
170 changes: 170 additions & 0 deletions
170
server/tests/integration/common/actions/effectifs.duplicates.actions.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
import { strict as assert } from "assert"; | ||
|
||
import { ObjectId } from "mongodb"; | ||
|
||
import { getDuplicatesEffectifsForOrganismeId } from "@/common/actions/effectifs.duplicates.actions"; | ||
import { Organisme } from "@/common/model/@types"; | ||
import { effectifsDb, organismesDb } from "@/common/model/collections"; | ||
import { createSampleEffectif, createRandomOrganisme } from "@tests/data/randomizedSample"; | ||
import { id } from "@tests/utils/testUtils"; | ||
|
||
const TEST_SIREN = "190404921"; | ||
|
||
const sampleOrganismeId = new ObjectId(id(1)); | ||
const sampleOrganisme: Organisme = { | ||
_id: sampleOrganismeId, | ||
...createRandomOrganisme({ siret: `${TEST_SIREN}00016` }), | ||
}; | ||
|
||
/** | ||
* Fonction utilitaire d'ajout en doublon d'effectif | ||
* @param sampleEffectif | ||
* @param nbDuplicates | ||
*/ | ||
const insertDuplicateEffectifs = async (sampleEffectif, nbDuplicates = 2) => { | ||
const insertedIdList: ObjectId[] = []; | ||
for (let index = 0; index < nbDuplicates; index++) { | ||
const { insertedId } = await effectifsDb().insertOne({ | ||
...sampleEffectif, | ||
id_erp_apprenant: `ID_ERP_${index}`, | ||
annee_scolaire: "2023-2024", | ||
}); | ||
insertedIdList.push(insertedId); | ||
} | ||
|
||
return insertedIdList; | ||
}; | ||
|
||
const sanitizeString = (string) => string.replace(/\s/g, "").toLowerCase(); | ||
|
||
describe("Test des actions Effectifs Duplicates", () => { | ||
describe("getDuplicatesEffectifsForOrganismeId", () => { | ||
beforeEach(async () => { | ||
// Création d'un organisme de test | ||
await organismesDb().insertOne(sampleOrganisme); | ||
}); | ||
|
||
it("Permet de vérifier la récupération de doublons d'effectifs", async () => { | ||
// Ajout de 2 doublons d'effectifs | ||
const sampleEffectif = createSampleEffectif({ organisme: sampleOrganisme, annee_scolaire: "2023-2024" }); | ||
await insertDuplicateEffectifs(sampleEffectif); | ||
|
||
const duplicates = await getDuplicatesEffectifsForOrganismeId(sampleOrganismeId); | ||
|
||
// Vérification de la récupération d'une liste avec un doublon identifié 2 fois sur les champs de la clé d'unicité | ||
assert.equal(duplicates.length, 1); | ||
assert.equal(duplicates[0].count, 2); | ||
assert.equal(duplicates[0].duplicates.length, 2); | ||
|
||
assert.equal(sanitizeString(duplicates[0]._id.nom_apprenant), sanitizeString(sampleEffectif.apprenant.nom)); | ||
assert.equal(sanitizeString(duplicates[0]._id.prenom_apprenant), sanitizeString(sampleEffectif.apprenant.prenom)); | ||
assert.deepEqual(duplicates[0]._id.date_de_naissance_apprenant, sampleEffectif.apprenant.date_de_naissance); | ||
assert.equal(duplicates[0]._id.annee_scolaire, sampleEffectif.annee_scolaire); | ||
assert.equal(duplicates[0]._id.formation_cfd, sampleEffectif.formation?.cfd); | ||
}); | ||
|
||
it("Permet de vérifier la non récupération de doublons d'effectifs", async () => { | ||
// Ajout d'effectif | ||
await insertDuplicateEffectifs( | ||
createSampleEffectif({ organisme: sampleOrganisme, annee_scolaire: "2023-2024" }), | ||
1 | ||
); | ||
const duplicates = await getDuplicatesEffectifsForOrganismeId(sampleOrganismeId); | ||
|
||
// Vérification de la récupération des doublons | ||
assert.equal(duplicates.length, 0); | ||
}); | ||
|
||
it("Permet de vérifier la récupération de doublons d'effectifs avec un prénom multi-casse", async () => { | ||
// Ajout de 2 doublons d'effectifs | ||
const sampleEffectif = createSampleEffectif({ | ||
organisme: sampleOrganisme, | ||
apprenant: { prenom: "SYlvAiN" }, | ||
annee_scolaire: "2023-2024", | ||
}); | ||
await insertDuplicateEffectifs(sampleEffectif, 5); | ||
|
||
const duplicates = await getDuplicatesEffectifsForOrganismeId(sampleOrganismeId); | ||
|
||
// Vérification de la récupération d'une liste avec un doublon identifié 5 fois sur les champs de la clé d'unicité | ||
assert.equal(duplicates.length, 1); | ||
assert.equal(duplicates[0].count, 5); | ||
assert.equal(duplicates[0].duplicates.length, 5); | ||
|
||
assert.equal(sanitizeString(duplicates[0]._id.nom_apprenant), sanitizeString(sampleEffectif.apprenant.nom)); | ||
assert.equal(sanitizeString(duplicates[0]._id.prenom_apprenant), sanitizeString(sampleEffectif.apprenant.prenom)); | ||
assert.deepEqual(duplicates[0]._id.date_de_naissance_apprenant, sampleEffectif.apprenant.date_de_naissance); | ||
assert.equal(duplicates[0]._id.annee_scolaire, sampleEffectif.annee_scolaire); | ||
assert.equal(duplicates[0]._id.formation_cfd, sampleEffectif.formation?.cfd); | ||
}); | ||
|
||
it("Permet de vérifier la récupération de doublons d'effectifs avec un nom multi-casse", async () => { | ||
// Ajout de 2 doublons d'effectifs | ||
const sampleEffectif = createSampleEffectif({ | ||
organisme: sampleOrganisme, | ||
apprenant: { nom: "mBaPpe" }, | ||
annee_scolaire: "2023-2024", | ||
}); | ||
await insertDuplicateEffectifs(sampleEffectif); | ||
|
||
const duplicates = await getDuplicatesEffectifsForOrganismeId(sampleOrganismeId); | ||
|
||
// Vérification de la récupération d'une liste avec un doublon identifié 2 fois sur les champs de la clé d'unicité | ||
assert.equal(duplicates.length, 1); | ||
assert.equal(duplicates[0].count, 2); | ||
assert.equal(duplicates[0].duplicates.length, 2); | ||
|
||
assert.equal(sanitizeString(duplicates[0]._id.nom_apprenant), sanitizeString(sampleEffectif.apprenant.nom)); | ||
assert.equal(sanitizeString(duplicates[0]._id.prenom_apprenant), sanitizeString(sampleEffectif.apprenant.prenom)); | ||
assert.deepEqual(duplicates[0]._id.date_de_naissance_apprenant, sampleEffectif.apprenant.date_de_naissance); | ||
assert.equal(duplicates[0]._id.annee_scolaire, sampleEffectif.annee_scolaire); | ||
assert.equal(duplicates[0]._id.formation_cfd, sampleEffectif.formation?.cfd); | ||
}); | ||
|
||
it("Permet de vérifier la récupération de doublons d'effectifs avec un prénom avec caractères spéciaux, accents et espace", async () => { | ||
// Ajout de 2 doublons d'effectifs | ||
const sampleEffectif = createSampleEffectif({ | ||
organisme: sampleOrganisme, | ||
apprenant: { prenom: "JeAn- éDouArd" }, | ||
annee_scolaire: "2023-2024", | ||
}); | ||
await insertDuplicateEffectifs(sampleEffectif, 5); | ||
|
||
const duplicates = await getDuplicatesEffectifsForOrganismeId(sampleOrganismeId); | ||
|
||
// Vérification de la récupération d'une liste avec un doublon identifié 5 fois sur les champs de la clé d'unicité | ||
assert.equal(duplicates.length, 1); | ||
assert.equal(duplicates[0].count, 5); | ||
assert.equal(duplicates[0].duplicates.length, 5); | ||
|
||
assert.equal(sanitizeString(duplicates[0]._id.nom_apprenant), sanitizeString(sampleEffectif.apprenant.nom)); | ||
assert.equal(sanitizeString(duplicates[0]._id.prenom_apprenant), "jeanédouard"); // Transformation du prenom_apprenant en champ normalisé | ||
assert.deepEqual(duplicates[0]._id.date_de_naissance_apprenant, sampleEffectif.apprenant.date_de_naissance); | ||
assert.equal(duplicates[0]._id.annee_scolaire, sampleEffectif.annee_scolaire); | ||
assert.equal(duplicates[0]._id.formation_cfd, sampleEffectif.formation?.cfd); | ||
}); | ||
|
||
it("Permet de vérifier la récupération de doublons d'effectifs avec un nom avec caractères spéciaux, accents et espace", async () => { | ||
// Ajout de 2 doublons d'effectifs | ||
const sampleEffectif = createSampleEffectif({ | ||
organisme: sampleOrganisme, | ||
apprenant: { nom: "M' BaPpé" }, | ||
annee_scolaire: "2023-2024", | ||
}); | ||
await insertDuplicateEffectifs(sampleEffectif, 5); | ||
|
||
const duplicates = await getDuplicatesEffectifsForOrganismeId(sampleOrganismeId); | ||
|
||
// Vérification de la récupération d'une liste avec un doublon identifié 5 fois sur les champs de la clé d'unicité | ||
assert.equal(duplicates.length, 1); | ||
assert.equal(duplicates[0].count, 5); | ||
assert.equal(duplicates[0].duplicates.length, 5); | ||
|
||
assert.equal(sanitizeString(duplicates[0]._id.nom_apprenant), "mbappé"); // Transformation du nom en champ normalisé | ||
assert.equal(sanitizeString(duplicates[0]._id.prenom_apprenant), sanitizeString(sampleEffectif.apprenant.prenom)); | ||
assert.deepEqual(duplicates[0]._id.date_de_naissance_apprenant, sampleEffectif.apprenant.date_de_naissance); | ||
assert.equal(duplicates[0]._id.annee_scolaire, sampleEffectif.annee_scolaire); | ||
assert.equal(duplicates[0]._id.formation_cfd, sampleEffectif.formation?.cfd); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
/** | ||
* Codes des statuts des apprenants | ||
*/ | ||
export const CODES_STATUT_APPRENANT = { | ||
inscrit: 2, | ||
apprenti: 3, | ||
abandon: 0, | ||
} as const; | ||
|
||
export const CODES_STATUT_APPRENANT_ENUM = [ | ||
CODES_STATUT_APPRENANT.abandon, | ||
CODES_STATUT_APPRENANT.inscrit, | ||
CODES_STATUT_APPRENANT.apprenti, | ||
]; | ||
|
||
/** | ||
* Sexe des apprenants (M=Homme, F=Femme) | ||
*/ | ||
export const SEXE_APPRENANT_ENUM = ["M", "F"]; | ||
|
||
export const NATIONALITE_APPRENANT_ENUM = [1, 2, 3]; | ||
/** | ||
* Nom des statuts | ||
*/ | ||
const LABELS_STATUT_APPRENANT = [ | ||
{ code: CODES_STATUT_APPRENANT.abandon, name: "abandon" }, | ||
{ code: CODES_STATUT_APPRENANT.inscrit, name: "inscrit" }, | ||
{ code: CODES_STATUT_APPRENANT.apprenti, name: "apprenti" }, | ||
]; | ||
|
||
/** | ||
* Fonction de récupération d'un nom de statut depuis son code | ||
* @param {*} statutCode | ||
* @returns | ||
*/ | ||
export const getStatutApprenantNameFromCode = (statutCode) => | ||
LABELS_STATUT_APPRENANT.find((item) => item.code === statutCode)?.name ?? "NC"; | ||
|
||
/** | ||
* Liste des nom des indicateurs | ||
*/ | ||
export const EFFECTIF_INDICATOR_NAMES = { | ||
apprentis: "apprenti", | ||
inscritsSansContrats: "inscrit sans contrat", | ||
rupturants: "rupturant", | ||
abandons: "abandon", | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
export type DuplicateEffectif = { | ||
_id: { | ||
nom_apprenant: string; | ||
prenom_apprenant: string; | ||
date_de_naissance_apprenant: string; | ||
annee_scolaire: string; | ||
formation_cfd: string; | ||
}; | ||
duplicates: [{ id: string; created_at: Date; source: string }]; | ||
}; |
Oops, something went wrong.