Skip to content

Commit

Permalink
Merge branch 'feature/fiab-formation' into preprod
Browse files Browse the repository at this point in the history
  • Loading branch information
moroine committed Dec 6, 2024
2 parents f3e36c3 + 12f36b5 commit ccb2a8c
Show file tree
Hide file tree
Showing 8 changed files with 143 additions and 108 deletions.
28 changes: 22 additions & 6 deletions server/src/common/actions/effectifs.actions.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { ICertification } from "api-alternance-sdk";
import Boom from "boom";
import { cloneDeep, isObject, merge, mergeWith, reduce, set, uniqBy } from "lodash-es";
import { ObjectId, WithoutId } from "mongodb";
import { ObjectId, type WithoutId } from "mongodb";
import { IOpcos, IRncp } from "shared/models";
import { IEffectif } from "shared/models/data/effectifs.model";
import { IEffectifDECA } from "shared/models/data/effectifsDECA.model";
Expand Down Expand Up @@ -127,23 +127,23 @@ export const lockEffectif = async (effectif: IEffectif) => {
return updated.value;
};

export const addComputedFields = async ({
export const addComputedFields = async <T extends IEffectif | WithoutId<IEffectifDECA>>({
organisme,
effectif,
certification,
}: {
organisme?: IOrganisme;
effectif?: IEffectif | WithoutId<IEffectif>;
effectif?: T;
certification: ICertification | null;
}): Promise<Partial<IEffectif["_computed"]>> => {
const computedFields: Partial<IEffectif["_computed"]> = {};
}): Promise<IEffectif["_computed"]> => {
const computedFields: IEffectif["_computed"] = {};

if (organisme) {
computedFields.organisme = generateOrganismeComputed(organisme);
}

if (effectif) {
const statut = createComputedStatutObject(effectif as IEffectif, new Date());
const statut = createComputedStatutObject(effectif, new Date());
computedFields.statut = statut;
}

Expand All @@ -161,6 +161,22 @@ export const addComputedFields = async ({
return computedFields;
};

export const withComputedFields = async <T extends IEffectif | WithoutId<IEffectifDECA>>(
effectif: T,
{
organisme,
certification,
}: {
organisme?: IOrganisme;
certification: ICertification | null;
}
): Promise<T> => {
return {
...effectif,
_computed: await addComputedFields({ organisme, effectif, certification }),
};
};

export async function getEffectifForm(effectifId: ObjectId): Promise<any> {
let effectif: IEffectif | IEffectifDECA | null = await effectifsDb().findOne({ _id: effectifId });

Expand Down
14 changes: 9 additions & 5 deletions server/src/common/actions/effectifs.statut.actions.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { captureException } from "@sentry/node";
import Boom from "boom";
import { cloneDeep } from "lodash-es";
import { MongoServerError, UpdateFilter } from "mongodb";
import { MongoServerError, UpdateFilter, type WithoutId } from "mongodb";
import { STATUT_APPRENANT, StatutApprenant } from "shared/constants";
import { IEffectif, IEffectifApprenant, IEffectifComputedStatut } from "shared/models/data/effectifs.model";
import type { IEffectifDECA } from "shared/models/data/effectifsDECA.model";
import { addDaysUTC } from "shared/utils";

import logger from "../logger";
Expand Down Expand Up @@ -43,7 +44,10 @@ function shouldUpdateStatut(effectif: IEffectif): boolean {
* @param {Date} evaluationDate La date à laquelle l'évaluation du statut est effectuée.
* @returns {IEffectifComputedStatut} L'objet de statut calculé pour l'effectif.
*/
export function createComputedStatutObject(effectif: IEffectif, evaluationDate: Date): IEffectifComputedStatut | null {
export function createComputedStatutObject(
effectif: IEffectif | WithoutId<IEffectifDECA>,
evaluationDate: Date
): IEffectifComputedStatut | null {
try {
const parcours = generateUnifiedParcours(effectif, evaluationDate);

Expand All @@ -59,7 +63,7 @@ export function createComputedStatutObject(effectif: IEffectif, evaluationDate:
{
context: "createComputedStatutObject",
evaluationDate,
effectifId: effectif._id,
effectifId: "_id" in effectif ? effectif._id : null,
errorStack: error instanceof Error ? error.stack : undefined,
}
);
Expand Down Expand Up @@ -97,7 +101,7 @@ function handleUpdateError(err: unknown, effectif: IEffectif) {
}

const generateUnifiedParcours = (
effectif: IEffectif,
effectif: IEffectif | WithoutId<IEffectifDECA>,
evaluationDate: Date
): { valeur: StatutApprenant; date: Date }[] => {
let parcours: { valeur: StatutApprenant; date: Date }[] = [];
Expand Down Expand Up @@ -139,7 +143,7 @@ function deduplicateAndSortParcours(parcours: { valeur: StatutApprenant; date: D
}

function determineStatutsByContrats(
effectif: IEffectif,
effectif: IEffectif | WithoutId<IEffectifDECA>,
evaluationDate?: Date
): { valeur: StatutApprenant; date: Date }[] {
if (!effectif.formation?.date_entree && !effectif.formation?.date_fin) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,28 +210,25 @@ export async function getEffectifCertification(effectif: Pick<IEffectif, "format
return getEffectiveEffectifCertification(effectif, certifications);
}

export function withEffectifFormation<T extends Pick<IEffectif, "formation">>(
export function fiabilisationEffectifFormation<T extends Pick<IEffectif, "formation">>(
effectif: T,
certification: ICertification | null
): T {
): T["formation"] {
if (!certification) {
return effectif;
return effectif.formation;
}

const niveau = certification.intitule.niveau.rncp?.europeen ?? certification.intitule.niveau.cfd?.europeen ?? null;

return {
...effectif,
formation: {
...effectif.formation,
cfd: certification.identifiant.cfd,
rncp: certification.identifiant.rncp,
libelle_long:
certification.intitule.cfd?.long ?? certification.intitule.rncp ?? effectif.formation?.libelle_long ?? null,
libelle_court:
certification.intitule.cfd?.court ?? certification.intitule.rncp ?? effectif.formation?.libelle_court ?? null,
niveau,
niveau_libelle: getNiveauFormationFromLibelle(niveau),
},
...effectif.formation,
cfd: certification.identifiant.cfd,
rncp: certification.identifiant.rncp,
libelle_long:
certification.intitule.cfd?.long ?? certification.intitule.rncp ?? effectif.formation?.libelle_long ?? null,
libelle_court:
certification.intitule.cfd?.court ?? certification.intitule.rncp ?? effectif.formation?.libelle_court ?? null,
niveau,
niveau_libelle: getNiveauFormationFromLibelle(niveau),
};
}
20 changes: 11 additions & 9 deletions server/src/jobs/hydrate/deca/hydrate-deca-raw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ import { normalize } from "path";
import { captureException } from "@sentry/node";
import { MongoClient, ObjectId, WithoutId } from "mongodb";
import { SOURCE_APPRENANT } from "shared/constants";
import { IEffectif, IOrganisme } from "shared/models";
import { IOrganisme } from "shared/models";
import { IRawBalDeca } from "shared/models/data/airbyteRawBalDeca.model";
import { zApprenant } from "shared/models/data/effectifs/apprenant.part";
import { zContrat } from "shared/models/data/effectifs/contrat.part";
import { IEffectifDECA } from "shared/models/data/effectifsDECA.model";
import { zodOpenApi } from "shared/models/zodOpenApi";
import { cyrb53Hash, getYearFromDate } from "shared/utils";

import { addComputedFields } from "@/common/actions/effectifs.actions";
import { withComputedFields } from "@/common/actions/effectifs.actions";
import { checkIfEffectifExists } from "@/common/actions/engine/engine.actions";
import { getOrganismeByUAIAndSIRET } from "@/common/actions/organismes/organismes.actions";
import parentLogger from "@/common/logger";
Expand All @@ -20,8 +20,8 @@ import { getBALMongodbUri } from "@/common/mongodb";
import { __dirname } from "@/common/utils/esmUtils";
import config from "@/config";
import {
fiabilisationEffectifFormation,
getEffectifCertification,
withEffectifFormation,
} from "@/jobs/fiabilisation/certification/fiabilisation-certification";

const logger = parentLogger.child({ module: "job:hydrate:contrats-deca-raw" });
Expand Down Expand Up @@ -284,11 +284,13 @@ async function createEffectif(document: IRawBalDeca, anneeScolaire: string): Pro
};

const certification = await getEffectifCertification(effectif);
effectif = withEffectifFormation(effectif, certification);

return {
...effectif,
_computed: await addComputedFields({ organisme, effectif: effectif as WithoutId<IEffectif>, certification }),
is_deca_compatible: !organisme.is_transmission_target,
};
return withComputedFields(
{
...effectif,
formation: fiabilisationEffectifFormation(effectif, certification),
is_deca_compatible: !organisme.is_transmission_target,
},
{ organisme, certification }
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@ import { captureException } from "@sentry/node";
import { addComputedFields } from "@/common/actions/effectifs.actions";
import logger from "@/common/logger";
import { effectifsDb, organismesDb } from "@/common/model/collections";
import {
getEffectifCertification,
withEffectifFormation,
} from "@/jobs/fiabilisation/certification/fiabilisation-certification";
import { getEffectifCertification } from "@/jobs/fiabilisation/certification/fiabilisation-certification";

export async function hydrateEffectifsLieuDeFormation() {
let nbEffectifsMisAJour = 0;
Expand Down Expand Up @@ -91,7 +88,7 @@ export async function hydrateEffectifsLieuDeFormationVersOrganismeFormateur() {
organisme_formateur_id: organismeFormateur._id,
_computed: await addComputedFields({
organisme: organismeFormateur,
effectif: withEffectifFormation(effectif, certification),
effectif,
certification,
}),
};
Expand Down
121 changes: 66 additions & 55 deletions server/src/jobs/ingestion/process-ingestion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { IOrganisme } from "shared/models/data/organismes.model";
import { NEVER, SafeParseReturnType, ZodIssueCode } from "zod";

import { updateVoeuxAffelnetEffectif } from "@/common/actions/affelnet.actions";
import { lockEffectif, addComputedFields, mergeEffectifWithDefaults } from "@/common/actions/effectifs.actions";
import { lockEffectif, mergeEffectifWithDefaults, withComputedFields } from "@/common/actions/effectifs.actions";
import {
buildNewHistoriqueStatutApprenant,
mapEffectifQueueToEffectif,
Expand All @@ -41,8 +41,8 @@ import dossierApprenantSchemaV3, {
} from "@/common/validation/dossierApprenantSchemaV3";

import {
fiabilisationEffectifFormation,
getEffectifCertification,
withEffectifFormation,
} from "../fiabilisation/certification/fiabilisation-certification";
import { fiabilisationUaiSiret } from "../fiabilisation/uai-siret/updateFiabilisation";

Expand Down Expand Up @@ -277,7 +277,7 @@ async function transformEffectifQueueV3ToEffectif(rawEffectifQueued: IEffectifQu
let organismeTarget: any;

const result = await dossierApprenantSchemaV3()
.transform(async (effectifQueued, ctx) => {
.transform(async (effectifQueued, ctx): Promise<{ effectif: IEffectif; organisme: IOrganisme }> => {
let [effectif, organismeFormateur, organismeResponsable] = await Promise.all([
transformEffectifQueueToEffectif(effectifQueued),
(async () => {
Expand Down Expand Up @@ -334,7 +334,6 @@ async function transformEffectifQueueV3ToEffectif(rawEffectifQueued: IEffectifQu
}

const certification = await getEffectifCertification(effectif);
effectif = withEffectifFormation(effectif, certification);

// Source: https://mission-apprentissage.slack.com/archives/C02FR2L1VB8/p1695295051135549
// We compute the real duration of the formation in months, only if we have both date_entree and date_fin
Expand All @@ -345,19 +344,25 @@ async function transformEffectifQueueV3ToEffectif(rawEffectifQueued: IEffectifQu
}

return {
effectif: {
...effectif,
organisme_id: organismeFormateur?._id,
organisme_formateur_id: organismeFormateur?._id,
organisme_responsable_id: organismeResponsable?._id,
lieu_de_formation: {
uai: effectifQueued.etablissement_lieu_de_formation_uai,
siret: effectifQueued.etablissement_lieu_de_formation_siret,
adresse: effectifQueued.etablissement_lieu_de_formation_adresse,
code_postal: effectifQueued.etablissement_lieu_de_formation_code_postal,
effectif: await withComputedFields(
{
...effectif,
formation: fiabilisationEffectifFormation(effectif, certification),
organisme_id: organismeFormateur?._id,
organisme_formateur_id: organismeFormateur?._id,
organisme_responsable_id: organismeResponsable?._id,
lieu_de_formation: {
uai: effectifQueued.etablissement_lieu_de_formation_uai,
siret: effectifQueued.etablissement_lieu_de_formation_siret,
adresse: effectifQueued.etablissement_lieu_de_formation_adresse,
code_postal: effectifQueued.etablissement_lieu_de_formation_code_postal,
},
_raw: {
formation: effectif.formation,
},
},
_computed: await addComputedFields({ organisme: organismeFormateur, effectif, certification }),
},
{ organisme: organismeFormateur, certification }
),
organisme: organismeFormateur,
};
})
Expand All @@ -378,47 +383,53 @@ async function transformEffectifQueueV1V2ToEffectif(rawEffectifQueued: IEffectif
const itemProcessingInfos: ItemProcessingInfos = {};
let organismeTarget;

const result = await dossierApprenantSchemaV1V2()
.transform(async (effectifQueued, ctx) => {
let [effectif, organisme] = await Promise.all([
transformEffectifQueueToEffectif(effectifQueued),
(async () => {
const { organisme, stats } = await findOrganismeWithStats(
effectifQueued.uai_etablissement,
effectifQueued.siret_etablissement
);
organismeTarget = organisme;
Object.assign(itemProcessingInfos, addPrefixToProperties("organisme_", stats));
return organisme;
})(),
]);

if (!organisme) {
ctx.addIssue({
code: ZodIssueCode.custom,
message: "organisme non trouvé",
path: ["uai_etablissement", "siret_etablissement"],
params: {
uai: effectifQueued.uai_etablissement,
siret: effectifQueued.siret_etablissement,
},
});
return NEVER;
}
const result: SafeParseReturnType<IEffectifQueue, { effectif: IEffectif; organisme: IOrganisme }> =
await dossierApprenantSchemaV1V2()
.transform(async (effectifQueued, ctx): Promise<{ effectif: IEffectif; organisme: IOrganisme }> => {
let [effectif, organisme] = await Promise.all([
transformEffectifQueueToEffectif(effectifQueued),
(async () => {
const { organisme, stats } = await findOrganismeWithStats(
effectifQueued.uai_etablissement,
effectifQueued.siret_etablissement
);
organismeTarget = organisme;
Object.assign(itemProcessingInfos, addPrefixToProperties("organisme_", stats));
return organisme;
})(),
]);

if (!organisme) {
ctx.addIssue({
code: ZodIssueCode.custom,
message: "organisme non trouvé",
path: ["uai_etablissement", "siret_etablissement"],
params: {
uai: effectifQueued.uai_etablissement,
siret: effectifQueued.siret_etablissement,
},
});
return NEVER;
}

const certification = await getEffectifCertification(effectif);
effectif = withEffectifFormation(effectif, certification);
const certification = await getEffectifCertification(effectif);

return {
effectif: {
...effectif,
organisme_id: organisme?._id,
_computed: await addComputedFields({ organisme, effectif, certification }),
},
organisme: organisme,
};
})
.safeParseAsync(rawEffectifQueued);
return {
effectif: await withComputedFields(
{
...effectif,
organisme_id: organisme?._id,
formation: fiabilisationEffectifFormation(effectif, certification),
_raw: {
formation: effectif.formation,
},
},
{ organisme, certification }
),
organisme: organisme,
};
})
.safeParseAsync(rawEffectifQueued);
return {
result,
itemProcessingInfos,
Expand Down
Loading

0 comments on commit ccb2a8c

Please sign in to comment.