Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: api de création de l'effectif #3613

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 58 additions & 1 deletion server/src/common/actions/effectifs.actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,17 @@ import { cloneDeep, isObject, merge, mergeWith, reduce, set, uniqBy } from "loda
import { ObjectId } from "mongodb";
import { IEffectif } from "shared/models/data/effectifs.model";
import { IOrganisme } from "shared/models/data/organismes.model";
import { cyrb53Hash, normalize } from "shared/utils/crypt";
import type { Paths } from "type-fest";

import { effectifsDb } from "@/common/model/collections";
import { defaultValuesEffectif } from "@/common/model/effectifs.model/effectifs.model";

import { stripEmptyFields } from "../utils/miscUtils";
import { IEffectifCreationSchema } from "../validation/effectifsCreationSchema";

import { legacySchema } from "./effectif.legacy_schema";

import { getOrganismeById } from "./organismes/organismes.actions";
/**
* Méthode de build d'un effectif
*
Expand Down Expand Up @@ -358,3 +360,58 @@ const flattenKeys = (obj: any, path: any = []) =>
!isObject(obj)
? { [path.join(".")]: obj }
: reduce(obj, (cum, next, key) => merge(cum, flattenKeys(next, [...path, key])), {});

export const createEffectifFromForm = async (data: IEffectifCreationSchema, organismeId: string) => {
const id_erp_apprenant = cyrb53Hash(
normalize(data.apprenant.prenom || "").trim() +
normalize(data.apprenant.nom || "").trim() +
(data.apprenant.date_de_naissance?.toString() || "").trim()
);

const organismeLieuId = new ObjectId(data.organisme.organisme_lieu_id);
const organismeFormationId = new ObjectId(data.organisme.organisme_formateur_id);
const organismeResponsableId = new ObjectId(data.organisme.organisme_responsable_id);

const organismeLieu = await getOrganismeById(organismeLieuId);

const newEffectif: IEffectif = {
_id: new ObjectId(),
source: "formulaire",
source_organisme_id: organismeId.toString(),
annee_scolaire: data.annee_scolaire,
validation_errors: [],
organisme_id: organismeLieuId,
organisme_responsable_id: organismeResponsableId,
organisme_formateur_id: organismeFormationId,
id_erp_apprenant,
apprenant: {
...data.apprenant,
historique_statut: [],
},
is_lock: {
apprenant: {},
formation: {},
},
formation: data.formation,
contrats: data.contrats,
created_at: new Date(),
updated_at: new Date(),
_computed: addEffectifComputedFields(organismeLieu),
};

// To be compliant to the unique indexes
const found = !!(await effectifsDb().findOne({
organisme_id: newEffectif.organisme_id,
annee_scolaire: newEffectif.annee_scolaire,
id_erp_apprenant: newEffectif.id_erp_apprenant,
"apprenant.nom": newEffectif.apprenant.nom,
"apprenant.prenom": newEffectif.apprenant.prenom,
"formation.cfd": newEffectif.formation?.cfd,
"formation.annee": newEffectif.formation?.annee,
}));
if (found) {
throw Boom.conflict("L'effectif existe déjà");
}
await effectifsDb().insertOne(newEffectif);
return;
};
21 changes: 21 additions & 0 deletions server/src/common/validation/effectifsCreationSchema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { zApprenant } from "shared/models/data/effectifs/apprenant.part";
import { zContrat } from "shared/models/data/effectifs/contrat.part";
import { zFormationEffectif } from "shared/models/data/effectifs/formation.part";
import { z } from "zod";

import { primitivesV1, primitivesV3 } from "@/common/validation/utils/zodPrimitives";

export const effectifCreationSchema = z.object({
annee_scolaire: primitivesV1.formation.annee_scolaire,
apprenant: zApprenant.omit({ historique_statut: true }),
contrats: z.array(zContrat),
formation: zFormationEffectif,
organisme: z.object({
organisme_responsable_id: z.string(),
organisme_formateur_id: z.string(),
organisme_lieu_id: z.string(),
type_cfa: primitivesV3.type_cfa,
}),
});

export type IEffectifCreationSchema = z.output<typeof effectifCreationSchema>;
14 changes: 13 additions & 1 deletion server/src/http/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import {
registerUnknownNetwork,
sendForgotPasswordRequest,
} from "@/common/actions/account.actions";
import { getEffectifForm, updateEffectifFromForm } from "@/common/actions/effectifs.actions";
import { getEffectifForm, updateEffectifFromForm, createEffectifFromForm } from "@/common/actions/effectifs.actions";
import {
deleteOldestDuplicates,
getDuplicatesEffectifsForOrganismeIdWithPagination,
Expand Down Expand Up @@ -104,6 +104,7 @@ import { passwordSchema, validateFullObjectSchema, validateFullZodObjectSchema }
import { SReqPostVerifyUser } from "@/common/validation/ApiERPSchema";
import { configurationERPSchema } from "@/common/validation/configurationERPSchema";
import { dossierApprenantSchemaV3WithMoreRequiredFieldsValidatingUAISiret } from "@/common/validation/dossierApprenantSchemaV3";
import { effectifCreationSchema, IEffectifCreationSchema } from "@/common/validation/effectifsCreationSchema";
import loginSchemaLegacy from "@/common/validation/loginSchemaLegacy";
import objectIdSchema from "@/common/validation/objectIdSchema";
import { registrationSchema, registrationUnknownNetworkSchema } from "@/common/validation/registrationSchema";
Expand Down Expand Up @@ -637,6 +638,17 @@ function setupRoutes(app: Application) {
)
)
.use("/transmission", transmissionRoutes())
.post(
"/effectif",
requireOrganismePermission("manageEffectifs"),
returnResult(async (req, res) => {
const data: IEffectifCreationSchema = await validateFullZodObjectSchema(
req.body,
effectifCreationSchema.shape
);
return await createEffectifFromForm(data, res.locals.organismeId);
})
)
);

/********************************
Expand Down
4 changes: 2 additions & 2 deletions shared/models/data/effectifs/apprenant.part.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export const zApprenant = zodOpenApi.object({
description: "Sexe de l'apprenant (M: Homme, F: Femme)",
})
.nullish(),
date_de_naissance: zodOpenApi.date({ description: "Date de naissance de l'apprenant" }).nullish(),
date_de_naissance: zodOpenApi.coerce.date({ description: "Date de naissance de l'apprenant" }).nullish(),
code_postal_de_naissance: zodOpenApi
.string({
description:
Expand All @@ -47,7 +47,7 @@ export const zApprenant = zodOpenApi.object({
description: "Apprenant en situation d'handicape (RQTH)",
})
.nullish(),
date_rqth: zodOpenApi
date_rqth: zodOpenApi.coerce
.date({
description: "Date de la reconnaissance travailleur handicapé",
})
Expand Down
6 changes: 3 additions & 3 deletions shared/models/data/effectifs/contrat.part.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ export const zContrat = zodOpenApi.object({
.openapi({ example: 10 })
.nullish(),
adresse: zAdresse.nullish(),
date_debut: zodOpenApi.date({ description: "Date de début du contrat" }),
date_fin: zodOpenApi.date({ description: "Date de fin du contrat" }).nullish(),
date_rupture: zodOpenApi.date({ description: "Date de rupture du contrat" }).nullish(),
date_debut: zodOpenApi.coerce.date({ description: "Date de début du contrat" }),
date_fin: zodOpenApi.coerce.date({ description: "Date de fin du contrat" }).nullish(),
date_rupture: zodOpenApi.coerce.date({ description: "Date de rupture du contrat" }).nullish(),
cause_rupture: zodOpenApi.string({ description: "Cause de rupture du contrat" }).nullish(),
});
10 changes: 5 additions & 5 deletions shared/models/data/effectifs/formation.part.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,18 @@ export const zFormationEffectif = z.object({
niveau_libelle: formationsProps.niveau_libelle.nullish(),
annee: z.number({ description: "Numéro de l'année dans la formation (promo)" }).int().nullish(),
// FIN champs collectés
date_obtention_diplome: z.date({ description: "Date d'obtention du diplôme" }).nullish(),
date_obtention_diplome: z.coerce.date({ description: "Date d'obtention du diplôme" }).nullish(),
duree_formation_relle: z.number({ description: "Durée réelle de la formation en mois" }).int().nullish(),
periode: z
.array(z.number().int(), {
description: "Période de la formation, en année (peut être sur plusieurs années)",
})
.nullish(),
// V3 - REQUIRED FIELDS (optionel pour l'instant pour supporter V2)
date_inscription: z.date({ description: "Date d'inscription" }).nullish(),
date_inscription: z.coerce.date({ description: "Date d'inscription" }).nullish(),
// V3 - OPTIONAL FIELDS
obtention_diplome: z.boolean({ description: "Diplôme obtenu" }).nullish(), // vrai si date_obtention_diplome non null
date_exclusion: z.date({ description: "Date d'exclusion" }).nullish(),
date_exclusion: z.coerce.date({ description: "Date d'exclusion" }).nullish(),
cause_exclusion: z.string({ description: "Cause de l'exclusion" }).nullish(),
referent_handicap: z
.object({
Expand All @@ -47,8 +47,8 @@ export const zFormationEffectif = z.object({
formation_presentielle: z.boolean({ description: "Formation en présentiel" }).nullish(),
duree_theorique: z.number({ description: "Durée théorique de la formation en année" }).int().nullish(), // legacy, should be empty soon
duree_theorique_mois: z.number({ description: "Durée théorique de la formation en mois" }).int().nullish(),
date_fin: z.date({ description: "Date de fin de la formation" }).nullish(),
date_entree: z.date({ description: "Date d'entrée en formation" }).nullish(),
date_fin: z.coerce.date({ description: "Date de fin de la formation" }).nullish(),
date_entree: z.coerce.date({ description: "Date d'entrée en formation" }).nullish(),
});

export type IFormationEffectif = z.output<typeof zFormationEffectif>;
24 changes: 24 additions & 0 deletions shared/utils/crypt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
export const cyrb53Hash = (str: string, seed = 0) => {
let h1 = 0xdeadbeef ^ seed;
let h2 = 0x41c6ce57 ^ seed;
for (let i = 0, ch; i < str.length; i++) {
ch = str.charCodeAt(i);
h1 = Math.imul(h1 ^ ch, 2654435761);
h2 = Math.imul(h2 ^ ch, 1597334677);
}
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507);
h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909);
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507);
h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909);

return (h2 >>> 0).toString(16).padStart(8, "0") + (h1 >>> 0).toString(16).padStart(8, "0");
};

export const normalize = (str: string) => {
return str === null || str === undefined
? ""
: str
.toLowerCase()
.normalize("NFD")
.replace(/[\u0300-\u036f]/g, "");
};
Loading