Skip to content

Commit

Permalink
feat: lbac 1630: training links (#1390)
Browse files Browse the repository at this point in the history
* feat: update training links logic

* refactor: getLBALink

* fix: typo
  • Loading branch information
kevbarns authored Jul 29, 2024
1 parent 0b91006 commit 5abc02d
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 102 deletions.
172 changes: 74 additions & 98 deletions server/src/services/trainingLinks.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import apiGeoAdresse from "../common/utils/apiGeoAdresse"
import { asyncForEach } from "../common/utils/asyncUtils"
import config from "../config.js"

import { getRomesFromRncp } from "./certification.service"

interface IWish {
id: string
cle_ministere_educatif?: string | null
Expand Down Expand Up @@ -42,12 +44,6 @@ const buildEmploiUrl = ({ baseUrl = `${config.publicUrl}/recherche-emploi`, para
return url.toString()
}

/**
* @description local function to get the formation related to the query
* @param {Object} query
* @param {string} filter
* @returns {Promise<IFormationCatalogue[]>}
*/
const getFormations = (
query: object,
filter: object = {
Expand All @@ -57,24 +53,20 @@ const getFormations = (
}
) => getDbCollection("formationcatalogues").find(query, filter).toArray()

/**
* @description get formation according to the available parameters passed to the API endpoint
* @param {object} wish wish data
* @returns {Promise<IFormationCatalogue[]>}
*/
const getTrainingsFromParameters = async (wish: IWish): Promise<IFormationCatalogue[]> => {
let formations
// search by cle ME
if (wish.cle_ministere_educatif) {
formations = await getFormations({ cle_ministere_educatif: wish.cle_ministere_educatif })
}

if (!formations || !formations.length) {
// search by uai_lieu_formation
if (wish.uai_lieu_formation) {
formations = await getFormations({ $or: [{ cfd: wish.cfd }, { rncp_code: wish.rncp }, { "bcn_mefs_10.mef10": wish.mef }], uai_formation: wish.uai_lieu_formation })
}
}
// KBA 2024_07_29 : commenté en attendant la remonté du champ uai_formation dans le catalogue RCO
// if (!formations || !formations.length) {
// // search by uai_lieu_formation
// if (wish.uai_lieu_formation) {
// formations = await getFormations({ $or: [{ cfd: wish.cfd }, { rncp_code: wish.rncp }, { "bcn_mefs_10.mef10": wish.mef }], uai_formation: wish.uai_lieu_formation })
// }
// }

if (!formations || !formations.length) {
// search by uai_formateur
Expand All @@ -96,38 +88,6 @@ const getTrainingsFromParameters = async (wish: IWish): Promise<IFormationCatalo
return formations
}

/**
* @description get training booking link for a specific training
* @param {object} wish wish data
* @returns {Promise<string>} LBA link
*/
const getPrdvLink = async (wish: IWish): Promise<string> => {
if (!wish.cle_ministere_educatif) {
return ""
}

const elligibleFormation = await getDbCollection("eligible_trainings_for_appointments").findOne(
{
cle_ministere_educatif: wish.cle_ministere_educatif,
lieu_formation_email: {
$ne: null,
$exists: true,
$not: /^$/,
},
},
{ projection: { _id: 1 } }
)

if (elligibleFormation) {
return buildEmploiUrl({
baseUrl: `${config.publicUrl}/espace-pro/form`,
params: { referrer: "lba", cleMinistereEducatif: wish.cle_ministere_educatif, ...utmData },
})
}

return ""
}

const getRomesGlobaux = async ({ rncp, cfd, mef }) => {
let romes = [] as string[]
const tmpFormations = await getDbCollection("formationcatalogues")
Expand Down Expand Up @@ -160,15 +120,45 @@ const getRomesGlobaux = async ({ rncp, cfd, mef }) => {
return romes
}

/**
* @description get link LBA for a specific training
* @param {object} wish wish data
* @returns {Promise<string>} LBA link
*/
const getPrdvLink = async (wish: IWish): Promise<string> => {
if (!wish.cle_ministere_educatif) {
return ""
}

const elligibleFormation = await getDbCollection("eligible_trainings_for_appointments").findOne(
{
cle_ministere_educatif: wish.cle_ministere_educatif,
lieu_formation_email: {
$ne: null,
$exists: true,
$not: /^$/,
},
},
{ projection: { _id: 1 } }
)

if (elligibleFormation) {
return buildEmploiUrl({
baseUrl: `${config.publicUrl}/espace-pro/form`,
params: { referrer: "lba", cleMinistereEducatif: wish.cle_ministere_educatif, ...utmData },
})
}

return ""
}

const getLBALink = async (wish: IWish): Promise<string> => {
// get related trainings from catalogue
// Try getting formations first
const formations = await getTrainingsFromParameters(wish)

// Handle single formation case
if (formations.length === 1) {
const { rome_codes, lieu_formation_geo_coordonnees } = formations[0]
const [latitude, longitude] = lieu_formation_geo_coordonnees!.split(",")
return buildEmploiUrl({ params: { romes: rome_codes as string[], lat: latitude, lon: longitude, radius: "60", ...utmData } })
}

// Extract postcode and get coordinates if available
const postCode = wish.code_insee || wish.code_postal
let wLat, wLon
if (postCode) {
Expand All @@ -178,54 +168,40 @@ const getLBALink = async (wish: IWish): Promise<string> => {
}
}

if (!formations || !formations.length) {
const romes = await getRomesGlobaux({ rncp: wish.rncp, cfd: wish.cfd, mef: wish.mef })
return buildEmploiUrl({ params: { ...(romes.length ? { romes: romes } : {}), lat: wLat ?? undefined, lon: wLon ?? undefined, radius: "60", ...utmData } })
}

let [formation] = formations

if (formations.length > 1 && wLat && wLon) {
let distance = 999999999
for (const [i, iFormation] of formations.entries()) {
if (iFormation.lieu_formation_geo_coordonnees) {
const [fLat, fLon] = iFormation.lieu_formation_geo_coordonnees.split(",")
const fDist = getDistance({ latitude: wLat, longitude: wLon }, { latitude: fLat, longitude: fLon })
if (fDist < distance) {
distance = fDist
formation = formations[i]
}
}
// Get romes based on rncp or training database
const romes = wish.rncp
? (await getRomesFromRncp(wish.rncp)) || (await getRomesGlobaux({ rncp: wish.rncp, cfd: wish.cfd, mef: wish.mef }))
: await getRomesGlobaux({ rncp: wish.rncp, cfd: wish.cfd, mef: wish.mef })

// Build url based on formations and coordinates
if (formations.length) {
let formation = formations[0]
let lat, lon
if (formations.length > 1 && wLat && wLon) {
// Find closest formation if multiple and coordinates available
formation = formations.reduce(
(closest, current) => {
if (!current.lieu_formation_geo_coordonnees) return closest
const [fLat, fLon] = current.lieu_formation_geo_coordonnees.split(",")
const currentDist = getDistance({ latitude: wLat, longitude: wLon }, { latitude: fLat, longitude: fLon })
return currentDist < closest.distance! ? { ...current, distance: currentDist } : closest
},
{ distance: 999999999, ...formation }
)
lat = formation.lieu_formation_geo_coordonnees?.split(",")[0]
lon = formation.lieu_formation_geo_coordonnees?.split(",")[1]
} else {
// Use formation coordinates or user coordinates
lat = formation.lieu_formation_geo_coordonnees?.split(",")[0] || wLat
lon = formation.lieu_formation_geo_coordonnees?.split(",")[1] || wLon
}
}

let lat, lon
if (formation.lieu_formation_geo_coordonnees) {
;[lat, lon] = formation.lieu_formation_geo_coordonnees.split(",")
return buildEmploiUrl({ params: { ...(romes.length ? { romes } : {}), lat, lon, radius: "60", ...utmData } })
} else {
;[lat, lon] = [wLat, wLon]
}

if (formations.length > 1 && !postCode) {
;[lat, lon] = [undefined, undefined]
// No formations found, use user coordinates if available
return buildEmploiUrl({ params: { ...(romes.length ? { romes } : {}), lat: wLat ?? undefined, lon: wLon ?? undefined, radius: "60", ...utmData } })
}

if (formations.length === 1) {
if (formation.rome_codes && formation.rome_codes.length) {
return buildEmploiUrl({ params: { romes: formation.rome_codes, lat: lat ?? undefined, lon: lon ?? undefined, radius: "60", ...utmData } })
}
}

const romes = await getRomesGlobaux({ rncp: wish.rncp, cfd: wish.cfd, mef: wish.mef })

return buildEmploiUrl({ params: { ...(romes.length ? { romes: romes } : {}), lat: lat ?? undefined, lon: lon ?? undefined, radius: "60", ...utmData } })
}

/**
* @description get LBA links from candidat's orientation wish
* @param {IWish[]} params wish array
* @returns {Promise<ILinks[]>} LBA link
*/
export const getTrainingLinks = async (params: IWish[]): Promise<ILinks[]> => {
const results: any[] = []
await asyncForEach(params, async (training) => {
Expand Down
8 changes: 4 additions & 4 deletions shared/models/formation.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,13 +202,14 @@ export type IFormationCatalogue = z.output<typeof zFormationCatalogueSchema>
export default {
zod: zFormationCatalogueSchema,
indexes: [
[{ lieu_formation_geopoint: "2dsphere" }, {}],
[{ cle_ministere_educatif: 1 }, {}],
[{ uai_formation: 1 }, {}],
[{ rome_codes: 1 }, {}],
[{ rncp_code: 1 }, {}],
[{ cfd: 1 }, {}],
[{ cfd_entree: 1 }, {}],
[{ uai_formation: 1 }, {}],
[{ niveau: 1 }, {}],
[{ rncp_code: 1 }, {}],
[{ rome_codes: 1 }, {}],
[{ parcoursup_id: 1 }, {}],
[{ published: 1 }, {}],
[{ to_update: 1 }, {}],
Expand All @@ -219,7 +220,6 @@ export default {
[{ id_certifinfo: 1 }, {}],
[{ tags: 1 }, {}],
[{ catalogue_published: 1 }, {}],
[{ lieu_formation_geopoint: "2dsphere" }, {}],
],
collectionName,
} as const satisfies IModelDescriptor
3 changes: 3 additions & 0 deletions shared/routes/trainingLinks.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ export const zTrainingLinksRoutes = {
uai_formateur: z.string().nullable().optional(),
uai_formateur_responsable: z.string().nullable().optional(),
code_insee: z.string().nullable().optional(),
siret_lieu_formation: z.string().nullable().optional(),
siret_formateur: z.string().nullable().optional(),
siret_formateur_responsable: z.string().nullable().optional(),
})
.strict()
// .refine(({ rncp, cfd, mef }) => !!(rncp || cfd || mef), { message: "Au moins un des champs suivants est obligatoire : CFD, RNCP, MEF" })
Expand Down

0 comments on commit 5abc02d

Please sign in to comment.