Skip to content

Commit

Permalink
feat: LBAC-1875 import des fiches rome V4 (#1194)
Browse files Browse the repository at this point in the history
  • Loading branch information
alanlr authored May 21, 2024
1 parent d9f6dfa commit 6e87b6a
Show file tree
Hide file tree
Showing 51 changed files with 1,671 additions and 1,646 deletions.
4 changes: 3 additions & 1 deletion .talismanrc
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ fileignoreconfig:
- filename: .github/workflows/release.yml
checksum: ffd104ff02d60abf3183694209c5191a0bb7479ce37d8243778275351b4d2228
- filename: .infra/.env_server
checksum: ea90495a7b8a9ba9a34adc380228d4ad6f0336d1685488aaacd228bf3bd11e18
checksum: 114465db37369d7d08a354b8361384eaedf66acd2ba3219403bea34c63ed49e3
- filename: .infra/env.ini
checksum: 60d461050d64c0b87831d6918a8696a8dd2f69cd86b4e6d94b40c3b7b285c320
- filename: .infra/files/configs/mongodb/mongod.conf
Expand Down Expand Up @@ -109,6 +109,8 @@ fileignoreconfig:
checksum: cdeae2ef94431bbe8fd1bb0f1bc0c007c938c1a1d4513316cc44cb966b7f4268
- filename: ui/components/ItemDetail/ItemDetail.tsx
checksum: 1fcc0442306f83b5e45bf7da67304527598d7749b9e2642c6d4628d3b4f15a9c
- filename: ui/components/ItemDetail/LbaJobDetail.tsx
checksum: e1c36f9a6cd1cec8d459da178e5c42d723760a63a867dac1f50dd941a334696c
- filename: ui/components/ItemDetail/loadedItemDetail.tsx
checksum: 9db6a64f202979b07251fd40445961d8db03bdb18b6668f9813bdbdf0d56aa65
- filename: ui/components/Ressources/conseilsEtAstuces.tsx
Expand Down
1 change: 1 addition & 0 deletions server/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
bonnesboites.json
*.xlsx
*SAVE.json
unix_fiche_emploi*
13 changes: 11 additions & 2 deletions server/src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,8 +211,17 @@ program
.option("-q, --queued", "Run job asynchronously", false)
.action(createJobAction("recruiters:get-missing-address-detail"))

// Temporaire, one shot à executer en recette et prod
program.command("import:rome").description("import référentiel fiche metier rome v3").option("-q, --queued", "Run job asynchronously", false).action(createJobAction("import:rome"))
program
.command("import:ficheromev4")
.description("import fiches métiers rome v4 (pas utilisé 29/04/2024)")
.option("-q, --queued", "Run job asynchronously", false)
.action(createJobAction("import:ficheromev4"))
program
.command("import:referentielrome")
.description("import référentiel rome v4 from XML")
.option("-q, --queued", "Run job asynchronously", false)
.action(createJobAction("import:referentielrome"))

// Temporaire, one shot à executer en recette et prod
program
.command("migration:remove-version-key-from-all-collections")
Expand Down
57 changes: 52 additions & 5 deletions server/src/common/apis/FranceTravail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ import { createReadStream } from "fs"
import querystring from "querystring"

import FormData from "form-data"
import { IFicheRome } from "shared/models"

import config from "@/config"
import { FTResponse } from "@/services/ftjob.service.types"
import { IAppelattionDetailsFromAPI, IFTAPIToken, IRomeDetailsFromAPI } from "@/services/rome.service.types"
import { IAppelattionDetailsFromAPI, IFTAPIToken, IRomeDetailsFromAPI, IRomeV4Short } from "@/services/rome.service.types"

import dayjs from "../../services/dayjs.service"
import { sentryCaptureException } from "../utils/sentryUtils"
Expand All @@ -14,14 +15,21 @@ import getApiClient from "./client"

const axiosClient = getApiClient({})

const ROME_ACESS = querystring.stringify({
const ROME_ACCESS = querystring.stringify({
grant_type: "client_credentials",
client_id: config.esdClientId,
client_secret: config.esdClientSecret,
scope: `application_${config.esdClientId} api_romev1 nomenclatureRome`,
})

const OFFRES_ACESS = querystring.stringify({
const ROME_V4_ACCESS = querystring.stringify({
grant_type: "client_credentials",
client_id: config.esdClientId,
client_secret: config.esdClientSecret,
scope: `application_${config.esdClientId} api_rome-metiersv1 nomenclatureRome`,
})

const OFFRES_ACCESS = querystring.stringify({
grant_type: "client_credentials",
client_id: config.esdClientId,
client_secret: config.esdClientSecret,
Expand All @@ -40,18 +48,25 @@ let tokenRomeFT: IFTAPIToken = {
token_type: "",
expires_in: 0,
}
let tokenRomeV4FT: IFTAPIToken = {
access_token: "",
scope: "",
token_type: "",
expires_in: 0,
}

const isTokenValid = (token: IFTAPIToken): any => token?.expire?.isAfter(dayjs())

const getFtAccessToken = async (access: "OFFRE" | "ROME", token): Promise<IFTAPIToken> => {
const getFtAccessToken = async (access: "OFFRE" | "ROME" | "ROMEV4", token): Promise<IFTAPIToken> => {
const isValid = isTokenValid(token)

if (isValid) {
return token
}

try {
const response = await axiosClient.post(`${config.franceTravailIO.authUrl}?realm=%2Fpartenaire`, access === "OFFRE" ? OFFRES_ACESS : ROME_ACESS, {
const tokenParams = access === "OFFRE" ? OFFRES_ACCESS : access === "ROME" ? ROME_ACCESS : ROME_V4_ACCESS
const response = await axiosClient.post(`${config.franceTravailIO.authUrl}?realm=%2Fpartenaire`, tokenParams, {
headers: { "Content-Type": "application/x-www-form-urlencoded" },
timeout: 3000,
})
Expand Down Expand Up @@ -191,6 +206,38 @@ export const getAppellationDetailsFromAPI = async (appellationCode: string): Pro
}
}

export const getRomeV4DetailsFromFT = async (romeCode: string): Promise<IFicheRome | null | undefined> => {
tokenRomeV4FT = await getFtAccessToken("ROMEV4", tokenRomeV4FT)

try {
const { data } = await axiosClient.get<IFicheRome>(`${config.franceTravailIO.baseUrl}/rome-metiers/v1/metiers/metier/${romeCode}`, {
headers: {
Authorization: `Bearer ${tokenRomeV4FT.access_token}`,
},
})

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

export const getRomeV4ListFromFT = async (): Promise<IRomeV4Short[] | null | undefined> => {
tokenRomeV4FT = await getFtAccessToken("ROMEV4", tokenRomeV4FT)
try {
const { data } = await axiosClient.get<IRomeV4Short[]>(`${config.franceTravailIO.baseUrl}/rome-metiers/v1/metiers/metier?champs=code`, {
headers: {
Authorization: `Bearer ${tokenRomeV4FT.access_token}`,
},
})
return data
} catch (error: any) {
sentryCaptureException(error, { extra: { responseData: error.response?.data } })
return null
}
}

/**
* Sends CSV file to France Travail API through a "form data".
*/
Expand Down
6 changes: 4 additions & 2 deletions server/src/common/model/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import EligibleTrainingsForAppointment from "./schema/eligibleTrainingsForAppoin
import eligibleTrainingsForAppointmentHistory from "./schema/eligibleTrainingsForAppointmentsHistory/eligibleTrainingsForAppointmentHistory.schema"
import EmailBlacklist from "./schema/emailBlacklist/emailBlacklist.schema"
import Etablissement from "./schema/etablissements/etablissement.schema"
import FicheMetierRomeV3 from "./schema/ficheRomeV3/ficheRomeV3"
import FicheMetierRomeV4 from "./schema/ficheRomeV4/ficheRomeV4"
import FormationCatalogue from "./schema/formationCatalogue/formationCatalogue.schema"
import GeoLocation from "./schema/geolocation/geolocation.schema"
import InternalJobs from "./schema/internalJobs/internalJobs.schema"
Expand All @@ -34,6 +34,7 @@ import Optout from "./schema/optout/optout.schema"
import Recruiter from "./schema/recruiter/recruiter.schema"
import ReferentielOnisep from "./schema/referentielOnisep/referentielOnisep.schema"
import ReferentielOpco from "./schema/referentielOpco/referentielOpco.schema"
import ReferentielRome from "./schema/referentielRome/referentielRome"
import Session from "./schema/session/session.schema"
import SiretDiffusibleStatus from "./schema/siretDiffusibleStatusSchema/siretDiffusibleStatusSchema.schema"
import UnsubscribedLbaCompany from "./schema/unsubscribedLbaCompany/unsubscribedLbaCompany.schema"
Expand Down Expand Up @@ -97,7 +98,8 @@ export {
EligibleTrainingsForAppointment,
EmailBlacklist,
Etablissement,
FicheMetierRomeV3,
FicheMetierRomeV4,
ReferentielRome,
FormationCatalogue,
GeoLocation,
InternalJobs,
Expand Down
23 changes: 0 additions & 23 deletions server/src/common/model/schema/ficheRomeV3/ficheRomeV3.ts

This file was deleted.

24 changes: 24 additions & 0 deletions server/src/common/model/schema/ficheRomeV4/ficheRomeV4.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { IFicheMetierRomeV4 } from "shared/models"

import { Schema, model } from "../../../mongodb"

export const ficheMetierRomeV4Schema = new Schema<IFicheMetierRomeV4>(
{
rome_code: {
type: String,
description: "Code Rome",
},
fiche_metier: {
type: Object,
description: "Fiche metier Rome V4",
},
},
{
versionKey: false,
}
)

ficheMetierRomeV4Schema.index({ rome_code: 1 })
ficheMetierRomeV4Schema.index({ "fiche_metier.appellations.code": 1 })

export default model<IFicheMetierRomeV4>("ficheMetierRomeV4", ficheMetierRomeV4Schema)
5 changes: 0 additions & 5 deletions server/src/common/model/schema/jobs/jobs.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,6 @@ export const jobsSchema = new Schema<IJob>(
default: [],
description: "Liste des romes liés au métier",
},
rome_detail: {
type: Object,
default: null,
description: "Détail du code ROME selon la nomenclature Pole emploi",
},
job_creation_date: {
type: Date,
default: null,
Expand Down
46 changes: 46 additions & 0 deletions server/src/common/model/schema/referentielRome/referentielRome.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { IReferentielRome } from "shared/index"

import { model, Schema } from "../../../mongodb.js"

const codeRomeSchema = new Schema<string>(
{ code_rome: { type: String, index: true }, intitule: { type: String }, code_ogr: { type: String } },
{
_id: false,
versionKey: false,
}
)

export const referentielRomeSchema = new Schema<IReferentielRome>(
{
numero: {
type: String,
description: "Numéro d'identification de la fiche emploi",
},
rome: codeRomeSchema,
appellations: {
type: Array,
items: {
libelle: String,
libelle_court: String,
code_ogr: String,
},
description: "Liste des appellations lié au code rome",
},
definition: String,
acces_metier: String,
competences: {
savoir_faire: Array,
savoir_etre_professionnel: Array,
savoirs: Array,
},
contextes_travail: {
type: Array,
},
mobilites: {
type: Array,
},
},
{ versionKey: false }
)

export default model<IReferentielRome>("referentielRomes", referentielRomeSchema)
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Db } from "mongodb"

import { logger } from "@/common/logger"

export const up = async (db: Db) => {
await db.collection("recruiters").updateMany(
{},
{
$unset: { "jobs.$[].rome_detail": 1 },
},
{
// @ts-expect-error bypassDocumentValidation is not properly set in @types/mongodb
bypassDocumentValidation: true,
}
)

await db.collection("fichemetierromev3").drop()

logger.info("20240424000000-rome_details_from_rome_v4")
}
15 changes: 10 additions & 5 deletions server/src/http/controllers/formulaire.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ import {
createJob,
createJobDelegations,
extendOffre,
getFormulaire,
getFormulaireWithRomeDetail,
getJob,
getJobWithRomeDetail,
patchOffre,
provideOffre,
updateFormulaire,
Expand All @@ -37,10 +38,13 @@ export default (server: Server) => {
},
async (req, res) => {
const { establishment_id } = req.params
const recruiterOpt = await getFormulaire({ establishment_id })

const recruiterOpt = await getFormulaireWithRomeDetail({ establishment_id })

if (!recruiterOpt) {
throw Boom.notFound(`pas de formulaire avec establishment_id=${establishment_id}`)
}

const jobsWithCandidatures = await Promise.all(
recruiterOpt.jobs.map(async (job) => {
const candidatures = await getApplicationsByJobId(job._id.toString())
Expand All @@ -59,7 +63,7 @@ export default (server: Server) => {
},
async (req, res) => {
const { establishment_id } = req.params
const recruiterOpt = await getFormulaire({ establishment_id })
const recruiterOpt = await getFormulaireWithRomeDetail({ establishment_id })
if (!recruiterOpt) {
throw Boom.notFound(`pas de formulaire avec establishment_id=${establishment_id}`)
}
Expand All @@ -84,7 +88,7 @@ export default (server: Server) => {
onRequest: [server.auth(zRoutes.get["/formulaire/delegation/:establishment_id"])],
},
async (req, res) => {
const result = await getFormulaire({ establishment_id: req.params.establishment_id })
const result = await getFormulaireWithRomeDetail({ establishment_id: req.params.establishment_id })

if (!result) {
throw Boom.badRequest()
Expand Down Expand Up @@ -184,10 +188,11 @@ export default (server: Server) => {
onRequest: [server.auth(zRoutes.get["/formulaire/offre/f/:jobId"])],
},
async (req, res) => {
const offre = await getJob(req.params.jobId.toString())
const offre = await getJobWithRomeDetail(req.params.jobId.toString())
if (!offre) {
throw Boom.badRequest("L'offre n'existe pas")
}

res.status(200).send(offre)
}
)
Expand Down
Loading

0 comments on commit 6e87b6a

Please sign in to comment.