diff --git a/server/src/http/controllers/v2/jobs.controller.v2.ts b/server/src/http/controllers/v2/jobs.controller.v2.ts
index 797912a6ff..33ced44908 100644
--- a/server/src/http/controllers/v2/jobs.controller.v2.ts
+++ b/server/src/http/controllers/v2/jobs.controller.v2.ts
@@ -108,6 +108,50 @@ export default (server: Server) => {
}
)
+ server.post(
+ "/v2/_private/jobs/provided/:id",
+ {
+ schema: zRoutes.post["/v2/_private/jobs/provided/:id"],
+ onRequest: server.auth(zRoutes.post["/v2/_private/jobs/provided/:id"]),
+ config,
+ },
+ async (req, res) => {
+ const { id } = req.params
+ const job = await getDbCollection("jobs_partners").findOne({ _id: id })
+ if (!job) {
+ throw badRequest("Job does not exist")
+ }
+
+ if (job.offer_status === JOB_STATUS_ENGLISH.POURVUE) {
+ throw badRequest("Job is already provided")
+ }
+ await getDbCollection("jobs_partners").findOneAndUpdate({ _id: id }, { $set: { offer_status: JOB_STATUS_ENGLISH.POURVUE } })
+ return res.status(200).send({})
+ }
+ )
+
+ server.post(
+ "/v2/_private/jobs/canceled/:id",
+ {
+ schema: zRoutes.post["/v2/_private/jobs/canceled/:id"],
+ onRequest: server.auth(zRoutes.post["/v2/_private/jobs/canceled/:id"]),
+ config,
+ },
+ async (req, res) => {
+ const { id } = req.params
+ const job = await getDbCollection("jobs_partners").findOne({ _id: id })
+ if (!job) {
+ throw badRequest("Job does not exists")
+ }
+
+ if (job.offer_status === JOB_STATUS_ENGLISH.ANNULEE) {
+ throw badRequest("Job is already canceled")
+ }
+ await getDbCollection("jobs_partners").findOneAndUpdate({ _id: id }, { $set: { offer_status: JOB_STATUS_ENGLISH.ANNULEE } })
+ return res.status(200).send({})
+ }
+ )
+
server.post(
"/v2/jobs/extend/:id",
{
diff --git a/server/src/jobs/recruiters/recruiterOfferExpirationReminderJob.ts b/server/src/jobs/recruiters/recruiterOfferExpirationReminderJob.ts
index 5d791dfd18..fde0d8c79f 100644
--- a/server/src/jobs/recruiters/recruiterOfferExpirationReminderJob.ts
+++ b/server/src/jobs/recruiters/recruiterOfferExpirationReminderJob.ts
@@ -1,6 +1,7 @@
import { internal } from "@hapi/boom"
import { groupBy } from "lodash-es"
import { ObjectId } from "mongodb"
+import { LBA_ITEM_TYPE } from "shared/constants/lbaitem"
import { JOB_STATUS } from "shared/models"
import { getStaticFilePath } from "@/common/utils/getStaticFilePath"
@@ -82,8 +83,8 @@ export const recruiterOfferExpirationReminderJob = async (threshold: number /* n
job_type: job.job_type,
job_level_label: job.job_level_label,
job_start_date: dayjs(job.job_start_date).format("DD/MM/YYYY"),
- supprimer: createCancelJobLink(userWithAccountToUserForToken(contactUser), job._id.toString()),
- pourvue: createProvidedJobLink(userWithAccountToUserForToken(contactUser), job._id.toString()),
+ supprimer: createCancelJobLink(userWithAccountToUserForToken(contactUser), job._id.toString(), LBA_ITEM_TYPE.OFFRES_EMPLOI_LBA),
+ pourvue: createProvidedJobLink(userWithAccountToUserForToken(contactUser), job._id.toString(), LBA_ITEM_TYPE.OFFRES_EMPLOI_LBA),
})),
threshold,
connectionUrl: createAuthMagicLink(userWithAccountToUserForToken(contactUser)),
diff --git a/server/src/services/appLinks.service.ts b/server/src/services/appLinks.service.ts
index 8581815c13..b2157ba2af 100644
--- a/server/src/services/appLinks.service.ts
+++ b/server/src/services/appLinks.service.ts
@@ -1,4 +1,5 @@
import { ApplicationIntention } from "shared/constants/application"
+import { LBA_ITEM_TYPE } from "shared/constants/lbaitem"
import { IJob } from "shared/models"
import { IUserWithAccount } from "shared/models/userWithAccount.model"
import { zRoutes } from "shared/routes"
@@ -95,7 +96,7 @@ export function createCfaUnsubscribeToken(email: string, siret: string) {
)
}
-export function createCancelJobLink(user: UserForAccessToken, jobId: string, utmData: string | undefined = undefined) {
+export function createCancelJobLink(user: UserForAccessToken, jobId: string, jobOrigin: LBA_ITEM_TYPE, utmData: string | undefined = undefined) {
const token = generateAccessToken(
user,
[
@@ -108,16 +109,25 @@ export function createCancelJobLink(user: UserForAccessToken, jobId: string, utm
querystring: undefined,
},
}),
+ generateScope({
+ schema: zRoutes.post["/v2/_private/jobs/canceled/:id"],
+ options: {
+ params: {
+ id: jobId,
+ },
+ querystring: undefined,
+ },
+ }),
],
{
expiresIn: "30d",
}
)
- return `${config.publicUrl}/espace-pro/offre/${jobId}/cancel?${utmData ? utmData : ""}&token=${token}`
+ return `${config.publicUrl}/espace-pro/offre/${jobOrigin}/${jobId}/cancel?${utmData ? utmData : ""}&token=${token}`
}
-export function createProvidedJobLink(user: UserForAccessToken, jobId: string, utmData: string | undefined = undefined) {
+export function createProvidedJobLink(user: UserForAccessToken, jobId: string, jobOrigin: LBA_ITEM_TYPE, utmData: string | undefined = undefined) {
const token = generateAccessToken(
user,
[
@@ -128,13 +138,20 @@ export function createProvidedJobLink(user: UserForAccessToken, jobId: string, u
querystring: undefined,
},
}),
+ generateScope({
+ schema: zRoutes.post["/v2/_private/jobs/provided/:id"],
+ options: {
+ params: { id: jobId },
+ querystring: undefined,
+ },
+ }),
],
{
expiresIn: "30d",
}
)
- return `${config.publicUrl}/espace-pro/offre/${jobId}/provided?${utmData ? utmData : ""}&token=${token}`
+ return `${config.publicUrl}/espace-pro/offre/${jobOrigin}/${jobId}/provided?${utmData ? utmData : ""}&token=${token}`
}
export function createViewDelegationLink(email: string, establishment_id: string, job_id: string, siret_formateur: string) {
diff --git a/server/src/services/application.service.ts b/server/src/services/application.service.ts
index d90648352a..3435e9a4e6 100644
--- a/server/src/services/application.service.ts
+++ b/server/src/services/application.service.ts
@@ -467,12 +467,10 @@ const buildRecruiterEmailUrls = async (application: IApplication, applicant: IAp
cancelJobUrl: "",
}
- if (application.job_id && user) {
- urls.jobProvidedUrl = createProvidedJobLink(userForToken, application.job_id, utmRecruiterData)
- urls.cancelJobUrl = createCancelJobLink(userForToken, application.job_id, utmRecruiterData)
- }
if (application.job_id) {
urls.jobUrl = `${config.publicUrl}${getDirectJobPath(application.job_origin, application.job_id)}${utmRecruiterData}`
+ urls.jobProvidedUrl = createProvidedJobLink(userForToken, application.job_id, application.job_origin, utmRecruiterData)
+ urls.cancelJobUrl = createCancelJobLink(userForToken, application.job_id, application.job_origin, utmRecruiterData)
}
return urls
@@ -1121,7 +1119,7 @@ export const processApplicationEmails = {
throw internal("Email entreprise destinataire rejeté.")
}
},
- // get data from applicant
+
async sendCandidatEmail(application: IApplication, applicant: IApplicant) {
const { job_origin } = application
const { url: urlOfDetail, urlWithoutUtm: urlOfDetailNoUtm } = buildUrlsOfDetail(application)
diff --git a/server/static/templates/mail-candidature-partenaire.mjml.ejs b/server/static/templates/mail-candidature-partenaire.mjml.ejs
index c9110e9948..7060313ab2 100644
--- a/server/static/templates/mail-candidature-partenaire.mjml.ejs
+++ b/server/static/templates/mail-candidature-partenaire.mjml.ejs
@@ -207,19 +207,19 @@
-
+
✅ Indiquer que mon offre est pourvue
-
+
📝 Supprimer mon offre
-
+
💬 Consulter la FAQ
diff --git a/server/static/templates/mail-candidature-spontanee.mjml.ejs b/server/static/templates/mail-candidature-spontanee.mjml.ejs
index 3ac0f2422e..2ddb60599c 100644
--- a/server/static/templates/mail-candidature-spontanee.mjml.ejs
+++ b/server/static/templates/mail-candidature-spontanee.mjml.ejs
@@ -248,7 +248,7 @@
- 💬 Consulter la FAQ
+ 💬 Consulter la FAQ
diff --git a/server/static/templates/mail-candidature.mjml.ejs b/server/static/templates/mail-candidature.mjml.ejs
index c9110e9948..7060313ab2 100644
--- a/server/static/templates/mail-candidature.mjml.ejs
+++ b/server/static/templates/mail-candidature.mjml.ejs
@@ -207,19 +207,19 @@
-
+
✅ Indiquer que mon offre est pourvue
-
+
📝 Supprimer mon offre
-
+
💬 Consulter la FAQ
diff --git a/shared/constants/lbaitem.ts b/shared/constants/lbaitem.ts
index dde86ab302..2cea34a98f 100644
--- a/shared/constants/lbaitem.ts
+++ b/shared/constants/lbaitem.ts
@@ -59,7 +59,7 @@ export const newItemTypeToOldItemType = (lbaItemType: LBA_ITEM_TYPE): LBA_ITEM_T
case LBA_ITEM_TYPE.RECRUTEURS_LBA:
return LBA_ITEM_TYPE_OLD.LBA
case LBA_ITEM_TYPE.OFFRES_EMPLOI_PARTENAIRES:
- return LBA_ITEM_TYPE_OLD.PE
+ return LBA_ITEM_TYPE_OLD.PARTNER_JOB
case LBA_ITEM_TYPE.FORMATION:
throw new Error("not used")
default:
diff --git a/shared/routes/v2/jobs.routes.v2.ts b/shared/routes/v2/jobs.routes.v2.ts
index 0bad64c952..2b47bd3cc9 100644
--- a/shared/routes/v2/jobs.routes.v2.ts
+++ b/shared/routes/v2/jobs.routes.v2.ts
@@ -180,6 +180,40 @@ export const zJobsRoutesV2 = {
},
},
post: {
+ "/v2/_private/jobs/provided/:id": {
+ method: "post",
+ path: "/v2/_private/jobs/provided/:id",
+ params: z
+ .object({
+ id: zObjectId,
+ })
+ .strict(),
+ response: {
+ "200": z.object({}).strict(),
+ },
+ securityScheme: {
+ auth: "access-token",
+ access: null,
+ resources: {},
+ },
+ },
+ "/v2/_private/jobs/canceled/:id": {
+ method: "post",
+ path: "/v2/_private/jobs/canceled/:id",
+ params: z
+ .object({
+ id: zObjectId,
+ })
+ .strict(),
+ response: {
+ "200": z.object({}).strict(),
+ },
+ securityScheme: {
+ auth: "access-token",
+ access: null,
+ resources: {},
+ },
+ },
"/v2/jobs/provided/:id": {
method: "post",
path: "/v2/jobs/provided/:id",
diff --git a/ui/components/ItemDetail/PartnerJobComponents/PartnerJobPostuler.tsx b/ui/components/ItemDetail/PartnerJobComponents/PartnerJobPostuler.tsx
index 6651fb57ff..a5625db290 100644
--- a/ui/components/ItemDetail/PartnerJobComponents/PartnerJobPostuler.tsx
+++ b/ui/components/ItemDetail/PartnerJobComponents/PartnerJobPostuler.tsx
@@ -8,6 +8,8 @@ import CandidatureLba from "../CandidatureLba/CandidatureLba"
import CandidatureParTelephone from "../CandidatureParTelephone"
export const PartnerJobPostuler = ({ job, isCollapsedHeader }: { job: ILbaItemPartnerJob; isCollapsedHeader: boolean }) => {
+ // KBA fix enum shared/models/lbaItem.model.ts
+ if (["Pourvue", "Annulée"].includes(job.job.status)) return null
if (job.contact?.email) {
return (
diff --git a/ui/next.config.mjs b/ui/next.config.mjs
index 224aa31d52..3fbd06d56c 100644
--- a/ui/next.config.mjs
+++ b/ui/next.config.mjs
@@ -168,6 +168,12 @@ const nextConfig = {
destination: "/recherche-formation",
permanent: true,
},
+ // KBA TODO WAR ROOM : to remove on 2025_03_10 : route without jobType will be obsolete
+ {
+ source: "/espace-pro/offre/:id/:option",
+ destination: "/espace-pro/offre/offres_emploi_lba/:id/:option",
+ permanent: true,
+ },
]
},
}
diff --git a/ui/pages/espace-pro/offre/[jobId]/[option].tsx b/ui/pages/espace-pro/offre/[jobType]/[jobId]/[option].tsx
similarity index 74%
rename from ui/pages/espace-pro/offre/[jobId]/[option].tsx
rename to ui/pages/espace-pro/offre/[jobType]/[jobId]/[option].tsx
index 0791588416..cfe683bd98 100644
--- a/ui/pages/espace-pro/offre/[jobId]/[option].tsx
+++ b/ui/pages/espace-pro/offre/[jobType]/[jobId]/[option].tsx
@@ -7,36 +7,40 @@ import { getDirectJobPath } from "shared/metier/lbaitemutils"
import Footer from "@/components/footer"
import Navigation from "@/components/navigation"
-import { cancelOffre, fillOffre } from "../../../../utils/api"
+import { cancelOffre, cancelPartnerJob, fillOffre, providedPartnerJob } from "../../../../../utils/api"
export default function MailActionsOnOffre() {
const router = useRouter()
- const { jobId, option, token } = router.query
+ const { jobId, option, token, jobType } = router.query as { jobId: string; option: string; token: string; jobType: LBA_ITEM_TYPE }
const [result, setResult] = useState("")
- const error = () => {
- setResult("Une erreur s'est produite. Merci de contacter le support de La bonne alternance")
- }
-
useEffect(() => {
- if (jobId && option) {
- if (option === "cancel") {
- cancelOffre(jobId, token)
- .then(() => {
- setResult("ok")
- })
- .catch(() => error())
- }
+ if (!jobId || !option || !jobType) return
+
+ const jobActions = {
+ [LBA_ITEM_TYPE.OFFRES_EMPLOI_LBA]: {
+ cancel: cancelOffre,
+ provided: fillOffre,
+ },
+ [LBA_ITEM_TYPE.OFFRES_EMPLOI_PARTENAIRES]: {
+ cancel: cancelPartnerJob,
+ provided: providedPartnerJob,
+ },
+ }
- if (option === "provided") {
- fillOffre(jobId, token)
- .then(() => {
- setResult("ok")
- })
- .catch(() => error())
- }
+ const action = jobActions[jobType]?.[option]
+ if (action && typeof action === "function") {
+ action(jobId, token)
+ .then(() => setResult("ok"))
+ .catch((error) => {
+ console.log(error)
+ setResult("Une erreur s'est produite. Merci de contacter le support de La bonne alternance")
+ return
+ })
+ } else {
+ setResult("Unsupported action.")
}
- }, [jobId, option])
+ }, [jobId, option, jobType])
const cssParameters = {
background: "#fff1e5",
diff --git a/ui/utils/api.ts b/ui/utils/api.ts
index cb58b8534a..ff8d91911d 100644
--- a/ui/utils/api.ts
+++ b/ui/utils/api.ts
@@ -43,6 +43,10 @@ export const createOffreByToken = (establishment_id: string, newOffre: IJobCreat
apiPost("/formulaire/:establishment_id/offre/by-token", { params: { establishment_id }, body: newOffre, headers: { authorization: `Bearer ${token}` } })
export const patchOffreDelegation = (jobId: string, siret: string) =>
apiPatch(`/formulaire/offre/:jobId/delegation`, { params: { jobId }, querystring: { siret_formateur: siret } }).catch(errorHandler)
+// need a function to cancel partner jobs : add the job_origin from the application in the url - refactor ui/pages/espace-pro/offre/[jobId]/[option].tsx needed
+export const cancelPartnerJob = (id, token) => apiPost("/v2/_private/jobs/canceled/:id", { params: { id }, headers: { authorization: `Bearer ${token}` } })
+export const providedPartnerJob = (id, token) => apiPost("/v2/_private/jobs/provided/:id", { params: { id }, headers: { authorization: `Bearer ${token}` } })
+// once offres_emploi_lba are definitly stored in jobs partners, we can move this call to /jobs/:jobId/cancel
export const cancelOffre = (jobId, token) => apiPut(`/formulaire/offre/:jobId/cancel`, { params: { jobId }, headers: { authorization: `Bearer ${token}` } })
export const cancelOffreFromAdmin = (jobId: string, data: IRoutes["put"]["/formulaire/offre/f/:jobId/cancel"]["body"]["_input"]) =>
apiPut("/formulaire/offre/f/:jobId/cancel", { params: { jobId }, body: data })