Skip to content

Commit

Permalink
feat(formation): ajout de la page formation (#492)
Browse files Browse the repository at this point in the history
feat(formation): ajout de la page formation
  • Loading branch information
gBusato authored Dec 17, 2024
1 parent 4ae9228 commit 9c8a03e
Show file tree
Hide file tree
Showing 110 changed files with 6,877 additions and 157 deletions.
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -176,3 +176,10 @@ seed.gz

tsconfig.tsbuildinfo
.infra_


# Vs code config
.vscode/terminals.json

# Cursor
.cursorrules
10 changes: 10 additions & 0 deletions server/src/modules/data/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,13 @@ import { getDataForPanoramaRegionRoute } from "./usecases/getDataForPanoramaRegi
import { getDemandesRestitutionIntentionsRoute } from "./usecases/getDemandesRestitutionIntentions/getDemandesRestitutionIntentions.route";
import { getDepartementRoute } from "./usecases/getDepartement/getDepartement.route";
import { getDepartementsRoute } from "./usecases/getDepartements/getDepartements.route";
import { getDomaineDeFormationRoute } from "./usecases/getDomaineDeFormation/getDomaineDeFormation.route";
import { getDomainesDeFormationRoute } from "./usecases/getDomainesDeFormation/getDomainesDeFormation.route";
import { getEtablissementRoute } from "./usecases/getEtablissement/getEtablissement.route";
import { getFormationRoute } from "./usecases/getFormation/getFormation.route";
import { getFormationCarteEtablissementsRoute } from "./usecases/getFormationCarteEtablissements/getFormationCarteEtablissements.route";
import { getFormationEtablissementsRoutes } from "./usecases/getFormationEtablissements/getFormationEtablissements.routes";
import { getFormationIndicateursRoute } from "./usecases/getFormationIndicateurs/getFormationIndicateurs.route";
import { getFormationsRoute } from "./usecases/getFormations/getFormations.routes";
import { getFormationsPilotageIntentionsRoute } from "./usecases/getFormationsPilotageIntentions/getFormationsPilotageIntentions.route";
import { getHeaderEtablissementRoute } from "./usecases/getHeaderEtablissement/getHeaderEtablissement.route";
Expand Down Expand Up @@ -63,5 +68,10 @@ export const registerDataModule = (server: Server) => {
...searchFiliereRoute(server),
...searchCampusRoute(server),
...getRepartitionPilotageIntentionsRoute(server),
...getDomainesDeFormationRoute(server),
...getFormationRoute(server),
...getDomaineDeFormationRoute(server),
...getFormationIndicateursRoute(server),
...getFormationCarteEtablissementsRoute(server),
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { getKbdClient } from "@/db/db";
import {
isInPerimetreIJAcademie,
isInPerimetreIJDepartement,
isInPerimetreIJRegion,
} from "@/modules/data/utils/isInPerimetreIJ";

export const getFilters = async () => {
const regions = await getKbdClient()
.selectFrom("region")
.select(["region.libelleRegion as label", "region.codeRegion as value"])
.where(isInPerimetreIJRegion)
.orderBy("region.libelleRegion", "asc")
.execute();

const academies = await getKbdClient()
.selectFrom("academie")
.select([
"academie.libelleAcademie as label",
"academie.codeAcademie as value",
"academie.codeRegion as codeRegion",
])
.where(isInPerimetreIJAcademie)
.orderBy("academie.libelleAcademie", "asc")
.execute();

const departements = await getKbdClient()
.selectFrom("departement")
.select([
"departement.libelleDepartement as label",
"departement.codeDepartement as value",
"departement.codeAcademie as codeAcademie",
"departement.codeRegion as codeRegion",
])
.where(isInPerimetreIJDepartement)
.orderBy("departement.libelleDepartement", "asc")
.execute();

return {
regions,
academies,
departements,
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { sql } from "kysely";
import { CURRENT_RENTREE } from "shared";
import type { formationSchema } from "shared/routes/schemas/get.domaine-de-formation.codeNsf.schema";
import { getDateRentreeScolaire } from "shared/utils/getRentreeScolaire";
import type { z } from "zod";

import { getKbdClient } from "@/db/db";
import { notHistoriqueUnlessCoExistant } from "@/modules/data/utils/notHistorique";
import { cleanNull } from "@/utils/noNull";

export const getFormations = async ({
codeNsf,
codeRegion,
codeDepartement,
codeAcademie,
}: {
codeNsf: string;
codeRegion?: string;
codeDepartement?: string;
codeAcademie?: string;
}) =>
getKbdClient()
.with("formations", (wb) =>
wb
.selectFrom("formationView")
.innerJoin("niveauDiplome", "niveauDiplome.codeNiveauDiplome", "formationView.codeNiveauDiplome")
.where("formationView.codeNsf", "=", codeNsf)
.where((w) => notHistoriqueUnlessCoExistant(w, CURRENT_RENTREE))
.select((sb) => [
sb.ref("formationView.codeNsf").as("codeNsf"),
sb.ref("formationView.cfd").as("cfd"),
sb.ref("formationView.libelleFormation").as("libelleFormation"),
sb.ref("formationView.dateOuverture").as("dateOuverture"),
sb.ref("niveauDiplome.libelleNiveauDiplome").as("libelleNiveauDiplome"),
sb.ref("niveauDiplome.codeNiveauDiplome").as("codeNiveauDiplome"),
sb.ref("formationView.typeFamille").as("typeFamille"),
])
.orderBy("formationView.libelleFormation", "asc")
.distinct()
)
.with("formation_renovee", (wb) =>
wb
.selectFrom("formations")
.leftJoin("formationHistorique", "formationHistorique.cfd", "formations.cfd")
.where("formations.dateOuverture", "<=", sql<Date>`${getDateRentreeScolaire(CURRENT_RENTREE)}`)
.where("formationHistorique.ancienCFD", "in", (eb) => eb.selectFrom("formationEtablissement").select("cfd"))
.select("formationHistorique.cfd")
.distinct()
)
.with("formation_etab", (wb) =>
wb
.selectFrom("formations")
.leftJoin("formationEtablissement", "formations.cfd", "formationEtablissement.cfd")
.innerJoin("dataEtablissement", "dataEtablissement.uai", "formationEtablissement.uai")
.selectAll("formations")
.select((sb) => [
sb.ref("formationEtablissement.uai").as("uai"),
sb.ref("formationEtablissement.voie").as("voie"),
sb.ref("dataEtablissement.codeRegion").as("codeRegion"),
sb.ref("dataEtablissement.codeAcademie").as("codeAcademie"),
sb.ref("dataEtablissement.codeDepartement").as("codeDepartement"),
])
.$call((q) => {
if (codeRegion) {
return q.where("codeRegion", "=", codeRegion);
}
return q;
})
.$call((q) => {
if (codeAcademie) {
return q.where("codeAcademie", "=", codeAcademie);
}
return q;
})
.$call((q) => {
if (codeDepartement) {
return q.where("codeDepartement", "=", codeDepartement);
}
return q;
})
)
.selectFrom("formations")
.leftJoin("formation_etab", "formations.cfd", "formation_etab.cfd")
.selectAll("formations")
.select((sb) => [
sb.fn.count<number>("formation_etab.uai").as("nbEtab"),
sql<boolean>`bool_or(voie = 'apprentissage' OR voie IS NULL)`.as("apprentissage"),
sql<boolean>`bool_or(voie = 'scolaire' OR voie IS NULL)`.as("scolaire"),
sb.fn
.coalesce(sql<boolean>`formations.cfd IN (SELECT cfd FROM formation_renovee)`, sql<boolean>`false`)
.as("isFormationRenovee"),
])
.distinct()
.groupBy([
"formations.cfd",
"formations.libelleFormation",
"formations.libelleNiveauDiplome",
"formations.codeNiveauDiplome",
"formations.typeFamille",
"formations.codeNsf",
"formations.dateOuverture",
])
.$castTo<z.infer<typeof formationSchema>>()
.execute()
.then(cleanNull);
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { getKbdClient } from "@/db/db";

export const getNsf = async (codeNsf: string) =>
getKbdClient().selectFrom("nsf").where("codeNsf", "=", codeNsf).selectAll().executeTakeFirst();
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { getFilters } from "./getFilters.dep";
export { getFormations } from "./getFormations.dep";
export { getNsf } from "./getNsf.dep";
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { createRoute } from "@http-wizard/core";
import { ROUTES } from "shared/routes/routes";

import type { Server } from "@/server/server";

import { getDomaineDeFormation } from "./getDomaineDeFormation.usecase";

const ROUTE = ROUTES["[GET]/domaine-de-formation/:codeNsf"];

export const getDomaineDeFormationRoute = (server: Server) => {
return createRoute(ROUTE.url, {
method: ROUTE.method,
schema: ROUTE.schema,
}).handle((props) =>
server.route({
...props,
handler: async (request, response) => {
const { codeNsf } = request.params;
const result = await getDomaineDeFormation(codeNsf, request.query);
response.status(200).send(result);
},
})
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import Boom from "@hapi/boom";
import type { QueryFilters } from "shared/routes/schemas/get.domaine-de-formation.codeNsf.schema";

import { getFilters, getFormations, getNsf } from "./dependencies";

const getDomaineDeFormationFactory =
(
deps = {
getNsf,
getFilters,
getFormations,
}
) =>
async (codeNsf: string, queryFilters: QueryFilters) => {
const { codeRegion, codeDepartement, codeAcademie } = queryFilters;
const [nsf, filters, formations] = await Promise.all([
deps.getNsf(codeNsf),
deps.getFilters(),
deps.getFormations({
codeNsf,
codeRegion,
codeDepartement,
codeAcademie,
}),
]);

if (!nsf) {
throw Boom.notFound(`Le domaine de formation avec le code ${codeNsf} est inconnue`);
}

return {
codeNsf,
libelleNsf: nsf.libelleNsf,
filters,
formations,
};
};

export const getDomaineDeFormation = getDomaineDeFormationFactory();
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { sql } from "kysely";
import { CURRENT_RENTREE } from "shared";
import type { NsfOption } from "shared/routes/schemas/get.domaine-de-formation.schema";

import { getKbdClient } from "@/db/db";
import { openForRentreeScolaire } from "@/modules/data/utils/openForRentreeScolaire";
import { getNormalizedSearchArray } from "@/modules/utils/normalizeSearch";
import { cleanNull } from "@/utils/noNull";

export const getNsfsAndFormations = async (search?: string) => {
let baseQuery = getKbdClient()
.selectFrom("formationView")
.leftJoin("nsf", "nsf.codeNsf", "formationView.codeNsf")
.select((sb) => [
sb.ref("nsf.libelleNsf").as("label"),
sb.ref("formationView.codeNsf").as("value"),
sb.ref("nsf.codeNsf").as("nsf"),
sb.val("nsf").as("type"),
])
.where("nsf.libelleNsf", "is not", null)
.where((eb) => openForRentreeScolaire(eb, CURRENT_RENTREE));

if (search) {
const searchArray = getNormalizedSearchArray(search);

baseQuery = baseQuery
.where((w) =>
w.and(
searchArray.map((search_word) => w(sql`unaccent(${w.ref("nsf.libelleNsf")})`, "ilike", `%${search_word}%`))
)
)
.union(
getKbdClient()
.selectFrom("formationView")
.leftJoin("nsf", "nsf.codeNsf", "formationView.codeNsf")
.leftJoin("niveauDiplome", "niveauDiplome.codeNiveauDiplome", "formationView.codeNiveauDiplome")
.where("nsf.libelleNsf", "is not", null)
.select((sb) => [
sql<string>`concat( ${sb.ref(
"niveauDiplome.libelleNiveauDiplome"
)}, ' - ', ${sb.ref("formationView.libelleFormation")})`.as("label"),
sb.ref("formationView.cfd").as("value"),
sb.ref("nsf.codeNsf").as("nsf"),
sb.val("formation").as("type"),
])
.where((w) =>
w.and(
searchArray.map((search_word) =>
w(
sql`concat(
unaccent(${w.ref("formationView.libelleFormation")}),
' ',
unaccent(${w.ref("formationView.cfd")})
)`,
"ilike",
`%${search_word}%`
)
)
)
)
);
}

return baseQuery.orderBy("label", "asc").distinct().$castTo<NsfOption>().execute().then(cleanNull);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { createRoute } from "@http-wizard/core";
import { ROUTES } from "shared/routes/routes";

import type { Server } from "@/server/server";

import { getDomainesDeFormation } from "./getDomainesDeFormation.usecase";

const ROUTE = ROUTES["[GET]/domaine-de-formation"];

export const getDomainesDeFormationRoute = (server: Server) => {
return createRoute(ROUTE.url, {
method: ROUTE.method,
schema: ROUTE.schema,
}).handle((props) =>
server.route({
...props,
handler: async (request, response) => {
const { search } = request.query;
const result = await getDomainesDeFormation(search);
response.status(200).send(result);
},
})
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { getNsfsAndFormations } from "./dependencies/getNsfsAndFormations";

const getDomainesDeFormationFactory =
(deps = { getNsfsAndFormations }) =>
async (search?: string) =>
deps.getNsfsAndFormations(search);

export const getDomainesDeFormation = getDomainesDeFormationFactory();
Loading

0 comments on commit 9c8a03e

Please sign in to comment.