Skip to content

Commit

Permalink
feat: lbac 2256: jobOpportunity (#1337)
Browse files Browse the repository at this point in the history
* feat: add jobOpportunity type

* feat: multi-usage route - wip

* feat: update logic - wip

* feat: recruteurs_lba - wip

* feat: job opportunity recruteur lba

* fix: review

* feat: job opportunity - wip

* feat: job opportunity france travail

* feat: missing FT duration

* fix: duration typing

* feat: update openapi

* fix: typing

* feat: split rome/rncp routes

* fix: typo
  • Loading branch information
kevbarns authored Jul 22, 2024
1 parent 5984e7a commit 6c072c9
Show file tree
Hide file tree
Showing 17 changed files with 639 additions and 93 deletions.
114 changes: 110 additions & 4 deletions server/src/http/controllers/jobs.controller.v2.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Boom from "boom"
import { IJob, ILbaItemFtJob, ILbaItemLbaJob, JOB_STATUS, assertUnreachable, zRoutes } from "shared"
import { LBA_ITEM_TYPE } from "shared/constants/lbaitem"
import { IJobOpportunityFranceTravailRncp, IJobOpportunityFranceTravailRome, IJobOpportunityRncp, IJobOpportunityRome } from "shared/routes/jobOpportunity.routes"

import { getDbCollection } from "@/common/utils/mongodbUtils"
import { getUserFromRequest } from "@/security/authenticationService"
Expand All @@ -10,6 +11,7 @@ import { getFileSignedURL } from "../../common/utils/awsUtils"
import { trackApiCall } from "../../common/utils/sendTrackingEvent"
import { sentryCaptureException } from "../../common/utils/sentryUtils"
import { getNearEtablissementsFromRomes } from "../../services/catalogue.service"
import { getRomesFromRncp } from "../../services/certification.service"
import { ACTIVE, ANNULEE, POURVUE } from "../../services/constant.service"
import dayjs from "../../services/dayjs.service"
import { entrepriseOnboardingWorkflow } from "../../services/etablissement.service"
Expand All @@ -26,10 +28,10 @@ import {
patchOffre,
provideOffre,
} from "../../services/formulaire.service"
import { getFtJobFromIdV2 } from "../../services/ftjob.service"
import { getJobsQuery } from "../../services/jobOpportunity.service"
import { getCompanyFromSiret } from "../../services/lbacompany.service"
import { addOffreDetailView, getLbaJobByIdV2 } from "../../services/lbajob.service"
import { getFtJobFromIdV2, getFtJobs } from "../../services/ftjob.service"
import { formatFranceTravailToJobOpportunity, formatOffreEmploiLbaToJobOpportunity, formatRecruteurLbaToJobOpportunity, getJobsQuery } from "../../services/jobOpportunity.service"
import { getCompanyFromSiret, getRecruteursLbaFromDB } from "../../services/lbacompany.service"
import { addOffreDetailView, getJobs, getLbaJobByIdV2 } from "../../services/lbajob.service"
import { getFicheMetierFromDB } from "../../services/rome.service"
import { Server } from "../server"

Expand Down Expand Up @@ -486,4 +488,108 @@ export default (server: Server) => {
}
}
)

server.get(
"/jobs/rome/recruteurs_lba",
{ schema: zRoutes.get["/jobs/rome/recruteurs_lba"], onRequest: server.auth(zRoutes.get["/jobs/rome/recruteurs_lba"]) },
async (req, res) => {
const payload: IJobOpportunityRome = req.query
const result = await getRecruteursLbaFromDB(payload)
return res.send(formatRecruteurLbaToJobOpportunity(result))
}
)

server.get(
"/jobs/rncp/recruteurs_lba",
{ schema: zRoutes.get["/jobs/rncp/recruteurs_lba"], onRequest: server.auth(zRoutes.get["/jobs/rncp/recruteurs_lba"]) },
async (req, res) => {
const payload: IJobOpportunityRncp = req.query
const romes = await getRomesFromRncp(payload.rncp)
if (!romes) {
throw Boom.internal(`Aucun code ROME n'a été trouvé à partir du code RNCP ${payload.rncp}`)
}
const result = await getRecruteursLbaFromDB({ ...payload, romes })
return res.send(formatRecruteurLbaToJobOpportunity(result))
}
)

server.get(
"/jobs/rome/offres_emploi_lba",
{ schema: zRoutes.get["/jobs/rome/offres_emploi_lba"], onRequest: server.auth(zRoutes.get["/jobs/rome/offres_emploi_lba"]) },
async (req, res) => {
const payload: IJobOpportunityRome = req.query
const result = await getJobs({
romes: payload.romes,
distance: payload.radius,
niveau: payload.diploma,
lat: payload.latitude,
lon: payload.longitude,
isMinimalData: false,
})
return res.send(formatOffreEmploiLbaToJobOpportunity(result))
}
)

server.get(
"/jobs/rncp/offres_emploi_lba",
{ schema: zRoutes.get["/jobs/rncp/offres_emploi_lba"], onRequest: server.auth(zRoutes.get["/jobs/rncp/offres_emploi_lba"]) },
async (req, res) => {
const payload: IJobOpportunityRncp = req.query
const romes = await getRomesFromRncp(payload.rncp)
if (!romes) {
throw Boom.internal(`Aucun code ROME n'a été trouvé à partir du code RNCP ${payload.rncp}`)
}
const result = await getJobs({
romes,
distance: payload.radius,
niveau: payload.diploma,
lat: payload.latitude,
lon: payload.longitude,
isMinimalData: false,
})
return res.send(formatOffreEmploiLbaToJobOpportunity(result))
}
)

server.get(
"/jobs/rome/offres_emploi_partenaires",
{ schema: zRoutes.get["/jobs/rome/offres_emploi_partenaires"], onRequest: server.auth(zRoutes.get["/jobs/rome/offres_emploi_partenaires"]) },
async () => {}
)

server.get(
"/jobs/rncp/offres_emploi_partenaires",
{ schema: zRoutes.get["/jobs/rncp/offres_emploi_partenaires"], onRequest: server.auth(zRoutes.get["/jobs/rncp/offres_emploi_partenaires"]) },
async () => {}
)

server.get(
"/jobs/rome/offres_emploi_france_travail",
{ schema: zRoutes.get["/jobs/rome/offres_emploi_france_travail"], onRequest: server.auth(zRoutes.get["/jobs/rome/offres_emploi_france_travail"]) },
async (req, res) => {
const payload: IJobOpportunityFranceTravailRome = req.query
const result = await getFtJobs({ jobLimit: 150, caller: "api-apprentissage", api: zRoutes.get["/jobs/offres_emploi_france_travail"].path, ...payload })
if ("error" in result) {
throw Boom.internal(result.message)
}
return res.send(formatFranceTravailToJobOpportunity(result.resultats))
}
)

server.get(
"/jobs/rncp/offres_emploi_france_travail",
{ schema: zRoutes.get["/jobs/rncp/offres_emploi_france_travail"], onRequest: server.auth(zRoutes.get["/jobs/rncp/offres_emploi_france_travail"]) },
async (req, res) => {
const payload: IJobOpportunityFranceTravailRncp = req.query
const romes = await getRomesFromRncp(payload.rncp)
if (!romes) {
throw Boom.internal(`Aucun code ROME n'a été trouvé à partir du code RNCP ${payload.rncp}`)
}
const result = await getFtJobs({ romes, jobLimit: 150, caller: "api-apprentissage", api: zRoutes.get["/jobs/offres_emploi_france_travail"].path, ...payload })
if ("error" in result) {
throw Boom.internal(result.message)
}
return res.send(formatFranceTravailToJobOpportunity(result.resultats))
}
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ const fixJobRythm = async () => {
if (job.job_rythm === "1 jours / 4 jours") {
job.job_rythm = TRAINING_RYTHM["1J4J"]
} else if (job.job_rythm === "" || job.job_rythm === "Non renseigné") {
job.job_rythm = null
job.job_rythm = TRAINING_RYTHM.INDIFFERENT
}
await updateOffre(job._id, { ...job })
})
Expand Down
44 changes: 44 additions & 0 deletions server/src/services/certification.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import axios from "axios"

import { sentryCaptureException } from "../common/utils/sentryUtils"
import config from "../config"

import { CertificationAPIApprentissage } from "./queryValidator.service.types"

const getFirstCertificationFromAPIApprentissage = async (rncp: string): Promise<CertificationAPIApprentissage | null> => {
try {
const { data } = await axios.get<CertificationAPIApprentissage[]>(`${config.apiApprentissage.baseUrl}/certification/v1?identifiant.rncp=${rncp}`, {
headers: { Authorization: `Bearer ${config.apiApprentissage.apiKey}` },
})

if (!data.length) return null

return data[0]
} catch (error: any) {
sentryCaptureException(error, { responseData: error.response?.data })
return null
}
}

const getRomesFromCertification = (certification: CertificationAPIApprentissage) => {
return certification.domaines.rome.rncp.map((x) => x.code)
}
export const getRomesFromRncp = async (rncp: string): Promise<string[] | null> => {
let certification = await getFirstCertificationFromAPIApprentissage(rncp)
if (!certification) {
return null
}
if (certification.periode_validite.rncp.actif) {
return getRomesFromCertification(certification)
} else {
const latestRNCP = certification.continuite.rncp.findLast((rncp) => rncp.actif === true)
if (!latestRNCP) {
return getRomesFromCertification(certification)
}
certification = await getFirstCertificationFromAPIApprentissage(latestRNCP.code)
if (!certification) {
return null
}
return getRomesFromCertification(certification)
}
}
4 changes: 2 additions & 2 deletions server/src/services/ftjob.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ const transformFtJobs = ({ jobs, radius, latitude, longitude, isMinimalData }: {
/**
* Récupère une liste d'offres depuis l'API France Travail
*/
const getFtJobs = async ({
export const getFtJobs = async ({
romes,
insee,
radius,
Expand All @@ -222,7 +222,7 @@ const getFtJobs = async ({
caller: string
diploma: string
api: string
}) => {
}): Promise<FTResponse | IApiError> => {
try {
const peContratsAlternances = "E2,FS" //E2 -> Contrat d'Apprentissage, FS -> contrat de professionalisation

Expand Down
23 changes: 21 additions & 2 deletions server/src/services/ftjob.service.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,25 @@ type FTEntreprise = {
siret: string
}

type FTFormation = {
codeFormatio: string
domaineLibelle: string
niveauLibelle: string
exigence: string
}

type FTOrigineOffre = {
origine: string
urlOrigine: string
partenaires: FTPartenaire[]
}

type FTPartenaire = {
nom: string
url: string
logo: string
}

export type FTJob = {
id: string
intitule: string
Expand Down Expand Up @@ -54,9 +73,9 @@ export type FTJob = {
secteurActivite?: string
secteurActiviteLibelle?: string
qualitesProfessionnelles?: [][]
origineOffre: object[]
origineOffre: FTOrigineOffre
offresManqueCandidats?: boolean
formations?: [][]
formations?: FTFormation[]
langues?: [][]
complementExercice?: string
appellationLibelle: string
Expand Down
Loading

0 comments on commit 6c072c9

Please sign in to comment.