diff --git a/.talismanrc b/.talismanrc index 8f53ab8a9..923d74bf7 100644 --- a/.talismanrc +++ b/.talismanrc @@ -132,7 +132,7 @@ fileignoreconfig: - filename: packages/backend/src/services/DemandeSejour.js checksum: 8a3761a96a2775987fea844c5e0d47c7c6f83ea2dfddfd878c30a90f7d9775cc - filename: packages/backend/src/services/Document.js - checksum: 59b5abe13b265c61dc6df8bbb92a3f989199dafaef0170d361f49e760d2ac385 + checksum: 1fbeae31395935da7b8daef1d3872151b793d433d1cde968d0607b4628ba50a8 - filename: packages/backend/src/services/User.js checksum: 78ddae12d185c4111aa837a178b75e6bda4083d06783ee6382503262298eefbc - filename: packages/backend/src/services/geo/Commune.js diff --git a/packages/backend/src/app.js b/packages/backend/src/app.js index 6b4201981..a9190a81a 100644 --- a/packages/backend/src/app.js +++ b/packages/backend/src/app.js @@ -90,7 +90,6 @@ app.use(`/hebergement`, routes.hebergement); app.use(`/siret`, routes.siret); app.use(`/documents`, routes.documents); app.use(`/geo`, routes.geo); -// TODO(eig): unhide when ok //app.use(`/eig`, routes.eig); app.use(`/message`, routes.message); app.use(`/territoire`, routes.territoire); diff --git a/packages/backend/src/controllers/authentication/email/validate.js b/packages/backend/src/controllers/authentication/email/validate.js index f453d4aec..b0c435c3c 100644 --- a/packages/backend/src/controllers/authentication/email/validate.js +++ b/packages/backend/src/controllers/authentication/email/validate.js @@ -3,6 +3,8 @@ const config = require("../../../config"); const User = require("../../../services/User"); const AppError = require("../../../utils/error"); +const MailUtils = require("../../../utils/mail"); +const Send = require("../../../services/mail").mailService.send; const logger = require("../../../utils/logger"); @@ -19,15 +21,13 @@ module.exports = async (req, res, next) => { }), ); } + + let email = null; try { - const { email } = await jwt.verify(validationToken, config.tokenSecret, { + const result = await jwt.verify(validationToken, config.tokenSecret, { algorithm: "ES512", }); - log.d({ email }); - const user = await User.activate(email); - log.d({ user }); - log.i("DONE"); - return res.status(200).json({ user }); + email = result.email; } catch (error) { log.w("DONE with error"); if (error instanceof jwt.TokenExpiredError) { @@ -40,4 +40,40 @@ module.exports = async (req, res, next) => { } return next(error); } + + if (!email) { + return next( + new AppError("Token invalide", { + name: "InvalidToken", + statusCode: 401, + }), + ); + } + + let user = null; + try { + user = await User.activate(email); + } catch (error) { + return next(error); + } + if (!user) { + return next( + new AppError("Token invalide", { + name: "InvalidToken", + statusCode: 401, + }), + ); + } + try { + await Send(MailUtils.usagers.authentication.sendAccountValided(email)); + } catch (error) { + return next( + new AppError("Erreur lors de l'envoi du mail", { + cause: error, + name: "MailError", + statusCode: 500, + }), + ); + } + return res.status(200).json({ user }); }; diff --git a/packages/backend/src/controllers/demandeSejour/depose.js b/packages/backend/src/controllers/demandeSejour/depose.js index af9f7d864..c6dbc755e 100644 --- a/packages/backend/src/controllers/demandeSejour/depose.js +++ b/packages/backend/src/controllers/demandeSejour/depose.js @@ -2,7 +2,7 @@ const dayjs = require("dayjs"); const yup = require("yup"); const DemandeSejour = require("../../services/DemandeSejour"); -const Hebergement = require("../../services/Hebergement"); +const Hebergement = require("../../services/hebergement/Hebergement"); const Send = require("../../services/mail").mailService.send; const PdfDeclaration2Mois = require("../../services/pdf/declaration2mois/generate"); const PdfDeclaration8jours = require("../../services/pdf/declaration8jours/generate"); diff --git a/packages/backend/src/controllers/demandeSejour/get.js b/packages/backend/src/controllers/demandeSejour/get.js index a1e26bf88..da7bcae16 100644 --- a/packages/backend/src/controllers/demandeSejour/get.js +++ b/packages/backend/src/controllers/demandeSejour/get.js @@ -30,7 +30,6 @@ module.exports = async function get(req, res, next) { total: demandes.total, }); } catch (error) { - console.log(error); log.w("DONE with error"); return next(error); } diff --git a/packages/backend/src/controllers/demandeSejour/getByDepartementCodes.js b/packages/backend/src/controllers/demandeSejour/getByDepartementCodes.js index b20eb6e4e..944ace053 100644 --- a/packages/backend/src/controllers/demandeSejour/getByDepartementCodes.js +++ b/packages/backend/src/controllers/demandeSejour/getByDepartementCodes.js @@ -1,6 +1,7 @@ const yup = require("yup"); const DemandeSejour = require("../../services/DemandeSejour"); +const Organisme = require("../../services/Organisme"); const logger = require("../../utils/logger"); const ValidationAppError = require("../../utils/validation-error"); const Sentry = require("@sentry/node"); @@ -57,6 +58,20 @@ async function getByDepartementCodes(req, res, next) { } catch (error) { return next(new ValidationAppError(error)); } + // Si c'est l'organisme siège social alors on recherche sur le siren, sinon on recherchera sur le siret + // On ajoute alors un param dans le search + if (params.search?.organismeId) { + const organisme = await Organisme.getOne({ + id: params.search.organismeId, + }); + if (organisme?.personneMorale) { + if (organisme?.personneMorale?.siegeSocial) { + params.search.siren = organisme.personneMorale.siren; + } else { + params.search.siret = organisme.personneMorale.siret; + } + } + } const demandesWithPagination = await DemandeSejour.getByDepartementCodes( params, territoireCode, diff --git a/packages/backend/src/controllers/demandeSejour/getById.js b/packages/backend/src/controllers/demandeSejour/getById.js index 800523e8c..b6f98015b 100644 --- a/packages/backend/src/controllers/demandeSejour/getById.js +++ b/packages/backend/src/controllers/demandeSejour/getById.js @@ -1,6 +1,6 @@ const { number } = require("yup"); const DemandeSejour = require("../../services/DemandeSejour"); - +const AppError = require("../../utils/error"); const logger = require("../../utils/logger"); const ValidationAppError = require("../../utils/validation-error"); @@ -10,6 +10,16 @@ module.exports = async function get(req, res, next) { log.i("IN"); let { declarationId } = req.params; + if (!declarationId) { + log.w("missing or invalid parameter"); + + return next( + new AppError("Paramètre incorrect", { + statusCode: 400, + }), + ); + } + try { declarationId = await number().required().validate(declarationId); } catch (error) { diff --git a/packages/backend/src/controllers/demandeSejour/getHebergement.js b/packages/backend/src/controllers/demandeSejour/getHebergement.js deleted file mode 100644 index b07ebbc8d..000000000 --- a/packages/backend/src/controllers/demandeSejour/getHebergement.js +++ /dev/null @@ -1,38 +0,0 @@ -const DemandeSejour = require("../../services/DemandeSejour"); - -const logger = require("../../utils/logger"); - -const log = logger(module.filename); - -module.exports = async function get(req, res) { - log.i("IN"); - const departements = req.departements.map((d) => d.value); - - const { sejourId, hebergementId } = req.params; - - if ([sejourId, hebergementId].includes(undefined)) { - return res.status(400).json({ - message: "Invalide requête", - }); - } - - try { - const response = await DemandeSejour.getHebergement( - sejourId, - departements, - hebergementId, - ); - if (!response.length) { - return res.status(404).json({ - message: "L'hébergement n'a pas été trouvé", - }); - } - return res.status(200).json({ ...response[0] }); - } catch (error) { - log.w("DONE with error"); - return res.status(400).json({ - message: - "une erreur est survenue durant la récupération des hebergements", - }); - } -}; diff --git a/packages/backend/src/controllers/demandeSejour/getHebergementsByDepartementCodes.js b/packages/backend/src/controllers/demandeSejour/getHebergementsByDepartementCodes.js index 969692740..75d89be64 100644 --- a/packages/backend/src/controllers/demandeSejour/getHebergementsByDepartementCodes.js +++ b/packages/backend/src/controllers/demandeSejour/getHebergementsByDepartementCodes.js @@ -43,6 +43,7 @@ module.exports = async function get(req, res) { sort: titleSorted ?? "nom", }, ); + return res.status(200).json({ count: data.total_count, hebergements: data.hebergements || [], diff --git a/packages/backend/src/controllers/demandeSejour/index.js b/packages/backend/src/controllers/demandeSejour/index.js index a4400b36c..8f60eba8b 100644 --- a/packages/backend/src/controllers/demandeSejour/index.js +++ b/packages/backend/src/controllers/demandeSejour/index.js @@ -3,7 +3,6 @@ module.exports.getDeprecated = require("./getDeprecated"); module.exports.getAdminStats = require("./getAdminStats"); module.exports.getExtract = require("./getExtract"); module.exports.getExtractHebergement = require("./getExtractHebergement"); -module.exports.getHebergement = require("./getHebergement"); module.exports.getHebergementsByDepartementCodes = require("./getHebergementsByDepartementCodes"); module.exports.getById = require("./getById"); module.exports.getByIdBo = require("./getByIdBo"); diff --git a/packages/backend/src/controllers/demandeSejour/update.js b/packages/backend/src/controllers/demandeSejour/update.js index 38a6443ba..aeb7098bd 100644 --- a/packages/backend/src/controllers/demandeSejour/update.js +++ b/packages/backend/src/controllers/demandeSejour/update.js @@ -22,14 +22,12 @@ module.exports = async function post(req, res, next) { }), ); } - try { declarationId = await number().required().validate(declarationId); parametre = await object().json().required().validate(parametre); } catch (error) { return next(new ValidationAppError(error)); } - try { const updatedDeclarationId = await DemandeSejour.update( type, diff --git a/packages/backend/src/controllers/documents/upload.js b/packages/backend/src/controllers/documents/upload.js index 6a82b34ac..7eaaac2e1 100644 --- a/packages/backend/src/controllers/documents/upload.js +++ b/packages/backend/src/controllers/documents/upload.js @@ -9,6 +9,7 @@ const log = logger(module.filename); module.exports = async (req, res, next) => { log.i("IN"); const { category } = req.body; + const { decoded } = req; const file = req.file; if (!category || !file) { log.w("DONE with error"); @@ -49,6 +50,7 @@ module.exports = async (req, res, next) => { category, mimetype, data, + decoded.id, ); log.d("DONE", uuid); return res.json({ uuid }); diff --git a/packages/backend/src/controllers/eig/available-ds.js b/packages/backend/src/controllers/eig/available-ds.js new file mode 100644 index 000000000..93288b233 --- /dev/null +++ b/packages/backend/src/controllers/eig/available-ds.js @@ -0,0 +1,47 @@ +const logger = require("../../utils/logger"); +const eigService = require("../../services/eig"); +const Organisme = require("../../services/Organisme"); + +const log = logger(module.filename); + +module.exports = async function getAvailableDs(req, res, next) { + log.i("IN"); + const { decoded } = req; + const { id: userId } = decoded; + log.d("userId", { userId }); + + let organismesId; + + try { + const organisme = await Organisme.getOne({ + use_id: userId, + }); + if (organisme.personneMorale?.porteurAgrement) { + const organismes = await Organisme.getBySiren( + organisme.personneMorale.siren, + ); + organismesId = organismes.map((o) => o.organismeId); + } else { + organismesId = [organisme.organismeId]; + } + } catch (error) { + log.w("DONE with error"); + return next(error); + } + + const { search } = req.query; + + if (!search || search === "") { + return res.status(200).json([]); + } + + try { + const ds = await eigService.getAvailableDs(organismesId, search); + return res.status(200).json(ds); + } catch (error) { + log.w("DONE with error"); + return next(error); + } finally { + log.i("OUT"); + } +}; diff --git a/packages/backend/src/controllers/eig/create.js b/packages/backend/src/controllers/eig/create.js index 43f26480b..01ed136f7 100644 --- a/packages/backend/src/controllers/eig/create.js +++ b/packages/backend/src/controllers/eig/create.js @@ -4,7 +4,7 @@ const { selectionSejourSchema } = require("../../schemas/eig"); const ValidationAppError = require("../../utils/validation-error"); const eigService = require("../../services/eig"); const DemandeSejour = require("../../services/DemandeSejour"); -const { idDeclarationeligibleToEig } = require("../../helpers/eig"); +const { isDeclarationligibleToEig } = require("../../helpers/eig"); const log = logger(module.filename); @@ -13,30 +13,48 @@ module.exports = async (req, res, next) => { log.i("IN", { body: req.body }); const { parametre } = req.body; - let eig; + if (!parametre?.declarationId) { + return res.status(400).send({ + errors: "Le champs declarationId est obligatoire", + name: "UnexpectedError", + }); + } + + let ds; try { - eig = await yup.object(selectionSejourSchema).validate(parametre, { - abortEarly: false, - stripUnknown: true, + ds = await DemandeSejour.getOne({ "ds.id": parametre.declarationId }); + if (!ds) { + return res + .status(404) + .send({ errors: "Aucune déclaration trouvée", name: "Not found" }); + } + } catch (err) { + return res.status(500).send({ errors: err.errors, name: err.name }); + } + + if (!isDeclarationligibleToEig(ds)) { + return res.status(400).send({ + message: "La déclaration n'est pas éligible à la création d'un EIG", }); - } catch (error) { - return next(new ValidationAppError(error)); } + let eig; + try { - const ds = await DemandeSejour.getOne({ "ds.id": eig.declarationId }); - if (!idDeclarationeligibleToEig(ds)) { - return res.status(400).send({ - message: "La déclaration n'est pas éligible à la création d'un EIG", + eig = await yup + .object(selectionSejourSchema(ds.dateDebut, ds.dateFin)) + .validate(parametre, { + abortEarly: false, + stripUnknown: true, }); - } - } catch (err) { - return res.status(400).send({ errors: err.errors, name: err.name }); + } catch (error) { + return next(new ValidationAppError(error)); } try { const eigId = await eigService.create({ + date: eig.date, declarationId: eig.declarationId, departement: eig.departement, userId, diff --git a/packages/backend/src/controllers/eig/depose.js b/packages/backend/src/controllers/eig/depose.js index e1be2b8be..1190165e2 100644 --- a/packages/backend/src/controllers/eig/depose.js +++ b/packages/backend/src/controllers/eig/depose.js @@ -7,6 +7,7 @@ const DemandeSejour = require("../../services/DemandeSejour"); const MailUtils = require("../../utils/mail"); const AppError = require("../../utils/error"); const { getEmails } = require("../../helpers/eigMail"); +const Commune = require("../../services/geo/Commune"); const Send = require("../../services/mail").mailService.send; const log = logger(module.filename); @@ -27,8 +28,21 @@ module.exports = async (req, res, next) => { return next(error); } + let ds; + + try { + ds = await DemandeSejour.getOne({ "ds.id": eig.declarationId }); + if (!ds) { + return res + .status(404) + .send({ errors: "Aucune déclaration trouvée", name: "Not found" }); + } + } catch (err) { + return res.status(500).send({ errors: err.errors, name: err.name }); + } + try { - await yup.object(syntheseSchema).validate(eig, { + await yup.object(syntheseSchema(ds.dateDebut, ds.dateFin)).validate(eig, { abortEarly: false, stripUnknown: true, }); @@ -66,17 +80,34 @@ module.exports = async (req, res, next) => { userName, } = await getEmails(eig.departement, eig.userId); + const hebergementCodeInsee = ds.hebergement.hebergements.find( + (h) => h?.coordonnees?.adresse?.departement === eig.departement, + )?.coordonnees?.adresse?.codeInsee; + + const communeName = ( + await Commune.get({ + code_insee: hebergementCodeInsee, + date: new Date(), + }) + )?.text; + emailsDDETS?.length && (await Send( MailUtils.bo.eig.sendToDDETS({ + communeName, + declarationSejour: ds, + departementName, dest: emailsDDETS, eig, + regionName, }), )); emailsDREETS?.length > 0 && (await Send( MailUtils.bo.eig.sendToDREETS({ + communeName, + declarationSejour: ds, departementName, dest: emailsDREETS, eig, @@ -86,10 +117,9 @@ module.exports = async (req, res, next) => { emailsOrganisateur?.length > 0 && (await Send( MailUtils.bo.eig.sendToOrganisme({ - departementName, - dest: emailsOrganisateur, + declarationSejour: ds, + dest: [...emailsOrganisateur, ds.responsableSejour.email], eig, - regionName, userName, }), )); @@ -97,10 +127,8 @@ module.exports = async (req, res, next) => { eig.emailAutresDestinataires?.length > 0 && (await Send( MailUtils.bo.eig.sendToAutre({ - departementName, dest: eig.emailAutresDestinataires, eig, - regionName, userName, }), )); diff --git a/packages/backend/src/controllers/eig/get-by-ds-id-admin.js b/packages/backend/src/controllers/eig/get-by-ds-id-admin.js index f60f51aa6..1b3a55ab3 100644 --- a/packages/backend/src/controllers/eig/get-by-ds-id-admin.js +++ b/packages/backend/src/controllers/eig/get-by-ds-id-admin.js @@ -10,7 +10,6 @@ module.exports = async function get(req, res, next) { try { const eigs = await eigService.getByDsIdAdmin(declarationId); - return res.status(200).json({ eigs }); } catch (error) { log.w("DONE with error"); diff --git a/packages/backend/src/controllers/eig/index.js b/packages/backend/src/controllers/eig/index.js index 048d1912d..d1cfecfea 100644 --- a/packages/backend/src/controllers/eig/index.js +++ b/packages/backend/src/controllers/eig/index.js @@ -8,3 +8,4 @@ module.exports.depose = require("./depose"); module.exports.delete = require("./delete"); module.exports.getAdmin = require("./get-admin"); module.exports.markAsRead = require("./mark-as-read"); +module.exports.getAvailableDs = require("./available-ds"); diff --git a/packages/backend/src/controllers/eig/mark-as-read.js b/packages/backend/src/controllers/eig/mark-as-read.js index b53392da3..ffaebe36c 100644 --- a/packages/backend/src/controllers/eig/mark-as-read.js +++ b/packages/backend/src/controllers/eig/mark-as-read.js @@ -1,8 +1,15 @@ const AppError = require("../../utils/error"); const eigService = require("../../services/eig"); +const Departement = require("../../services/geo/Departement"); +const Region = require("../../services/geo/Region"); const logger = require("../../utils/logger"); -const { statuts } = require("../../helpers/eig"); +const { + statuts, + isUserDreetsWhoDeliveredAgrement, + isUserDdetsWhereEigHappened, + mustMarkAsRead, +} = require("../../helpers/eig"); const DemandeSejour = require("../../services/DemandeSejour"); const MailUtils = require("../../utils/mail"); const Send = require("../../services/mail").mailService.send; @@ -41,10 +48,36 @@ module.exports = async function markAsRead(req, res, next) { ); } + if (!mustMarkAsRead(territoireCode, eig)) { + log.w("L'EIG n'as pas à être marqué comme lu par le user"); + return next( + new AppError("Statut incompatible", { + statusCode: 400, + }), + ); + } + + const typeReader = isUserDreetsWhoDeliveredAgrement( + territoireCode, + eig.agrementRegionObtention, + ) + ? "DREETS" + : isUserDdetsWhereEigHappened(territoireCode, eig.departement) + ? "DDETS" + : null; + + if (!typeReader) { + return next( + new AppError("L'utilisateur BO n'a aucune action a faire", { + statusCode: 400, + }), + ); + } + try { - await eigService.markAsRead(eigId); + await eigService.markAsRead(eigId, typeReader); await DemandeSejour.insertEvent( - `DDETS/DREETS ${territoireCode}`, + `${typeReader} ${territoireCode}`, eig.declarationId, null, userId, @@ -57,6 +90,18 @@ module.exports = async function markAsRead(req, res, next) { return next(error); } + let territoireName; + + try { + territoireName = + typeReader === "DREETS" + ? await Region.fetchOne(territoireCode) + : await Departement.fetchOne(territoireCode); + } catch (error) { + log.w("DONE with error"); + return next(error); + } + try { const destinataires = await DemandeSejour.getEmailToList(eig.organismeId); destinataires?.length && @@ -64,6 +109,9 @@ module.exports = async function markAsRead(req, res, next) { MailUtils.bo.eig.sendMarkAsRead({ dest: destinataires, eig, + territoireCode, + territoireName: territoireName.text, + typeReader, }), )); } catch (error) { diff --git a/packages/backend/src/controllers/eig/update.js b/packages/backend/src/controllers/eig/update.js index 8b58cc48b..f74a61801 100644 --- a/packages/backend/src/controllers/eig/update.js +++ b/packages/backend/src/controllers/eig/update.js @@ -1,10 +1,7 @@ const logger = require("../../utils/logger"); const yup = require("yup"); const { updateSchemaAdapteur } = require("../../schemas/eig"); -const { - UpdateTypes, - idDeclarationeligibleToEig, -} = require("../../helpers/eig"); +const { UpdateTypes, isDeclarationligibleToEig } = require("../../helpers/eig"); const ValidationAppError = require("../../utils/validation-error"); const eigService = require("../../services/eig"); const DemandeSejour = require("../../services/DemandeSejour"); @@ -19,29 +16,39 @@ module.exports = async (req, res, next) => { log.i("IN", { body: req.body }); const { parametre, type } = req.body; - let eig; + let ds; try { - eig = await yup.object(updateSchemaAdapteur(type)).validate(parametre, { - abortEarly: false, - stripUnknown: true, + const checkEig = await eigService.getById({ eigId }); + ds = await DemandeSejour.getOne({ + "ds.id": parametre.declarationId ?? checkEig.declarationId, }); - } catch (error) { - return next(new ValidationAppError(error)); + if (!ds) { + return res + .status(404) + .send({ errors: "Aucune déclaration trouvée", name: "Not found" }); + } + } catch (err) { + return res.status(500).send({ errors: err.errors, name: err.name }); } - try { - const checkEig = await eigService.getById({ eigId }); - const ds = await DemandeSejour.getOne({ - "ds.id": checkEig.declarationId, + if (!isDeclarationligibleToEig(ds)) { + return res.status(400).send({ + message: "La déclaration n'est pas éligible à la création d'un EIG", }); - if (!idDeclarationeligibleToEig(ds)) { - return res.status(400).send({ - message: "La déclaration n'est pas éligible à la création d'un EIG", + } + + let eig; + + try { + eig = await yup + .object(updateSchemaAdapteur(type, [ds.dateDebut, ds.dateFin])) + .validate(parametre, { + abortEarly: false, + stripUnknown: true, }); - } - } catch (err) { - return res.status(400).send({ errors: err.errors, name: err.name }); + } catch (error) { + return next(new ValidationAppError(error)); } try { @@ -49,6 +56,7 @@ module.exports = async (req, res, next) => { switch (type) { case UpdateTypes.DECLARATION_SEJOUR: updatedEigId = await eigService.updateDS(eigId, { + date: eig.date, declarationId: eig.declarationId, departement: eig.departement, }); diff --git a/packages/backend/src/controllers/hebergement/activate.js b/packages/backend/src/controllers/hebergement/activate.js new file mode 100644 index 000000000..f6cb5f8ef --- /dev/null +++ b/packages/backend/src/controllers/hebergement/activate.js @@ -0,0 +1,69 @@ +const yup = require("yup"); +const logger = require("../../utils/logger"); +const ValidationAppError = require("../../utils/validation-error"); +const HebergementSchema = require("../../schemas/hebergement"); +const AppError = require("../../utils/error"); +const Hebergement = require("../../services/hebergement/Hebergement"); +const HebergementHelper = require("../../helpers/hebergement"); + +const log = logger(module.filename); + +module.exports = async function activate(req, res, next) { + const hebergementId = req.params.id; + const { body, decoded } = req; + const userId = decoded.id; + + const { nom, coordonnees, informationsLocaux, informationsTransport } = body; + + if ( + !nom || + !coordonnees || + !informationsLocaux || + !informationsTransport || + !hebergementId + ) { + log.w("missing or invalid parameter"); + + return next( + new AppError("Paramètre incorrect", { + statusCode: 400, + }), + ); + } + let hebergement; + + try { + hebergement = await yup.object(HebergementSchema.schema(false)).validate( + { + coordonnees, + informationsLocaux, + informationsTransport, + nom, + }, + { + abortEarly: false, + stripped: true, + }, + ); + } catch (error) { + return next(new ValidationAppError(error)); + } + + try { + await Hebergement.updateWithoutHistory( + userId, + hebergementId, + HebergementHelper.statuts.ACTIF, + hebergement, + ); + + log.i("DONE"); + return res.sendStatus(200); + } catch (error) { + if (error.cause === "archive") { + return next(new AppError(error)); + } + log.w("DONE with error"); + return next(error); + } +}; diff --git a/packages/backend/src/controllers/hebergement/get.js b/packages/backend/src/controllers/hebergement/get.js index 5ec9b788c..df289a3e0 100644 --- a/packages/backend/src/controllers/hebergement/get.js +++ b/packages/backend/src/controllers/hebergement/get.js @@ -1,4 +1,5 @@ -const Hebergement = require("../../services/Hebergement"); +const Hebergement = require("../../services/hebergement/Hebergement"); +const Organisme = require("../../services/Organisme"); const logger = require("../../utils/logger"); @@ -6,11 +7,32 @@ const log = logger(module.filename); module.exports = async function get(req, res) { log.i("IN"); - const { decoded } = req; - const { id: userId } = decoded; - + const { id: userId } = req.decoded; + const search = req.query.search ? JSON.parse(req.query.search) : {}; + let hebergements; try { - const hebergements = await Hebergement.getByUserId(userId); + const organismeUserConnected = await Organisme.getOne({ + use_id: userId, + }); + const searchByUserId = + !search?.organismeId || + organismeUserConnected?.organismeId === search.organismeId; + if (!searchByUserId) { + const organismeSiege = await Organisme.getSiege( + organismeUserConnected.personneMorale.siret, + ); + // L'organisme Siege est bien le même organisme que l'utilisateur connecté + if (organismeSiege.organismeId === organismeUserConnected?.organismeId) { + hebergements = await Hebergement.getBySiren( + organismeSiege.personneMorale.siren, + search, + ); + } + } + if (searchByUserId) { + // Si c'est l'oganisme de l'utilisateur connecté on remonte tous les hébergements de cet utilisateur + hebergements = await Hebergement.getByUserId(userId, search); + } log.d(hebergements); return res.status(200).json({ hebergements }); } catch (error) { diff --git a/packages/backend/src/controllers/hebergement/getByDepartements.js b/packages/backend/src/controllers/hebergement/getByDepartements.js index 29c1244ae..6db586038 100644 --- a/packages/backend/src/controllers/hebergement/getByDepartements.js +++ b/packages/backend/src/controllers/hebergement/getByDepartements.js @@ -1,4 +1,4 @@ -const Hebergement = require("../../services/Hebergement"); +const Hebergement = require("../../services/hebergement/Hebergement"); const logger = require("../../utils/logger"); const Sentry = require("@sentry/node"); diff --git a/packages/backend/src/controllers/hebergement/getById.js b/packages/backend/src/controllers/hebergement/getById.js index aa7aa2ae4..7a902ce22 100644 --- a/packages/backend/src/controllers/hebergement/getById.js +++ b/packages/backend/src/controllers/hebergement/getById.js @@ -1,4 +1,4 @@ -const Hebergement = require("../../services/Hebergement"); +const Hebergement = require("../../services/hebergement/Hebergement"); const AppError = require("../../utils/error"); const logger = require("../../utils/logger"); diff --git a/packages/backend/src/controllers/hebergement/getBySiren.js b/packages/backend/src/controllers/hebergement/getBySiren.js new file mode 100644 index 000000000..f0d4f563f --- /dev/null +++ b/packages/backend/src/controllers/hebergement/getBySiren.js @@ -0,0 +1,28 @@ +const Hebergement = require("../../services/hebergement/Hebergement"); +const AppError = require("../../utils/error"); + +const logger = require("../../utils/logger"); + +const log = logger(module.filename); + +module.exports = async function get(req, res, next) { + log.i("IN"); + const hebergementSiren = req.params.siren; + if (!hebergementSiren) { + log.w("missing or invalid parameter"); + + return next( + new AppError("Paramètre incorrect", { + statusCode: 400, + }), + ); + } + try { + const hebergement = await Hebergement.getBySiren(hebergementSiren); + log.d(hebergement); + return res.status(200).json({ hebergement }); + } catch (error) { + log.w("DONE with error"); + return next(error); + } +}; diff --git a/packages/backend/src/controllers/hebergement/getExtract.js b/packages/backend/src/controllers/hebergement/getExtract.js index 38f425f15..8306bb8ba 100644 --- a/packages/backend/src/controllers/hebergement/getExtract.js +++ b/packages/backend/src/controllers/hebergement/getExtract.js @@ -1,4 +1,4 @@ -const Hebergement = require("../../services/Hebergement"); +const Hebergement = require("../../services/hebergement/Hebergement"); const logger = require("../../utils/logger"); const dayjs = require("dayjs"); diff --git a/packages/backend/src/controllers/hebergement/index.js b/packages/backend/src/controllers/hebergement/index.js index 423f324f7..002577878 100644 --- a/packages/backend/src/controllers/hebergement/index.js +++ b/packages/backend/src/controllers/hebergement/index.js @@ -1,6 +1,10 @@ module.exports.get = require("./get"); module.exports.getByDepartements = require("./getByDepartements"); module.exports.getById = require("./getById"); +module.exports.getBySiren = require("./getBySiren"); module.exports.getExtract = require("./getExtract"); module.exports.post = require("./post"); +module.exports.postBrouillon = require("./post-brouillon"); module.exports.update = require("./update"); +module.exports.updateBrouillon = require("./update-brouillon"); +module.exports.activate = require("./activate"); diff --git a/packages/backend/src/controllers/hebergement/post-brouillon.js b/packages/backend/src/controllers/hebergement/post-brouillon.js new file mode 100644 index 000000000..9d51bea21 --- /dev/null +++ b/packages/backend/src/controllers/hebergement/post-brouillon.js @@ -0,0 +1,54 @@ +const yup = require("yup"); +const Hebergement = require("../../services/hebergement/Hebergement"); +const HebergementHelper = require("../../helpers/hebergement"); +const HebergementSchema = require("../../schemas/hebergement"); +const logger = require("../../utils/logger"); +const ValidationAppError = require("../../utils/validation-error"); +const FOUser = require("../../services/FoUser"); + +const log = logger(module.filename); + +module.exports = async function postbrouillon(req, res, next) { + log.i("postbrouillon - IN"); + const { body, decoded } = req; + + const { nom, coordonnees, informationsLocaux, informationsTransport } = body; + + const userId = decoded.id; + + let hebergement; + try { + hebergement = await yup.object(HebergementSchema.schema(true)).validate( + { + coordonnees: coordonnees ?? {}, + informationsLocaux: informationsLocaux ?? {}, + informationsTransport: informationsTransport ?? {}, + nom, + }, + { + abortEarly: false, + stripped: true, + }, + ); + } catch (error) { + return next(new ValidationAppError(error)); + } + + try { + const organismeId = await FOUser.getUserOrganisme(userId); + const id = await Hebergement.create( + userId, + organismeId, + HebergementHelper.statuts.BROUILLON, + hebergement, + ); + + return res.status(200).json({ + id, + message: "sauvegarde hebegement OK", + }); + } catch (error) { + log.w("DONE with error"); + return next(error); + } +}; diff --git a/packages/backend/src/controllers/hebergement/post.js b/packages/backend/src/controllers/hebergement/post.js index 37ae6c5bf..418b8c586 100644 --- a/packages/backend/src/controllers/hebergement/post.js +++ b/packages/backend/src/controllers/hebergement/post.js @@ -1,5 +1,6 @@ const yup = require("yup"); -const Hebergement = require("../../services/Hebergement"); +const Hebergement = require("../../services/hebergement/Hebergement"); +const HebergementHelper = require("../../helpers/hebergement"); const HebergementSchema = require("../../schemas/hebergement"); const logger = require("../../utils/logger"); const ValidationAppError = require("../../utils/validation-error"); @@ -28,7 +29,7 @@ module.exports = async function post(req, res, next) { let hebergement; try { - hebergement = await yup.object(HebergementSchema.schema()).validate( + hebergement = await yup.object(HebergementSchema.schema(false)).validate( { coordonnees, informationsLocaux, @@ -46,7 +47,12 @@ module.exports = async function post(req, res, next) { try { const organismeId = await FOUser.getUserOrganisme(userId); - const id = await Hebergement.create(userId, organismeId, hebergement); + const id = await Hebergement.create( + userId, + organismeId, + HebergementHelper.statuts.ACTIF, + hebergement, + ); return res.status(200).json({ id, diff --git a/packages/backend/src/controllers/hebergement/update-brouillon.js b/packages/backend/src/controllers/hebergement/update-brouillon.js new file mode 100644 index 000000000..10c94ab53 --- /dev/null +++ b/packages/backend/src/controllers/hebergement/update-brouillon.js @@ -0,0 +1,69 @@ +const yup = require("yup"); +const logger = require("../../utils/logger"); +const ValidationAppError = require("../../utils/validation-error"); +const HebergementSchema = require("../../schemas/hebergement"); +const AppError = require("../../utils/error"); +const Hebergement = require("../../services/hebergement/Hebergement"); +const HebergementHelper = require("../../helpers/hebergement"); + +const log = logger(module.filename); + +module.exports = async function updateBrouillon(req, res, next) { + const hebergementId = req.params.id; + const { body, decoded } = req; + const userId = decoded.id; + + const { nom, coordonnees, informationsLocaux, informationsTransport } = body; + + if ( + !nom || + !coordonnees || + !informationsLocaux || + !informationsTransport || + !hebergementId + ) { + log.w("missing or invalid parameter"); + + return next( + new AppError("Paramètre incorrect", { + statusCode: 400, + }), + ); + } + let hebergement; + + try { + hebergement = await yup.object(HebergementSchema.schema(true)).validate( + { + coordonnees, + informationsLocaux, + informationsTransport, + nom, + }, + { + abortEarly: false, + stripped: true, + }, + ); + } catch (error) { + return next(new ValidationAppError(error)); + } + + try { + await Hebergement.updateWithoutHistory( + userId, + hebergementId, + HebergementHelper.statuts.BROUILLON, + hebergement, + ); + + log.i("DONE"); + return res.sendStatus(200); + } catch (error) { + if (error.cause === "archive") { + return next(new AppError(error)); + } + log.w("DONE with error"); + return next(error); + } +}; diff --git a/packages/backend/src/controllers/hebergement/update.js b/packages/backend/src/controllers/hebergement/update.js index 359c0ac2f..942efc72d 100644 --- a/packages/backend/src/controllers/hebergement/update.js +++ b/packages/backend/src/controllers/hebergement/update.js @@ -1,6 +1,6 @@ const yup = require("yup"); -const Hebergement = require("../../services/Hebergement"); +const Hebergement = require("../../services/hebergement/Hebergement"); const logger = require("../../utils/logger"); const ValidationAppError = require("../../utils/validation-error"); const HebergementSchema = require("../../schemas/hebergement"); @@ -33,7 +33,7 @@ module.exports = async function post(req, res, next) { let hebergement; try { - hebergement = await yup.object(HebergementSchema.schema()).validate( + hebergement = await yup.object(HebergementSchema.schema(false)).validate( { coordonnees, informationsLocaux, diff --git a/packages/backend/src/controllers/organisme/getExtract.js b/packages/backend/src/controllers/organisme/getExtract.js index ff10149fa..c8f3ccdb4 100644 --- a/packages/backend/src/controllers/organisme/getExtract.js +++ b/packages/backend/src/controllers/organisme/getExtract.js @@ -116,4 +116,4 @@ module.exports = async function get(_req, res, next) { log.w("DONE with error"); return next(error); } -}; \ No newline at end of file +}; diff --git a/packages/backend/src/controllers/organisme/update.js b/packages/backend/src/controllers/organisme/update.js index 7e56dbf4c..440edc5c1 100644 --- a/packages/backend/src/controllers/organisme/update.js +++ b/packages/backend/src/controllers/organisme/update.js @@ -37,8 +37,8 @@ module.exports = async function update(req, res, next) { throw new AppError( "Le siret ne peux pas etre modifié car l'organisme est complet", { - statusCode: 403, name: "Forbidden - siret update - organisme complete", + statusCode: 403, }, ); } @@ -54,8 +54,8 @@ module.exports = async function update(req, res, next) { throw new AppError( "Le siret ne peux pas etre modifé car il existe déjà en base", { - statusCode: 403, name: "Forbidden - siret update - organisme incomplete", + statusCode: 403, }, ); } diff --git a/packages/backend/src/controllers/territoire/get-fiche-id-by-ter-code.js b/packages/backend/src/controllers/territoire/get-fiche-id-by-ter-code.js new file mode 100644 index 000000000..89aa03e62 --- /dev/null +++ b/packages/backend/src/controllers/territoire/get-fiche-id-by-ter-code.js @@ -0,0 +1,30 @@ +const AppError = require("../../utils/error"); +const Territoire = require("../../services/Territoire"); + +const logger = require("../../utils/logger"); + +const log = logger(module.filename); + +module.exports = async function getFicheIdByTerCode(req, res, next) { + log.i("IN"); + const { territoireCode } = req.params; + + if (!territoireCode) { + return next( + new AppError("Paramètre manquant territoireCode", { + statusCode: 400, + }), + ); + } + const territoire = await Territoire.readFicheIdByTerCode(territoireCode); + if (!territoire) { + log.w("DONE with error"); + return next( + new AppError("Fiche territoire non trouvée", { + statusCode: 404, + }), + ); + } + log.i("DONE"); + return res.json({ territoire }); +}; diff --git a/packages/backend/src/controllers/territoire/index.js b/packages/backend/src/controllers/territoire/index.js index f2280d6a5..de5a2a792 100644 --- a/packages/backend/src/controllers/territoire/index.js +++ b/packages/backend/src/controllers/territoire/index.js @@ -1,3 +1,4 @@ module.exports.getOne = require("./get-one"); +module.exports.getFicheIdByTerCode = require("./get-fiche-id-by-ter-code"); module.exports.list = require("./list"); module.exports.update = require("./update"); diff --git a/packages/backend/src/crons/demandes-sejours/notifyRappelActionsBO.js b/packages/backend/src/crons/demandes-sejours/notifyRappelActionsBO.js index d99010b12..39a48ea66 100644 --- a/packages/backend/src/crons/demandes-sejours/notifyRappelActionsBO.js +++ b/packages/backend/src/crons/demandes-sejours/notifyRappelActionsBO.js @@ -26,12 +26,15 @@ const generateRappelQuery = ( additionalOrderBy = "", ) => ` WITH hebergement_exploded AS ( - SELECT - ds.id, - jsonb_array_elements(ds.hebergement->'hebergements') ->> 'dateDebut' AS date_debut_hebergement, - jsonb_array_elements(ds.hebergement->'hebergements') -> 'coordonnees' -> 'adresse' ->> 'codeInsee' AS code_insee - FROM - front.demande_sejour ds + SELECT + DS.ID as "id", + DSTH.DATE_DEBUT as "date_debut_hebergement", + A.CODE_INSEE as "code_insee" + FROM + FRONT.DEMANDE_SEJOUR DS + INNER JOIN FRONT.DEMANDE_SEJOUR_TO_HEBERGEMENT DSTH ON DSTH.DEMANDE_SEJOUR_ID = DS.ID + INNER JOIN FRONT.HEBERGEMENT H ON H.ID = DSTH.HEBERGEMENT_ID + INNER JOIN FRONT.ADRESSE A ON A.ID = H.ADRESSE_ID ) SELECT ds.id, diff --git a/packages/backend/src/helpers/__tests__/eig.js b/packages/backend/src/helpers/__tests__/eig.js new file mode 100644 index 000000000..36016eeac --- /dev/null +++ b/packages/backend/src/helpers/__tests__/eig.js @@ -0,0 +1,70 @@ +const { isDeclarationligibleToEig } = require("../eig"); +const { statuts: dsStatuts } = require("../ds-statuts"); +const { expect } = require("@playwright/test"); + +describe("isDeclarationligibleToEig", () => { + const declaration = { + dateDebut: "2024-09-01", + dateFin: "2024-09-07", + id: 26, + libelle: "declaration1", + }; + + const allStatuts = Object.values(dsStatuts); + + const okStatuts = [ + dsStatuts.SEJOUR_EN_COURS, + dsStatuts.VALIDEE_8J, + dsStatuts.TERMINEE, + ]; + + const badStatus = allStatuts.filter((s) => !okStatuts.includes(s)); + + it.each(okStatuts)( + "should return true for statut %p and good date range", + (statut) => { + declaration.statut = statut; + jest.useFakeTimers().setSystemTime(new Date("2024-09-01")); + expect(isDeclarationligibleToEig(declaration)).toBe(true); + + jest.useFakeTimers().setSystemTime(new Date("2024-09-06")); + expect(isDeclarationligibleToEig(declaration)).toBe(true); + + jest.useFakeTimers().setSystemTime(new Date("2024-09-07")); + expect(isDeclarationligibleToEig(declaration)).toBe(true); + + jest.useFakeTimers().setSystemTime(new Date("2024-09-14")); + expect(isDeclarationligibleToEig(declaration)).toBe(true); + }, + ); + + it.each(badStatus)( + "should return false for statut %p and good date range", + (statut) => { + declaration.statut = statut; + jest.useFakeTimers().setSystemTime(new Date("2024-09-01")); + expect(isDeclarationligibleToEig(declaration)).toBe(false); + + jest.useFakeTimers().setSystemTime(new Date("2024-09-06")); + expect(isDeclarationligibleToEig(declaration)).toBe(false); + + jest.useFakeTimers().setSystemTime(new Date("2024-09-07")); + expect(isDeclarationligibleToEig(declaration)).toBe(false); + + jest.useFakeTimers().setSystemTime(new Date("2024-09-14")); + expect(isDeclarationligibleToEig(declaration)).toBe(false); + }, + ); + + it.each(allStatuts)( + "should return false for statut %p and bad date range", + (statut) => { + declaration.statut = statut; + jest.useFakeTimers().setSystemTime(new Date("2024-08-31")); + expect(isDeclarationligibleToEig(declaration)).toBe(false); + + jest.useFakeTimers().setSystemTime(new Date("2024-09-15")); + expect(isDeclarationligibleToEig(declaration)).toBe(false); + }, + ); +}); diff --git a/packages/backend/src/helpers/eig.js b/packages/backend/src/helpers/eig.js index 29b2e6ee0..f95a862e5 100644 --- a/packages/backend/src/helpers/eig.js +++ b/packages/backend/src/helpers/eig.js @@ -109,12 +109,41 @@ module.exports.UpdateTypes = { TYPE_EVENEMENT: "TYPE_EVENEMENT", }; -module.exports.idDeclarationeligibleToEig = (d) => +module.exports.isDeclarationligibleToEig = (d) => d.dateDebut <= dayjs().format("YYYY-MM-DD") && dayjs(d.dateFin).add(1, "week").format("YYYY-MM-DD") >= dayjs().format("YYYY-MM-DD") && - ![ - dsStatuts.statuts.BROUILLON, - dsStatuts.statuts.ABANDONNEE, - dsStatuts.statuts.ANNULEE, + [ + dsStatuts.statuts.VALIDEE_8J, + dsStatuts.statuts.SEJOUR_EN_COURS, + dsStatuts.statuts.TERMINEE, ].includes(d.statut); + +const isUserDreetsWhoDeliveredAgrement = ( + territoireCode, + agrementRegionObtention, +) => territoireCode === agrementRegionObtention; + +const isUserDdetsWhereEigHappened = (territoireCode, eigDepartement) => + territoireCode === eigDepartement; + +module.exports.isUserDreetsWhoDeliveredAgrement = + isUserDreetsWhoDeliveredAgrement; + +module.exports.isUserDdetsWhereEigHappened = isUserDdetsWhereEigHappened; + +module.exports.mustMarkAsRead = (territoireCode, eig) => { + return ( + (!eig.readByDdets && + isUserDdetsWhereEigHappened(territoireCode, eig.departement)) || + (!eig.readByDreets && + isUserDreetsWhoDeliveredAgrement( + territoireCode, + eig.agrementRegionObtention, + )) + ); +}; + +module.exports.isTypeActive = (type) => { + return ![Types[Categorie.VICTIMES].VIOLS].includes(type); +}; diff --git a/packages/backend/src/helpers/hebergement.js b/packages/backend/src/helpers/hebergement.js new file mode 100644 index 000000000..89d7d063c --- /dev/null +++ b/packages/backend/src/helpers/hebergement.js @@ -0,0 +1,5 @@ +module.exports.statuts = { + ACTIF: "actif", + BROUILLON: "brouillon", + DESACTIVE: "desactive", +}; diff --git a/packages/backend/src/middlewares/can-update-ds.js b/packages/backend/src/middlewares/can-update-ds.js index c9a41285d..10e0c239c 100644 --- a/packages/backend/src/middlewares/can-update-ds.js +++ b/packages/backend/src/middlewares/can-update-ds.js @@ -1,11 +1,16 @@ const { number } = require("yup"); const ValidationAppError = require("../utils/validation-error"); const DemandeSejour = require("../services/DemandeSejour"); +const Organisme = require("../services/Organisme"); + const { statuts } = require("../helpers/ds-statuts"); const AppError = require("../utils/error"); async function canUpdateDs(req, _res, next) { let { declarationId } = req.params; + const { id: userId } = req.decoded; + let organisme; + let sejour; try { declarationId = await number().required().validate(declarationId); @@ -14,6 +19,23 @@ async function canUpdateDs(req, _res, next) { } try { + organisme = await Organisme.getOne({ + use_id: userId, + }); + sejour = await DemandeSejour.getById(declarationId); + + if (organisme.organismeId !== sejour.organismeId) { + next( + new AppError( + `Organisme non autorisé à faire une mise à jour sur cette demande de séjour`, + { + name: "Not Authorized", + statusCode: 403, + }, + ), + ); + } + const statut = await DemandeSejour.getStatut(declarationId); if ( [ diff --git a/packages/backend/src/middlewares/checkPermissionBODeclarationSejour.js b/packages/backend/src/middlewares/checkPermissionBODeclarationSejour.js index 2646136d2..70506dc64 100644 --- a/packages/backend/src/middlewares/checkPermissionBODeclarationSejour.js +++ b/packages/backend/src/middlewares/checkPermissionBODeclarationSejour.js @@ -23,16 +23,33 @@ async function checkPermissionDeclarationSejour(req, res, next) { // Requête SQL simplifiée, ne récupérant que les informations brutes nécessaires const query = ` - SELECT ds.id, ds.hebergement, o.personne_morale, agr.region_obtention + SELECT ds.id, o.personne_morale, agr.region_obtention FROM front.demande_sejour ds INNER JOIN front.organismes o ON o.id = ds.organisme_id LEFT JOIN front.agrements agr ON agr.organisme_id = ds.organisme_id WHERE ds.id = $1 `; + const queryHebegements = ` + SELECT DISTINCT + A.DEPARTEMENT AS DEPARTEMENT + FROM + FRONT.DEMANDE_SEJOUR_TO_HEBERGEMENT DSTH + INNER JOIN FRONT.HEBERGEMENT H ON H.ID = DSTH.HEBERGEMENT_ID + INNER JOIN FRONT.ADRESSE A ON A.ID = H.ADRESSE_ID + WHERE + DEMANDE_SEJOUR_ID = $1 + `; + let sejour; + let departementsHebergements; try { const { rows } = await pool.query(query, [declarationId]); + departementsHebergements = + (await pool.query(queryHebegements, [declarationId]))?.rows.map( + (r) => r.departement, + ) ?? []; + if (!rows || rows.length === 0) { return next( new AppError( @@ -54,14 +71,9 @@ async function checkPermissionDeclarationSejour(req, res, next) { ); } - const { hebergement, personne_morale, region_obtention } = sejour; + const { personne_morale, region_obtention } = sejour; // Traitement des données JSON pour vérifier les départements - const hebergements = hebergement?.hebergements || []; - const departementsHebergements = hebergements.map( - (h) => h.coordonnees?.adresse?.departement, - ); - const hasValidDepartement = departements.some((dep) => departementsHebergements.includes(dep.value), ); diff --git a/packages/backend/src/middlewares/checkPermissionBOEIG.js b/packages/backend/src/middlewares/checkPermissionBOEIG.js new file mode 100644 index 000000000..3b2359e89 --- /dev/null +++ b/packages/backend/src/middlewares/checkPermissionBOEIG.js @@ -0,0 +1,61 @@ +const logger = require("../utils/logger"); +const AppError = require("../utils/error"); +const { getById } = require("../services/eig"); +const { statuts, mustMarkAsRead } = require("../helpers/eig"); + +const log = logger(module.filename); + +async function checkPermissionEIG(req, res, next) { + const { id: userId, territoireCode } = req.decoded; + const { id: eigId } = req.params; + + if (!eigId || isNaN(eigId)) { + return next( + new AppError("Vous n'êtes pas autorisé à accéder à cet EIG", { + statusCode: 400, + }), + ); + } + + log.i("IN", { eigId, userId }); + + let eig; + + try { + eig = await getById({ eigId }); + } catch (err) { + return res.status(400).send({ errors: err.errors, name: err.name }); + } + + if (!eig) { + log.w("EIG introuvable"); + return next( + new AppError("Not found", { + statusCode: 400, + }), + ); + } + + if (eig.statut === statuts.BROUILLON) { + log.w("L'EIG a un statut brouillon et ne peut pas être lu"); + return next( + new AppError("Statut incompatible", { + statusCode: 400, + }), + ); + } + + if (mustMarkAsRead(territoireCode, eig)) { + log.w("L'EIG doit d'abord être marqué comme lu"); + return next( + new AppError("l'EIG doit d'abord être marqué comme lu", { + statusCode: 400, + }), + ); + } + + log.i("DONE"); + next(); +} + +module.exports = checkPermissionEIG; diff --git a/packages/backend/src/middlewares/checkPermissionHebergement.js b/packages/backend/src/middlewares/checkPermissionHebergement.js index a072f3099..30d8f0521 100644 --- a/packages/backend/src/middlewares/checkPermissionHebergement.js +++ b/packages/backend/src/middlewares/checkPermissionHebergement.js @@ -1,6 +1,7 @@ const logger = require("../utils/logger"); const AppError = require("../utils/error"); -const pool = require("../utils/pgpool").getPool(); +const Organisme = require("../services/Organisme"); +const Hebergement = require("../services/hebergement/Hebergement"); const log = logger(module.filename); @@ -17,16 +18,22 @@ async function checkPermissionHebergement(req, res, next) { ); } - const query = ` - SELECT h.id - FROM - front.hebergement h - JOIN front.user_organisme uo ON uo.org_id = h.organisme_id - WHERE h.id = $1 - AND uo.use_id = $2 - `; - const { rows } = await pool.query(query, [hebergementId, userId]); - if (!rows || rows.length !== 1) { + const organisme = await Organisme.getOne({ + use_id: userId, + }); + + const siren = + organisme.typeOrganisme === "personne_morale" && + organisme.personneMorale?.porteurAgrement === true + ? organisme.personneMorale?.siren + : ""; + + const hebergements = await Hebergement.getByIdAndMySiren( + hebergementId, + userId, + siren, + ); + if (!hebergements || hebergements.length !== 1) { return next( new AppError("Vous n'êtes pas autorisé à accéder à cet hébergement", { statusCode: 403, diff --git a/packages/backend/src/middlewares/checkStatutHebergement.js b/packages/backend/src/middlewares/checkStatutHebergement.js new file mode 100644 index 000000000..fd28e437f --- /dev/null +++ b/packages/backend/src/middlewares/checkStatutHebergement.js @@ -0,0 +1,39 @@ +const logger = require("../utils/logger"); +const AppError = require("../utils/error"); +const Hebergement = require("../services/hebergement/Hebergement"); + +const log = logger(module.filename); + +function checkStatutHebergement(statut) { + return async (req, res, next) => { + const { id: hebergementId } = req.params; + + if (!hebergementId) { + return next( + new AppError("Paramètres invalides", { + statusCode: 400, + }), + ); + } + + let hebergementStatut; + try { + hebergementStatut = await Hebergement.getStatut(hebergementId); + } catch (error) { + log.w("DONE with error"); + return next(error); + } + + if (statut !== hebergementStatut) { + return next( + new AppError("Vous n'êtes pas autorisé à accéder à cet hébergement", { + statusCode: 403, + }), + ); + } + + next(); + }; +} + +module.exports = checkStatutHebergement; diff --git a/packages/backend/src/middlewares/utils/checkPermissionDeclarationSejour.js b/packages/backend/src/middlewares/utils/checkPermissionDeclarationSejour.js index e81e9a93c..7d625a315 100644 --- a/packages/backend/src/middlewares/utils/checkPermissionDeclarationSejour.js +++ b/packages/backend/src/middlewares/utils/checkPermissionDeclarationSejour.js @@ -1,6 +1,7 @@ const logger = require("../../utils/logger"); const AppError = require("../../utils/error"); -const pool = require("../../utils/pgpool").getPool(); +const Organisme = require("../../services/Organisme"); +const DemandeSejour = require("../../services/DemandeSejour"); const log = logger(module.filename); @@ -21,18 +22,23 @@ const checkPermissionDeclarationSejourUtils = async ( ), ); } + const organisme = await Organisme.getOne({ + use_id: userId, + }); - const query = ` - SELECT ds.id - FROM - front.demande_sejour ds - JOIN front.user_organisme uo ON uo.org_id = ds.organisme_id - JOIN front.users u ON uo.use_id = u.id - WHERE ds.id = $1 - AND u.id = $2 - `; - const { rows } = await pool.query(query, [declarationId, userId]); - if (!rows || rows.length !== 1) { + const siren = + organisme.typeOrganisme === "personne_morale" && + organisme.personneMorale?.porteurAgrement === true + ? organisme.personneMorale?.siren + : ""; + + const sejour = await DemandeSejour.getByIdOrUserSiren( + declarationId, + siren, + userId, + ); + + if (!sejour || sejour.length !== 1) { return next( new AppError( "Vous n'êtes pas autorisé à accéder à cette déclaration de séjour", diff --git a/packages/backend/src/routes/__hidden_test_eig__/eig/delete.test_hide.js b/packages/backend/src/routes/__hidden_test_eig__/eig/delete.test_hide.js new file mode 100644 index 000000000..a61cd1f50 --- /dev/null +++ b/packages/backend/src/routes/__hidden_test_eig__/eig/delete.test_hide.js @@ -0,0 +1,60 @@ +const checkJWT = require("../../../middlewares/checkJWT"); +const checkPermissionEIG = require("../../../middlewares/checkPermissionEIG"); +const canUpdateEig = require("../../../middlewares/can-update-or-delete-eig"); +const AppError = require("../../../utils/error"); +const request = require("supertest"); +const app = require("../../../app"); +const eigService = require("../../../services/eig"); + +jest.mock("../../../middlewares/checkJWT"); +jest.mock("../../../middlewares/checkPermissionEIG"); +jest.mock("../../../middlewares/can-update-or-delete-eig"); +jest.mock("../../../services/eig"); +jest.mock("../../../services/DemandeSejour"); +jest.mock("../../../helpers/eigMail"); +jest.mock("../../../utils/mail"); +jest.mock("../../../services/geo/Commune"); +jest.mock("../../../services/mail"); + +describe("DELETE /eig/:id", () => { + const user = { + id: 1, + }; + beforeEach(() => { + jest.clearAllMocks(); + checkJWT.mockImplementation((req, res, next) => { + req.decoded = { ...user }; + next(); + }); + checkPermissionEIG.mockImplementation((req, res, next) => { + next(); + }); + canUpdateEig.mockImplementation((req, res, next) => { + next(); + }); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it("should return an error 403 if you don't have permission for eig", async () => { + checkPermissionEIG.mockImplementation((req, res, next) => { + return next( + new AppError("Vous n'êtes pas autorisé à accéder à cet EIG", { + statusCode: 403, + }), + ); + }); + + const response = await request(app).delete("/eig/1"); + expect(response.statusCode).toBe(403); + expect(eigService.delete).not.toHaveBeenCalled(); + }); + + it("should depose an eig if everything is ok", async () => { + const response = await request(app).delete("/eig/1"); + expect(response.statusCode).toBe(200); + expect(eigService.delete).toHaveBeenCalled(); + }); +}); diff --git a/packages/backend/src/routes/__hidden_test_eig__/eig/get-admin-by-ds-id.test_hide.js b/packages/backend/src/routes/__hidden_test_eig__/eig/get-admin-by-ds-id.test_hide.js new file mode 100644 index 000000000..bea449f5f --- /dev/null +++ b/packages/backend/src/routes/__hidden_test_eig__/eig/get-admin-by-ds-id.test_hide.js @@ -0,0 +1,57 @@ +const boCheckJWT = require("../../../middlewares/bo-check-JWT"); +const checkPermissionBODeclarationSejour = require("../../../middlewares/checkPermissionBODeclarationSejour"); +const getDepartements = require("../../../middlewares/getDepartements"); +const request = require("supertest"); +const app = require("../../../app"); +const eigService = require("../../../services/eig"); + +jest.mock("../../../middlewares/bo-check-JWT"); + +jest.mock("../../../middlewares/checkPermissionBODeclarationSejour"); +jest.mock("../../../middlewares/getDepartements"); +jest.mock("../../../services/eig"); + +describe("GET /eig/admin/ds/:declarationId", () => { + afterEach(() => { + jest.clearAllMocks(); + }); + const user = { + id: 1, + }; + beforeEach(() => { + jest.resetAllMocks(); + boCheckJWT.mockImplementation((req, res, next) => { + req.decoded = { + ...user, + roles: ["eig", "DemandeSejour_Lecture", "DemandeSejour_Ecriture"], + }; + next(); + }); + + getDepartements.mockImplementation((req, res, next) => { + next(); + }); + checkPermissionBODeclarationSejour.mockImplementation((req, res, next) => { + next(); + }); + }); + + it("shoud return a 403 if the admin don't have role eig", async () => { + boCheckJWT.mockImplementationOnce((req, res, next) => { + req.decoded = { + ...user, + roles: ["DemandeSejour_Lecture", "DemandeSejour_Ecriture"], + }; + next(); + }); + const response = await request(app).get("/eig/admin/ds/1"); + expect(response.statusCode).toBe(403); + expect(eigService.getByDsIdAdmin).not.toHaveBeenCalled(); + }); + + it("should call getByDsIdAdmin if everything is ok", async () => { + const response = await request(app).get("/eig/admin/ds/1"); + expect(response.statusCode).toBe(200); + expect(eigService.getByDsIdAdmin).toHaveBeenCalled(); + }); +}); diff --git a/packages/backend/src/routes/__hidden_test_eig__/eig/get-admin-by-id.test_hide.js b/packages/backend/src/routes/__hidden_test_eig__/eig/get-admin-by-id.test_hide.js new file mode 100644 index 000000000..b862a4f04 --- /dev/null +++ b/packages/backend/src/routes/__hidden_test_eig__/eig/get-admin-by-id.test_hide.js @@ -0,0 +1,77 @@ +const boCheckJWT = require("../../../middlewares/bo-check-JWT"); +const checkPermissionBOEIG = require("../../../middlewares/checkPermissionBOEIG"); +const request = require("supertest"); +const app = require("../../../app"); +const eigService = require("../../../services/eig"); +const AppError = require("../../../utils/error"); +const { getEmails } = require("../../../helpers/eigMail"); + +jest.mock("../../../middlewares/bo-check-JWT"); + +jest.mock("../../../middlewares/checkPermissionBODeclarationSejour"); +jest.mock("../../../middlewares/getDepartements"); +jest.mock("../../../services/eig"); +jest.mock("../../../middlewares/checkPermissionBOEIG"); +jest.mock("../../../helpers/eigMail"); + +describe("GET /eig/admin/id", () => { + afterEach(() => { + jest.clearAllMocks(); + }); + const user = { + id: 1, + }; + beforeEach(() => { + jest.resetAllMocks(); + boCheckJWT.mockImplementation((req, res, next) => { + req.decoded = { + ...user, + roles: ["eig", "DemandeSejour_Lecture", "DemandeSejour_Ecriture"], + }; + next(); + }); + checkPermissionBOEIG.mockImplementation((req, res, next) => { + next(); + }); + getEmails.mockResolvedValue({}); + eigService.getById.mockResolvedValue({ + declarationId: 1, + }); + }); + + it("shoud return a 403 if the admin don't have role eig", async () => { + boCheckJWT.mockImplementationOnce((req, res, next) => { + req.decoded = { + ...user, + roles: ["DemandeSejour_Lecture", "DemandeSejour_Ecriture"], + }; + next(); + }); + const response = await request(app).get("/eig/admin/1"); + expect(response.statusCode).toBe(403); + expect(eigService.getById).not.toHaveBeenCalled(); + }); + + it("should return an error 403 if you don't have dclaration sejour permission for eig", async () => { + checkPermissionBOEIG.mockImplementation((req, res, next) => { + return next( + new AppError( + "Vous n'êtes pas autorisé à accéder à cette déclaration de séjour", + { + statusCode: 403, + }, + ), + ); + }); + + const response = await request(app).get("/eig/admin/1"); + expect(response.statusCode).toBe(403); + expect(eigService.getById).not.toHaveBeenCalled(); + }); + + it("should call getById if everything is ok", async () => { + const response = await request(app).get("/eig/admin/1"); + expect(response.statusCode).toBe(200); + expect(eigService.getById).toHaveBeenCalled(); + }); +}); diff --git a/packages/backend/src/routes/__hidden_test_eig__/eig/get-admin.test_hide.js b/packages/backend/src/routes/__hidden_test_eig__/eig/get-admin.test_hide.js new file mode 100644 index 000000000..63b71e2f5 --- /dev/null +++ b/packages/backend/src/routes/__hidden_test_eig__/eig/get-admin.test_hide.js @@ -0,0 +1,45 @@ +const boCheckJWT = require("../../../middlewares/bo-check-JWT"); +const request = require("supertest"); +const app = require("../../../app"); +const eigService = require("../../../services/eig"); + +jest.mock("../../../middlewares/bo-check-JWT"); +jest.mock("../../../services/eig"); + +describe("GET /eig/admin", () => { + afterEach(() => { + jest.clearAllMocks(); + }); + const user = { + id: 1, + }; + beforeEach(() => { + jest.resetAllMocks(); + boCheckJWT.mockImplementation((req, res, next) => { + req.decoded = { + ...user, + roles: ["eig", "DemandeSejour_Lecture", "DemandeSejour_Ecriture"], + }; + next(); + }); + }); + + it("shoud return a 403 if the admin don't have role eig", async () => { + boCheckJWT.mockImplementationOnce((req, res, next) => { + req.decoded = { + ...user, + roles: ["DemandeSejour_Lecture", "DemandeSejour_Ecriture"], + }; + next(); + }); + const response = await request(app).get("/eig/admin"); + expect(response.statusCode).toBe(403); + expect(eigService.getAdmin).not.toHaveBeenCalled(); + }); + + it("should call getByDsIdAdmin if everything is ok", async () => { + const response = await request(app).get("/eig/admin"); + expect(response.statusCode).toBe(200); + expect(eigService.getAdmin).toHaveBeenCalled(); + }); +}); diff --git a/packages/backend/src/routes/__hidden_test_eig__/eig/get-by-ds-id.test_hide.js b/packages/backend/src/routes/__hidden_test_eig__/eig/get-by-ds-id.test_hide.js new file mode 100644 index 000000000..5e8dca2cf --- /dev/null +++ b/packages/backend/src/routes/__hidden_test_eig__/eig/get-by-ds-id.test_hide.js @@ -0,0 +1,52 @@ +const checkJWT = require("../../../middlewares/checkJWT"); +const checkPermissionDeclarationSejour = require("../../../middlewares/checkPermissionDeclarationSejour"); +const AppError = require("../../../utils/error"); +const request = require("supertest"); +const app = require("../../../app"); +const eigService = require("../../../services/eig"); + +jest.mock("../../../middlewares/checkJWT"); +jest.mock("../../../middlewares/checkPermissionDeclarationSejour"); +jest.mock("../../../services/eig"); + +describe("GET /eig/ds/:declarationId", () => { + afterEach(() => { + jest.clearAllMocks(); + }); + const user = { + id: 1, + }; + beforeEach(() => { + jest.resetAllMocks(); + checkJWT.mockImplementation((req, res, next) => { + req.decoded = { ...user }; + next(); + }); + checkPermissionDeclarationSejour.mockImplementation((req, res, next) => { + next(); + }); + }); + + it("should return an error 403 if you don't have dclaration sejour permission for eig", async () => { + checkPermissionDeclarationSejour.mockImplementation((req, res, next) => { + return next( + new AppError( + "Vous n'êtes pas autorisé à accéder à cette déclaration de séjour", + { + statusCode: 403, + }, + ), + ); + }); + + const response = await request(app).get("/eig/ds/1"); + expect(response.statusCode).toBe(403); + expect(eigService.getByDsId).not.toHaveBeenCalled(); + }); + + it("should call getByDsId if everything is ok", async () => { + const response = await request(app).get("/eig/ds/1"); + expect(response.statusCode).toBe(200); + expect(eigService.getByDsId).toHaveBeenCalled(); + }); +}); diff --git a/packages/backend/src/routes/__hidden_test_eig__/eig/get-by-id.test_hide.js b/packages/backend/src/routes/__hidden_test_eig__/eig/get-by-id.test_hide.js new file mode 100644 index 000000000..6ae7dadc6 --- /dev/null +++ b/packages/backend/src/routes/__hidden_test_eig__/eig/get-by-id.test_hide.js @@ -0,0 +1,59 @@ +const checkJWT = require("../../../middlewares/checkJWT"); +const checkPermissionEIG = require("../../../middlewares/checkPermissionEIG"); +const AppError = require("../../../utils/error"); +const request = require("supertest"); +const app = require("../../../app"); +const eigService = require("../../../services/eig"); +const { getEmails } = require("../../../helpers/eigMail"); + +jest.mock("../../../middlewares/checkJWT"); +jest.mock("../../../middlewares/checkPermissionEIG"); +jest.mock("../../../services/eig"); +jest.mock("../../../helpers/eigMail"); + +describe("GET /eig/id", () => { + const user = { + id: 1, + }; + afterEach(() => { + jest.clearAllMocks(); + }); + + beforeEach(() => { + jest.clearAllMocks(); + checkJWT.mockImplementation((req, res, next) => { + req.decoded = { ...user }; + next(); + }); + checkPermissionEIG.mockImplementation((req, res, next) => { + next(); + }); + eigService.getById.mockResolvedValue({ + declarationId: 1, + }); + getEmails.mockResolvedValue({}); + }); + + it("should return an error 403 if you don't have dclaration sejour permission for eig", async () => { + checkPermissionEIG.mockImplementation((req, res, next) => { + return next( + new AppError( + "Vous n'êtes pas autorisé à accéder à cette déclaration de séjour", + { + statusCode: 403, + }, + ), + ); + }); + + const response = await request(app).get("/eig/1"); + expect(response.statusCode).toBe(403); + expect(eigService.getById).not.toHaveBeenCalled(); + }); + + it("should call getByDsId if everything is ok", async () => { + const response = await request(app).get("/eig/1"); + expect(response.statusCode).toBe(200); + expect(eigService.getById).toHaveBeenCalled(); + }); +}); diff --git a/packages/backend/src/routes/__hidden_test_eig__/eig/get-me.test_hide.js b/packages/backend/src/routes/__hidden_test_eig__/eig/get-me.test_hide.js new file mode 100644 index 000000000..2f265adfd --- /dev/null +++ b/packages/backend/src/routes/__hidden_test_eig__/eig/get-me.test_hide.js @@ -0,0 +1,35 @@ +const checkJWT = require("../../../middlewares/checkJWT"); +const request = require("supertest"); +const app = require("../../../app"); +const eigService = require("../../../services/eig"); + +jest.mock("../../../middlewares/checkJWT"); +jest.mock("../../../services/eig"); + +describe("GET /eig/me", () => { + const user = { + id: 1, + }; + beforeEach(() => { + jest.resetAllMocks(); + checkJWT.mockImplementation((req, res, next) => { + req.decoded = { ...user }; + next(); + }); + }); + afterEach(() => { + jest.clearAllMocks(); + }); + + it("should send a 400 if query params are not ok", async () => { + const response = await request(app).get("/eig/me?sortBy=badSort"); + expect(response.statusCode).toBe(400); + expect(eigService.getByUserId).not.toHaveBeenCalled(); + }); + + it("should call getByDsId if everything is ok", async () => { + const response = await request(app).get("/eig/me"); + expect(response.statusCode).toBe(200); + expect(eigService.getByUserId).toHaveBeenCalled(); + }); +}); diff --git a/packages/backend/src/routes/__hidden_test_eig__/eig/post-depose.test_hide.js b/packages/backend/src/routes/__hidden_test_eig__/eig/post-depose.test_hide.js new file mode 100644 index 000000000..e29adae43 --- /dev/null +++ b/packages/backend/src/routes/__hidden_test_eig__/eig/post-depose.test_hide.js @@ -0,0 +1,111 @@ +const checkJWT = require("../../../middlewares/checkJWT"); +const checkPermissionEIG = require("../../../middlewares/checkPermissionEIG"); +const canUpdateEig = require("../../../middlewares/can-update-or-delete-eig"); +const AppError = require("../../../utils/error"); +const request = require("supertest"); +const app = require("../../../app"); +const eigService = require("../../../services/eig"); +const DemandeSejour = require("../../../services/DemandeSejour"); +const yup = require("yup"); +const { getEmails } = require("../../../helpers/eigMail"); +const MailUtils = require("../../../utils/mail"); + +jest.mock("../../../middlewares/checkJWT"); +jest.mock("../../../middlewares/checkPermissionEIG"); +jest.mock("../../../middlewares/can-update-or-delete-eig"); +jest.mock("../../../services/eig"); +jest.mock("../../../services/DemandeSejour"); +jest.mock("../../../helpers/eigMail"); +jest.mock("../../../utils/mail"); +jest.mock("../../../services/geo/Commune"); +jest.mock("../../../services/mail"); + +describe("POST /depose/:id", () => { + const user = { + id: 1, + }; + beforeEach(() => { + jest.clearAllMocks(); + checkJWT.mockImplementation((req, res, next) => { + req.decoded = { ...user }; + next(); + }); + checkPermissionEIG.mockImplementation((req, res, next) => { + next(); + }); + canUpdateEig.mockImplementation((req, res, next) => { + next(); + }); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it("should return an error 403 if you don't have permission for eig", async () => { + checkPermissionEIG.mockImplementation((req, res, next) => { + return next( + new AppError("Vous n'êtes pas autorisé à accéder à cet EIG", { + statusCode: 403, + }), + ); + }); + + const response = await request(app) + .post("/eig/depose/1") + .send({ parametre: { declarationId: 1 } }); + expect(response.statusCode).toBe(403); + expect(eigService.depose).not.toHaveBeenCalled(); + }); + + it("should depose an eig if everything is ok", async () => { + DemandeSejour.getOne.mockResolvedValue({ + dateDebut: new Date("2024-01-01"), + dateFin: new Date("2024-01-10"), + hebergement: { hebergements: [] }, + id: 1, + responsableSejour: { email: "a@a.com" }, + }); + eigService.getById.mockResolvedValue({ + declarationId: 1, + emailAutresDestinataires: ["autre@autre.com"], + }); + jest.spyOn(yup, "object").mockImplementation(() => ({ + validate: (parametre) => { + return parametre; + }, + })); + getEmails.mockResolvedValue({}); + + const response = await request(app) + .post("/eig/depose/1") + .send({ parametre: { declarationId: 1 } }); + expect(response.statusCode).toBe(200); + expect(eigService.depose).toHaveBeenCalled(); + expect(MailUtils.bo.eig.sendToAutre).toHaveBeenCalled(); + + getEmails.mockResolvedValue({ emailsDDETS: ["a@a.com"] }); + await request(app) + .post("/eig/depose/1") + .send({ parametre: { declarationId: 1 } }); + expect(MailUtils.bo.eig.sendToDDETS).toHaveBeenCalledTimes(1); + expect(MailUtils.bo.eig.sendToDREETS).toHaveBeenCalledTimes(0); + expect(MailUtils.bo.eig.sendToOrganisme).toHaveBeenCalledTimes(0); + + getEmails.mockResolvedValue({ emailsDREETS: ["a@a.com"] }); + await request(app) + .post("/eig/depose/1") + .send({ parametre: { declarationId: 1 } }); + expect(MailUtils.bo.eig.sendToDDETS).toHaveBeenCalledTimes(1); + expect(MailUtils.bo.eig.sendToDREETS).toHaveBeenCalledTimes(1); + expect(MailUtils.bo.eig.sendToOrganisme).toHaveBeenCalledTimes(0); + + getEmails.mockResolvedValue({ emailsOrganisateur: ["a@a.com"] }); + await request(app) + .post("/eig/depose/1") + .send({ parametre: { declarationId: 1 } }); + expect(MailUtils.bo.eig.sendToDDETS).toHaveBeenCalledTimes(1); + expect(MailUtils.bo.eig.sendToDREETS).toHaveBeenCalledTimes(1); + expect(MailUtils.bo.eig.sendToOrganisme).toHaveBeenCalledTimes(1); + }); +}); diff --git a/packages/backend/src/routes/__hidden_test_eig__/eig/post-mark-as-read.test_hide.js b/packages/backend/src/routes/__hidden_test_eig__/eig/post-mark-as-read.test_hide.js new file mode 100644 index 000000000..fb1ac0b78 --- /dev/null +++ b/packages/backend/src/routes/__hidden_test_eig__/eig/post-mark-as-read.test_hide.js @@ -0,0 +1,104 @@ +const boCheckJWT = require("../../../middlewares/bo-check-JWT"); + +const request = require("supertest"); +const app = require("../../../app"); +const eigService = require("../../../services/eig"); +const { statuts } = require("../../../helpers/eig"); + +jest.mock("../../../middlewares/bo-check-JWT"); +jest.mock("../../../services/eig"); +jest.mock("../../../services/DemandeSejour"); +jest.mock("../../../utils/mail"); +jest.mock("../../../services/mail"); +jest.mock("../../../services/geo/Region"); +jest.mock("../../../services/geo/Departement"); + +describe("GET /eig/admin/:id/mark-as-read", () => { + afterEach(() => { + jest.clearAllMocks(); + }); + const user = { + id: 1, + }; + beforeEach(() => { + jest.resetAllMocks(); + boCheckJWT.mockImplementation((req, res, next) => { + req.decoded = { + ...user, + roles: ["eig", "DemandeSejour_Lecture", "DemandeSejour_Ecriture"], + }; + next(); + }); + eigService.getById.mockReturnValue({ + agrementRegionObtention: "BRE", + statut: statuts.ENVOYE, + territoireCode: 92, + }); + }); + + it("shoud return a 403 if the admin don't have role eig", async () => { + boCheckJWT.mockImplementation((req, res, next) => { + req.decoded = { + ...user, + roles: ["DemandeSejour_Lecture", "DemandeSejour_Ecriture"], + }; + next(); + }); + const response = await request(app) + .post("/eig/admin/1/mark-as-read") + .send({}); + expect(response.statusCode).toBe(403); + expect(eigService.markAsRead).not.toHaveBeenCalled(); + }); + + it("should return a 400 if eig don't have the good statut", async () => { + eigService.getById.mockReturnValue({ statut: statuts.LU }); + const response = await request(app) + .post("/eig/admin/1/mark-as-read") + .send({}); + + expect(response.statusCode).toBe(400); + expect(eigService.markAsRead).not.toHaveBeenCalled(); + }); + + it("should return a 400 The user don't have any action to do", async () => { + boCheckJWT.mockImplementation((req, res, next) => { + req.decoded = { + ...user, + roles: ["DemandeSejour_Lecture", "DemandeSejour_Ecriture", "eig"], + territoireCode: 93, + }; + next(); + }); + const responseDep = await request(app) + .post("/eig/admin/1/mark-as-read") + .send({}); + + expect(responseDep.statusCode).toBe(400); + expect(eigService.markAsRead).not.toHaveBeenCalled(); + + boCheckJWT.mockImplementation((req, res, next) => { + req.decoded = { + ...user, + roles: ["DemandeSejour_Lecture", "DemandeSejour_Ecriture", "eig"], + territoireCode: "IDF", + }; + next(); + }); + const responseREG = await request(app) + .post("/eig/admin/1/mark-as-read") + .send({}); + + expect(responseREG.statusCode).toBe(400); + expect(eigService.markAsRead).not.toHaveBeenCalled(); + }); + + it("should call markAsRead if everything is of", async () => { + const response = await request(app) + .post("/eig/admin/1/mark-as-read") + .send({}); + + expect(response.statusCode).toBe(200); + expect(eigService.markAsRead).toHaveBeenCalledWith("1", "DDETS"); + }); +}); diff --git a/packages/backend/src/routes/__hidden_test_eig__/eig/post.test_hide.js b/packages/backend/src/routes/__hidden_test_eig__/eig/post.test_hide.js new file mode 100644 index 000000000..358cf737c --- /dev/null +++ b/packages/backend/src/routes/__hidden_test_eig__/eig/post.test_hide.js @@ -0,0 +1,95 @@ +const checkJWT = require("../../../middlewares/checkJWT"); +const checkPermissionDeclarationSejourForEig = require("../../../middlewares/checkPermissionDeclarationSejourEig"); +const DemandeSejour = require("../../../services/DemandeSejour"); +const request = require("supertest"); +const app = require("../../../app"); +const { isDeclarationligibleToEig } = require("../../../helpers/eig"); +const yup = require("yup"); +const eigService = require("../../../services/eig"); + +jest.mock("../../../middlewares/checkJWT"); +jest.mock("../../../middlewares/checkPermissionDeclarationSejourEig"); +jest.mock("../../../services/DemandeSejour"); +jest.mock("../../../helpers/eig"); +jest.mock("../../../services/eig"); + +describe("POST /eig", () => { + afterEach(() => { + jest.clearAllMocks(); + }); + const user = { + id: 1, + }; + beforeEach(() => { + jest.clearAllMocks(); + checkJWT.mockImplementation((req, res, next) => { + req.decoded = { ...user }; + next(); + }); + checkPermissionDeclarationSejourForEig.mockImplementation( + (req, res, next) => { + next(); + }, + ); + }); + + it("should return an error 404 if declaration is missing", async () => { + DemandeSejour.getOne.mockResolvedValue(null); + + const response = await request(app) + .post("/eig") + .send({ parametre: { declarationId: 1 } }); + expect(response.statusCode).toBe(404); + }); + + it("should return an error 400 if declaration is not eligible", async () => { + DemandeSejour.getOne.mockResolvedValue({ id: 1 }); + isDeclarationligibleToEig.mockReturnValue(false); + const response = await request(app) + .post("/eig") + .send({ parametre: { declarationId: 1 } }); + expect(response.statusCode).toBe(400); + }); + + it("should return a validation error if the body is not validate by yup", async () => { + DemandeSejour.getOne.mockResolvedValue({ + dateDebut: new Date("2024-01-01"), + dateFin: new Date("2024-01-10"), + id: 1, + }); + isDeclarationligibleToEig.mockReturnValue(true); + jest.spyOn(yup, "object").mockImplementation(() => ({ + validate: () => { + throw new Error(); + }, + })); + const response = await request(app) + .post("/eig") + .send({ parametre: { declarationId: 1 } }); + + expect(yup.object).toHaveBeenCalled(); + expect(response.statusCode).toBe(400); + }); + + it("should call eig create service if everything is ok", async () => { + DemandeSejour.getOne.mockResolvedValue({ + dateDebut: new Date("2024-01-01"), + dateFin: new Date("2024-01-10"), + id: 1, + }); + isDeclarationligibleToEig.mockReturnValue(true); + jest.spyOn(yup, "object").mockImplementation(() => ({ + validate: (parametre) => { + return parametre; + }, + })); + eigService.create.mockResolvedValueOnce(1); + + const response = await request(app) + .post("/eig") + .send({ parametre: { declarationId: 1 } }); + + expect(eigService.create).toHaveBeenCalled(); + expect(response.statusCode).toBe(200); + }); +}); diff --git a/packages/backend/src/routes/__hidden_test_eig__/eig/put-update.test_hide.js b/packages/backend/src/routes/__hidden_test_eig__/eig/put-update.test_hide.js new file mode 100644 index 000000000..58196a0e4 --- /dev/null +++ b/packages/backend/src/routes/__hidden_test_eig__/eig/put-update.test_hide.js @@ -0,0 +1,171 @@ +const checkJWT = require("../../../middlewares/checkJWT"); +const checkPermissionEIG = require("../../../middlewares/checkPermissionEIG"); +const checkPermissionDeclarationSejourForEig = require("../../../middlewares/checkPermissionDeclarationSejourEig"); +const canUpdateEig = require("../../../middlewares/can-update-or-delete-eig"); +const DemandeSejour = require("../../../services/DemandeSejour"); +const request = require("supertest"); +const app = require("../../../app"); +const eigService = require("../../../services/eig"); +const AppError = require("../../../utils/error"); +const { + isDeclarationligibleToEig, + UpdateTypes, +} = require("../../../helpers/eig"); +const yup = require("yup"); + +jest.mock("../../../middlewares/checkJWT"); +jest.mock("../../../middlewares/checkPermissionEIG"); +jest.mock("../../../middlewares/checkPermissionDeclarationSejourEig"); +jest.mock("../../../middlewares/can-update-or-delete-eig"); +jest.mock("../../../services/DemandeSejour"); +jest.mock("../../../services/eig"); +jest.mock("../../../helpers/eig"); + +describe("PUT /eig", () => { + const user = { + id: 1, + }; + beforeEach(() => { + jest.clearAllMocks(); + checkJWT.mockImplementation((req, res, next) => { + req.decoded = { ...user }; + next(); + }); + checkPermissionEIG.mockImplementation((req, res, next) => { + next(); + }); + checkPermissionDeclarationSejourForEig.mockImplementation( + (req, res, next) => { + next(); + }, + ); + canUpdateEig.mockImplementation((req, res, next) => { + next(); + }); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it("should return an error 404 if declaration is missing", async () => { + DemandeSejour.getOne.mockResolvedValue(null); + + const response = await request(app) + .put("/eig/1") + .send({ parametre: { declarationId: 1 } }); + expect(response.statusCode).toBe(404); + expect(eigService.updateDS).not.toHaveBeenCalled(); + expect(eigService.updateType).not.toHaveBeenCalled(); + expect(eigService.updateRenseignementsGeneraux).not.toHaveBeenCalled(); + expect(eigService.updateEmailAutresDestinataires).not.toHaveBeenCalled(); + }); + + it("should return an error 403 if you don't have permission for eig", async () => { + checkPermissionEIG.mockImplementation((req, res, next) => { + return next( + new AppError("Vous n'êtes pas autorisé à accéder à cet EIG", { + statusCode: 403, + }), + ); + }); + + const response = await request(app) + .put("/eig/1") + .send({ parametre: { declarationId: 1 } }); + expect(response.statusCode).toBe(403); + expect(eigService.updateDS).not.toHaveBeenCalled(); + expect(eigService.updateType).not.toHaveBeenCalled(); + expect(eigService.updateRenseignementsGeneraux).not.toHaveBeenCalled(); + expect(eigService.updateEmailAutresDestinataires).not.toHaveBeenCalled(); + }); + + it("should return an error 403 if you don't have dclaration sejour permission for eig", async () => { + checkPermissionDeclarationSejourForEig.mockImplementation( + (req, res, next) => { + return next( + new AppError( + "Vous n'êtes pas autorisé à accéder à cette déclaration de séjour", + { + statusCode: 403, + }, + ), + ); + }, + ); + DemandeSejour.getOne.mockResolvedValue({ id: 1 }); + isDeclarationligibleToEig.mockReturnValue(false); + + const response = await request(app) + .put("/eig/1") + .send({ parametre: { declarationId: 1 } }); + expect(response.statusCode).toBe(403); + expect(eigService.updateDS).not.toHaveBeenCalled(); + expect(eigService.updateType).not.toHaveBeenCalled(); + expect(eigService.updateRenseignementsGeneraux).not.toHaveBeenCalled(); + expect(eigService.updateEmailAutresDestinataires).not.toHaveBeenCalled(); + }); + + it("should return an error 400 if declaration is not eligible", async () => { + DemandeSejour.getOne.mockResolvedValue({ id: 1 }); + isDeclarationligibleToEig.mockReturnValue(false); + const response = await request(app) + .put("/eig/1") + .send({ parametre: { declarationId: 1 } }); + expect(response.statusCode).toBe(400); + expect(eigService.updateDS).not.toHaveBeenCalled(); + expect(eigService.updateType).not.toHaveBeenCalled(); + expect(eigService.updateRenseignementsGeneraux).not.toHaveBeenCalled(); + expect(eigService.updateEmailAutresDestinataires).not.toHaveBeenCalled(); + }); + + it("should call eig update service if everything is ok", async () => { + DemandeSejour.getOne.mockResolvedValue({ + dateDebut: new Date("2024-01-01"), + dateFin: new Date("2024-01-10"), + id: 1, + }); + isDeclarationligibleToEig.mockReturnValue(true); + jest.spyOn(yup, "object").mockImplementation(() => ({ + validate: (parametre) => { + return parametre; + }, + })); + + const responseDS = await request(app) + .put("/eig/1") + .send({ + parametre: { declarationId: 1 }, + type: UpdateTypes.DECLARATION_SEJOUR, + }); + expect(eigService.updateDS).toHaveBeenCalled(); + expect(responseDS.statusCode).toBe(200); + + const responseType = await request(app) + .put("/eig/1") + .send({ + parametre: { declarationId: 1 }, + type: UpdateTypes.TYPE_EVENEMENT, + }); + expect(eigService.updateType).toHaveBeenCalled(); + expect(responseType.statusCode).toBe(200); + + const responseRG = await request(app) + .put("/eig/1") + .send({ + parametre: { declarationId: 1 }, + type: UpdateTypes.RENSEIGNEMENT_GENERAUX, + }); + expect(eigService.updateRenseignementsGeneraux).toHaveBeenCalled(); + expect(responseRG.statusCode).toBe(200); + + const responseMail = await request(app) + .put("/eig/1") + .send({ + parametre: { declarationId: 1 }, + type: UpdateTypes.EMAIL_AUTRES_DESTINATAIRES, + }); + expect(eigService.updateEmailAutresDestinataires).toHaveBeenCalled(); + expect(responseMail.statusCode).toBe(200); + }); +}); diff --git a/packages/backend/src/routes/__tests__/hebergement/FU-getById.test.js b/packages/backend/src/routes/__tests__/hebergement/FU-getById.test.js new file mode 100644 index 000000000..5c99a8769 --- /dev/null +++ b/packages/backend/src/routes/__tests__/hebergement/FU-getById.test.js @@ -0,0 +1,61 @@ +const request = require("supertest"); +const app = require("../../../app"); // Chemin vers ton application Express +const Hebergement = require("../../../services/hebergement/Hebergement"); +const CheckJWT = require("../../../middlewares/checkJWT"); +const checkPermissionHebergement = require("../../../middlewares/checkPermissionHebergement"); + +// Mock des services et middlewares +jest.mock("../../../services/hebergement/Hebergement"); +jest.mock("../../../middlewares/checkJWT"); +jest.mock("../../../middlewares/checkPermissionHebergement"); +jest.mock( + "../../../middlewares/checkStatutHebergement", + () => () => (req, res, next) => { + next(); + }, +); +describe("GET /hebergement/:id", () => { + const user = { + id: 1, + role: "admin", + }; + + beforeEach(() => { + jest.clearAllMocks(); + + // Mock des middlewares + CheckJWT.mockImplementation((req, res, next) => { + req.decoded = { ...user }; + next(); + }); + checkPermissionHebergement.mockImplementation((req, res, next) => { + next(); + }); + }); + + it("devrait retourner un hébergement par ID avec succès", async () => { + const mockHebergement = { + id: "123", + }; + + // Mock de Hebergement.getById + Hebergement.getById.mockResolvedValue(mockHebergement); + + const response = await request(app).get("/hebergement/123"); + + // Vérification des résultats + expect(response.status).toBe(200); + expect(Hebergement.getById).toHaveBeenCalledWith("123"); + }); + + it("devrait retourner une erreur 400 si l'ID est manquant ou invalide", async () => { + CheckJWT.mockImplementationOnce((req, res, next) => { + req.decoded = { ...user }; + req.params = {}; + next(); + }); + + const response = await request(app).get("/hebergement/2"); + expect(response.status).toBe(400); + }); +}); diff --git a/packages/backend/src/routes/__tests__/hebergement/admin-getById.test.js b/packages/backend/src/routes/__tests__/hebergement/admin-getById.test.js new file mode 100644 index 000000000..3e124eee8 --- /dev/null +++ b/packages/backend/src/routes/__tests__/hebergement/admin-getById.test.js @@ -0,0 +1,61 @@ +const request = require("supertest"); +const app = require("../../../app"); // Chemin vers ton application Express +const Hebergement = require("../../../services/hebergement/Hebergement"); +const boCheckJWT = require("../../../middlewares/bo-check-JWT"); +const checkPermissionHebergement = require("../../../middlewares/checkPermissionHebergement"); + +// Mock des services et middlewares +jest.mock("../../../services/hebergement/Hebergement"); +jest.mock("../../../middlewares/bo-check-JWT"); +jest.mock("../../../middlewares/checkPermissionHebergement"); +jest.mock( + "../../../middlewares/checkStatutHebergement", + () => () => (req, res, next) => { + next(); + }, +); +describe("GET /hebergement/admin/:id", () => { + const user = { + id: 1, + role: "admin", + }; + + beforeEach(() => { + jest.clearAllMocks(); + + // Mock des middlewares + boCheckJWT.mockImplementation((req, res, next) => { + req.decoded = { ...user }; + next(); + }); + checkPermissionHebergement.mockImplementation((req, res, next) => { + next(); + }); + }); + + it("devrait retourner un hébergement par ID avec succès", async () => { + const mockHebergement = { + id: "123", + }; + + // Mock de Hebergement.getById + Hebergement.getById.mockResolvedValue(mockHebergement); + + const response = await request(app).get("/hebergement/admin/123"); + + // Vérification des résultats + expect(response.status).toBe(200); + expect(Hebergement.getById).toHaveBeenCalledWith("123"); + }); + + it("devrait retourner une erreur 400 si l'ID est manquant ou invalide", async () => { + boCheckJWT.mockImplementationOnce((req, res, next) => { + req.decoded = { ...user }; + req.params = {}; + next(); + }); + + const response = await request(app).get("/hebergement/admin/2"); + expect(response.status).toBe(400); + }); +}); diff --git a/packages/backend/src/routes/__tests__/hebergement/post.test.js b/packages/backend/src/routes/__tests__/hebergement/post.test.js new file mode 100644 index 000000000..021e3fd38 --- /dev/null +++ b/packages/backend/src/routes/__tests__/hebergement/post.test.js @@ -0,0 +1,114 @@ +const request = require("supertest"); +const app = require("../../../app"); +const Hebergement = require("../../../services/hebergement/Hebergement"); +const checkJWT = require("../../../middlewares/checkJWT"); +const checkPermissionHebergement = require("../../../middlewares/checkPermissionHebergement"); +const yup = require("yup"); +const FOUser = require("../../../services/FoUser"); + +// Mock de la méthode Hebergement.update +jest.mock("../../../services/hebergement/Hebergement"); +jest.mock("../../../middlewares/checkJWT"); +jest.mock("../../../middlewares/checkPermissionHebergement"); +jest.mock("../../../schemas/hebergement"); +jest.mock("../../../services/FoUser"); +jest.mock( + "../../../middlewares/checkStatutHebergement", + () => () => (req, res, next) => { + next(); + }, +); +describe("POST /", () => { + const user = { + id: 1, + }; + beforeEach(() => { + jest.clearAllMocks(); + checkJWT.mockImplementation((req, res, next) => { + req.decoded = { ...user }; + next(); + }); + checkPermissionHebergement.mockImplementation((req, res, next) => { + next(); + }); + }); + + it("should return 400 if a required parameter is missing", async () => { + const res = await request(app) + .post("/hebergement") + .send({ nom: "Hébergement" }) // Paramètre manquant + .expect(400); + expect(res.statusCode).toBe(400); + }); + + it("should return 200 if the hebergement is posted successfully", async () => { + Hebergement.create.mockResolvedValueOnce(true); + FOUser.getUserOrganisme.mockResolvedValueOnce(1); + + const body = { + coordonnees: { lat: 10, lon: 20 }, + informationsLocaux: {}, + informationsTransport: {}, + nom: "Hébergement 1", + }; + + jest.spyOn(yup, "object").mockImplementationOnce(() => ({ + validate: (parametre) => { + return parametre; + }, + })); + const res = await request(app).post("/hebergement").send(body); + + expect(res.status).toBe(200); + expect(Hebergement.create).toHaveBeenCalledWith( + 1, + 1, + "actif", + expect.objectContaining(body), + ); + }); + + it("should return 400 if validation fails with yup", async () => { + // Simule l'échec de la validation avec yup + jest.spyOn(yup, "object").mockImplementationOnce(() => ({ + validate: () => { + throw new Error("error"); + }, + })); + + const res = await request(app) + .post("/hebergement") + .send({ + coordonnees: {}, + informationsLocaux: "Info", + informationsTransport: "Info", + nom: "Hébergement", + }) + .expect(400); + + expect(res.status).toBe(400); + }); + + it("should return 404 if Hebergement.update throws an archive error", async () => { + const archiveError = new Error("Hebergement archived"); + archiveError.cause = "archive"; + Hebergement.update.mockRejectedValueOnce(archiveError); + + jest.spyOn(yup, "object").mockImplementationOnce(() => ({ + validate: (parametre) => { + return parametre; + }, + })); + + const res = await request(app) + .post("/hebergement/123") + .send({ + coordonnees: { lat: 10, lon: 20 }, + informationsLocaux: "Info locaux", + informationsTransport: "Info transport", + nom: "Hébergement", + }); + + expect(res.status).toBe(400); + }); +}); diff --git a/packages/backend/src/routes/__tests__/hebergement/update.test.js b/packages/backend/src/routes/__tests__/hebergement/update.test.js new file mode 100644 index 000000000..132b3c8d3 --- /dev/null +++ b/packages/backend/src/routes/__tests__/hebergement/update.test.js @@ -0,0 +1,111 @@ +const request = require("supertest"); +const app = require("../../../app"); +const Hebergement = require("../../../services/hebergement/Hebergement"); +const checkJWT = require("../../../middlewares/checkJWT"); +const checkPermissionHebergement = require("../../../middlewares/checkPermissionHebergement"); +const yup = require("yup"); + +// Mock de la méthode Hebergement.update +jest.mock("../../../services/hebergement/Hebergement"); +jest.mock("../../../middlewares/checkJWT"); +jest.mock("../../../middlewares/checkPermissionHebergement"); +jest.mock( + "../../../middlewares/checkStatutHebergement", + () => () => (req, res, next) => { + next(); + }, +); +jest.mock("../../../schemas/hebergement"); + +describe("POST /:id", () => { + const user = { + id: 1, + }; + beforeEach(() => { + jest.clearAllMocks(); + checkJWT.mockImplementation((req, res, next) => { + req.decoded = { ...user }; + next(); + }); + checkPermissionHebergement.mockImplementation((req, res, next) => { + next(); + }); + }); + + it("should return 400 if a required parameter is missing", async () => { + const res = await request(app) + .post("/hebergement/1") + .send({ nom: "Hébergement" }) // Paramètre manquant + .expect(400); + expect(res.statusCode).toBe(400); + }); + + it("should return 200 if the hebergement is updated successfully", async () => { + Hebergement.update.mockResolvedValueOnce(true); + + const body = { + coordonnees: { lat: 10, lon: 20 }, + informationsLocaux: {}, + informationsTransport: {}, + nom: "Hébergement 1", + }; + + jest.spyOn(yup, "object").mockImplementationOnce(() => ({ + validate: (parametre) => { + return parametre; + }, + })); + const res = await request(app).post("/hebergement/123").send(body); + + expect(res.status).toBe(200); + expect(Hebergement.update).toHaveBeenCalledWith( + 1, // userId + "123", // hebergementId + expect.objectContaining(body), + ); + }); + + it("should return 400 if validation fails with yup", async () => { + // Simule l'échec de la validation avec yup + jest.spyOn(yup, "object").mockImplementationOnce(() => ({ + validate: () => { + throw new Error("error"); + }, + })); + + const res = await request(app) + .post("/hebergement/123") + .send({ + coordonnees: {}, + informationsLocaux: "Info", + informationsTransport: "Info", + nom: "Hébergement", + }) + .expect(400); + + expect(res.status).toBe(400); + }); + + it("should return 404 if Hebergement.update throws an archive error", async () => { + const archiveError = new Error("Hebergement archived"); + archiveError.cause = "archive"; + Hebergement.update.mockRejectedValueOnce(archiveError); + + jest.spyOn(yup, "object").mockImplementationOnce(() => ({ + validate: (parametre) => { + return parametre; + }, + })); + + const res = await request(app) + .post("/hebergement/123") + .send({ + coordonnees: { lat: 10, lon: 20 }, + informationsLocaux: "Info locaux", + informationsTransport: "Info transport", + nom: "Hébergement", + }); + + expect(res.status).toBe(400); + }); +}); diff --git a/packages/backend/src/routes/eig.js b/packages/backend/src/routes/eig.js index 07ca83a85..ef61c86a9 100644 --- a/packages/backend/src/routes/eig.js +++ b/packages/backend/src/routes/eig.js @@ -4,6 +4,7 @@ const canUpdateEig = require("../middlewares/can-update-or-delete-eig"); const checkPermissionDeclarationSejourForEig = require("../middlewares/checkPermissionDeclarationSejourEig"); const checkPermissionDeclarationSejour = require("../middlewares/checkPermissionDeclarationSejour"); const checkPermissionEIG = require("../middlewares/checkPermissionEIG"); +const checkPermissionBOEIG = require("../middlewares/checkPermissionBOEIG"); const boCheckRole = require("../middlewares/bo-check-role"); const boCheckJWT = require("../middlewares/bo-check-JWT"); @@ -35,9 +36,16 @@ router.get( checkPermissionDeclarationSejour, eigController.getByDsId, ); +router.get("/available-ds", checkJWT, eigController.getAvailableDs); router.get("/admin", boCheckJWT, boCheckRoleEig, eigController.getAdmin); router.get("/:id", checkJWT, checkPermissionEIG, eigController.getById); -router.get("/admin/:id", boCheckJWT, boCheckRoleEig, eigController.getById); +router.get( + "/admin/:id", + boCheckJWT, + boCheckRoleEig, + checkPermissionBOEIG, + eigController.getById, +); router.post( "/", checkJWT, @@ -71,7 +79,6 @@ router.delete( router.post( "/admin/:id/mark-as-read", boCheckJWT, - getDepartements, boCheckRoleEig, eigController.markAsRead, ); diff --git a/packages/backend/src/routes/hebergement.js b/packages/backend/src/routes/hebergement.js index 544e53ed7..6378c68d4 100644 --- a/packages/backend/src/routes/hebergement.js +++ b/packages/backend/src/routes/hebergement.js @@ -5,8 +5,10 @@ const router = express.Router(); const checkJWT = require("../middlewares/checkJWT"); const boCheckJWT = require("../middlewares/bo-check-JWT"); const checkPermissionHebergement = require("../middlewares/checkPermissionHebergement"); +const checkStatutHebergement = require("../middlewares/checkStatutHebergement"); const getDepartements = require("../middlewares/getDepartements"); const hebergementController = require("../controllers/hebergement"); +const HebergementHelper = require("../helpers/hebergement"); router.get( "/admin/", @@ -21,20 +23,38 @@ router.get( getDepartements, hebergementController.getExtract, ); - -// Gère une connexion via mot de passe. router.get( "/:id", checkJWT, checkPermissionHebergement, hebergementController.getById, ); -router.get("/admin/:id", boCheckJWT, hebergementController.getById); +router.get( + "/admin/:id", + boCheckJWT, + checkStatutHebergement(HebergementHelper.statuts.ACTIF), + hebergementController.getById, +); +router.get("/siren/:siren", checkJWT, hebergementController.getBySiren); router.get("/", checkJWT, hebergementController.get); router.post("/", checkJWT, hebergementController.post); +router.post("/brouillon", checkJWT, hebergementController.postBrouillon); +router.put( + "/:id/brouillon", + checkJWT, + checkStatutHebergement(HebergementHelper.statuts.BROUILLON), + hebergementController.updateBrouillon, +); +router.put( + "/:id/activate", + checkJWT, + checkStatutHebergement(HebergementHelper.statuts.BROUILLON), + hebergementController.activate, +); router.post( "/:id", checkJWT, + checkStatutHebergement(HebergementHelper.statuts.ACTIF), checkPermissionHebergement, hebergementController.update, ); diff --git a/packages/backend/src/routes/sejour.js b/packages/backend/src/routes/sejour.js index 5e813d4e2..0c7250c0c 100644 --- a/packages/backend/src/routes/sejour.js +++ b/packages/backend/src/routes/sejour.js @@ -47,13 +47,6 @@ router.get( getDepartements, demandeSejourController.getHebergementsByDepartementCodes, ); -router.get( - "/admin/hebergement/:sejourId/:hebergementId", - boCheckJWT, - boCheckRoleDS, - getDepartements, - demandeSejourController.getHebergement, -); router.get( "/admin/historique/:declarationId", boCheckJWT, diff --git a/packages/backend/src/routes/territoire.js b/packages/backend/src/routes/territoire.js index 3a909bd7e..4ea694314 100644 --- a/packages/backend/src/routes/territoire.js +++ b/packages/backend/src/routes/territoire.js @@ -7,6 +7,11 @@ const BOcheckJWT = require("../middlewares/bo-check-JWT"); router.get("/list", BOcheckJWT, territoireController.list); router.get("/get-one/:idTerritoire", BOcheckJWT, territoireController.getOne); +router.get( + "/get-fiche-id-by-ter-code/:territoireCode", + BOcheckJWT, + territoireController.getFicheIdByTerCode, +); router.put("/:id", BOcheckJWT, territoireController.update); module.exports = router; diff --git a/packages/backend/src/schemas/__tests__/eig.js b/packages/backend/src/schemas/__tests__/eig.js new file mode 100644 index 000000000..297770262 --- /dev/null +++ b/packages/backend/src/schemas/__tests__/eig.js @@ -0,0 +1,203 @@ +const yup = require("yup"); +const { + selectionSejourSchema, + eigTypesSchema, + informationsGeneralesSchema, + emailAutresDestinatairesSchema, +} = require("../eig"); +const { Types, Categorie } = require("../../helpers/eig"); +const { + fonctionOptions, +} = require("../../helpers/declaration/informations-personnel"); + +describe("YUP - eig", () => { + test("selectionSejourSchema", async () => { + const eig1 = { + date: new Date("2021-01-02"), + declarationId: 1, + departement: "92", + }; + const eig2 = { + date: new Date("2021-01-02"), + departement: "92", + }; + const eig3 = { + date: new Date("2021-01-02"), + declarationId: 1, + }; + const eig4 = { + declarationId: 1, + departement: "92", + }; + + const schema = yup.object( + selectionSejourSchema("2021-01-01", "2021-01-10"), + ); + expect(await schema.validate(eig1)).toEqual(eig1); + await expect(() => schema.validate(eig2)).rejects.toThrow( + "Ce champ est obligatoire", + ); + await expect(() => schema.validate(eig3)).rejects.toThrow( + "Ce champ est obligatoire", + ); + await expect(() => schema.validate(eig4)).rejects.toThrow( + "Ce champ est obligatoire", + ); + await expect(() => + schema.validate({ ...eig4, date: "bad date" }), + ).rejects.toThrow("La date n'est pas au format attendu"); + await expect(() => + schema.validate({ ...eig4, date: "2020-12-31" }), + ).rejects.toThrow( + "La date de l'incident doit être supérieure à la date de début de séjour", + ); + await expect(() => + schema.validate({ ...eig4, date: "2021-01-11" }), + ).rejects.toThrow( + "La date de l'incident doit être inférieure à la date de fin de séjour", + ); + }); + + test("eigTypesSchemaCRUD", async () => { + const eig1 = { + types: ["bad type"], + }; + const eig2 = { + types: [Types[Categorie.FONCTIONNEMENT_ORGANISME].AUTRE], + }; + const eig3 = { + types: [Types[Categorie.SANTE].AUTRE], + }; + const eig4 = { + types: [Types[Categorie.SECURITE].AUTRE], + }; + const eig5 = { + types: [Types[Categorie.VICTIMES].AUTRE], + }; + + const schema = yup.object(eigTypesSchema); + + await expect(() => schema.validate(eig1)).rejects.toThrow( + "La valeur insérée ne fait pas partie de la liste des possibles", + ); + + // champs "autre" pour la catégorie FONCTIONNEMENT_ORGANISME + await expect(() => schema.validate(eig2)).rejects.toThrow( + "Ce champ est obligatoire", + ); + await expect(() => + schema.validate({ ...eig2, fonctionnementAutrePrecision: "aaaa" }), + ).rejects.toThrow("Ce champ doit faire au moins 5 caractères"); + expect( + await schema.validate({ ...eig2, fonctionnementAutrePrecision: "aaaaa" }), + ).toEqual({ ...eig2, fonctionnementAutrePrecision: "aaaaa" }); + + // champs "autre" pour la catégorie SANTE + await expect(() => schema.validate(eig3)).rejects.toThrow( + "Ce champ est obligatoire", + ); + await expect(() => + schema.validate({ ...eig3, santeAutrePrecision: "aaaa" }), + ).rejects.toThrow("Ce champ doit faire au moins 5 caractères"); + expect( + await schema.validate({ ...eig3, santeAutrePrecision: "aaaaa" }), + ).toEqual({ ...eig3, santeAutrePrecision: "aaaaa" }); + + // champs "autre" pour la catégorie SECURITE + await expect(() => schema.validate(eig4)).rejects.toThrow( + "Ce champ est obligatoire", + ); + await expect(() => + schema.validate({ ...eig4, securiteAutrePrecision: "aaaa" }), + ).rejects.toThrow("Ce champ doit faire au moins 5 caractères"); + expect( + await schema.validate({ ...eig4, securiteAutrePrecision: "aaaaa" }), + ).toEqual({ ...eig4, securiteAutrePrecision: "aaaaa" }); + + // champs "autre" pour la catégorie VICTIMES + await expect(() => schema.validate(eig5)).rejects.toThrow( + "Ce champ est obligatoire", + ); + await expect(() => + schema.validate({ ...eig5, victimesAutrePrecision: "aaaa" }), + ).rejects.toThrow("Ce champ doit faire au moins 5 caractères"); + expect( + await schema.validate({ ...eig5, victimesAutrePrecision: "aaaaa" }), + ).toEqual({ ...eig5, victimesAutrePrecision: "aaaaa" }); + }); + + test("informationsGeneralesSchema", async () => { + const personnel = [ + { + competence: "eeee", + dateNaissance: new Date("1986-05-14"), + listeFonction: [fonctionOptions[0].value], + nom: "nom", + prenom: "prenom", + telephone: "0666666666", + }, + ]; + + const eig = { + deroulement: "aaaaa", + dispositionInformations: "bbbbb", + dispositionRemediation: "ccccc", + dispositionVictimes: "ddddd", + personnel, + }; + + const schema = yup.object(informationsGeneralesSchema); + expect(await schema.validate(eig)).toEqual(eig); + + await expect(() => + schema.validate({ ...eig, deroulement: "a" }), + ).rejects.toThrow("Ce champ doit faire au moins 5 caractères"); + await expect(() => + schema.validate({ ...eig, deroulement: undefined }), + ).rejects.toThrow("Ce champ est obligatoire"); + + await expect(() => + schema.validate({ ...eig, dispositionInformations: "a" }), + ).rejects.toThrow("Ce champ doit faire au moins 5 caractères"); + await expect(() => + schema.validate({ ...eig, dispositionInformations: undefined }), + ).rejects.toThrow("Ce champ est obligatoire"); + + await expect(() => + schema.validate({ ...eig, dispositionRemediation: "a" }), + ).rejects.toThrow("Ce champ doit faire au moins 5 caractères"); + await expect(() => + schema.validate({ ...eig, dispositionRemediation: undefined }), + ).rejects.toThrow("Ce champ est obligatoire"); + + await expect(() => + schema.validate({ ...eig, dispositionVictimes: "a" }), + ).rejects.toThrow("Ce champ doit faire au moins 5 caractères"); + await expect(() => + schema.validate({ ...eig, dispositionVictimes: undefined }), + ).rejects.toThrow("Ce champ est obligatoire"); + + await expect(() => + schema.validate({ ...eig, personnel: [] }), + ).rejects.toThrow("Vous devez saisir au moins 1 personnel"); + await expect(() => + schema.validate({ ...eig, personnel: undefined }), + ).rejects.toThrow("Ce champ est obligatoire"); + }); + + test("emailAutresDestinatairesSchema", async () => { + const eig1 = { + emailAutresDestinataires: ["a@a.com", "b@b.fr"], + }; + const eig2 = { + emailAutresDestinataires: ["a@"], + }; + + const schema = yup.object(emailAutresDestinatairesSchema); + + expect(await schema.validate(eig1)).toEqual(eig1); + await expect(() => schema.validate(eig2)).rejects.toThrow( + "Format de courriel invalide", + ); + }); +}); diff --git a/packages/backend/src/schemas/eig.js b/packages/backend/src/schemas/eig.js index ede7c5b17..2e82a5651 100644 --- a/packages/backend/src/schemas/eig.js +++ b/packages/backend/src/schemas/eig.js @@ -1,25 +1,50 @@ const yup = require("yup"); -const { UpdateTypes, Types, Categorie } = require("../helpers/eig"); +const { + UpdateTypes, + Types, + Categorie, + isTypeActive, +} = require("../helpers/eig"); const personne = require("./parts/personne.js"); -const selectionSejourSchema = { +const selectionSejourSchema = (dateDebut, dateFin) => ({ + date: yup + .date() + .typeError("La date n'est pas au format attendu") + .min( + dateDebut, + "La date de l'incident doit être supérieure à la date de début de séjour", + ) + .max( + dateFin, + "La date de l'incident doit être inférieure à la date de fin de séjour", + ) + .required("Ce champ est obligatoire"), declarationId: yup .number() .integer("Ce champ doit contenir un nombre entier") - .required(), - departement: yup.string().required("ce champ est obligatoire"), -}; + .required("Ce champ est obligatoire"), + departement: yup.string().required("Ce champ est obligatoire"), +}); const eigTypeBase = yup .string() .oneOf( [ - ...Object.values(Types[Categorie.VICTIMES]), - ...Object.values(Types[Categorie.SECURITE]), - ...Object.values(Types[Categorie.SANTE]), - ...Object.values(Types[Categorie.FONCTIONNEMENT_ORGANISME]), + ...Object.values(Types[Categorie.VICTIMES]).filter((type) => + isTypeActive(type), + ), + ...Object.values(Types[Categorie.SECURITE]).filter((type) => + isTypeActive(type), + ), + ...Object.values(Types[Categorie.SANTE]).filter((type) => + isTypeActive(type), + ), + ...Object.values(Types[Categorie.FONCTIONNEMENT_ORGANISME]).filter( + (type) => isTypeActive(type), + ), ], - "la valeur insérée ne fait pas partie de la liste des possibles", + "La valeur insérée ne fait pas partie de la liste des possibles", ) .required(); @@ -71,7 +96,9 @@ const eigTypesSchemaCRUD = { }, otherwise: (precision) => precision.nullable().strip(), then: (precision) => - precision.min(5, "Ce champ est obligatoire").required(), + precision + .required("Ce champ est obligatoire") + .min(5, "Ce champ doit faire au moins 5 caractères"), }), santeAutrePrecision: yup .string() @@ -82,7 +109,9 @@ const eigTypesSchemaCRUD = { }, otherwise: (precision) => precision.nullable().strip(), then: (precision) => - precision.min(5, "Ce champ est obligatoire").required(), + precision + .required("Ce champ est obligatoire") + .min(5, "Ce champ doit faire au moins 5 caractères"), }), securiteAutrePrecision: yup .string() @@ -93,9 +122,15 @@ const eigTypesSchemaCRUD = { }, otherwise: (precision) => precision.nullable().strip(), then: (precision) => - precision.min(5, "Ce champ est obligatoire").required(), + precision + .required("Ce champ est obligatoire") + .min(5, "Ce champ doit faire au moins 5 caractères"), }), - types: yup.array().of(eigTypeBase).min(1).required(), + types: yup + .array() + .of(eigTypeBase) + .min(1) + .required("Ce champ est obligatoire"), victimesAutrePrecision: yup .string() .nullable() @@ -105,24 +140,29 @@ const eigTypesSchemaCRUD = { }, otherwise: (precision) => precision.nullable().strip(), then: (precision) => - precision.min(5, "Ce champ est obligatoire").required(), + precision + .required("Ce champ est obligatoire") + .min(5, "Ce champ doit faire au moins 5 caractères"), }), }; const informationsGeneralesSchema = { - deroulement: yup.string().min(5, "Ce champ est obligatoire").required(), + deroulement: yup + .string() + .min(5, "Ce champ doit faire au moins 5 caractères") + .required("Ce champ est obligatoire"), dispositionInformations: yup .string() - .min(5, "Ce champ est obligatoire") - .required(), + .min(5, "Ce champ doit faire au moins 5 caractères") + .required("Ce champ est obligatoire"), dispositionRemediation: yup .string() - .min(5, "Ce champ est obligatoire") - .required(), + .min(5, "Ce champ doit faire au moins 5 caractères") + .required("Ce champ est obligatoire"), dispositionVictimes: yup .string() - .min(5, "Ce champ est obligatoire") - .required(), + .min(5, "Ce champ doit faire au moins 5 caractères") + .required("Ce champ est obligatoire"), personnel: yup .array() .of( @@ -140,7 +180,7 @@ const informationsGeneralesSchema = { ), ) .min(1, "Vous devez saisir au moins 1 personnel") - .required(), + .required("Ce champ est obligatoire"), }; const emailAutresDestinatairesSchema = { @@ -149,22 +189,23 @@ const emailAutresDestinatairesSchema = { .of(yup.string().email("Format de courriel invalide").nullable()), }; -const syntheseSchema = { - ...selectionSejourSchema, +const syntheseSchema = (dateDebut, dateFin) => ({ + ...selectionSejourSchema(dateDebut, dateFin), ...eigTypesDepose, ...informationsGeneralesSchema, ...emailAutresDestinatairesSchema, -}; +}); module.exports.selectionSejourSchema = selectionSejourSchema; module.exports.eigTypesSchema = eigTypesSchemaCRUD; module.exports.informationsGeneralesSchema = informationsGeneralesSchema; module.exports.syntheseSchema = syntheseSchema; +module.exports.emailAutresDestinatairesSchema = emailAutresDestinatairesSchema; -module.exports.updateSchemaAdapteur = (type) => { +module.exports.updateSchemaAdapteur = (type, dateRange) => { switch (type) { case UpdateTypes.DECLARATION_SEJOUR: - return selectionSejourSchema; + return selectionSejourSchema(dateRange[0], dateRange[1]); case UpdateTypes.TYPE_EVENEMENT: return eigTypesSchemaCRUD; case UpdateTypes.RENSEIGNEMENT_GENERAUX: diff --git a/packages/backend/src/schemas/hebergement.js b/packages/backend/src/schemas/hebergement.js index e3be65f82..298393fef 100644 --- a/packages/backend/src/schemas/hebergement.js +++ b/packages/backend/src/schemas/hebergement.js @@ -3,18 +3,30 @@ const yup = require("yup"); const telephoneSchema = require("./parts/telephone"); const adresseSchema = require("./parts/adresse.js")({ isFromAPIAdresse: true }); -const coordonneesSchema = () => ({ - adresse: yup.object(adresseSchema).required(), +const coordonneesSchema = (isBrouillon = false) => ({ + adresse: yup.object(adresseSchema).when([], { + is: () => isBrouillon, + then: (schema) => schema.notRequired(), + }), email: yup.string().email("Format de l'adresse courriel invalide").nullable(), - nomGestionnaire: yup.string().required(), - numTelephone1: telephoneSchema(), + nomGestionnaire: yup.string().when([], { + is: () => isBrouillon, + then: (schema) => schema.notRequired(), + }), + numTelephone1: telephoneSchema().when([], { + is: () => isBrouillon, + then: (schema) => schema.notRequired(), + }), numTelephone2: telephoneSchema().notRequired(), }); -const informationsLocauxSchema = () => ({ - accessibilite: yup - .string() - .required("Le choix d'un niveau d'accessibilté est obligatoire"), +const informationsLocauxSchema = (isBrouillon = false) => ({ + accessibilite: yup.string().when([], { + is: () => isBrouillon, + then: (schema) => schema.notRequired(), + else: (schema) => + schema.required("Le choix d'un niveau d'accessibilté est obligatoire"), + }), accessibilitePrecision: yup .string() .nullable() @@ -24,21 +36,44 @@ const informationsLocauxSchema = () => ({ }, then: (schema) => schema.strip(), }), - amenagementsSpecifiques: yup.boolean().required(), - chambresDoubles: yup - .boolean() - .required( - "Il est impératif de renseigner si les couples sont dans des chambres séparés", - ), - chambresUnisexes: yup - .boolean() - .required("Il est impératif de renseigner si les chambres sont unisexes"), - couchageIndividuel: yup - .boolean() - .required("Il est impératif de renseigner l'individualité des couchages'"), - descriptionLieuHebergement: yup - .string() - .required("Une description du lieu d'hébergement est obligatoire"), + amenagementsSpecifiques: yup.boolean().when([], { + is: () => isBrouillon, + then: (schema) => schema.notRequired(), + else: (schema) => + schema.required( + "Il est impératif de renseigner si les aménagements spécifiques", + ), + }), + chambresDoubles: yup.boolean().when([], { + is: () => isBrouillon, + then: (schema) => schema.notRequired(), + else: (schema) => + schema.required( + "Il est impératif de renseigner si les couples sont dans des chambres séparés", + ), + }), + chambresUnisexes: yup.boolean().when([], { + is: () => isBrouillon, + then: (schema) => schema.notRequired(), + else: (schema) => + schema.required( + "Il est impératif de renseigner si les chambres sont unisexes", + ), + }), + couchageIndividuel: yup.boolean().when([], { + is: () => isBrouillon, + then: (schema) => schema.notRequired(), + else: (schema) => + schema.required( + "Il est impératif de renseigner l'individualité des couchages", + ), + }), + descriptionLieuHebergement: yup.string().when([], { + is: () => isBrouillon, + then: (schema) => schema.notRequired(), + else: (schema) => + schema.required("Une description du lieu d'hébergement est obligatoire"), + }), fileDernierArreteAutorisationMaire: yup.mixed().when("reglementationErp", { is: true, otherwise: (schema) => schema.nullable().strip(), @@ -48,7 +83,8 @@ const informationsLocauxSchema = () => ({ test: (fileDernierArreteAutorisationMaire, context) => { return ( !!fileDernierArreteAutorisationMaire || - !!context.parent.fileDerniereAttestationSecurite + !!context.parent.fileDerniereAttestationSecurite || + isBrouillon ); }, }), @@ -63,7 +99,8 @@ const informationsLocauxSchema = () => ({ test: (fileDerniereAttestationSecurite, context) => { return ( !!fileDerniereAttestationSecurite || - !!context.parent.fileDernierArreteAutorisationMaire + !!context.parent.fileDernierArreteAutorisationMaire || + isBrouillon ); }, }), @@ -72,61 +109,102 @@ const informationsLocauxSchema = () => ({ is: false, otherwise: (schema) => schema.nullable().strip(), then: (schema) => - schema.required( - "Il est impératif de télécharger la réponse de l'exploitant ou du propriétaire", - ), + schema.when([], { + is: () => isBrouillon, + then: (schema) => schema.notRequired(), + else: (schema) => + schema.required( + "Il est impératif de télécharger la réponse de l'exploitant ou du propriétaire", + ), + }), }), litsDessus: yup.boolean().when("nombreLitsSuperposes", { is: (nombreLitsSuperposes) => !!nombreLitsSuperposes, otherwise: (schema) => schema.nullable().strip(), then: (schema) => + schema.when([], { + is: () => isBrouillon, + then: (schema) => schema.notRequired(), + else: (schema) => + schema.required( + "Il est nécessaire de renseigner si les lits du dessus seront utilisés", + ), + }), + }), + nombreLits: yup.number().when([], { + is: () => isBrouillon, + then: (schema) => schema.notRequired(), + else: (schema) => + schema.required("Il est impératif de renseigner le nombre de lits"), + }), + nombreLitsSuperposes: yup.number().nullable(), + nombreMaxPersonnesCouchage: yup.number().when([], { + is: () => isBrouillon, + then: (schema) => schema.notRequired(), + else: (schema) => schema.required( - "Il est nécessaire de renseigner si les lits du dessus seront utilisés", + "Il est impératif de renseigner le nombre de maximal de personnes par espace de couchage", ), }), - nombreLits: yup - .number() - .required("Il est impératif de renseigner le nombre de lits"), - nombreLitsSuperposes: yup.number().nullable(), - nombreMaxPersonnesCouchage: yup - .number() - .required( - "Il est impératif de renseigner le nombre de maximal de personnes par espace de couchage", - ), - pension: yup - .string() - .required("Le choix d'un type de pension est obligatoire"), + pension: yup.string().when([], { + is: () => isBrouillon, + then: (schema) => schema.notRequired(), + else: (schema) => + schema.required("Le choix d'un type de pension est obligatoire"), + }), precisionAmenagementsSpecifiques: yup .string() .when("amenagementsSpecifiques", { is: (amenagementsSpecifiques) => !!amenagementsSpecifiques, otherwise: (schema) => schema.nullable().strip(), then: (schema) => - schema - .min( - 1, - "Il est impératif de préciser ce que les aménagements ont de spécifiques", - ) - .required(), + schema.when([], { + is: () => isBrouillon, + then: (schema) => schema.notRequired(), + else: (schema) => + schema + .min( + 1, + "Il est impératif de préciser ce que les aménagements ont de spécifiques", + ) + .required(), + }), }), - prestationsHotelieres: yup.array().required(), - rangementIndividuel: yup - .boolean() - .required( - "Il est impératif de renseigner si des rangements individuels sont à disposition", - ), - - reglementationErp: yup - .boolean() - .required( - "Il est impératif de renseigner si vous le local est soumis à la réglementation ERP (établissement recevant du public)", - ), - type: yup - .string() - .required("Il est impératif de renseigner le type d'hébergement"), - visiteLocaux: yup - .boolean() - .required("Il est impératif de renseigner si vous avez visité les locaux"), + prestationsHotelieres: yup.array().when([], { + is: () => isBrouillon, + then: (schema) => schema.notRequired(), + else: (schema) => schema.required(), + }), + rangementIndividuel: yup.boolean().when([], { + is: () => isBrouillon, + then: (schema) => schema.notRequired(), + else: (schema) => + schema.required( + "Il est impératif de renseigner si des rangements individuels sont à disposition", + ), + }), + reglementationErp: yup.boolean().when([], { + is: () => isBrouillon, + then: (schema) => schema.notRequired(), + else: (schema) => + schema.required( + "Il est impératif de renseigner si vous le local est soumis à la réglementation ERP (établissement recevant du public)", + ), + }), + type: yup.string().when([], { + is: () => isBrouillon, + then: (schema) => schema.notRequired(), + else: (schema) => + schema.required("Il est impératif de renseigner le type d'hébergement)"), + }), + visiteLocaux: yup.boolean().when([], { + is: () => isBrouillon, + then: (schema) => schema.notRequired(), + else: (schema) => + schema.required( + "Il est impératif de renseigner si vous avez visité les locaux)", + ), + }), visiteLocauxAt: yup .date("Vous devez saisir une date valide au format JJ/MM/AAAA") .typeError("date invalide") @@ -134,36 +212,57 @@ const informationsLocauxSchema = () => ({ is: (visiteLocaux) => !!visiteLocaux, otherwise: (schema) => schema.nullable().strip(), then: (schema) => - schema - .max(new Date(), "La date doit être inférieure à la date du jour.") - .nullable(), + schema.when([], { + is: () => isBrouillon, + then: (schema) => schema.notRequired(), + else: (schema) => + schema + .max( + new Date(), + "La date doit être inférieure à la date du jour.", + ) + .nullable(), + }), }), }); -const informationsTransportSchema = () => ({ - deplacementProximite: yup - .string() - .min( - 1, - "Il est impératif de préciser le mode de transport utilisé pour les déplacements à proximité", - ) - .required(), - excursion: yup - .string() - .min( - 1, - "Il est impératif de préciser le mode de transport utilisé pour les excursions", - ) - .required(), - vehiculesAdaptes: yup - .boolean() - .required("Il est impératif de renseigner si les véhicules sont adaptés"), +const informationsTransportSchema = (isBrouillon = false) => ({ + deplacementProximite: yup.string().when([], { + is: () => isBrouillon, + then: (schema) => schema.notRequired(), + else: (schema) => + schema + .min( + 1, + "Il est impératif de préciser le mode de transport utilisé pour les déplacements à proximité", + ) + .required(), + }), + excursion: yup.string().when([], { + is: () => isBrouillon, + then: (schema) => schema.notRequired(), + else: (schema) => + schema + .min( + 1, + "Il est impératif de préciser le mode de transport utilisé pour les excursions", + ) + .required(), + }), + vehiculesAdaptes: yup.boolean().when([], { + is: () => isBrouillon, + then: (schema) => schema.notRequired(), + else: (schema) => + schema.required( + "Il est impératif de renseigner si les véhicules sont adaptés", + ), + }), }); -const schema = () => ({ - coordonnees: yup.object(coordonneesSchema()), - informationsLocaux: yup.object(informationsLocauxSchema()), - informationsTransport: yup.object(informationsTransportSchema()), +const schema = (isBrouillon = false) => ({ + coordonnees: yup.object(coordonneesSchema(isBrouillon)), + informationsLocaux: yup.object(informationsLocauxSchema(isBrouillon)), + informationsTransport: yup.object(informationsTransportSchema(isBrouillon)), nom: yup.string().required(), }); diff --git a/packages/backend/src/services/DemandeSejour.js b/packages/backend/src/services/DemandeSejour.js index b360395ae..76589a58e 100644 --- a/packages/backend/src/services/DemandeSejour.js +++ b/packages/backend/src/services/DemandeSejour.js @@ -4,6 +4,9 @@ const logger = require("../utils/logger"); const { applyFilters, applyPagination } = require("../helpers/queryParams"); const pool = require("../utils/pgpool").getPool(); const dsStatus = require("../helpers/ds-statuts"); +const { + getByDSId: getHebergementsByDSIds, +} = require("./hebergement/Hebergement"); const { sanityzePaginationParams, sanityzeFiltersParams, @@ -11,10 +14,6 @@ const { const log = logger(module.filename); -const getDepartementWhereQuery = (departementCodes, params) => { - return `jsonb_path_query_array(hebergement, '$.hebergements[*].coordonnees.adresse.departement') ?| ($${params.length})::text[]`; -}; - /* see: https://day.js.org/docs/en/display/difference if dateDebut = 2021-12-01 and dateFin = 2012-12-03, dayjs(dateFin).diff(dateDebut, "day") will return 2 because it does a diff between 2021-12-01 00:00:00 and 2021-12-03 00:00:00. It would be the same with @@ -60,6 +59,8 @@ const query = { projet_sejour, sanitaires, files, + sejourEtranger, + sejourItinerant, ) => [ ` INSERT INTO front.demande_sejour( @@ -78,9 +79,11 @@ const query = { transport, projet_sejour, sanitaires, - files + files, + sejour_etranger, + sejour_itinerant ) - VALUES ('BROUILLON',$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15) + VALUES ('BROUILLON',$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17) RETURNING id as "declarationId" ;`, @@ -100,6 +103,8 @@ const query = { projet_sejour, sanitaires, files, + sejourEtranger, + sejourItinerant, ], ], create: ( @@ -227,23 +232,43 @@ RETURNING [declarationId, vacanciers, personnel, hebergement, attestation], ], get: () => - // $1 organisationIds `SELECT - ds.id as "declarationId", - ds.statut as "statut", - ds.id_fonctionnelle as "idFonctionnelle", - ds.departement_suivi as "departementSuivi", - ds.libelle as "libelle", - o.personne_morale->>'siret' as "siret", - ds.date_debut as "dateDebut", - ds.date_fin as "dateFin", - ds.periode as "periode", - ds.edited_at as "editedAt" - FROM front.demande_sejour ds - JOIN front.organismes o ON o.id = ds.organisme_id - WHERE - o.id = ANY ($1) - `, + ds.id as "declarationId", + ds.statut as "statut", + ds.id_fonctionnelle as "idFonctionnelle", + ds.departement_suivi as "departementSuivi", + ds.organisme_id as "organismeId", + ds.libelle as "libelle", + ds.periode as "periode", + ds.date_debut::text as "dateDebut", + ds.date_fin::text as "dateFin", + ds.created_at as "createdAt", + ds.edited_at as "editedAt", + o.personne_morale->>'siret' as "siret", + o.personne_morale->'etablissementPrincipal' as "organismeAgree", + dsm.message as "message", + CASE + WHEN (dsm.read_at IS NULL AND dsm.front_user_id IS NULL) THEN 'NON LU' + WHEN (dsm.read_at IS NOT NULL AND dsm.front_user_id IS NULL) THEN 'LU' + WHEN (dsm.back_user_id IS NULL) THEN 'REPONDU' + END AS "messageEtat", + CASE + WHEN (dsm.read_at IS NULL AND dsm.front_user_id IS NULL) THEN 1 -- NON LU + WHEN (dsm.read_at IS NOT NULL AND dsm.front_user_id IS NULL) THEN 2 -- LU + WHEN (dsm.back_user_id IS NULL) THEN 3 -- REPONDU + END AS "messageOrdreEtat", + dsm.read_at AS "messageReadAt", + dsm.created_at AS "messageCreatedAt", + COALESCE(dsm.read_at, dsm.created_at) AS "messageLastAt" +FROM front.demande_sejour ds +JOIN front.organismes o ON o.id = ds.organisme_id +LEFT JOIN front.demande_sejour_message dsm ON dsm.declaration_id = ds.id AND dsm.id = ( + SELECT MAX(dsmax.id) + FROM front.demande_sejour_message dsmax + WHERE dsmax.declaration_id = ds.id) +WHERE + o.id = ANY ($1) +`, getAdminStats: (departements, territoireCode) => [ ` SELECT @@ -271,12 +296,22 @@ FROM FROM front.demande_sejour_message dsmax WHERE dsmax.declaration_id = ds.id) WHERE - jsonb_path_query_array(hebergement, '$.hebergements[*].coordonnees.adresse.departement') ?| ($1)::text[] - OR a.region_obtention = '${territoireCode}' + EXISTS ( + SELECT + 1 + FROM + FRONT.DEMANDE_SEJOUR_TO_HEBERGEMENT DSTH + LEFT JOIN FRONT.HEBERGEMENT H ON H.ID = DSTH.HEBERGEMENT_ID + LEFT JOIN FRONT.ADRESSE A ON A.ID = H.ADRESSE_ID + WHERE + DSTH.DEMANDE_SEJOUR_ID = DS.ID + AND A.DEPARTEMENT = ANY ($1) + ) + OR A.REGION_OBTENTION = '${territoireCode}' `, [departements], ], - getByDepartementCodes: (search, territoireCode, departementQuery, params) => { + getByDepartementCodes: (search, territoireCode) => { return ` SELECT ds.id as "declarationId", @@ -292,7 +327,17 @@ SELECT o.personne_morale as "personneMorale", o.personne_physique as "personnePhysique", o.type_organisme as "typeOrganisme", - ds.hebergement #>> '{hebergements, 0, coordonnees, adresse, departement}' = ANY ($${params.length}) as "estInstructeurPrincipal", + ( + SELECT + DEPARTEMENT = ANY ($1) + FROM + FRONT.DEMANDE_SEJOUR_TO_HEBERGEMENT DSTH + LEFT JOIN FRONT.HEBERGEMENT H ON H.ID = DSTH.HEBERGEMENT_ID + LEFT JOIN FRONT.ADRESSE A ON A.ID = H.ADRESSE_ID + WHERE DSTH.DEMANDE_SEJOUR_ID = DS.ID + ORDER BY DSTH.DATE_DEBUT + LIMIT 1 + ) as "estInstructeurPrincipal", dsm.message as "message", CASE WHEN (dsm.read_at IS NULL AND dsm.back_user_id IS NULL) THEN 'NON LU' @@ -316,12 +361,24 @@ FROM front.demande_sejour ds WHERE dsmax.declaration_id = ds.id) WHERE statut <> 'BROUILLON' - AND ((${departementQuery}) - OR a.region_obtention = '${territoireCode}') + AND ( + EXISTS ( + SELECT + 1 + FROM + FRONT.DEMANDE_SEJOUR_TO_HEBERGEMENT DSTH + LEFT JOIN FRONT.HEBERGEMENT H ON H.ID = DSTH.HEBERGEMENT_ID + LEFT JOIN FRONT.ADRESSE A ON A.ID = H.ADRESSE_ID + WHERE + DSTH.DEMANDE_SEJOUR_ID = DS.ID + AND A.DEPARTEMENT = ANY ($1) + ) + OR + a.region_obtention = '${territoireCode}') ${search.map((s) => ` AND ${s} `).join("")} `; }, - getByDepartementCodesTotal: (search, territoireCode, departementQuery) => { + getByDepartementCodesTotal: (search, territoireCode) => { return ` SELECT COUNT(DISTINCT ds.id) FROM front.demande_sejour ds @@ -330,8 +387,20 @@ LEFT JOIN front.agrements a ON a.organisme_id = ds.organisme_id LEFT JOIN front.demande_sejour_message dsm ON dsm.declaration_id = ds.id WHERE statut <> 'BROUILLON' - AND ((${departementQuery}) - OR a.region_obtention = '${territoireCode}') + AND ( + EXISTS ( + SELECT + 1 + FROM + FRONT.DEMANDE_SEJOUR_TO_HEBERGEMENT DSTH + LEFT JOIN FRONT.HEBERGEMENT H ON H.ID = DSTH.HEBERGEMENT_ID + LEFT JOIN FRONT.ADRESSE A ON A.ID = H.ADRESSE_ID + WHERE + DSTH.DEMANDE_SEJOUR_ID = DS.ID + AND A.DEPARTEMENT = ANY ($1) + ) + OR + a.region_obtention = '${territoireCode}') ${search.map((s) => ` AND ${s} `).join("")} `; }, @@ -356,7 +425,8 @@ WHERE COALESCE(ds.projet_sejour, '{}'::jsonb) as "projetSejour", ds.sanitaires as "informationsSanitaires", ds.attestation, - ds.hebergement as "hebergement", + sejour_etranger as "sejourEtranger", + sejour_itinerant as "sejourItinerant", ds.organisme as "organisme", o.personne_morale as "personneMorale", o.personne_physique as "personnePhysique", @@ -372,6 +442,16 @@ WHERE [declarationId, departements], ]; }, + getByIdOrUserSiren: ` + SELECT distinct(ds.id) + FROM + front.demande_sejour ds + JOIN front.user_organisme uo ON uo.org_id = ds.organisme_id + JOIN front.users u ON uo.use_id = u.id + JOIN front.organismes o ON o.id = ds.organisme_id + WHERE ds.id = $1 + AND (u.id = $3 OR o.personne_morale->>'siren' = $2) + `, getDeprecated: (organismeIds) => [ `SELECT ds.id as "declarationId", @@ -389,7 +469,6 @@ WHERE ds.vacanciers as "vacanciers", ds.personnel as "personnel", ds.transport as "transport", - ds.hebergement as "hebergements", COALESCE(ds.projet_sejour, '{}'::jsonb) as "projetSejour", ds.sanitaires as "sanitaires", ds.files as "files", @@ -488,7 +567,7 @@ JOIN front.user_organisme uo ON u.id = uo.use_id WHERE uo.org_id = $1 `, - getExtract: (departementQuery, territoireCode) => ` + getExtract: (territoireCode) => ` SELECT ds.id as id, ds.libelle as libelle, @@ -503,26 +582,20 @@ SELECT FROM front.demande_sejour ds JOIN front.organismes o ON o.id = ds.organisme_id LEFT JOIN front.agrements a ON a.organisme_id = ds.organisme_id -WHERE ((${departementQuery}) +WHERE ( + EXISTS ( + SELECT + 1 + FROM + FRONT.DEMANDE_SEJOUR_TO_HEBERGEMENT DSTH + LEFT JOIN FRONT.HEBERGEMENT H ON H.ID = DSTH.HEBERGEMENT_ID + LEFT JOIN FRONT.ADRESSE A ON A.ID = H.ADRESSE_ID + WHERE + DSTH.DEMANDE_SEJOUR_ID = DS.ID + AND A.DEPARTEMENT = ANY ($1) + ) AND statut <> 'BROUILLON' OR a.region_obtention = '${territoireCode}')`, - getHebergement: (demandeSejourId, departementCodes, hebergementIndex) => [ - ` -SELECT - ds.id AS demande_sejour_id, - ds.date_debut AS date_sejour, - ds.departement_suivi AS departement, - h.hebergement AS hebergement -FROM - front.demande_sejour ds, - jsonb_array_elements(ds.hebergement -> 'hebergements') WITH ORDINALITY AS h(hebergement, ordinality) -WHERE - ds.id = $1 - AND h.hebergement -> 'coordonnees' -> 'adresse' ->> 'departement' = ANY($2) - AND ordinality = $3 - `, - [demandeSejourId, departementCodes, hebergementIndex], - ], getHebergementsByDepartementCodes: ( departementCodes, { search, limit, offset, order, sort }, @@ -530,35 +603,33 @@ WHERE ` WITH filtered_hebergements AS ( SELECT - ds.id AS "declarationId", - ds.date_debut as "dateSejour", - ds.departement_suivi as departement, - h.hebergement ->> 'nom' AS nom, - h.hebergement ->> 'dateFin' AS "dateFin", - h.hebergement ->> 'dateDebut' AS "dateDebut", - h.hebergement -> 'coordonnees' ->> 'email' AS email, - h.hebergement -> 'coordonnees' ->> 'numTelephone1' AS telephone, - h.hebergement -> 'coordonnees' ->> 'nomGestionnaire' AS "nomGestionnaire", - h.hebergement -> 'coordonnees' -> 'adresse' AS adresse, - h.hebergement -> 'informationsLocaux' ->> 'type' AS "typeHebergement", - h.hebergement -> 'informationsLocaux' ->> 'visiteLocauxAt' AS "dateVisite", - CASE - WHEN h.hebergement -> 'informationsLocaux' ->> 'reglementationErp' = 'true' THEN TRUE - WHEN h.hebergement -> 'informationsLocaux' ->> 'reglementationErp' = 'false' THEN FALSE - ELSE NULL - END AS "reglementationErp", - ordinality AS "hebergementIndex" + DS.ID AS "declarationId", + H.ID AS "hebergementId", + DS.DATE_DEBUT AS "dateSejour", + DS.DEPARTEMENT_SUIVI AS "departement", + H.NOM AS "nom", + DSTH.DATE_DEBUT AS "dateDebut", + DSTH.DATE_FIN AS "dateFin", + H.EMAIL AS EMAIL, + H.TELEPHONE_1 AS TELEPHONE, + H.NOM_GESTIONNAIRE AS "nomGestionnaire", + A.LABEL AS ADRESSE, + (SELECT VALUE FROM FRONT.HEBERGEMENT_TYPE WHERE ID = H.ID) AS "typeHebergement", + H.VISITE_LOCAUX_AT AS "dateVisite", + H.REGLEMENTATION_ERP AS "reglementationErp" FROM - front.demande_sejour ds, - jsonb_array_elements(ds.hebergement -> 'hebergements') WITH ORDINALITY AS h(hebergement, ordinality) + FRONT.HEBERGEMENT H + LEFT JOIN FRONT.ADRESSE A ON A.ID = H.ADRESSE_ID + INNER JOIN FRONT.DEMANDE_SEJOUR_TO_HEBERGEMENT DSTH ON DSTH.HEBERGEMENT_ID = H.ID + INNER JOIN FRONT.DEMANDE_SEJOUR DS ON DS.ID = DSTH.DEMANDE_SEJOUR_ID WHERE - ds.statut <> 'BROUILLON' - AND h.hebergement -> 'coordonnees' -> 'adresse' ->> 'departement' = ANY($1) - AND ( - unaccent(h.hebergement ->> 'nom') ILIKE '%' || unaccent($2) || '%' OR - h.hebergement -> 'coordonnees' ->> 'email' ILIKE '%' || $2 || '%' OR - unaccent(h.hebergement -> 'coordonnees' -> 'adresse' ->> 'label') ILIKE '%' || unaccent($2) || '%' - ) + DS.STATUT <> 'BROUILLON' + AND A.DEPARTEMENT = ANY($1) + AND ( + unaccent(h.nom) ILIKE '%' || unaccent($2) || '%' + OR h.email ILIKE '%' || $2 || '%' + OR unaccent(a.label) ILIKE '%' || unaccent($2) || '%' + ) ORDER BY "${sort}" ${order} ), total_count AS ( @@ -600,13 +671,14 @@ SELECT ds.transport as "informationsTransport", COALESCE(ds.projet_sejour, '{}'::jsonb) as "projetSejour", ds.sanitaires as "informationsSanitaires", - ds.hebergement as "hebergement", ds.organisme as "organisme", ds.files as "files", ds.attestation as "attestation", ds.declaration_2m as "declaration2mois", o.personne_morale->>'siret' as "siret", - ds.edited_at as "editedAt" + ds.edited_at as "editedAt", + sejour_etranger as "sejourEtranger", + sejour_itinerant as "sejourItinerant" FROM front.demande_sejour ds JOIN front.organismes o ON o.id = ds.organisme_id WHERE 1=1 @@ -631,23 +703,17 @@ ${Object.keys(criterias) WHERE uo.use_id = $1; `, [ - userId, - // countBrouillon - dsStatus.statuts.BROUILLON, - // countDeclarationAcompleter + userId, // countBrouillon + dsStatus.statuts.BROUILLON, // countDeclarationAcompleter dsStatus.statuts.A_MODIFIER, dsStatus.statuts.A_MODIFIER_8J, - dsStatus.statuts.ATTENTE_8_JOUR, - // countDeclarationEnInstruction + dsStatus.statuts.ATTENTE_8_JOUR, // countDeclarationEnInstruction dsStatus.statuts.TRANSMISE, dsStatus.statuts.TRANSMISE_8J, dsStatus.statuts.EN_COURS, - dsStatus.statuts.EN_COURS_8J, - // countDeclarationFinalisee - dsStatus.statuts.VALIDEE_8J, - // countSejourEnCours - dsStatus.statuts.SEJOUR_EN_COURS, - // countTerminee + dsStatus.statuts.EN_COURS_8J, // countDeclarationFinalisee + dsStatus.statuts.VALIDEE_8J, // countSejourEnCours + dsStatus.statuts.SEJOUR_EN_COURS, // countTerminee dsStatus.statuts.TERMINEE, ], ], @@ -691,8 +757,7 @@ WHERE VALUES ($1,$2,$3,$4,$5,$6,$7,NOW(),NOW()) RETURNING id as "eventId" - `, - /* + ` /* * La query peut insérer plusieurs hébergements d'un coup, d'ùou la necessité de generer plusieurs lignes via le .map * de la requete. Par exemple, si l'on veut inserer 2 hebergements, on utilisera la syntaxe * linkToHebergements(2) avec comme params le tableau (flat) @@ -700,7 +765,7 @@ WHERE * * Le .map genere le texte suivant : * ($1, $2, $3, $4), ($1, $5, $6, $7) - * */ + * */, linkToHebergements: (nbRows) => ` INSERT INTO FRONT.DEMANDE_SEJOUR_TO_HEBERGEMENT ( @@ -733,9 +798,11 @@ WHERE UPDATE front.demande_sejour ds SET hebergement = $1, + sejour_etranger = $2, + sejour_itinerant = $3, edited_at = NOW() WHERE - ds.id = $2 + ds.id = $4 RETURNING id as "declarationId" `, @@ -928,6 +995,8 @@ module.exports.copy = async (declaration) => { declaration.projetSejour, declaration.informationsSanitaires, declaration.files, + declaration.hebergement?.sejourEtranger ?? null, + declaration.hebergement?.sejourItinerant ?? null, ), ); log.d(response); @@ -1020,15 +1089,37 @@ module.exports.getDeprecated = async ({ sortBy }, organismesId) => { module.exports.getOne = async (criterias = {}) => { log.i("getOne - IN", { criterias }); - const { rows: demandes, rowCount } = await pool.query( + const { rows: declarations, rowCount } = await pool.query( ...query.getOne(criterias), ); if (rowCount !== 1) { log.w("getOne - DONE with unexpected result", { rowCount }); return null; } + const declaration = declarations[0]; + + const hebergements = await getHebergementsByDSIds(declaration.id); + log.i("getOne - DONE"); - return demandes[0]; + return { + ...declaration, + hebergement: { + hebergements, + sejourEtranger: declaration.sejourEtranger ?? null, + sejourItinerant: declaration.sejourItinerant ?? null, + }, + }; +}; + +module.exports.getByIdOrUserSiren = async (id, siren, userId) => { + log.i("getByIdOrUserSiren - IN"); + const response = await pool.query(query.getByIdOrUserSiren, [ + id, + siren, + userId, + ]); + log.d("getByIdOrUserSiren - DONE"); + return response.rows; }; module.exports.getByDepartementCodes = async ( @@ -1056,10 +1147,17 @@ module.exports.getByDepartementCodes = async ( log.i("getByDepartementCodes - IN"); - const params = []; + const params = [departementCodes]; const searchQuery = []; - // Search management + if (search?.siren && search.siren.length) { + searchQuery.push(`o.personne_morale->>'siren' = $${params.length + 1}`); + params.push(`${search.siren}`); + } + if (search?.siret && search.siret.length) { + searchQuery.push(`o.personne_morale->>'siret' = $${params.length + 1}`); + params.push(`${search.siret}`); + } if (search?.idFonctionnelle && search.idFonctionnelle.length) { searchQuery.push(`id_fonctionnelle ILIKE $${params.length + 1}`); params.push(`%${search.idFonctionnelle}%`); @@ -1094,18 +1192,9 @@ module.exports.getByDepartementCodes = async ( searchQuery.push(`dsm.message is not null`); } - /* - * Cette Partie du code soit toujours etre appelé juste avant la fonction query.getByDepartementCodes - * pour maintenir la coherence de l'ordre des paramètres dans les requetes - * */ - params.push(departementCodes); - const departementQuery = getDepartementWhereQuery(departementCodes, params); - let queryWithPagination = query.getByDepartementCodes( searchQuery, territoireCode, - departementQuery, - params, ); const stats = await pool.query( ...query.getAdminStats(departementCodes, territoireCode), @@ -1146,16 +1235,13 @@ module.exports.getByDepartementCodes = async ( } const total = await pool.query( - query.getByDepartementCodesTotal( - searchQuery, - territoireCode, - departementQuery, - ), + query.getByDepartementCodesTotal(searchQuery, territoireCode), params, ); log.i("getByDepartementCodes - DONE"); const totalValue = total.rows.find((t) => t.count)?.count; + return { demandes_sejour: response.rows, stats: Object.entries(stats.rows[0]).reduce((acc, [key, value]) => { @@ -1169,26 +1255,24 @@ module.exports.getByDepartementCodes = async ( module.exports.getById = async (declarationId, departements) => { log.i("getById - IN", { declarationId }); - const { rows: declarations } = await pool.query( - ...query.getById(declarationId, departements), - ); - log.d(declarations); + const declaration = + (await pool.query(...query.getById(declarationId, departements))) + .rows?.[0] ?? null; + const hebergements = await getHebergementsByDSIds(declarationId); - log.i("getById - DONE"); - return declarations[0]; -}; + if (!declaration) { + return null; + } -module.exports.getHebergement = async ( - demandeSejourId, - departementCodes, - hebergementId, -) => { - log.i("getHebergement - IN"); - const { rows } = await pool.query( - ...query.getHebergement(demandeSejourId, departementCodes, hebergementId), - ); - log.d("getHebergement - DONE"); - return rows; + log.i("getById - DONE"); + return { + ...declaration, + hebergement: { + hebergements, + sejourEtranger: declaration.sejourEtranger ?? null, + sejourItinerant: declaration.sejourItinerant ?? null, + }, + }; }; module.exports.getHebergementsByDepartementCodes = async ( @@ -1309,6 +1393,8 @@ module.exports.update = async (type, declarationId, parametre) => { await linkToHebergements(client, declarationId, parametre.hebergements); response = await client.query(query.updateHebergement, [ parametre, + parametre?.sejourEtranger ?? null, + parametre?.sejourItinerant ?? null, declarationId, ]); await client.query("COMMIT"); @@ -1437,14 +1523,9 @@ module.exports.getEmailBackCc = async (departements) => { module.exports.getExtract = async (territoireCode, departementCodes) => { log.i("getExtract - IN"); - const departementQuery = getDepartementWhereQuery(departementCodes, [ + const { rows: data } = await pool.query(query.getExtract(territoireCode), [ departementCodes, ]); - - const { rows: data } = await pool.query( - query.getExtract(departementQuery, territoireCode), - [departementCodes], - ); log.i("getExtract - DONE"); return data; }; diff --git a/packages/backend/src/services/Document.js b/packages/backend/src/services/Document.js index 2cb63d178..083c31a1b 100644 --- a/packages/backend/src/services/Document.js +++ b/packages/backend/src/services/Document.js @@ -24,24 +24,36 @@ const s3Client = new S3Client({ }); const query = { - create: (category, filename, mime_type, file) => [ + create: (category, filename, mime_type, userId, file) => [ ` INSERT INTO doc.documents - (category, filename, mime_type, file) - VALUES ($1, $2, $3, $4) RETURNING uuid`, - [category, filename, mime_type, file], + (category, filename, mime_type, user_id, file) + VALUES ($1, $2, $3, $4, $5) RETURNING uuid`, + [category, filename, mime_type, userId, file], ], getByUuid: (uuid) => [ ` SELECT uuid, - category, - filename, - mime_type as mimeType, - file + category, + filename, + mime_type as "mimeType", + user_id as "userId", + file FROM doc.documents WHERE uuid = $1;`, [uuid], ], + getFileMetaData: (uuid) => [ + ` + SELECT + uuid, + filename as "name", + user_id as "userId", + created_at as "createdAt" + FROM doc.documents + WHERE uuid = $1;`, + [uuid], + ], }; module.exports.getFile = async (uuid) => { @@ -60,18 +72,42 @@ module.exports.getFile = async (uuid) => { } }; -module.exports.createFile = async (filename, category, mimetype, data) => { +module.exports.getFileMetaData = async (uuid) => { + log.i("IN"); + try { + const { rows, rowCount } = await poolDoc.query( + ...query.getFileMetaData(uuid), + ); + if (rowCount > 0) { + log.i("DONE", rows[0]); + return rows[0]; + } + log.i("DONE"); + return null; + } catch (err) { + log.w(err); + throw new AppError("query.getFileMetaData failed", { cause: err }); + } +}; + +module.exports.createFile = async ( + filename, + category, + mimetype, + data, + userid, +) => { log.i("createFile pg - In"); try { log.i("createFile pg", { category, filename, mimetype }); const { rows: [{ uuid }], } = await poolDoc.query( - ...query.create(category, filename, mimetype, data), + ...query.create(category, filename, mimetype, userid, data), ); log.i("createFile pg - Done"); log.i("upload file to s3"); - await uploadToS3(filename, category, mimetype, data, uuid); + await uploadToS3(filename, category, mimetype, userid, data, uuid); log.i("upload file to s3 - Done"); return uuid; } catch (err) { @@ -89,23 +125,29 @@ async function uploadToS3( filename, category, mimetype, + userid, data, uuid = crypto.randomUUID(), ) { log.i("uploadToS3 - In"); try { log.d("uploadToS3", category, filename); - const encodedFilename = Buffer.from(filename, "latin1").toString("base64"); + const encodedFilename = filename + .normalize("NFD") + .replace(/[\u0300-\u036f]/g, "") + .replace(/[^a-zA-Z0-9._-]/g, "_"); + const extension = filename.split(".").pop(); await s3Client.send( new PutObjectCommand({ Body: data, Bucket: S3_BUCKET_NAME, - Key: `${S3_BUCKET_ROOT_DIR}/${uuid}.pdf`, + Key: `${S3_BUCKET_ROOT_DIR}/${uuid}.${extension}`, Metadata: { category, created_at: String(new Date()), mimetype: mimetype, originalname: encodedFilename, + userid: `${userid}`, }, }), ); diff --git a/packages/backend/src/services/Organisme.js b/packages/backend/src/services/Organisme.js index 400a90f74..6e73450f9 100644 --- a/packages/backend/src/services/Organisme.js +++ b/packages/backend/src/services/Organisme.js @@ -196,6 +196,13 @@ const query = { JOIN front.user_organisme uo ON o.id = org_id WHERE o.personne_morale->>'siret' = $1 `, + getIsComplet: ` + SELECT + complet + FROM + FRONT.ORGANISMES + WHERE id = $1 + `, getListe: ` SELECT o.id AS "organismeId", @@ -320,6 +327,13 @@ FROM back.organisme_non_agree ona WHERE o.personne_morale->>'siren' = $1 AND o.personne_morale->>'siegeSocial' = 'true' `, + getSiret: ` + SELECT + personne_morale ->> 'siret' as siret + FROM + FRONT.ORGANISMES + WHERE id = $1 + `, link: ` INSERT INTO front.user_organisme (use_id, org_id) VALUES($1, $2) @@ -351,20 +365,6 @@ FROM back.organisme_non_agree ona edited_at = NOW() WHERE id = $1 `, - getIsComplet: ` - SELECT - complet - FROM - FRONT.ORGANISMES - WHERE id = $1 - `, - getSiret: ` - SELECT - personne_morale ->> 'siret' as siret - FROM - FRONT.ORGANISMES - WHERE id = $1 - `, }; module.exports.create = async (type, parametre) => { diff --git a/packages/backend/src/services/Territoire.js b/packages/backend/src/services/Territoire.js index 7bb9eab6d..3a7d41b45 100644 --- a/packages/backend/src/services/Territoire.js +++ b/packages/backend/src/services/Territoire.js @@ -5,6 +5,11 @@ const pool = require("../utils/pgpool").getPool(); const log = logger(module.filename); const query = { + getFicheIdByTerCode: ` + select + fte.id AS id + FROM back.fiche_territoire fte + WHERE ter_code = $1`, getOne: ` select fte.id AS territoire_id, @@ -68,6 +73,15 @@ module.exports.fetch = async (criterias = {}) => { }); }; +module.exports.readFicheIdByTerCode = async (territoireCode) => { + log.i("readFicheIdByTerCode - IN"); + const { rows } = await pool.query(query.getFicheIdByTerCode, [ + territoireCode, + ]); + log.i("readFicheIdByTerCode - DONE"); + return rows[0]; +}; + module.exports.readOne = async (idTerritoire) => { log.i("fetch - IN"); const { rows } = await pool.query(query.getOne, [idTerritoire]); diff --git a/packages/backend/src/services/adresse.js b/packages/backend/src/services/adresse.js index 7ef8af2c6..2114b6e92 100644 --- a/packages/backend/src/services/adresse.js +++ b/packages/backend/src/services/adresse.js @@ -1,3 +1,5 @@ +const pool = require("../utils/pgpool").getPool(); + const query = { editCleInsee: ` UPDATE FRONT.ADRESSE @@ -16,6 +18,31 @@ const query = { CLE_INSEE = $1 OR LABEL = $2 `, + getById: ` + SELECT + LABEL as "label", + CODE_INSEE as "codeInsee", + CODE_POSTAL as "codePostal", + LONG as "long", + LAT as "lat", + DEPARTEMENT as "departement" +FROM + FRONT.ADRESSE +WHERE id = $1 + `, + getByIds: ` + SELECT + id as "id", + LABEL as "label", + CODE_INSEE as "codeInsee", + CODE_POSTAL as "codePostal", + LONG as "long", + LAT as "lat", + DEPARTEMENT as "departement" +FROM + FRONT.ADRESSE +WHERE id = ANY ($1) + `, insert: ` INSERT INTO FRONT.ADRESSE ( @@ -71,3 +98,13 @@ module.exports.saveAdresse = async (client, adresse) => { return existingAdresse.id; }; + +module.exports.getById = async (id) => { + const { rows } = await pool.query(query.getById, [id]); + return rows?.[0] ?? null; +}; + +module.exports.getByIds = async (ids) => { + const { rows } = await pool.query(query.getByIds, [ids]); + return rows ?? []; +}; diff --git a/packages/backend/src/services/eig.js b/packages/backend/src/services/eig.js index 3f9a08ce0..8d93b4044 100644 --- a/packages/backend/src/services/eig.js +++ b/packages/backend/src/services/eig.js @@ -8,13 +8,14 @@ const log = logger(module.filename); const query = { create: ` INSERT INTO - FRONT.EIG (USER_ID, STATUT_ID, DEMANDE_SEJOUR_ID, DEPARTEMENT, IS_ATTESTE) + FRONT.EIG (USER_ID, STATUT_ID, DEMANDE_SEJOUR_ID, DEPARTEMENT, DATE, IS_ATTESTE) VALUES ( $1, (SELECT ID FROM FRONT.EIG_STATUT WHERE STATUT = '${statuts.BROUILLON}'), $2, $3, + $4, FALSE ) RETURNING id @@ -48,7 +49,10 @@ WHERE SELECT EIG.ID, EIG.created_at as "createdAt", + EIG.date as "date", EIG.DEPARTEMENT as "departement", + EIG.READ_BY_DREETS as "readByDreets", + EIG.READ_BY_DDETS as "readByDdets", S.STATUT AS "statut", DS.ID_FONCTIONNELLE AS "idFonctionnelle", DS.LIBELLE, @@ -58,10 +62,12 @@ SELECT DS.ORGANISME -> 'personneMorale' ->> 'raisonSociale' AS "raisonSociale", DS.ORGANISME -> 'personnePhysique' ->> 'prenom' AS "prenom", DS.ORGANISME -> 'personnePhysique' ->> 'nomUsage' AS "nom", - ARRAY_AGG(ET.TYPE) as "types" + ARRAY_AGG(ET.TYPE) as "types", + AGR.region_obtention as "agrementRegionObtention" FROM FRONT.EIG EIG INNER JOIN FRONT.USER_ORGANISME UO ON EIG.USER_ID = UO.USE_ID + LEFT JOIN FRONT.AGREMENTS AGR on AGR.ORGANISME_ID = UO.ORG_ID LEFT JOIN FRONT.EIG_TO_EIG_TYPE E2ET ON E2ET.EIG_ID = EIG.ID LEFT JOIN FRONT.EIG_TYPE ET ON ET.ID = E2ET.EIG_TYPE_ID LEFT JOIN FRONT.DEMANDE_SEJOUR DS ON DS.ID = EIG.DEMANDE_SEJOUR_ID @@ -72,7 +78,31 @@ WHERE GROUP BY EIG.ID, S.ID, - DS.ID + DS.ID, + AGR.ID + `, + getAvailableDs: ` + SELECT + ds.id AS "id", + ds.id_fonctionnelle AS "idFonctionnelle", + ds.libelle AS "libelle", + ds.date_debut AS "dateDebut", + ds.date_fin AS "dateFin" + FROM + front.demande_sejour ds + JOIN front.organismes o ON o.id = ds.organisme_id + WHERE + ( + UNACCENT (libelle) ILIKE '%' || UNACCENT ($1) || '%' + OR UNACCENT (id_fonctionnelle) ILIKE '%' || UNACCENT ($1) || '%' + ) + -- ces regles implementent en sql la logique de isDeclarationligibleToEig + AND ds.statut IN ('VALIDEE 8J', 'SEJOUR EN COURS', 'TERMINEE') + AND ds.date_debut <= DATE_TRUNC('day', NOW()) + AND DATE_TRUNC('day', NOW()) <= ds.date_fin + INTERVAL '1 week' + AND o.id = ANY ($2) + LIMIT + 10 `, getById: ` SELECT @@ -80,6 +110,10 @@ EIG.ID, EIG.USER_ID AS "userId", S.STATUT AS "statut", EIG.DEPARTEMENT AS "departement", + (SELECT LABEL FROM GEO.TERRITOIRES WHERE CODE = EIG.DEPARTEMENT) as "departementLibelle", + EIG.date as "date", + EIG.READ_BY_DREETS as "readByDreets", + EIG.READ_BY_DDETS as "readByDdets", DS.ID AS "declarationId", DS.ID_FONCTIONNELLE AS "idFonctionnelle", DS.LIBELLE as "libelle", @@ -92,7 +126,14 @@ EIG.ID, DS.PERSONNEL -> 'encadrants' as "encadrants", DS.PERSONNEL -> 'accompagnants' as "accompagnants", DS.STATUT as "dsStatut", - JSONB_PATH_QUERY_ARRAY(DS.HEBERGEMENT,'$.hebergements.coordonnees.adresse.label') as "adresses", + (SELECT DISTINCT + ARRAY_AGG(A.LABEL) as "label" + FROM + FRONT.DEMANDE_SEJOUR_TO_HEBERGEMENT DSTH + INNER JOIN FRONT.HEBERGEMENT H ON H.ID = DSTH.HEBERGEMENT_ID + INNER JOIN FRONT.ADRESSE A ON A.ID = H.ADRESSE_ID + WHERE + DEMANDE_SEJOUR_ID = DS.ID) as "adresses", DS.PERIODE as "saison", ARRAY_AGG(JSON_BUILD_OBJECT('type',ET.TYPE,'categorie',EC.CATEGORIE,'precision',E2ET.PRECISIONS)) as "types", EIG.DEROULEMENT as "deroulement", @@ -101,8 +142,11 @@ EIG.ID, EIG.DISPOSITION_INFORMATIONS as "dispositionInformations", EIG.IS_ATTESTE as "isAtteste", EIG.PERSONNEL as "personnel", - EIG.EMAIL_AUTRES_DESTINATAIRES as "emailAutresDestinataires" + EIG.EMAIL_AUTRES_DESTINATAIRES as "emailAutresDestinataires", + AGR.region_obtention as "agrementRegionObtention" FROM FRONT.EIG EIG + INNER JOIN FRONT.USER_ORGANISME UO ON EIG.USER_ID = UO.USE_ID + LEFT JOIN FRONT.AGREMENTS AGR on AGR.ORGANISME_ID = UO.ORG_ID LEFT JOIN FRONT.EIG_TO_EIG_TYPE E2ET ON E2ET.EIG_ID = EIG.ID LEFT JOIN FRONT.EIG_TYPE ET ON ET.ID = E2ET.EIG_TYPE_ID LEFT JOIN FRONT.EIG_CATEGORIE EC ON EC.ID = ET.EIG_CATEGORIE_ID @@ -113,7 +157,8 @@ WHERE GROUP BY EIG.ID, S.ID, - DS.ID; + DS.ID, + AGR.ID; `, getEmailByTerCode: ` WITH @@ -158,28 +203,53 @@ WHERE VALUES ${values.join(",")} `, - markAsRead: ` + markAsReadDdets: ` UPDATE FRONT.EIG SET - STATUT_ID = ( - SELECT - ID - FROM - FRONT.EIG_STATUT - WHERE - STATUT = 'LU' -) + READ_BY_DDETS = TRUE, + STATUT_ID = CASE + WHEN READ_BY_DREETS THEN ( + SELECT + ID + FROM + FRONT.EIG_STATUT + WHERE + STATUT = 'LU' + ) + ELSE STATUT_ID + END, + EDITED_AT = NOW() WHERE ID = $1 `, + markAsReadDreets: ` +UPDATE FRONT.EIG +SET + READ_BY_DREETS = TRUE, + STATUT_ID = CASE + WHEN READ_BY_DDETS THEN ( + SELECT + ID + FROM + FRONT.EIG_STATUT + WHERE + STATUT = 'LU' + ) + ELSE STATUT_ID + END, + EDITED_AT = NOW() +WHERE + ID = $1 + `, updateDs: ` UPDATE FRONT.EIG SET DEMANDE_SEJOUR_ID = $1, DEPARTEMENT=$2, + DATE=$3, EDITED_AT = NOW() WHERE - ID = $3 + ID = $4 RETURNING id `, updateEmailAutresDestinataires: ` @@ -206,13 +276,19 @@ RETURNING id `, }; -module.exports.create = async ({ userId, declarationId, departement }) => { +module.exports.create = async ({ + userId, + declarationId, + departement, + date, +}) => { log.i("create - IN"); const response = await pool.query(query.create, [ userId, declarationId, departement, + date, ]); log.d(response); const { id } = response.rows[0]; @@ -273,12 +349,16 @@ module.exports.getStatut = async (eigId) => { return response.rows?.[0]?.statut ?? null; }; -module.exports.updateDS = async (eigId, { declarationId, departement }) => { +module.exports.updateDS = async ( + eigId, + { declarationId, departement, date }, +) => { log.i("updateDS - IN"); const response = await pool.query(query.updateDs, [ declarationId, departement, + date, eigId, ]); log.d(response); @@ -444,6 +524,14 @@ const getEigs = async ( params.push(`${search?.departement}`); } + if (search?.dateRange?.start && search?.dateRange?.end) { + searchQuery.push( + `eig.date BETWEEN $${params.length + 1} AND $${params.length + 2}`, + ); + params.push(`${search.dateRange.start}`); + params.push(`${search.dateRange.end}`); + } + let queryWithPagination = query.get( where, searchQuery.map((s) => ` AND ${s} `).join(""), @@ -558,9 +646,22 @@ module.exports.delete = async ({ eigId }) => { return eigId; }; -module.exports.markAsRead = async (eigId) => { - log.i("updateStatut - IN", { eigId }); - await pool.query(query.markAsRead, [eigId]); - log.i("updateStatut - DONE"); +module.exports.markAsRead = async (eigId, type) => { + log.i("markAsReadDdets - IN", { eigId }); + if (type === "DREETS") { + await pool.query(query.markAsReadDreets, [eigId]); + } + if (type === "DDETS") { + await pool.query(query.markAsReadDdets, [eigId]); + } + log.i("markAsReadDdets - DONE"); return eigId; }; + +module.exports.getAvailableDs = async (organismeId, search) => { + const { rows: data } = await pool.query(query.getAvailableDs, [ + search, + organismeId, + ]); + return data; +}; diff --git a/packages/backend/src/services/geo/Commune.js b/packages/backend/src/services/geo/Commune.js index a15bc7a5d..afc6ddfef 100644 --- a/packages/backend/src/services/geo/Commune.js +++ b/packages/backend/src/services/geo/Commune.js @@ -9,30 +9,30 @@ const query = { select: ({ query: q, values }) => format( ` - SELECT - geo_com_code_fijais as value, - geo_com_label as text, - geo_com_date_debut as "dateDebut", - geo_com_date_fin as "dateFin" + SELECT + code as value, + label as text, + date_debut as "dateDebut", + date_fin as "dateFin" FROM geo.communes - WHERE 1=1 + WHERE 1=1 ${q} - ORDER BY geo_com_label ASC + ORDER BY label ASC `, ...values, ), selectWithDep: (criterias) => [ ` - SELECT - geo_com_code_fijais as value, - geo_com_label as text, - geo_com_date_debut as "dateDebut", - geo_com_date_fin as "dateFin", - c.geo_ter_code, - t.geo_ter_label + SELECT + code as value, + label as text, + date_debut as "dateDebut", + date_fin as "dateFin", + c.code, + t.label FROM geo.communes c - JOIN geo.territoires t on t.geo_ter_code = c.geo_ter_code - WHERE 1=1 + JOIN geo.territoires t on t.code = c.code + WHERE 1=1 ${Object.keys(criterias) .filter( (criteria) => @@ -45,7 +45,7 @@ const query = { ) .map((criteria, i) => ` AND ${criteria} = $${i + 1}`) .join(" ")} - ORDER BY geo_com_label ASC + ORDER BY label ASC `, Object.values(criterias), ], @@ -53,20 +53,20 @@ const query = { const dict = { code: (code) => ` - AND geo_com_code_fijais IN (${format.literal(code)})`, + AND code IN (${format.literal(code)})`, date: (date) => ` AND ( TO_DATE(${format.literal( date, - )}, 'YYYY-MM-DD') >= geo_com_date_debut OR geo_com_date_debut is null + )}, 'YYYY-MM-DD') >= date_debut OR date_debut is null ) AND ( - geo_com_date_fin > TO_DATE(${format.literal( + date_fin > TO_DATE(${format.literal( date, - )}, 'YYYY-MM-DD') OR geo_com_date_fin is null + )}, 'YYYY-MM-DD') OR date_fin is null )`, departement: (departement) => ` - AND geo_ter_code = ${format.literal(departement)}`, + AND code = ${format.literal(departement)}`, }; const transpose = (search) => diff --git a/packages/backend/src/services/Hebergement.js b/packages/backend/src/services/hebergement/Hebergement.js similarity index 52% rename from packages/backend/src/services/Hebergement.js rename to packages/backend/src/services/hebergement/Hebergement.js index 2ce294089..444b2adf5 100644 --- a/packages/backend/src/services/Hebergement.js +++ b/packages/backend/src/services/hebergement/Hebergement.js @@ -1,11 +1,37 @@ /* eslint-disable no-param-reassign */ -const logger = require("../utils/logger"); -const { saveAdresse } = require("./adresse"); -const pool = require("../utils/pgpool").getPool(); +const logger = require("../../utils/logger"); +const { + saveAdresse, + getById: getAdressById, + getByIds: getAdressByIds, +} = require("../adresse"); +const { + queryGetFields, + mapDBHebergement, + mapDBHebergementToDSHebergement, +} = require("./helpers"); +const pool = require("../../utils/pgpool").getPool(); const log = logger(module.filename); const query = { + associatePrestation: (nbRows) => ` +INSERT INTO + FRONT.HEBERGEMENT_TO_PRESTATIONS_HOTELIERES (HEBERGEMENT_ID, PRESTATION_ID) +VALUES +${new Array(nbRows) + .fill(null) + .map( + (_, index) => + `($1, (SELECT ID FROM FRONT.HEBERGEMENT_PRESTATIONS_HOTELIERES WHERE VALUE = $${index + 2}))`, + ) + .join(",")} + `, + removePrestationsHoteliere: ` + DELETE FROM front.hebergement_to_prestations_hotelieres + WHERE + hebergement_id = $1; + `, create: ` INSERT INTO front.hebergement( organisme_id, @@ -47,7 +73,8 @@ const query = { FILE_DERNIERE_ATTESTATION_SECURITE, VISITE_LOCAUX_AT, ACCESSIBILITE_PRECISION, - AMENAGEMENTS_SPECIFIQUES_PRECISION + AMENAGEMENTS_SPECIFIQUES_PRECISION, + STATUT_ID ) VALUES ( $1, --organisme_id, @@ -89,22 +116,65 @@ const query = { $35, --FILE_DERNIERE_ATTESTATION_SECURITE $36, --VISITE_LOCAUX_AT $37, --ACCESSIBILITE_PRECISION - $38 --AMENAGEMENTS_SPECIFIQUES_PRECISION + $38, --AMENAGEMENTS_SPECIFIQUES_PRECISION + (SELECT ID FROM FRONT.HEBERGEMENT_STATUT WHERE VALUE = $39) -- STATUT ) RETURNING id `, - associatePrestation: (nbRows) => ` -INSERT INTO - FRONT.HEBERGEMENT_TO_PRESTATIONS_HOTELIERES (HEBERGEMENT_ID, PRESTATION_ID) -VALUES -${new Array(nbRows) - .fill(null) - .map( - (_, index) => - `($1, (SELECT ID FROM FRONT.HEBERGEMENT_PRESTATIONS_HOTELIERES WHERE VALUE = $${index + 2}))`, - ) - .join(",")} + update: ` + UPDATE front.hebergement + SET + edited_by = $2, + edited_at = NOW(), + nom = $3, + coordonnees = $4, + informations_locaux = $5, + informations_transport = $6, + email = $7, + adresse_id = $8, + telephone_1 = $9, + telephone_2 = $10, + nom_gestionnaire = $11, + type_id = (SELECT id FROM front.hebergement_type WHERE value = $12), + type_pension_id = (SELECT id FROM front.hebergement_type_pension WHERE value = $13), + nombre_lits = $14, + lit_dessus = $15, + nombre_lits_superposes = $16, + nombre_max_personnes_couchage = $17, + visite_locaux = $18, + accessibilite_id = (SELECT id FROM front.hebergement_accessibilite WHERE value = $19), + chambres_doubles = $20, + chambres_unisexes = $21, + reglementation_erp = $22, + couchage_individuel = $23, + rangement_individuel = $24, + amenagements_specifiques = $25, + description_lieu_hebergement = $26, + excursion_description = $27, + deplacement_proximite_description = $28, + vehicules_adaptes = $29, + file_reponse_exploitant_ou_proprietaire = $30, + file_dernier_arrete_autorisation_maire = $31, + file_derniere_attestation_securite = $32, + visite_locaux_at = $33, + accessibilite_precision = $34, + amenagements_specifiques_precision = $35, + statut_id = (SELECT id FROM front.hebergement_statut WHERE value = $36) + WHERE + id = $1 `, + getByDSId: ` + SELECT + ID as "hebergementId", + NOM, + DSTH.DATE_DEBUT as "dateDebut", + DSTH.DATE_FIN as "dateFin", + ${queryGetFields} + FROM + FRONT.HEBERGEMENT H + INNER JOIN FRONT.DEMANDE_SEJOUR_TO_HEBERGEMENT DSTH ON DSTH.HEBERGEMENT_ID = H.ID + AND DSTH.DEMANDE_SEJOUR_ID = $1 ; + `, getByDepartementCodes: ( departementCodes, { search, limit, offset, order, sort }, @@ -112,30 +182,28 @@ ${new Array(nbRows) ` WITH filtered_hebergements AS ( SELECT - h.id AS "id", - h.nom AS "nom", - h.coordonnees -> 'adresse' ->> 'departement' AS "departement", - h.coordonnees ->> 'email' AS email, - h.coordonnees ->> 'numTelephone1' AS telephone, - h.coordonnees ->> 'nomGestionnaire' AS "nomGestionnaire", - h.coordonnees -> 'adresse' AS adresse, - h.informations_locaux ->> 'type' AS "typeHebergement", - h.informations_locaux ->> 'visiteLocauxAt' AS "dateVisite", - CASE - WHEN h.informations_locaux ->> 'reglementationErp' = 'true' THEN TRUE - WHEN h.informations_locaux ->> 'reglementationErp' = 'false' THEN FALSE - ELSE NULL - END AS "reglementationErp" + H.ID AS "id", + A.DEPARTEMENT AS "departement", + H.EMAIL AS EMAIL, + H.TELEPHONE_1 AS TELEPHONE, + H.NOM_GESTIONNAIRE AS "nomGestionnaire", + A.LABEL AS ADRESSE, + H.NOM AS "nom", + ( SELECT VALUE FROM FRONT.HEBERGEMENT_TYPE WHERE ID = H.ID) AS "typeHebergement", + H.VISITE_LOCAUX_AT AS "dateVisite", + H.REGLEMENTATION_ERP AS "reglementationErp" FROM - front.hebergement h + FRONT.HEBERGEMENT H + LEFT JOIN FRONT.ADRESSE A ON A.ID = H.ADRESSE_ID WHERE - h.coordonnees -> 'adresse' ->> 'departement' = ANY($1) + A.DEPARTEMENT = ANY($1) AND ( - unaccent(h.nom) ILIKE '%' || unaccent($2) || '%' OR - h.coordonnees ->> 'email' ILIKE '%' || $2 || '%' OR - unaccent(h.coordonnees -> 'adresse' ->> 'label') ILIKE '%' || unaccent($2) || '%' + unaccent(h.nom) ILIKE '%' || unaccent($2) || '%' + OR h.email ILIKE '%' || $2 || '%' + OR unaccent(a.label) ILIKE '%' || unaccent($2) || '%' ) - AND CURRENT IS TRUE + AND CURRENT IS TRUE + AND h.statut_id = (SELECT id FROM front.hebergement_statut WHERE value = 'actif') ORDER BY "${sort}" ${order} ), total_count AS ( @@ -154,61 +222,80 @@ ${new Array(nbRows) `, [departementCodes, search, limit, offset], ], - getById: (id) => [ - ` + getById: ` SELECT id, - supprime, nom, - coordonnees, - informations_locaux as "informationsLocaux", - informations_transport as "informationsTransport", - created_at as "createdAt", - edited_at as "editedAt" - FROM front.hebergement + ${queryGetFields} + FROM front.hebergement h WHERE id = $1 `, - [id], - ], + getByIdAndMySiren: ` + SELECT distinct(h.id) + FROM + front.hebergement h + JOIN front.user_organisme uo ON uo.org_id = h.organisme_id + JOIN front.organismes o ON o.id = uo.org_id + WHERE h.id = $1 + AND (uo.use_id = $2 OR o.personne_morale->>'siren' = $3) + `, + getBySiren: ` + SELECT + h.id, + h.nom, + a.departement as departement, + a.label as adresse, + h.supprime, + h.created_at as "createdAt", + h.edited_at as "editedAt", + hs.value as "statut" + FROM front.hebergement h + JOIN front.organismes o ON h.organisme_id = o.id + LEFT JOIN front.adresse a on a.id = h.adresse_id + LEFT JOIN front.hebergement_statut hs on hs.id = h.statut_id + WHERE o.personne_morale->>'siren' = $1 + `, getByUserId: ` SELECT - id, - nom, - coordonnees#> '{adresse, departement}' as departement, - coordonnees#> '{adresse, label}' as adresse, - supprime, - created_at as "createdAt", - edited_at as "editedAt" + h.id as "id", + nom as "nom", + a.label as "adresse", + a.departement as "departement", + hs.value as "statut" FROM front.hebergement h - JOIN front.user_organisme uo ON uo.org_id = h.organisme_id - WHERE uo.use_id = $1 AND CURRENT IS TRUE + LEFT JOIN front.user_organisme uo ON uo.org_id = h.organisme_id + LEFT JOIN front.adresse a ON a.id = h.adresse_id + LEFT JOIN front.hebergement_statut hs on hs.id = h.statut_id + WHERE uo.use_id = $1 + AND CURRENT IS TRUE `, getPreviousValueForHistory: ` SELECT - HEBERGEMENT_ID AS "hebergementUuid", - ORGANISME_ID as "organismeId", - CREATED_BY as "createdBy", - CREATED_AT as "createdAt", - CURRENT as "current" + h.hebergement_id AS "hebergementUuid", + h.organisme_id AS "organismeId", + h.created_by AS "createdBy", + h.created_at AS "createdAt", + h.current AS "current", + hs.value AS "statut" FROM - FRONT.HEBERGEMENT + front.hebergement h + LEFT JOIN front.hebergement_statut hs ON hs.id = h.statut_id WHERE - ID = $1; + h.id = $1; `, historize: ` UPDATE front.hebergement SET current = FALSE WHERE id = $1 `, - update: ` - UPDATE front.hebergement - SET - nom = $2, - coordonnees = $3, - informations_locaux = $4, - informations_transport = $5, - edited_at = NOW() - WHERE id = $1 + getStatut: ` + SELECT + value AS "statut" + FROM + front.hebergement h + LEFT JOIN front.hebergement_statut hs ON h.statut_id = hs.id + WHERE + h.id = $1 `, }; @@ -228,11 +315,13 @@ ${new Array(nbRows) */ const create = async ( client, - { createdBy, createdAt, updatedBy, organismeId }, + { createdBy, createdAt, updatedBy, organismeId, statut }, { nom, coordonnees, informationsLocaux, informationsTransport }, hebergemenetUuid, ) => { - const adresseId = await saveAdresse(client, coordonnees.adresse); + const adresseId = coordonnees.adresse + ? await saveAdresse(client, coordonnees.adresse) + : null; const { rows } = await client.query(query.create, [ organismeId, // $1 createdBy, @@ -272,10 +361,12 @@ const create = async ( informationsLocaux.visiteLocauxAt, informationsLocaux.accessibilitePrecision, informationsLocaux.precisionAmenagementsSpecifiques, + statut, ]); const hebergementId = rows[0].id; const prestationsHotelieres = informationsLocaux.prestationsHotelieres; + if (prestationsHotelieres.length > 0) { await client.query( query.associatePrestation(prestationsHotelieres.length), @@ -286,7 +377,7 @@ const create = async ( return hebergementId; }; -module.exports.create = async (userId, organismeId, hebergement) => { +module.exports.create = async (userId, organismeId, statut, hebergement) => { const client = await pool.connect(); let hebergementId; @@ -297,8 +388,9 @@ module.exports.create = async (userId, organismeId, hebergement) => { { createdAt: new Date(), createdBy: userId, - updatedBy: userId, organismeId, + statut, + updatedBy: userId, }, hebergement, ); @@ -313,9 +405,90 @@ module.exports.create = async (userId, organismeId, hebergement) => { return hebergementId; }; +// Utilisée par exemple lorsque l'on modifie un hebergement en statut brouillon +module.exports.updateWithoutHistory = async ( + userId, + hebergementId, + statut, + hebergement, +) => { + log.i("updateWithoutHistory - IN"); + const { nom, coordonnees, informationsLocaux, informationsTransport } = + hebergement; + + const client = await pool.connect(); + + try { + await client.query("BEGIN"); + const adresseId = coordonnees.adresse + ? await saveAdresse(client, coordonnees.adresse) + : null; + await client.query(query.update, [ + hebergementId, + userId, + nom, + coordonnees, + informationsLocaux, + informationsTransport, + coordonnees.email, + adresseId, + coordonnees.numTelephone1, + coordonnees.numTelephone2, + coordonnees.nomGestionnaire, + informationsLocaux.type, + informationsLocaux.pension, + informationsLocaux.nombreLits, + informationsLocaux.litsDessus, + informationsLocaux.nombreLitsSuperposes, + informationsLocaux.nombreMaxPersonnesCouchage, + informationsLocaux.visiteLocaux, + informationsLocaux.accessibilite, + informationsLocaux.chambresDoubles, + informationsLocaux.chambresUnisexes, + informationsLocaux.reglementationErp, + informationsLocaux.couchageIndividuel, + informationsLocaux.rangementIndividuel, + informationsLocaux.amenagementsSpecifiques, + informationsLocaux.descriptionLieuHebergement, + informationsTransport.excursion, + informationsTransport.deplacementProximite, + informationsTransport.vehiculesAdaptes, + informationsLocaux.fileReponseExploitantOuProprietaire?.uuid ?? null, + informationsLocaux.fileDernierArreteAutorisationMaire?.uuid ?? null, + informationsLocaux.fileDerniereAttestationSecurite?.uuid ?? null, + informationsLocaux.visiteLocauxAt, + informationsLocaux.accessibilitePrecision, + informationsLocaux.precisionAmenagementsSpecifiques, + statut, + ]); + + await client.query(query.removePrestationsHoteliere, [hebergementId]); + const prestationsHotelieres = informationsLocaux.prestationsHotelieres; + if (prestationsHotelieres.length > 0) { + await client.query( + query.associatePrestation(prestationsHotelieres.length), + [hebergementId, ...prestationsHotelieres], + ); + } + + await client.query("COMMIT"); + } catch (error) { + await client.query("ROLLBACK"); + throw error; + } finally { + client.release(); + } + + log.i("updateWithoutHistory - DONE"); + return hebergementId; +}; + module.exports.update = async (userId, hebergementId, hebergement) => { + log.i("update - IN"); const { - rows: [{ hebergementUuid, organismeId, createdBy, createdAt, current }], + rows: [ + { hebergementUuid, organismeId, createdBy, createdAt, current, statut }, + ], } = await pool.query(query.getPreviousValueForHistory, [hebergementId]); const client = await pool.connect(); @@ -332,10 +505,11 @@ module.exports.update = async (userId, hebergementId, hebergement) => { newHebergementId = await create( client, { - createdBy, createdAt, - updatedBy: userId, + createdBy, organismeId, + statut, + updatedBy: userId, }, hebergement, hebergementUuid, @@ -348,7 +522,7 @@ module.exports.update = async (userId, hebergementId, hebergement) => { } finally { client.release(); } - + log.i("update - DONE"); return newHebergementId; }; @@ -361,19 +535,97 @@ module.exports.getByDepartementCodes = async (departementsCodes, params) => { return rows[0]; }; -module.exports.getByUserId = async (userId) => { +module.exports.getByUserId = async (userId, search) => { log.i("getByUserId - IN", { userId }); - const response = await pool.query(query.getByUserId, [userId]); + + let queryGet = query.getByUserId; + const params = [userId]; + if (search.statut) { + queryGet = ` + ${queryGet} + AND h.statut_id = (SELECT id FROM front.hebergement_statut WHERE value = $2) + `; + params.push(search.statut); + } + + const response = await pool.query(queryGet, params); log.d("getByUserId - DONE"); const hebergements = response.rows; return hebergements; }; +module.exports.getBySiren = async (siren, search) => { + log.i("getBySiren - IN", { siren }); + + let queryGet = query.getBySiren; + const params = [siren]; + if (search.statut) { + queryGet = ` + ${queryGet} + AND h.statut_id = (SELECT id FROM front.hebergement_statut WHERE value = $2) + `; + params.push(search.statut); + } + + const response = await pool.query(queryGet, params); + log.d("getBySiren - DONE"); + return response.rows; +}; + +module.exports.getByIdAndMySiren = async (id, userId, siren) => { + log.i("getByIdAndSiren - IN"); + const response = await pool.query(query.getByIdAndMySiren, [ + id, + userId, + siren, + ]); + log.d("getByIdAndSiren - DONE"); + return response.rows; +}; + module.exports.getById = async (id) => { log.i("getById - IN", { id }); - const { rows: hebergements, rowCount } = await pool.query( - ...query.getById(id), - ); + const { rows: hebergements, rowCount } = await pool.query(query.getById, [ + id, + ]); + + if (rowCount === 0) { + return null; + } + + const hebergement = hebergements[0]; + const adresse = hebergement.adresseId + ? await getAdressById(hebergement.adresseId) + : null; + log.d("getById - DONE"); - return rowCount > 0 ? hebergements[0] : null; + return await mapDBHebergement(hebergement, adresse); +}; + +module.exports.getByDSId = async (dsId) => { + log.i("getByDSId - IN", { dsId }); + const { rows: hebergements, rowCount } = await pool.query(query.getByDSId, [ + dsId, + ]); + + if (rowCount === 0) { + return []; + } + const adresses = await getAdressByIds(hebergements.map((h) => h.adresseId)); + return await Promise.all( + hebergements.map((h) => + mapDBHebergementToDSHebergement( + h, + adresses.find((a) => a.id === h.adresseId), + ), + ), + ); +}; + +module.exports.getStatut = async (hebergementId) => { + log.i("getStatut - IN"); + const response = await pool.query(query.getStatut, [hebergementId]); + log.d(response); + log.i("getStatut - DONE"); + return response.rows?.[0]?.statut ?? null; }; diff --git a/packages/backend/src/services/hebergement/helpers.js b/packages/backend/src/services/hebergement/helpers.js new file mode 100644 index 000000000..5317ef979 --- /dev/null +++ b/packages/backend/src/services/hebergement/helpers.js @@ -0,0 +1,154 @@ +const { getFileMetaData } = require("../Document"); + +module.exports.queryGetFields = ` + H.NOM AS "nom", + H.EMAIL AS "email", + H.ADRESSE_ID AS "adresseId", + H.TELEPHONE_1 AS "numTelephone1", + H.TELEPHONE_2 AS "numTelephone2", + H.NOM_GESTIONNAIRE AS "nomGestionnaire", + ( + SELECT + VALUE + FROM + FRONT.HEBERGEMENT_TYPE + WHERE + ID = H.TYPE_ID + ) AS "type", + ( + SELECT + VALUE + FROM + FRONT.HEBERGEMENT_TYPE_PENSION + WHERE + ID = H.TYPE_PENSION_ID + ) AS "typePension", + H.NOMBRE_LITS AS "nombreLits", + H.LIT_DESSUS AS "litDessus", + H.NOMBRE_LITS_SUPERPOSES AS "nombreLitsSuperposes", + H.NOMBRE_MAX_PERSONNES_COUCHAGE AS "nombreMaxPersonnesCouchage", + H.VISITE_LOCAUX AS "visiteLocaux", + H.VISITE_LOCAUX_AT AS "visiteLocauxAt", + H.ACCESSIBILITE_ID AS "accessibiliteId", + ( + SELECT + VALUE + FROM + FRONT.HEBERGEMENT_ACCESSIBILITE + WHERE + ID = H.ACCESSIBILITE_ID + ) AS "accessibilite", + H.ACCESSIBILITE_PRECISION AS "accessibilitePrecision", + H.CHAMBRES_DOUBLES AS "chambresDoubles", + H.CHAMBRES_UNISEXES AS "chambresUnisexes", + H.REGLEMENTATION_ERP AS "reglementationErp", + H.COUCHAGE_INDIVIDUEL AS "couchageIndividuel", + H.RANGEMENT_INDIVIDUEL AS "rangementIndividuel", + H.AMENAGEMENTS_SPECIFIQUES AS "amenagementsSpecifiques", + H.AMENAGEMENTS_SPECIFIQUES_PRECISION AS "amenagementsSpecifiquesPrecision", + H.DESCRIPTION_LIEU_HEBERGEMENT AS "descriptionLieuHebergement", + H.EXCURSION_DESCRIPTION AS "excursionDescription", + H.DEPLACEMENT_PROXIMITE_DESCRIPTION AS "deplacementProximiteDescription", + H.VEHICULES_ADAPTES AS "vehiculesAdaptes", + H.FILE_REPONSE_EXPLOITANT_OU_PROPRIETAIRE AS "fileReponseExploitantOuProprietaire", + H.file_dernier_arrete_autorisation_maire AS "fileDernierArreteAutorisationMaire", + H.FILE_DERNIERE_ATTESTATION_SECURITE AS "fileDerniereAttestationSecurite", + ( + SELECT + ARRAY_AGG(HPH.VALUE) + FROM + FRONT.HEBERGEMENT_TO_PRESTATIONS_HOTELIERES HTPH + LEFT JOIN FRONT.HEBERGEMENT_PRESTATIONS_HOTELIERES HPH ON HPH.ID = HTPH.PRESTATION_ID + WHERE + HEBERGEMENT_ID = H.ID + ) AS "prestationsHoteliere", + (select value from front.hebergement_statut where id = h.statut_id) as "statut" +`; + +const mapHebergementToCoordonnees = (hebergement, adresse) => { + return { + adresse: adresse + ? { + codeInsee: adresse?.codeInsee ?? null, + codePostal: adresse?.codePostal ?? null, + coordinates: [adresse?.long, adresse?.lat], + departement: adresse?.departement, + label: adresse?.label, + } + : null, + email: hebergement.email ?? null, + nomGestionnaire: hebergement.nomGestionnaire ?? null, + numTelephone1: hebergement.numTelephone1 ?? null, + numTelephone2: hebergement.numTelephone2 ?? null, + }; +}; + +const mapHebergementToInformationsLocaux = async (hebergement) => { + return { + accessibilite: hebergement.accessibilite ?? null, + accessibilitePrecision: hebergement.accessibilitePrecision ?? null, + amenagementsSpecifiques: hebergement.amenagementsSpecifiques ?? null, + chambresDoubles: hebergement.chambresDoubles ?? null, + chambresUnisexes: hebergement.chambresUnisexes ?? null, + couchageIndividuel: hebergement.couchageIndividuel ?? null, + descriptionLieuHebergement: hebergement.descriptionLieuHebergement ?? null, + fileDernierArreteAutorisationMaire: + (await getFileMetaData(hebergement.fileDernierArreteAutorisationMaire)) ?? + null, + fileDerniereAttestationSecurite: + (await getFileMetaData(hebergement.fileDerniereAttestationSecurite)) ?? + null, + fileReponseExploitantOuProprietaire: + (await getFileMetaData( + hebergement.fileReponseExploitantOuProprietaire, + )) ?? null, + litsDessus: hebergement.litDessus ?? null, + nombreLits: hebergement.nombreLits ?? null, + nombreLitsSuperposes: hebergement.nombreLitsSuperposes ?? null, + nombreMaxPersonnesCouchage: hebergement.nombreMaxPersonnesCouchage ?? null, + pension: hebergement.typePension ?? null, + precisionAmenagementsSpecifiques: + hebergement.amenagementsSpecifiquesPrecision ?? null, + prestationsHotelieres: hebergement.prestationsHoteliere ?? [], + rangementIndividuel: hebergement.rangementIndividuel ?? null, + reglementationErp: hebergement.reglementationErp ?? null, + type: hebergement.type ?? null, + visiteLocaux: hebergement.visiteLocaux ?? null, + visiteLocauxAt: hebergement.visiteLocauxAt ?? null, + }; +}; + +const mapHebergementToInformationsTransport = (hebergement) => { + return { + deplacementProximite: hebergement.deplacementProximiteDescription ?? null, + excursion: hebergement.excursionDescription ?? null, + vehiculesAdaptes: hebergement.vehiculesAdaptes ?? null, + }; +}; + +const mapDBHebergement = async (hebergement, adresse) => { + return { + coordonnees: mapHebergementToCoordonnees(hebergement, adresse), + id: hebergement.id, + informationsLocaux: await mapHebergementToInformationsLocaux(hebergement), + informationsTransport: mapHebergementToInformationsTransport(hebergement), + nom: hebergement.nom, + statut: hebergement.statut ?? null, + }; +}; + +const mapDBHebergementToDSHebergement = async (hebergement, adresse) => { + return { + coordonnees: mapHebergementToCoordonnees(hebergement, adresse), + dateDebut: hebergement.dateDebut, + dateFin: hebergement.dateFin, + hebergementId: hebergement.hebergementId, + informationsLocaux: await mapHebergementToInformationsLocaux(hebergement), + informationsTransport: mapHebergementToInformationsTransport(hebergement), + nom: hebergement.nom, + }; +}; + +module.exports.mapDBHebergement = mapDBHebergement; +module.exports.mapDBHebergementToDSHebergement = + mapDBHebergementToDSHebergement; diff --git a/packages/backend/src/utils/mail.js b/packages/backend/src/utils/mail.js index acf8fcbb5..f88af4f43 100644 --- a/packages/backend/src/utils/mail.js +++ b/packages/backend/src/utils/mail.js @@ -323,7 +323,13 @@ module.exports = { }, }, eig: { - sendMarkAsRead: ({ dest, eig }) => { + sendMarkAsRead: ({ + dest, + eig, + typeReader, + territoireCode, + territoireName, + }) => { log.i("sendMarkAsRead - In", { dest, }); @@ -339,8 +345,7 @@ module.exports = { { p: [ `Bonjour`, - `Le rapport de l'évènement indésirable grave que vous avez déposé le ${dayjs(eig.dateDepot).format("DD/MM/YYYY")} et qui s’est déroulé lors du séjour ${eig.libelle} a été consulté par un agent de la préfecture. Si c’est nécessaire, cette personne pourra vous contacter via la messagerie de la plateforme VAO`, - `Cordialement,`, + `La déclaration d’un évènement indésirable grave que vous avez déposée le ${dayjs(eig.dateDepot).format("DD/MM/YYYY")} et qui s’est déroulé ${dayjs(eig.date).format("DD/MM/YYYY")} lors du séjour dans le département ${eig.departement}, a été consultée ${typeReader === "DREETS" ? `par un agent de la préfecture de la région ${territoireName}` : `par un agent de la DDETS ${territoireCode} du département ${territoireName}`}. Si c’est nécessaire, cette personne pourra vous contacter via la messagerie de la plateforme VAO. `, ], type: "p", }, @@ -360,7 +365,7 @@ module.exports = { return params; }, - sendToAutre: ({ dest, eig, userName, departementName, regionName }) => { + sendToAutre: ({ dest, eig, userName }) => { log.i("sendToDREETS - In", { dest, }); @@ -374,26 +379,31 @@ module.exports = { const link = `${frontBODomain}/eig/${eig.id}`; const html = sendTemplate.getBody( - "Portail VAO - Déclaration d’un Evènement indésirable grave", + `VAO : Déclaration d’un évènement indésirable grave par ${orgName}`, [ { p: [ `Bonjour`, - `${userName} a déclaré un évènement indésirable grave survenu lors du séjour ${eig.idFonctionnelle}, ${eig.libelle}, organisé par ${orgName}`, - `Le qualificatif de l’incident est :`, + `${userName} a déclaré un évènement indésirable grave survenu le ${dayjs(eig.date).format("DD/MM/YYYY")} lors du séjour ${eig.libelle}.`, + `Le type de l’événement est :`, generateTypes(eig), ], type: "p", }, + { + p: [ + `Vous pouvez retrouver les détails de cet EIG dans votre espace VAO en vous connectant avec votre compte utilisateur nominatif`, + ], + type: "p", + }, { link, - text: "Vous pouvez retrouver les détails de cet EIG dans votre espace VAO en cliquant ici", + text: "SE CONNECTER A VAO", type: "link", }, { p: [ - `Cette déclaration a été envoyée à la préfecture du département de ${departementName} ainsi qu'à la région ${regionName}`, - "Cordialement", + `Cette déclaration a été envoyée à la direction départementale de l'emploi, du travail, des solidarités et de la protection des populations (DDETS-PP), ainsi qu’à la direction régionale de l’économie, de l’emploi, du travail et des solidarités (DREETS) ayant délivré l’agrément VAO`, ], type: "p", }, @@ -413,7 +423,14 @@ module.exports = { return params; }, - sendToDDETS: ({ dest, eig }) => { + sendToDDETS: ({ + dest, + eig, + departementName, + declarationSejour, + regionName, + communeName, + }) => { log.i("sendToDDETS - In", { dest, }); @@ -427,24 +444,33 @@ module.exports = { const link = `${frontBODomain}/eig/${eig.id}`; const html = sendTemplate.getBody( - "Portail VAO - Déclaration d’un Evènement indésirable grave", + `VAO : Déclaration d’un évènement indésirable grave par ${orgName}`, [ { p: [ `Bonjour`, - `L’organisme ${orgName} a déclaré un évènement indésirable grave lors du séjour ${eig.idFonctionnelle}, ${eig.libelle}, qui s’est déroulé dans votre département.`, - `Le type d'évènement est :`, + `L’organisme ${orgName} a déclaré un évènement indésirable grave, qui s’est produit le ${dayjs(eig.date).format("DD/MM/YYYY")}, lors d’un séjour de vacances adaptées organisées (VAO), sur la commune de ${communeName} dans votre département ${departementName}`, + `Référence de la déclaration de séjour : ${declarationSejour.libelle} - ${eig.idFonctionnelle} du ${dayjs(declarationSejour.dateDebut).format("DD/MM/YYYY")} au ${dayjs(declarationSejour.dateFin).format("DD/MM/YYYY")}`, + `Le type d'évènement déclaré est :`, generateTypes(eig), ], type: "p", }, + { + p: [ + "Vous pouvez retrouver les détails de cet EIG dans votre espace VAO en vous connectant avec votre compte utilisateur nominatif", + ], + type: "p", + }, { link, - text: "Vous pouvez retrouver les détails de cet EIG dans votre espace VAO en cliquant ici", + text: "SE CONNECTER A VAO", type: "link", }, { - p: ["Cordialement"], + p: [ + `Cet email a également été envoyé à la DREETS de la région ${regionName} qui a délivré l’agrément de l’organisme déclarant`, + ], type: "p", }, ], @@ -463,7 +489,13 @@ module.exports = { return params; }, - sendToDREETS: ({ dest, eig, departementName }) => { + sendToDREETS: ({ + dest, + eig, + departementName, + declarationSejour, + communeName, + }) => { log.i("sendToDREETS - In", { dest, }); @@ -477,24 +509,33 @@ module.exports = { const link = `${frontBODomain}/eig/${eig.id}`; const html = sendTemplate.getBody( - "Portail VAO - Déclaration d’un Evènement indésirable grave", + `VAO : Déclaration d’un évènement indésirable grave par ${orgName}`, [ { p: [ `Bonjour`, - `L’organisme ${orgName}, qui a reçu son agrément dans votre région, a déclaré un évènement indésirable grave lors du séjour ${eig.idFonctionnelle}, ${eig.libelle}, qui s’est déroulé dans le département ${departementName} `, - `Le type d'évènement est :`, + `L’organisme ${orgName}, dont l’agrément vacances adaptées organisées (VAO) a été délivré dans votre région, a déclaré un évènement indésirable grave qui s’est produit le ${dayjs(eig.date).format("DD/MM/YYYY")}, lors d’un séjour organisé dans la commune de ${communeName} dans le département de ${departementName}.`, + `Référence de la déclaration de séjour : ${declarationSejour.libelle} - ${eig.idFonctionnelle} du ${dayjs(declarationSejour.dateDebut).format("DD/MM/YYYY")} au ${dayjs(declarationSejour.dateFin).format("DD/MM/YYYY")}`, + `Le type d'évènement déclaré est :`, generateTypes(eig), ], type: "p", }, + { + p: [ + "Vous pouvez retrouver les détails de cet EIG dans votre espace VAO en vous connectant avec votre compte utilisateur nominatif", + ], + type: "p", + }, { link, - text: "Vous pouvez retrouver les détails de cet EIG dans votre espace VAO en cliquant ici", + text: "SE CONNECTER A VAO", type: "link", }, { - p: ["Cordialement"], + p: [ + `Cette déclaration d’EIG a également été envoyée à la DDETS du département ${departementName}`, + ], type: "p", }, ], @@ -513,13 +554,7 @@ module.exports = { return params; }, - sendToOrganisme: ({ - dest, - eig, - userName, - departementName, - regionName, - }) => { + sendToOrganisme: ({ dest, eig, userName, declarationSejour }) => { log.i("sendToDREETS - In", { dest, }); @@ -530,29 +565,35 @@ module.exports = { } const orgName = eig.raisonSociale ?? `${eig?.prenom} ${eig?.nom}`; - const link = `${frontBODomain}/eig/${eig.id}`; + const link = `${frontUsagersDomain}/eig/${eig.id}`; const html = sendTemplate.getBody( - "Portail VAO - Déclaration d’un Evènement indésirable grave", + `VAO : Déclaration d’un évènement indésirable grave par ${orgName}`, [ { p: [ `Bonjour`, - `${userName} a déclaré un évènement indésirable grave survenu lors du séjour ${eig.idFonctionnelle}, ${eig.libelle}`, - `Le qualificatif de l’incident est :`, + `${userName} a déclaré un évènement indésirable grave survenu le ${dayjs(eig.date).format("DD/MM/YYYY")} lors du séjour ${eig.libelle}.`, + `Référence de la déclaration de séjour : ${declarationSejour.libelle} - ${eig.idFonctionnelle} du ${dayjs(declarationSejour.dateDebut).format("DD/MM/YYYY")} au ${dayjs(declarationSejour.dateFin).format("DD/MM/YYYY")}`, + `Le type de l’événement est :`, generateTypes(eig), ], type: "p", }, + { + p: [ + `Vous pouvez retrouver les détails de cet EIG dans votre espace VAO en vous connectant avec votre compte utilisateur nominatif`, + ], + type: "p", + }, { link, - text: "Vous pouvez retrouver les détails de cet EIG dans votre espace VAO en cliquant ici", + text: "SE CONNECTER A VAO", type: "link", }, { p: [ - `Cette déclaration a été envoyée à la préfecture du département de ${departementName} ainsi qu'à la région ${regionName}`, - "Cordialement", + `Cette déclaration a été envoyée à la direction départementale de l'emploi, du travail, des solidarités et de la protection des populations (DDETS-PP), ainsi qu’à la direction régionale de l’économie, de l’emploi, du travail et des solidarités (DREETS) ayant délivré l’agrément VAO.`, ], type: "p", }, @@ -576,6 +617,46 @@ module.exports = { }, usagers: { authentication: { + sendAccountValided: (email) => { + const link = `${frontUsagersDomain}/connexion/`; + const html = sendTemplate.getBody( + "Portail VAO - Prochaines étapes", + [ + { + p: [ + "Bonjour,", + "Vous venez de valider votre compte en tant qu’organisateur de séjours VAO. Voici les étapes à compléter pour pouvoir déclarer vos premiers séjours : ", + "- Rejoignez votre organisme en indiquant son numéro de SIRET", + "- complétez votre fiche organisme avec les informations légales et nécessaires à la déclaration d’un séjour", + "- créez les fiches des hébergements où vos vacanciers vont séjourner", + "- faites vos premières déclarations de séjour", + ], + type: "p", + }, + { + p: [ + "Si vous avez besoin d’accompagnement, vous pouvez contacter notre <a href='https://vao-assistance.atlassian.net/servicedesk/customer/portals'>équipe support</a>", + ], + type: "p", + }, + { + link, + text: "Se connecter à VAO", + type: "link", + }, + ], + `L'équipe du SI VAO<BR><a href=${frontUsagersDomain}>Portail VAO</a>`, + ); + const params = { + from: senderEmail, + html, + replyTo: senderEmail, + subject: `Portail VAO - Prochaines étapes`, + to: email, + }; + + return params; + }, sendForgottenPassword: ({ email, token }) => { log.i("sendForgottenPassword - In", { email, diff --git a/packages/frontend-bo/nuxt.config.ts b/packages/frontend-bo/nuxt.config.ts index 83cf50087..39a69686b 100644 --- a/packages/frontend-bo/nuxt.config.ts +++ b/packages/frontend-bo/nuxt.config.ts @@ -24,6 +24,7 @@ export default defineNuxtConfig({ "nuxt-maplibre", "nuxt-security", "vue-dsfr-nuxt-module", + "@samk-dev/nuxt-vcalendar", ], runtimeConfig: { public: { diff --git a/packages/frontend-bo/package.json b/packages/frontend-bo/package.json index 72754705f..cb86f1e36 100644 --- a/packages/frontend-bo/package.json +++ b/packages/frontend-bo/package.json @@ -19,6 +19,7 @@ "@gouvminint/vue-dsfr": "~7.1.0", "@pinia/nuxt": "~0.5.4", "@sentry/vue": "~8.31.0", + "@samk-dev/nuxt-vcalendar": "^1.0.4", "@socialgouv/dsfr-toaster-nuxt-module": "~1.3.5", "@vao/shared": "1.0.0", "@vee-validate/i18n": "~4.13.0", diff --git a/packages/frontend-bo/src/components/demandes-sejour/eigs.vue b/packages/frontend-bo/src/components/demandes-sejour/eigs.vue index 97239873c..488a79dcf 100644 --- a/packages/frontend-bo/src/components/demandes-sejour/eigs.vue +++ b/packages/frontend-bo/src/components/demandes-sejour/eigs.vue @@ -1,13 +1,13 @@ <template> <DsfrAccordionsGroup v-model="expandedIndex" - @update:model-value="openModal(props.eigs[expandedIndex])" + @update:model-value="openModal(expandedIndex)" > <DsfrAccordion - v-for="eig in eigs" + v-for="eig in props.eigs" :id="eig.id" :key="eig.id" - :title="getTitle(props.eig)" + :title="getTitle(eig)" > <EigSynthese v-if="eigStore.currentEig" :eig="eigStore.currentEig" /> </DsfrAccordion> @@ -15,19 +15,21 @@ <ValidationModal modal-ref="modal-eig-list-consult" name="consult-eig" - :opened="eigToRead != null" + :opened="modalDetails != null" title="Consultation d’un EIG" :on-close="closeEigModal" - :on-validate="() => readEig(eigToRead)" - >Vous vous apprêtez à consulter un Evènement Indésirable Grave. Cette - consultation enverra un email de notification à l’organisme. + :on-validate="() => readEig()" + >Vous vous apprêtez à consulter une déclaration d’un Evènement Indésirable + Grave. Cette consultation enverra un email de notification à l’organisme. </ValidationModal> </template> <script setup> -import { eigModel, ValidationModal } from "@vao/shared"; +import { ValidationModal } from "@vao/shared"; import dayjs from "dayjs"; +const toaster = useToaster(); + const props = defineProps({ eigs: { type: Array, default: () => [] }, }); @@ -35,20 +37,21 @@ const props = defineProps({ const emits = defineEmits(["getEigs"]); const eigStore = useEigStore(); +const userStore = useUserStore(); const getTitle = (eig) => - `EIG ${eig.id} déposé le ${dayjs(eig.dateDepot).format("DD/MM/YYYY")} / statut : ${eig.statut}`; + `EIG ${eig.id} déposé le ${dayjs(eig.dateDepot).format("DD/MM/YYYY")} / statut : ${eig.readByDreets && eig.readByDdets ? "LU" : "NON LU"}`; const expandedIndex = ref(-1); -const eigToRead = ref(null); +const modalDetails = ref(null); -const readEig = async (id) => { +const readEig = async () => { try { - await eigStore.markAsRead(id); - expandedId.value = id; + await eigStore.markAsRead(modalDetails.value.eigId); + expandedIndex.value = modalDetails.value.index; emits("getEigs"); - await eigStore.setCurrentEig(id); + await eigStore.setCurrentEig(modalDetails.value.eigId); closeEigModal(); } catch (error) { toaster.error("Une erreur est survenue lors de la lecture de l'eig"); @@ -56,15 +59,22 @@ const readEig = async (id) => { } }; -const openModal = async (eig) => { - if (eig.statut === eigModel.Statuts.ENVOYE) { - eigToRead.value = eig.id; +const openModal = async (index) => { + const eig = props.eigs[index]; + if (!eig) { + return; + } + + console.log(eig); + + if (utilsEig.mustMarkAsRead(eig, userStore.user)) { + expandedIndex.value = -1; + modalDetails.value = { eigId: eig.id, index }; } else { - expandedId.value = eig.id; await eigStore.setCurrentEig(eig.id); } }; -const closeEigModal = () => (eigToRead.value = null); +const closeEigModal = () => (modalDetails.value = null); </script> <style scoped></style> diff --git a/packages/frontend-bo/src/components/demandes-sejour/liste.vue b/packages/frontend-bo/src/components/demandes-sejour/liste.vue index fa63f93a4..a33517f41 100644 --- a/packages/frontend-bo/src/components/demandes-sejour/liste.vue +++ b/packages/frontend-bo/src/components/demandes-sejour/liste.vue @@ -182,6 +182,7 @@ definePageMeta({ const props = defineProps({ organisme: { type: String, required: false, default: null }, + organismeId: { type: Number, required: false, default: null }, display: { type: String, required: true }, }); @@ -226,6 +227,7 @@ const searchState = reactive({ libelle: route.query.libelle, idFonctionnelle: route.query.idFonctionnelle, organisme: props.organisme ?? route.query.organisme, + organismeId: props.organismeId ?? null, statuts: route.query.statuts ? route.query.statuts .split(",") diff --git a/packages/frontend-bo/src/components/eig/Synthese.vue b/packages/frontend-bo/src/components/eig/Synthese.vue index 6396f36d9..d62b1e662 100644 --- a/packages/frontend-bo/src/components/eig/Synthese.vue +++ b/packages/frontend-bo/src/components/eig/Synthese.vue @@ -1,7 +1,7 @@ <template> <div class="fr-container"> <h5>Informations de séjour</h5> - <Summary :eig="eig" /> + <Summary :eig="eig" env="BO" /> <h5>Personnel présent lors des événement</h5> <UtilsDisplayEncadrementAccompagnement :personnel="eig.personnel ?? []" /> <h5>Les faits</h5> diff --git a/packages/frontend-bo/src/components/hebergements/Lies-a-des-sejours.vue b/packages/frontend-bo/src/components/hebergements/Lies-a-des-sejours.vue index 784e17256..6dc9bde71 100644 --- a/packages/frontend-bo/src/components/hebergements/Lies-a-des-sejours.vue +++ b/packages/frontend-bo/src/components/hebergements/Lies-a-des-sejours.vue @@ -41,7 +41,6 @@ const headers = [ { column: "adresse", text: "Adresse", - format: (row) => row.adresse.label, }, { column: "telephone", @@ -70,7 +69,7 @@ const headers = [ const redirectOnHebergement = (row) => { navigateTo( - `/hebergements/lie-a-des-sejours/${row.declarationId}/${row.hebergementIndex}`, + `/hebergements/lie-a-des-sejours/${row.declarationId}/${row.hebergementId}`, ); }; diff --git a/packages/frontend-bo/src/components/user/Compte.vue b/packages/frontend-bo/src/components/user/Compte.vue index c731ef879..521b4d934 100644 --- a/packages/frontend-bo/src/components/user/Compte.vue +++ b/packages/frontend-bo/src/components/user/Compte.vue @@ -144,6 +144,20 @@ </div> </div> </div> + <div + v-if="serviceCompetence !== competence.NATIONALE" + class="fr-fieldset__element fr-col-12 fr-col-sm-8 fr-col-md-8 fr-col-lg-8 fr-col-xl-8" + > + <div class="fr-input-group"> + <DsfrButton + :disabled="!territoireCode" + type="button" + label="Accéder à la fiche d’informations du territoire" + primary + @click="navigateFicheTerritoire" + /> + </div> + </div> <div class="fr-fieldset__element fr-col-12 fr-col-sm-8 fr-col-md-8 fr-col-lg-8 fr-col-xl-8" > @@ -266,6 +280,7 @@ onMounted(async () => { const log = logger("pages/comptes/"); const usersStore = useUserStore(); +const territoireStore = useTerritoireStore(); const toaster = useToaster(); const config = useRuntimeConfig(); const regionStore = useRegionStore(); @@ -494,6 +509,13 @@ const displayInfos = { }; const displayType = ref("UnexpectedError"); +async function navigateFicheTerritoire() { + const idTerritoire = await territoireStore.getFicheIdByTerritoireCode( + territoireCode.value, + ); + navigateTo(`/territoires/${idTerritoire}`); +} + async function close() { navigateTo("/comptes/liste"); } diff --git a/packages/frontend-bo/src/composables/useMenuNavItem.js b/packages/frontend-bo/src/composables/useMenuNavItem.js index ee3021c94..49a2bab09 100644 --- a/packages/frontend-bo/src/composables/useMenuNavItem.js +++ b/packages/frontend-bo/src/composables/useMenuNavItem.js @@ -88,15 +88,14 @@ export const useMenuNavItems = () => { }, ] : []), - // TODO(eig): unhide when ok - /*...(roles.includes("eig") - ? [ - { - text: "EIG", - to: "/eig", - }, - ] - : []),*/ + /* ...(roles.includes("eig") + ? [ + { + text: "EIG", + to: "/eig", + }, + ] + : []),*/ ]; }); diff --git a/packages/frontend-bo/src/pages/eig/[eigId].vue b/packages/frontend-bo/src/pages/eig/[eigId].vue index f1a391b18..c0aa6dcea 100644 --- a/packages/frontend-bo/src/pages/eig/[eigId].vue +++ b/packages/frontend-bo/src/pages/eig/[eigId].vue @@ -19,6 +19,7 @@ onMounted(async () => { await eigStore.setCurrentEig(route.params.eigId); } catch (e) { navigateTo("/eig"); + throw e; } }); </script> diff --git a/packages/frontend-bo/src/pages/eig/index.vue b/packages/frontend-bo/src/pages/eig/index.vue index 40d371144..e084e3d68 100644 --- a/packages/frontend-bo/src/pages/eig/index.vue +++ b/packages/frontend-bo/src/pages/eig/index.vue @@ -95,6 +95,14 @@ /> </div> </div> + <div + class="fr-fieldset__element fr-fieldset__element--inline fr-col-12 fr-col-md-3 fr-col-lg-2" + > + <RangeDatePicker + v-model="searchState.dateRange" + label="Date de l'eig" + /> + </div> </div> </form> </div> @@ -115,12 +123,12 @@ <ValidationModal modal-ref="modal-eig-list-consult" name="consult-eig" - :opened="eigToRead != null" + :opened="eigIdToRead != null" title="Consultation d’un EIG" :on-close="closeEigModal" - :on-validate="() => readEig(eigToRead)" - >Vous vous apprêtez à consulter un Evènement Indésirable Grave. Cette - consultation enverra un email de notification à l’organisme. + :on-validate="() => readEig(eigIdToRead)" + >Vous vous apprêtez à consulter une déclaration d’un Evènement Indésirable + Grave. Cette consultation enverra un email de notification à l’organisme. </ValidationModal> </div> </template> @@ -130,6 +138,8 @@ import dayjs from "dayjs"; import { eigModel, EigStatusBadge, + EigTypeListe, + RangeDatePicker, TableWithBackendPagination, ValidationModal, } from "@vao/shared"; @@ -141,10 +151,11 @@ definePageMeta({ }); const departementStore = useDepartementStore(); +const usersStore = useUserStore(); +const eigStore = useEigStore(); const toaster = useToaster(); -const eigStore = useEigStore(); const defaultLimit = 10; const defaultOffset = 0; @@ -159,6 +170,7 @@ const searchState = reactive({ type: null, organisme: null, departement: null, + dateRange: null, }); try { @@ -264,11 +276,6 @@ const onSelect = (value, key) => { }; const headers = [ - { - column: "id", - text: "ID", - sort: true, - }, { column: "organisme", text: "Organisme", @@ -280,7 +287,7 @@ const headers = [ text: "Déclaration", sort: true, }, - { column: "departement", text: "Territoire (n° département)", sort: true }, + { column: "departement", text: "Territoire", sort: true }, { column: "libelle", text: "Séjour", @@ -296,21 +303,37 @@ const headers = [ { column: "types", text: "Types d'événement", - format: (value) => - (value.types ?? []).map((t) => mapEigToLabel[t]).join(", "), + component: ({ types }) => ({ + component: EigTypeListe, + types: (types ?? []).map((t) => mapEigToLabel[t]), + }), + }, + { + column: "date", + text: "Dates de l'incident", + format: (value) => dayjs(value.date).format("DD/MM/YYYY"), + sort: true, }, { column: "dateDepot", - text: "Dates de depot", + text: "Dates de dépôt", format: (value) => dayjs(value.dateDepot).format("DD/MM/YYYY"), sort: true, }, { column: "statut", text: "Statut", - component: ({ statut }) => ({ + component: ({ + statut, + readByDreets, + readByDdets, + agrementRegionObtention, + departement, + }) => ({ component: EigStatusBadge, - statut: statut, + statut, + dreets: { isRead: readByDreets, territoireCode: agrementRegionObtention }, + ddets: { isRead: readByDdets, territoireCode: departement }, }), sort: true, }, @@ -342,14 +365,15 @@ const readEig = async (id) => { } }; -const eigToRead = ref(null); +const eigIdToRead = ref(null); const openModal = (state) => { - if (eigStore.getStatut(state.id) === eigModel.Statuts.ENVOYE) { - eigToRead.value = state.id; + const eig = eigStore.getById(state.id); + if (utilsEig.mustMarkAsRead(eig, usersStore.user)) { + eigIdToRead.value = state.id; } else { navigateTo(`/eig/${state.id}`); } }; -const closeEigModal = () => (eigToRead.value = null); +const closeEigModal = () => (eigIdToRead.value = null); </script> diff --git a/packages/frontend-bo/src/pages/index.vue b/packages/frontend-bo/src/pages/index.vue index b83dddda2..fa6c83b78 100644 --- a/packages/frontend-bo/src/pages/index.vue +++ b/packages/frontend-bo/src/pages/index.vue @@ -2,8 +2,8 @@ <div> <h1>Bienvenue {{ user.prenom }} {{ user.nom }}</h1> <div> - <cardsNumber :values="topCards" /> - <cardsNumber :values="bottomCards" /> + <CardsNumber :values="topCards" /> + <CardsNumber :values="bottomCards" /> </div> </div> </template> diff --git a/packages/frontend-bo/src/pages/organismes/[[organismeId]].vue b/packages/frontend-bo/src/pages/organismes/[[organismeId]].vue index fa7c3ead6..a4ed16da1 100644 --- a/packages/frontend-bo/src/pages/organismes/[[organismeId]].vue +++ b/packages/frontend-bo/src/pages/organismes/[[organismeId]].vue @@ -122,6 +122,7 @@ v-if="organismeName" display="Organisme" :organisme="organismeName" + :organisme-id="route.params.organismeId" ></DemandesSejourListe> </DsfrTabContent> </DsfrTabs> diff --git a/packages/frontend-bo/src/pages/sejours/[declarationId].vue b/packages/frontend-bo/src/pages/sejours/[declarationId].vue index 99663cfc6..4fe44809e 100644 --- a/packages/frontend-bo/src/pages/sejours/[declarationId].vue +++ b/packages/frontend-bo/src/pages/sejours/[declarationId].vue @@ -60,12 +60,12 @@ const tabs = [ tabId: "tabpanel-messagerie", href: "messagerie", }, - // { - // label: "EIG", - // tabPanelId: "tabpanel-eig-panel", - // tabId: "tabpanel-eig", - // href: "eig", - // }, + /*{ + label: "EIG", + tabPanelId: "tabpanel-eig-panel", + tabId: "tabpanel-eig", + href: "eig", + },*/ ]; const defaultTab = tabs.findIndex(({ href }) => route.name.includes(href)); diff --git a/packages/frontend-bo/src/pages/territoires/[[territoireId]].vue b/packages/frontend-bo/src/pages/territoires/[[territoireId]].vue index 8773774da..cfcf5a248 100644 --- a/packages/frontend-bo/src/pages/territoires/[[territoireId]].vue +++ b/packages/frontend-bo/src/pages/territoires/[[territoireId]].vue @@ -107,7 +107,7 @@ const userStore = useUserStore(); const titleUser = computed(() => !TerritoireStore.territoire ? null - : `Liste des comptes ${TerritoireStore.territoire.type === "DEP" ? "du département" : "de la région et de ses départements"}` + : `Liste des comptes ${TerritoireStore.territoire.type === "DEP" ? "du département" : "de la région et de ses départements"}`, ); const titleTerritoire = computed(() => { if (!TerritoireStore.territoire) { diff --git a/packages/frontend-bo/src/pages/territoires/liste.vue b/packages/frontend-bo/src/pages/territoires/liste.vue index 464db55b4..c8bfe1779 100644 --- a/packages/frontend-bo/src/pages/territoires/liste.vue +++ b/packages/frontend-bo/src/pages/territoires/liste.vue @@ -51,6 +51,16 @@ const headers = [ text: "Référent VAO", sort: true, }, + { + column: "serviceTelephone", + text: "Téléphone", + sort: true, + }, + { + column: "serviceMail", + text: "Boite fonctionnelle", + sort: true, + }, { column: "actions", component: (row) => { diff --git a/packages/frontend-bo/src/stores/demande-sejour.js b/packages/frontend-bo/src/stores/demande-sejour.js index 8ae6bfaeb..44c332386 100644 --- a/packages/frontend-bo/src/stores/demande-sejour.js +++ b/packages/frontend-bo/src/stores/demande-sejour.js @@ -184,7 +184,7 @@ export const useDemandeSejourStore = defineStore("demandeSejour", { this.isGetHebergementLoading = true; try { const data = await $fetchBackend( - `/sejour/admin/hebergement/${demandeSejourId}/${hebergementId}`, + `/hebergement/admin/${hebergementId}`, { method: "GET", credentials: "include", @@ -269,13 +269,10 @@ export const useDemandeSejourStore = defineStore("demandeSejour", { }, async getEigs(declarationId) { - const response = await $fetchBackend( - `/eig/admin/ds/${declarationId}/enregistrement-2-mois`, - { - method: "GET", - credentials: "include", - }, - ); + const response = await $fetchBackend(`/eig/admin/ds/${declarationId}`, { + method: "GET", + credentials: "include", + }); this.eigs = response.eigs; return response; }, diff --git a/packages/frontend-bo/src/stores/eig.js b/packages/frontend-bo/src/stores/eig.js index 65e0dc884..61fcde2e2 100644 --- a/packages/frontend-bo/src/stores/eig.js +++ b/packages/frontend-bo/src/stores/eig.js @@ -11,8 +11,8 @@ export const useEigStore = defineStore("eig", { }), actions: { - getStatut(eigId) { - return this.eigs.find((eig) => eig.id === eigId).statut; + getById(eigId) { + return this.eigs.find((eig) => eig.id === eigId); }, async setCurrentEig(eigId) { if (!eigId) { diff --git a/packages/frontend-bo/src/stores/territoire.js b/packages/frontend-bo/src/stores/territoire.js index 01151cd04..ddec36f5e 100644 --- a/packages/frontend-bo/src/stores/territoire.js +++ b/packages/frontend-bo/src/stores/territoire.js @@ -50,6 +50,23 @@ export const useTerritoireStore = defineStore("territoire", { throw err; } }, + async getFicheIdByTerritoireCode(TerritoireCode) { + log.i("get - IN"); + try { + const row = await $fetchBackend( + `/territoire/get-fiche-id-by-ter-code/${TerritoireCode}`, + { + method: "GET", + credentials: "include", + }, + ); + log.i("get - DONE"); + return row.territoire.id; + } catch (err) { + log.w("get - DONE with error", err); + throw err; + } + }, async update(idTerritoire, params) { log.i("update - IN"); try { diff --git a/packages/frontend-bo/src/utils/utils-eig.js b/packages/frontend-bo/src/utils/utils-eig.js new file mode 100644 index 000000000..84dacca77 --- /dev/null +++ b/packages/frontend-bo/src/utils/utils-eig.js @@ -0,0 +1,15 @@ +const isUserDreetsWhoDeliveredAgrement = (user, eig) => + user.territoireCode === eig.agrementRegionObtention; + +const isUserDdetsWhereEigHappened = (user, eig) => + user.territoireCode === eig.departement; + +const mustMarkAsRead = (eig, user) => + (!eig.readByDdets && isUserDdetsWhereEigHappened(user, eig)) || + (!eig.readByDreets && isUserDreetsWhoDeliveredAgrement(user, eig)); + +export default { + isUserDreetsWhoDeliveredAgrement, + isUserDdetsWhereEigHappened, + mustMarkAsRead, +}; diff --git a/packages/frontend-usagers/nuxt.config.ts b/packages/frontend-usagers/nuxt.config.ts index fa8acc960..7efb63e10 100644 --- a/packages/frontend-usagers/nuxt.config.ts +++ b/packages/frontend-usagers/nuxt.config.ts @@ -25,6 +25,7 @@ export default defineNuxtConfig({ "nuxt-maplibre", "nuxt-security", "vue-dsfr-nuxt-module", + "@samk-dev/nuxt-vcalendar", ], runtimeConfig: { public: { diff --git a/packages/frontend-usagers/package.json b/packages/frontend-usagers/package.json index 15e54bcd5..747e3da51 100644 --- a/packages/frontend-usagers/package.json +++ b/packages/frontend-usagers/package.json @@ -16,6 +16,7 @@ "@gouvfr/dsfr": "~1.12.1", "@gouvminint/vue-dsfr": "~7.1.0", "@pinia/nuxt": "~0.5.4", + "@samk-dev/nuxt-vcalendar": "^1.0.4", "@sentry/vue": "~8.31.0", "@socialgouv/dsfr-toaster-nuxt-module": "~1.3.5", "@vao/shared": "^1.0.0", diff --git a/packages/frontend-usagers/src/components/DS/Eigs.vue b/packages/frontend-usagers/src/components/DS/Eigs.vue index 98b5944b4..f0aa0d3ee 100644 --- a/packages/frontend-usagers/src/components/DS/Eigs.vue +++ b/packages/frontend-usagers/src/components/DS/Eigs.vue @@ -27,7 +27,7 @@ <script setup> import dayjs from "dayjs"; import EigStatusBadge from "@vao/shared/src/components/eig/EigStatusBadge.vue"; -import { ValidationModal, TableFull } from "@vao/shared"; +import { TableFull, ValidationModal } from "@vao/shared"; import { mapEigToLabel } from "@vao/shared/src/utils/eigUtils"; const DsfrButton = resolveComponent("DsfrButton"); @@ -76,9 +76,17 @@ const headers = [ { column: "statut", text: "Statut", - component: ({ statut }) => ({ + component: ({ + statut, + readByDreets, + readByDdets, + departement, + agrementRegionObtention, + }) => ({ component: EigStatusBadge, - statut: statut, + statut, + dreets: { isRead: readByDreets, territoireCode: agrementRegionObtention }, + ddets: { isRead: readByDdets, territoireCode: departement }, }), sort: true, }, diff --git a/packages/frontend-usagers/src/components/DS/hebergements-sejour-detail.vue b/packages/frontend-usagers/src/components/DS/hebergements-sejour-detail.vue index 7ea130846..ae82267ff 100644 --- a/packages/frontend-usagers/src/components/DS/hebergements-sejour-detail.vue +++ b/packages/frontend-usagers/src/components/DS/hebergements-sejour-detail.vue @@ -603,6 +603,7 @@ const emit = defineEmits(["cancel", "update"]); const log = logger("components/DS/hebergement-sejour-detail"); const hebergementStore = useHebergementStore(); +const demandeSejourStore = useDemandeSejourStore(); const zoom = 15; const markerLatLng = computed(() => { @@ -916,7 +917,9 @@ async function addHebergement(hebergement) { if (!id) { return; } - await hebergementStore.fetch(); + await hebergementStore.fetch({ + organismeId: demandeSejourStore.demandeCourante.organismeId, + }); handleHebergementIdChange(id); resetApiStatut(); closeAddHebergement(); diff --git a/packages/frontend-usagers/src/components/DS/hebergements-sejour.vue b/packages/frontend-usagers/src/components/DS/hebergements-sejour.vue index 308c822be..48ae33848 100644 --- a/packages/frontend-usagers/src/components/DS/hebergements-sejour.vue +++ b/packages/frontend-usagers/src/components/DS/hebergements-sejour.vue @@ -70,7 +70,9 @@ v-else :hebergement="hebergementCourant" :date-debut-ini="nextMinDate" - :date-fin-ini="dayjs(props.dateFin).format('YYYY-MM-DD')" + :date-fin-ini=" + dayjs(demandeSejourStore.demandeCourante.dateFin).format('YYYY-MM-DD') + " :modifiable="props.modifiable" @update="addNuitee" @cancel="onCloseNuitee" @@ -82,13 +84,11 @@ import { DsfrButtonGroup } from "@gouvminint/vue-dsfr"; import { useField, useForm } from "vee-validate"; import dayjs from "dayjs"; +import { hebergement as hebergementUtils } from "@vao/shared"; const toaster = useToaster(); const props = defineProps({ - dateDebut: { type: String, required: true }, - dateFin: { type: String, required: true }, - hebergement: { type: Object, required: true }, modifiable: { type: Boolean, default: true }, isDownloading: { type: Boolean, required: false, default: false }, message: { type: String, required: false, default: null }, @@ -108,16 +108,22 @@ const headers = [ "Actions", ]; const hebergementStore = useHebergementStore(); +const demandeSejourStore = useDemandeSejourStore(); const nuiteeOpened = ref(false); const currentIndex = ref(-1); const validationSchema = computed(() => { - return DeclarationSejour.hebergementSchema(props.dateDebut, props.dateFin); + return DeclarationSejour.hebergementSchema( + demandeSejourStore.demandeCourante.dateDebut, + demandeSejourStore.demandeCourante.dateFin, + ); }); const initialValues = { - sejourEtranger: props.hebergement.sejourEtranger ?? false, - hebergements: props.hebergement.hebergements ?? [], + sejourEtranger: + demandeSejourStore.demandeCourante.hebergement?.sejourEtranger ?? false, + hebergements: + demandeSejourStore.demandeCourante.hebergement?.hebergements ?? [], }; const { meta, values } = useForm({ @@ -134,6 +140,11 @@ const { const { value: hebergements, handleChange: onHebergementsChange } = useField("hebergements"); +hebergementStore.fetch({ + organismeId: demandeSejourStore.demandeCourante.organismeId, + statut: hebergementUtils.statut.ACTIF, +}); + const syntheseRows = computed(() => { if (hebergementStore.hebergements.length > 0) { return hebergements.value.map((hebergement, index) => { @@ -165,7 +176,7 @@ const syntheseRows = computed(() => { ? dayjs(hebergement.dateFin).format("DD/MM/YYYY") : "", hebergement.nom ?? "", - hebergement.adresse ?? "", + hebergement.coordonnees?.adresse?.label ?? "", { component: DsfrButtonGroup, buttons: buttons, @@ -186,11 +197,15 @@ const hebergementCourant = ref(); const nextMinDate = computed(() => { if (currentIndex.value !== -1) { - return dayjs(props.dateDebut).format("YYYY-MM-DD"); + return dayjs(demandeSejourStore.demandeCourante.dateDebut).format( + "YYYY-MM-DD", + ); } if (hebergements.value.length === 0) { - return dayjs(props.dateDebut).format("YYYY-MM-DD"); + return dayjs(demandeSejourStore.demandeCourante.dateDebut).format( + "YYYY-MM-DD", + ); } return dayjs( Math.max( @@ -261,8 +276,8 @@ async function addNuitee(hebergement) { const isSejourComplet = computed(() => DeclarationSejour.isSejourComplet( hebergements.value, - props.dateDebut, - props.dateFin, + demandeSejourStore.demandeCourante.dateDebut, + demandeSejourStore.demandeCourante.dateFin, ), ); @@ -277,8 +292,6 @@ async function next() { }; emit("update", data, "hebergements"); } - -hebergementStore.fetch(); </script> <style lang="scss" scoped></style> diff --git a/packages/frontend-usagers/src/components/EIG/Recap.vue b/packages/frontend-usagers/src/components/EIG/Recap.vue index 184f94efa..d4dcb4910 100644 --- a/packages/frontend-usagers/src/components/EIG/Recap.vue +++ b/packages/frontend-usagers/src/components/EIG/Recap.vue @@ -8,7 +8,7 @@ " message="Erreur dans la selection des types" ></EIGError> - <Summary :eig="eigStore.currentEig" /> + <Summary :eig="eigStore.currentEig" env="USAGER" /> <h5>Personnel présent lors des événement</h5> <EIGError :is-error="!!errors.personnel" @@ -65,7 +65,7 @@ <article>{{ eigStore.currentEig.dispositionInformations }}</article> <hr /> <article> - La déclaration de cet EIG sera envoyé à : + La déclaration de cet incident sera envoyée à : <h6 class="fr-mb-0">DDETS</h6> <ul class="fr-mt-0 fr-mb-4w"> <li v-for="email in eigStore.currentEig.emailsDDETS" :key="email"> @@ -85,24 +85,26 @@ </li> </ul> </article> + <!-- TODO: hise in a first time. We keep the logic for later <div class="fr-fieldset__element"> - <div class="fr-input-group"> - <DsfrInputGroup - autocomplete="off" - type="text" - name="email-autres-destinataires" - label="Envoyer la déclaration à d'autres destinataires (optionnel)" - :label-visible="true" - hint="Renseigner les adresses mail séparées par des virgules" - required - :is-valid="emailAutresDestinatairesMeta.valid" - :error-message="emailAutresDestinatairesMessage" - :disabled="!eigStore.canModify" - :model-value="displayEmailAutresDestinataires" - @update:model-value="setEmailAutresDestinataires" - /> + <div class="fr-input-group"> + <DsfrInputGroup + autocomplete="off" + type="text" + name="email-autres-destinataires" + label="Envoyer la déclaration à d'autres destinataires (optionnel)" + :label-visible="true" + hint="Renseigner les adresses mail séparées par des virgules" + required + :is-valid="emailAutresDestinatairesMeta.valid" + :error-message="emailAutresDestinatairesMessage" + :disabled="!eigStore.canModify" + :model-value="displayEmailAutresDestinataires" + @update:model-value="setEmailAutresDestinataires" + /> + </div> </div> - </div> + --> <hr /> <div class="fr-fieldset__element fr-col-12"> <DsfrCheckbox @@ -175,22 +177,22 @@ const { value: isAtteste, handleChange: onIsAttesteChange } = useField("isAtteste"); const { value: emailAutresDestinataires, - handleChange: onEmailAutresDestinatairesChange, - errorMessage: emailAutresDestinatairesMessage, - meta: emailAutresDestinatairesMeta, + /*handleChange: onEmailAutresDestinatairesChange, + errorMessage: emailAutresDestinatairesMessage, + meta: emailAutresDestinatairesMeta,*/ } = useField("emailAutresDestinataires"); -const displayEmailAutresDestinataires = computed(() => +/*const displayEmailAutresDestinataires = computed(() => (emailAutresDestinataires.value ?? []).join(","), -); -const setEmailAutresDestinataires = (emailsInString) => { +);*/ +/*const setEmailAutresDestinataires = (emailsInString) => { onEmailAutresDestinatairesChange( emailsInString .split(",") .map((email) => email.trim()) .filter((e) => e.length > 0), ); -}; +};*/ const headers = [ { diff --git a/packages/frontend-usagers/src/components/EIG/RenseignementsGeneraux.vue b/packages/frontend-usagers/src/components/EIG/RenseignementsGeneraux.vue index 25f39c272..cadebf948 100644 --- a/packages/frontend-usagers/src/components/EIG/RenseignementsGeneraux.vue +++ b/packages/frontend-usagers/src/components/EIG/RenseignementsGeneraux.vue @@ -1,7 +1,7 @@ <template> - <h6>Renseignements generaux</h6> + <h5>Renseignements généraux</h5> <dsfr-alert class="fr-mb-6v"> - <Summary :eig="eigStore.currentEig" /> + <Summary :eig="eigStore.currentEig" env="USAGER" /> </dsfr-alert> <div class="fr-fieldset"> <h6>Personnel présent lors de événements</h6> @@ -44,8 +44,13 @@ </div> </div> </div> - <h6>Les faits</h6> - <div class="fr-fieldset__element"> + </div> + <h6>Les faits</h6> + <dsfr-alert type="warning" class="fr-mb-4w" + >Merci de ne pas mettre d'éléments nominatifs + </dsfr-alert> + <div class="fr-container fr-my-2v"> + <dsfr-fieldset> <DsfrInputGroup name="deroulement" :required="true" @@ -58,8 +63,8 @@ :is-valid="deroulementMeta" @update:model-value="deroulementChange" /> - </div> - <div class="fr-fieldset__element"> + </dsfr-fieldset> + <dsfr-fieldset> <DsfrInputGroup name="dispositionRemediation" :required="true" @@ -72,8 +77,8 @@ :is-valid="dispositionRemediationMeta" @update:model-value="dispositionRemediationChange" /> - </div> - <div class="fr-fieldset__element"> + </dsfr-fieldset> + <dsfr-fieldset> <DsfrInputGroup name="dispositionVictimes" :required="true" @@ -86,8 +91,8 @@ :is-valid="dispositionVictimesMeta" @update:model-value="dispositionVictimesChange" /> - </div> - <div class="fr-fieldset__element"> + </dsfr-fieldset> + <dsfr-fieldset> <DsfrInputGroup name="dispositionInformations" :required="true" @@ -100,7 +105,7 @@ :is-valid="dispositionInformationsMeta" @update:model-value="dispositionInformationsChange" /> - </div> + </dsfr-fieldset> </div> <UtilsNavigationButtons :show-buttons="props.showButtons" @@ -135,7 +140,7 @@ const dsPersonnel = computed(() => [ ]); const initialValues = { - personnel: eigStore.currentEig.personnel ?? dsPersonnel.value, + personnel: eigStore.currentEig.personnel ?? [], deroulement: eigStore.currentEig.deroulement, dispositionInformations: eigStore.currentEig.dispositionInformations, dispositionRemediation: eigStore.currentEig.dispositionRemediation, diff --git a/packages/frontend-usagers/src/components/EIG/SelectionSejour.vue b/packages/frontend-usagers/src/components/EIG/SelectionSejour.vue index 2b896aca0..13a51c2bb 100644 --- a/packages/frontend-usagers/src/components/EIG/SelectionSejour.vue +++ b/packages/frontend-usagers/src/components/EIG/SelectionSejour.vue @@ -1,36 +1,35 @@ <template> <div> - <h6>Sélectionner un séjour</h6> - <div class="fr-fieldset"> + <h5>Sélectionner un séjour</h5> + <dsfr-alert v-if="eigStore.currentEig" class="fr-mb-6v"> + <Summary :eig="eigStore.currentEig" env="USAGER" /> + </dsfr-alert> + <fieldset class="fr-fieldset"> <div class="fr-fieldset__element"> <DsfrTag - v-if="selectedDemandeLabel" + v-if="eigStore.selectedDemandeLabel" tag-name="button" icon="fr-icon-delete-line" class="fr-mb-4v" - :label="selectedDemandeLabel" + :label="eigStore.selectedDemandeLabel" :disabled="!eigStore.canModify" - @click="() => onDeclarationIdChange(null)" + @click="() => onRemoveDeclaration()" /> <DsfrSearchBar + v-model="search" label="Selectionner le séjour" class="fr-mb-2v" - :model-value="search" placeholder="Rechercher par code ou libellé" :disabled="!eigStore.canModify" - @update:model-value="search = $event" + @update:model-value="fetchAvailableDsDebounce($event)" /> - <div - v-for="demande in search.length > 0 ? filteredDemandes : []" - :key="demande.declarationId" - > + <div v-for="demande in filteredDemandes" :key="demande.id"> <DsfrTag tag-name="button" class="fr-my-2v" @click=" () => { - onDeclarationIdChange(demande.declarationId); - search = ''; + onChooseDeclaration(demande.id); } " > @@ -40,11 +39,11 @@ </div> <div class="fr-fieldset__element"> <DsfrSelect - v-if="!!selectedDemande" - label="Selection du département ou a eu lieu l'EIG" + v-if="!!eigStore.selectedDemande" + label="Sélection du département où a eu lieu l'incident" name="departements" :close-on-select="true" - :options="departementsOptions" + :options="eigStore.departementsOptions" :is-valid="departementMeta.valid" :disabled="!eigStore.canModify" :error-message="departementErrorMessage" @@ -52,8 +51,25 @@ @update:model-value="onDepartementChange" /> </div> - </div> - <div v-if="props.showButtons" class="fr-fieldset"> + <div class="fr-fieldset__element"> + <DsfrInputGroup + v-if="!!eigStore.selectedDemande" + name="date" + type="date" + label="Date de l'incident" + :label-visible="true" + :min="eigStore.selectedDemandeDateRange?.[0]" + :max="eigStore.selectedDemandeDateRange?.[1]" + :model-value="date" + :readonly="!eigStore.canModify" + :is-valid="dateMeta.valid" + :error-message="dateErrorMessage" + placeholder="Date de l'incident" + @update:model-value="onDateChange" + /> + </div> + </fieldset> + <fieldset v-if="props.showButtons" class="fr-fieldset"> <DsfrButton v-if="!props.isDownloading" id="next-step" @@ -65,15 +81,19 @@ :message="props.message" :is-downloading="props.isDownloading" /> - </div> + </fieldset> </div> </template> <script setup> import * as yup from "yup"; import { useField, useForm } from "vee-validate"; -import { eigModel, eigSchema } from "@vao/shared"; +import { eigModel, eigSchema, Summary } from "@vao/shared"; import { getTagSejourLibelle } from "@vao/shared/src/utils/eigUtils"; +import dayjs from "dayjs"; +import { DsfrAlert } from "@gouvminint/vue-dsfr"; + +const toaster = useToaster(); const emit = defineEmits(["next", "update"]); @@ -84,44 +104,29 @@ const props = defineProps({ }); const eigStore = useEigStore(); -const demandeSejourStore = useDemandeSejourStore(); -demandeSejourStore.fetchDemandes(); - -const search = ref(""); - const route = useRoute(); -const filteredDemandes = computed(() => - demandeSejourStore.demandes - .filter( - (d) => - eig.isDeclarationligibleToEig(d) && - (new RegExp(search.value, "i").test(d.idFonctionnelle) || - new RegExp(search.value, "i").test(d.libelle)) && - d.declarationId !== declarationId.value, - ) - .slice(0, 10), -); -const selectedDemande = computed(() => { - return ( - demandeSejourStore.demandes?.find( - (d) => d.declarationId === declarationId.value, - ) ?? null - ); -}); +const search = ref(null); -const selectedDemandeLabel = computed(() => { - if (!selectedDemande.value) { - return null; +let timeout; +const fetchAvailableDsDebounce = (search) => { + if (timeout) { + clearTimeout(timeout); } - return getTagSejourLibelle(selectedDemande.value); -}); + timeout = setTimeout(async () => { + try { + await eigStore.setAvailableDs(search); + } catch (error) { + toaster.error( + "Une erreur est survenue lors de la récupération de la demande", + ); + throw error; + } + }, 300); +}; -const departementsOptions = computed( - () => - selectedDemande.value?.hebergements?.hebergements - ?.map((h) => h?.coordonnees?.adresse?.departement) - .filter((d) => !!d) ?? [], +const filteredDemandes = computed(() => + eigStore.availableDs.filter((d) => d.id !== declarationId.value), ); const validationSchema = yup.object(eigSchema.selectionSejourSchema); @@ -130,6 +135,9 @@ const initialValues = { eigStore.currentEig?.declarationId ?? (!isNaN(route.query.dsId) ? parseInt(route.query.dsId) : null), departement: eigStore.currentEig?.departement ?? null, + date: eigStore.currentEig?.date + ? dayjs(eigStore.currentEig?.date).format("YYYY-MM-DD") + : null, }; const { meta, values } = useForm({ @@ -140,6 +148,7 @@ const { meta, values } = useForm({ const { value: declarationId, handleChange: onDeclarationIdChange } = useField("declarationId"); + const { value: departement, handleChange: onDepartementChange, @@ -147,6 +156,45 @@ const { meta: departementMeta, } = useField("departement"); +const { + value: date, + handleChange: onDateChange, + errorMessage: dateErrorMessage, + meta: dateMeta, +} = useField("date"); + +onMounted(async () => { + if (initialValues.declarationId) { + await eigStore.setSelectedDemande(initialValues.declarationId); + } + if (!departement.value) { + setDepartement(); + } +}); + +const setDepartement = () => { + if (eigStore.departementsOptions.length === 1) { + onDepartementChange(eigStore.departementsOptions[0]); + } else { + onDepartementChange(null, false); + } +}; + +const onChooseDeclaration = async (id) => { + onDeclarationIdChange(id); + await eigStore.setSelectedDemande(id); + search.value = null; + await eigStore.setAvailableDs(null); + setDepartement(); +}; + +const onRemoveDeclaration = async () => { + onDeclarationIdChange(null); + await eigStore.setSelectedDemande(null); + onDepartementChange(null, false); + onDateChange(null, false); +}; + const next = () => { if (!eigStore.canModify) { return emit("next"); @@ -164,6 +212,12 @@ const next = () => { eigModel.UpdateTypes.DECLARATION_SEJOUR, ); }; + +onUnmounted(() => { + if (timeout) { + clearTimeout(timeout); + } +}); </script> <style scoped lang="scss"></style> diff --git a/packages/frontend-usagers/src/components/EIG/Summary.vue b/packages/frontend-usagers/src/components/EIG/Summary.vue deleted file mode 100644 index 884887eb0..000000000 --- a/packages/frontend-usagers/src/components/EIG/Summary.vue +++ /dev/null @@ -1,59 +0,0 @@ -<template> - <div class="fr-col-7"> - <div v-for="detail in currentEigValues" :key="detail.label"> - <strong>{{ detail.label }} : </strong - ><span v-if="typeof detail.value === 'string'">{{ detail.value }}</span> - <div v-if="Array.isArray(detail.value)"> - <ul> - <li v-for="item in detail.value" :key="item">{{ item }}</li> - </ul> - </div> - </div> - </div> -</template> - -<script setup> -import dayjs from "dayjs"; -import { mapEigToLabel } from "@vao/shared/src/utils/eigUtils"; - -const props = defineProps({ eig: { type: Object, required: true } }); - -const currentEigValues = computed(() => [ - { - label: "Déclaration", - value: props.eig?.idFonctionnelle ?? "", - }, - { - label: "Séjour", - value: props.eig?.libelle ?? "", - }, - { - label: "Organisme", - value: props.eig.raisonSociale ?? `${props.eig?.prenom} ${props.eig?.nom}`, - }, - { - label: "Date (début / fin)", - value: - props.eig.dateDebut && props.eig.dateFin - ? `${dayjs(props.eig.dateDebut).format("DD/MM/YYYY")} - ${dayjs(props.eig.dateFin).format("DD/MM/YYYY")}` - : "", - }, - { - label: "Saison", - value: props.eig?.saison ?? "", - }, - { - label: "Adresse du / des lieux de séjour", - value: props.eig?.adresses ?? [], - }, - { - label: "type d'événements", - value: - props.eig?.types?.map( - (t) => mapEigToLabel[t.type] + (t.precision ? " : " + t.precision : ""), - ) ?? [], - }, -]); -</script> - -<style scoped lang="scss"></style> diff --git a/packages/frontend-usagers/src/components/EIG/Type.vue b/packages/frontend-usagers/src/components/EIG/Type.vue index b3fc93d3b..62e418814 100644 --- a/packages/frontend-usagers/src/components/EIG/Type.vue +++ b/packages/frontend-usagers/src/components/EIG/Type.vue @@ -1,6 +1,6 @@ <template> <dsfr-alert class="fr-mb-6v"> - <Summary :eig="eigStore.currentEig" /> + <Summary :eig="eigStore.currentEig" env="USAGER" /> </dsfr-alert> <h6>Type d'événement</h6> <div class="fr-fieldset"> @@ -173,6 +173,7 @@ import { useField, useForm } from "vee-validate"; import * as yup from "yup"; import { eigModel, eigSchema, Summary } from "@vao/shared"; import { mapEigToLabel } from "@vao/shared/src/utils/eigUtils"; +import { isTypeActive } from "@vao/shared/src/models"; const emit = defineEmits(["previous", "next", "update"]); @@ -242,32 +243,40 @@ const { const types = { [eigModel.Categorie.VICTIMES]: Object.values( eigModel.Types[eigModel.Categorie.VICTIMES], - ).map((t) => ({ - label: mapEigToLabel[t], - name: t, - value: t, - })), + ) + .filter((t) => isTypeActive(t)) + .map((t) => ({ + label: mapEigToLabel[t], + name: t, + value: t, + })), [eigModel.Categorie.SANTE]: Object.values( eigModel.Types[eigModel.Categorie.SANTE], - ).map((t) => ({ - label: mapEigToLabel[t], - name: t, - value: t, - })), + ) + .filter((t) => isTypeActive(t)) + .map((t) => ({ + label: mapEigToLabel[t], + name: t, + value: t, + })), [eigModel.Categorie.SECURITE]: Object.values( eigModel.Types[eigModel.Categorie.SECURITE], - ).map((t) => ({ - label: mapEigToLabel[t], - name: t, - value: t, - })), + ) + .filter((t) => isTypeActive(t)) + .map((t) => ({ + label: mapEigToLabel[t], + name: t, + value: t, + })), [eigModel.Categorie.FONCTIONNEMENT_ORGANISME]: Object.values( eigModel.Types[eigModel.Categorie.FONCTIONNEMENT_ORGANISME], - ).map((t) => ({ - label: mapEigToLabel[t], - name: t, - value: t, - })), + ) + .filter((t) => isTypeActive(t)) + .map((t) => ({ + label: mapEigToLabel[t], + name: t, + value: t, + })), }; const expandedIndex = ref(-1); diff --git a/packages/frontend-usagers/src/components/HebergementWithSave.vue b/packages/frontend-usagers/src/components/HebergementWithSave.vue index b7cf2408b..7ca607a8b 100644 --- a/packages/frontend-usagers/src/components/HebergementWithSave.vue +++ b/packages/frontend-usagers/src/components/HebergementWithSave.vue @@ -7,8 +7,11 @@ :message="props.message" :is-save-visible="props.isSaveVisible" :default-back-route="props.defaultBackRoute" + :mode-brouillon-activated="props.modeBrouillonActivated" :cdn-url="props.cdnUrl" @submit="submit" + @submit-brouillon="submitBrouillon" + @activate="activate" @cancel="cancel" > <template #search="scope"> @@ -49,20 +52,28 @@ const props = defineProps({ default: () => ({}), }, isDisabled: { type: Boolean, default: false }, - labelNext: { type: String, default: "Ajouter hébergement" }, isDownloading: { type: Boolean, default: false }, message: { type: String, required: false, default: null }, isSaveVisible: { type: Boolean, default: false }, defaultBackRoute: { type: String, required: true }, cdnUrl: { type: String, required: true }, + modeBrouillonActivated: { type: Boolean, default: false }, }); -const emit = defineEmits(["cancel", "submit"]); +const emit = defineEmits(["cancel", "submit", "submit-brouillon", "activate"]); const submit = (hebergement) => { emit("submit", hebergement); }; +const submitBrouillon = (hebergement) => { + emit("submit-brouillon", hebergement); +}; + +const activate = (hebergement) => { + emit("activate", hebergement); +}; + const cancel = () => { emit("cancel"); }; diff --git a/packages/frontend-usagers/src/components/organisme/personne-morale.vue b/packages/frontend-usagers/src/components/organisme/personne-morale.vue index f4c3a049c..3f61ade20 100644 --- a/packages/frontend-usagers/src/components/organisme/personne-morale.vue +++ b/packages/frontend-usagers/src/components/organisme/personne-morale.vue @@ -277,7 +277,7 @@ <div class="fr-fieldset__element"> <div class="fr-input-group fr-col-12"> <DsfrTable - :title="`Etablissements secondaires (${etablissements.length}) dont ${openedEtablissements.length} actifs et ${authorizedEtablissements.length} autorisés à organiser des séjours`" + :title="`Etablissements secondaires (${etablissements.length}) dont ${openedEtablissements.length} autorisé(s) à organiser des séjours et ${authorizedEtablissements.length} activé(s)`" :headers="[ 'SIRET', 'Dénomination', @@ -407,7 +407,7 @@ const etablissementFilter = ref({ siret: "", denomination: "", commune: "", - autorisation: "Tous", + autorisation: "Autorisés à organiser des séjours", }); const initialValues = { diff --git a/packages/frontend-usagers/src/components/personnes.vue b/packages/frontend-usagers/src/components/personnes.vue index 7d7a4c7c0..cd911f818 100644 --- a/packages/frontend-usagers/src/components/personnes.vue +++ b/packages/frontend-usagers/src/components/personnes.vue @@ -4,13 +4,13 @@ On cherche a rapprocher le bouton du tableau --> <div v-if="props.personnes.length > 0" class="fr-mb-n6v"> <DsfrTable + :key="'table-' + props.personnes.length" :title="props.titre" :headers="headersToDisplay" :rows="personnesToDisplay" :results-displayed="10" :current-page="currentPage" - :pagination="true" - @update:current-page="updateCurrentPage" + :pagination="(personnesToDisplay ?? []).length > 10" /> </div> <DsfrButton @@ -151,8 +151,4 @@ function updatePersonne(data) { function onClose() { modalPersonne.opened = false; } - -const updateCurrentPage = (val) => { - currentPageState.value = val++; -}; </script> diff --git a/packages/frontend-usagers/src/composables/useMenuNavItem.js b/packages/frontend-usagers/src/composables/useMenuNavItem.js index a105ac7cc..e4c666b2e 100644 --- a/packages/frontend-usagers/src/composables/useMenuNavItem.js +++ b/packages/frontend-usagers/src/composables/useMenuNavItem.js @@ -47,20 +47,19 @@ export const useMenuNavItems = () => { text: "Mes hébergements", to: "/hebergements/liste", }, - // TODO(eig): unhide when ok - /*{ - title: "EIG", - links: [ - { - text: "Mes EIG", - to: "/eig/liste", - }, - { - text: "Créer un EIG", - to: "/eig", - }, - ], - },*/ + /* { + title: "EIG", + links: [ + { + text: "Mes EIG", + to: "/eig/liste", + }, + { + text: "Créer un EIG", + to: "/eig", + }, + ], + },*/ ]; }); }; diff --git a/packages/frontend-usagers/src/pages/demande-sejour/[[declarationId]].vue b/packages/frontend-usagers/src/pages/demande-sejour/[[declarationId]].vue index cec57fa98..c8123259a 100644 --- a/packages/frontend-usagers/src/pages/demande-sejour/[[declarationId]].vue +++ b/packages/frontend-usagers/src/pages/demande-sejour/[[declarationId]].vue @@ -127,9 +127,6 @@ <div id="hebergements"> <DSHebergementsSejour v-if="hash === 'hebergements'" - :date-debut="demandeCourante.dateDebut" - :date-fin="demandeCourante.dateFin" - :hebergement="demandeCourante.hebergement ?? {}" :modifiable="canModify" :is-downloading="apiStatus.isDownloading" :message="apiStatus.message" @@ -288,6 +285,8 @@ const demandeCourante = computed(() => { return demandeSejourStore.demandeCourante; }); +const organismeStore = useOrganismeStore(); + const initialSelectedIndex = parseInt( route.query?.defaultTabIndex ? route.query.defaultTabIndex : 0, ); @@ -372,18 +371,15 @@ const tabTitles = computed(() => [ }, ] : []), - // TODO(eig): unhide when ok - /* - ...(sejourId.value + /* ...(sejourId.value ? [ { title: "EIG", tabId: "declaration-sejour-tab-4", panelId: "declaration-sejour-content-4", - } + }, ] - : []), - */ + : []),*/ ]); const sommaireOptions = demandeSejourMenus @@ -420,7 +416,7 @@ const titles = { titleStart.value + "étape 6 sur 8 | Informations sanitaires" + titleEnd, "#hebergements": () => titleStart.value + "étape 7 sur 8 | Sélection des hébergements" + titleEnd, - "#synthese": () => titleStart.value + "étape 8 sur 8 | Synthèse" + titleEnd, + "#synthese": () => titleStart + "étape 8 sur 8 | Synthèse" + titleEnd, 1: () => titleStart.value + "Documents joints" + titleEnd, 2: () => titleStart.value + "Historique de la déclaration" + titleEnd, 3: () => titleStart.value + "Messagerie" + titleEnd, @@ -451,10 +447,14 @@ const hash = computed(() => { const canModify = computed(() => { return ( !demandeCourante.value.statut || - demandeCourante.value.statut === DeclarationSejour.statuts.BROUILLON || - demandeCourante.value.statut === DeclarationSejour.statuts.A_MODIFIER || - demandeCourante.value.statut === DeclarationSejour.statuts.ATTENTE_8_JOUR || - demandeCourante.value.statut === DeclarationSejour.statuts.A_MODIFIER_8J + ((demandeCourante.value.statut === DeclarationSejour.statuts.BROUILLON || + demandeCourante.value.statut === DeclarationSejour.statuts.A_MODIFIER || + demandeCourante.value.statut === + DeclarationSejour.statuts.ATTENTE_8_JOUR || + demandeCourante.value.statut === + DeclarationSejour.statuts.A_MODIFIER_8J) && + organismeStore.organismeCourant?.organismeId === + demandeCourante.value.organisme?.organismeId) ); }); @@ -689,6 +689,7 @@ function nextHash() { return navigateTo({ path: `/demande-sejour/${sejourId.value}`, hash: "#" + sommaireOptions[index + 1], + "#synthese": () => titleStart.value + "étape 8 sur 8 | Synthèse" + titleEnd, }); } diff --git a/packages/frontend-usagers/src/pages/eig/liste.vue b/packages/frontend-usagers/src/pages/eig/liste.vue index 46a597ad0..da3f03470 100644 --- a/packages/frontend-usagers/src/pages/eig/liste.vue +++ b/packages/frontend-usagers/src/pages/eig/liste.vue @@ -65,6 +65,14 @@ /> </div> </div> + <div + class="fr-fieldset__element fr-fieldset__element--inline fr-col-12 fr-col-md-3 fr-col-lg-2" + > + <RangeDatePicker + v-model="searchState.dateRange" + label="Date de l'eig" + /> + </div> </div> </form> </div> @@ -105,6 +113,8 @@ import dayjs from "dayjs"; import EigStatusBadge from "@vao/shared/src/components/eig/EigStatusBadge.vue"; import { eigModel, + EigTypeListe, + RangeDatePicker, TableWithBackendPagination, ValidationModal, } from "@vao/shared"; @@ -127,6 +137,7 @@ const searchState = reactive({ statut: null, idFonctionnelle: null, type: null, + dateRange: null, }); const paginateResults = async (sortValue, limitValue, currentPageValue) => { @@ -222,8 +233,8 @@ const onSelect = (value, key) => { const headers = [ { - column: "id", - text: "ID", + column: "idFonctionnelle", + text: "Déclaration", sort: true, }, { @@ -232,11 +243,8 @@ const headers = [ format: (value) => dayjs(value.createdAt).format("DD/MM/YYYY"), sort: true, }, - { - column: "idFonctionnelle", - text: "Déclaration", - sort: true, - }, + { column: "departement", text: "Territoire", sort: true }, + { column: "libelle", text: "Séjour", @@ -252,15 +260,31 @@ const headers = [ { column: "types", text: "Types d'événement", - format: (value) => - (value.types ?? []).map((t) => mapEigToLabel[t]).join(", "), + component: ({ types }) => ({ + component: EigTypeListe, + types: (types ?? []).map((t) => mapEigToLabel[t]), + }), + }, + { + column: "date", + text: "Dates de l'incident", + format: (value) => dayjs(value.date).format("DD/MM/YYYY"), + sort: true, }, { column: "statut", text: "Statut", - component: ({ statut }) => ({ + component: ({ + statut, + readByDreets, + readByDdets, + agrementRegionObtention, + departement, + }) => ({ component: EigStatusBadge, statut: statut, + dreets: { isRead: readByDreets, territoireCode: agrementRegionObtention }, + ddets: { isRead: readByDdets, territoireCode: departement }, }), sort: true, }, @@ -297,7 +321,9 @@ const updateCurrentPage = (val) => { }; const navigate = (state) => { - navigateTo(`/eig/${state.id}`); + navigateTo( + `/eig/${state.id}${state.statut !== eigModel.Statuts.BROUILLON ? "#eig-recap" : ""}`, + ); }; const eigToDelete = ref(null); diff --git a/packages/frontend-usagers/src/pages/hebergements/[[hebergementId]].vue b/packages/frontend-usagers/src/pages/hebergements/[[hebergementId]].vue index b5c2f3fd1..71ce6b6aa 100644 --- a/packages/frontend-usagers/src/pages/hebergements/[[hebergementId]].vue +++ b/packages/frontend-usagers/src/pages/hebergements/[[hebergementId]].vue @@ -8,9 +8,22 @@ <div class="fr-grid-row"> <div class="fr-col"> - <h1 v-if="hebergementId"> - Hébergement {{ hebergementStore.hebergementCourant.nom }} - </h1> + <div v-if="hebergementId" class="title"> + <h1> + Hébergement {{ hebergementStore.hebergementCourant.nom }} + {{ hebergementStore.hebergementCourant.statut }} + </h1> + <DsfrBadge + v-if=" + hebergementStore.hebergementCourant.statut === + hebergementUtils.statut.ACTIF + " + actif + type="success" + :label="hebergementStore.hebergementCourant.statut" + class="pointer" + /> + </div> <h1 v-else>Création d'un nouveau lieu d'hébergement</h1> </div> </div> @@ -30,8 +43,11 @@ default-back-route="/hebergements" :is-downloading="apiStatus.isDownloading" :message="apiStatus.message" + mode-brouillon-activated is-save-visible @submit="updateOrCreate" + @submit-brouillon="updateOrCreateBrouillon" + @activate="activate" /> </div> </div> @@ -42,6 +58,8 @@ definePageMeta({ middleware: ["is-connected", "check-hebergement-id-param"], }); +import hebergementUtils from "@vao/shared/src/utils/hebergement"; +import { DsfrBadge } from "@gouvminint/vue-dsfr"; const config = useRuntimeConfig(); @@ -83,35 +101,95 @@ const links = [ }, ]; +const uploadFiles = async (hebergement) => { + try { + await hebergementStore.updaloadFiles(hebergement); + } catch (e) { + toaster.error({ + titleTag: "h2", + description: e.message ?? "Erreur lors de la sauvegarde de l'hébergement", + }); + resetApiStatut(); + throw e; + } +}; + async function updateOrCreate(hebergement) { log.d("updateOrCreate - IN", { hebergement }); setApiStatut( `${hebergementId.value ? "Modification" : "création"} de l'hébergement en cours`, ); + await uploadFiles(hebergement); + // Sauvegarde de l'hébergement try { - await hebergementStore.updaloadFiles(hebergement); - } catch (e) { + await hebergementStore.updateOrCreate(hebergement, hebergementId.value); + log.d("hebergement sauvegardé"); + toaster.success({ titleTag: "h2", description: "Hébergement sauvegardé" }); + + await navigateTo("/hebergements/liste"); + } catch (error) { toaster.error({ titleTag: "h2", - description: e.message ?? "Erreur lors de la sauvegarde de l'hébergement", + description: + error.data.message ?? "Erreur lors de la sauvegarde de l'hébergement", }); + log.w("updateOrCreate - erreur", { error }); + } finally { resetApiStatut(); - return; } +} + +async function updateOrCreateBrouillon(hebergement) { + log.d("updateOrCreate - IN", { hebergement }); + setApiStatut( + `${hebergementId.value ? "Modification" : "création"} de l'hébergement en mode brouillon`, + ); + + await uploadFiles(hebergement); // Sauvegarde de l'hébergement try { - await hebergementStore.updateOrCreate(hebergement, hebergementId.value); + const res = await hebergementStore.updateOrCreateBrouillon( + hebergement, + hebergementId.value, + ); log.d("hebergement sauvegardé"); toaster.success({ titleTag: "h2", description: "Hébergement sauvegardé" }); - return await navigateTo("/hebergements/liste"); + await navigateTo(`/hebergements/${res}`); } catch (error) { toaster.error({ titleTag: "h2", description: - error.data.message ?? "Erreur lors de la sauvegarde de l'hébergement", + error.data.message ?? + "Erreur lors de la sauvegarde de l'hébergement en mode brouillon", + }); + log.w("updateOrCreate - erreur", { error }); + } finally { + resetApiStatut(); + } +} + +async function activate(hebergement) { + log.d("updateOrCreate - IN", { hebergement }); + setApiStatut( + `${hebergementId.value ? "Modification" : "création"} de l'hébergement en mode brouillon`, + ); + await uploadFiles(hebergement); + + try { + await hebergementStore.activate(hebergement, hebergementId.value); + log.d("hebergement sauvegardé"); + toaster.success({ titleTag: "h2", description: "Hébergement sauvegardé" }); + + await navigateTo("/hebergements/liste"); + } catch (error) { + toaster.error({ + titleTag: "h2", + description: + error.data.message ?? + "Erreur lors de la sauvegarde de l'hébergement en mode brouillon", }); log.w("updateOrCreate - erreur", { error }); } finally { @@ -120,4 +198,10 @@ async function updateOrCreate(hebergement) { } </script> -<style scoped></style> +<style scoped> +.title { + display: flex; + align-items: center; + justify-content: space-between; +} +</style> diff --git a/packages/frontend-usagers/src/pages/hebergements/liste.vue b/packages/frontend-usagers/src/pages/hebergements/liste.vue index 02b661142..c521b7647 100644 --- a/packages/frontend-usagers/src/pages/hebergements/liste.vue +++ b/packages/frontend-usagers/src/pages/hebergements/liste.vue @@ -76,7 +76,7 @@ </template> <script setup> -import { TableFull } from "@vao/shared"; +import { hebergement as hebergementUtils, TableFull } from "@vao/shared"; const hebergementStore = useHebergementStore(); hebergementStore.fetch(); @@ -106,6 +106,7 @@ const search = reactive({ adresse: null, }); +const DsfrBadge = resolveComponent("DsfrBadge"); const headers = [ { column: "nom", @@ -123,6 +124,23 @@ const headers = [ text: "Adresse", sort: true, }, + { + column: "statut", + text: "Statut", + format: ({ statut }) => { + return { + component: DsfrBadge, + label: statut, + noIcon: true, + type: + statut === hebergementUtils.statut.ACTIF + ? "success" + : statut === hebergementUtils.statut.BROUILLON + ? "info" + : "error", + }; + }, + }, ]; const navigate = (hebergement) => navigateTo(`/hebergements/${hebergement.id}`); </script> diff --git a/packages/frontend-usagers/src/stores/demande-sejour.js b/packages/frontend-usagers/src/stores/demande-sejour.js index 3e7d17668..e29df0f44 100644 --- a/packages/frontend-usagers/src/stores/demande-sejour.js +++ b/packages/frontend-usagers/src/stores/demande-sejour.js @@ -1,5 +1,5 @@ import { defineStore } from "pinia"; -import { logger, $fetchBackend } from "#imports"; +import { $fetchBackend, logger } from "#imports"; const log = logger("stores/demande-sejour"); diff --git a/packages/frontend-usagers/src/stores/eig.js b/packages/frontend-usagers/src/stores/eig.js index 65039f981..2abf24c52 100644 --- a/packages/frontend-usagers/src/stores/eig.js +++ b/packages/frontend-usagers/src/stores/eig.js @@ -1,12 +1,15 @@ import { defineStore } from "pinia"; import { $fetchBackend, eig, logger } from "#imports"; import { eigModel } from "@vao/shared"; +import { getTagSejourLibelle } from "@vao/shared/src/utils/eigUtils"; const log = logger("stores/hebergement"); export const useEigStore = defineStore("eig", { state: () => ({ eigs: [], + availableDs: [], + selectedDemande: null, currentEig: null, total: 0, }), @@ -22,6 +25,25 @@ export const useEigStore = defineStore("eig", { })) ); }, + selectedDemandeLabel() { + if (!this.selectedDemande) { + return null; + } + return getTagSejourLibelle(this.selectedDemande); + }, + selectedDemandeDateRange() { + if (!this.selectedDemande) { + return null; + } + return [this.selectedDemande.dateDebut, this.selectedDemande.dateFin]; + }, + departementsOptions() { + return ( + this.selectedDemande?.hebergement?.hebergements + ?.map((h) => h?.coordonnees?.adresse?.departement) + .filter((d) => !!d) ?? [] + ); + }, }, actions: { async setCurrentEig(eigId) { @@ -122,5 +144,41 @@ export const useEigStore = defineStore("eig", { throw err; } }, + async setAvailableDs(search = null) { + try { + const res = await $fetchBackend(`/eig/available-ds`, { + method: "GET", + credentials: "include", + params: { search }, + }); + + this.availableDs = res; + } catch (err) { + this.availableDs = []; + log.w("getAvailableDs - DONE with error", err); + throw err; + } + }, + async setSelectedDemande(id) { + log.i("setSelectedDemande - IN"); + if (!id) { + this.selectedDemande = null; + return; + } + + try { + const { demande } = await $fetchBackend(`/sejour/${id}`, { + method: "GET", + credentials: "include", + }); + if (demande) { + log.i("setDemandeCourante - DONE"); + this.selectedDemande = demande; + } + } catch (err) { + log.w("setSelectedDemande - DONE with error", err); + this.selectedDemande = null; + } + }, }, }); diff --git a/packages/frontend-usagers/src/stores/hebergement.js b/packages/frontend-usagers/src/stores/hebergement.js index a465fb67a..2d73ac0dd 100644 --- a/packages/frontend-usagers/src/stores/hebergement.js +++ b/packages/frontend-usagers/src/stores/hebergement.js @@ -1,5 +1,5 @@ import { defineStore } from "pinia"; -import { logger, $fetchBackend } from "#imports"; +import { $fetchBackend, logger } from "#imports"; import UploadFile from "~/utils/UploadFile"; const log = logger("stores/hebergement"); @@ -10,11 +10,32 @@ export const useHebergementStore = defineStore("hebergement", { hebergementCourant: null, }), actions: { - async fetch() { + async fetchBySiren(siren) { + try { + log.i("fetchBySiren - IN"); + const { hebergements } = await $fetchBackend( + `/hebergement/siren/${siren}`, + { + credentials: "include", + }, + ); + if (hebergements) { + this.hebergements = hebergements; + } + log.d("fetchBySiren - DONE"); + } catch (err) { + this.hebergements = []; + log.i("fetchBySiren - DONE with error"); + } + }, + async fetch(search) { try { log.i("fetch - IN"); const { hebergements } = await $fetchBackend("/hebergement", { credentials: "include", + params: { + search, + }, }); if (hebergements) { this.hebergements = hebergements; @@ -57,7 +78,35 @@ export const useHebergementStore = defineStore("hebergement", { log.i("updateOrCreate - Done", { id }); return id ?? hebergementId; }, + async updateOrCreateBrouillon(hebergement, hebergementId) { + log.i("updateOrCreate - IN", { hebergement }); + + const url = hebergementId + ? `/hebergement/${hebergementId}/brouillon` + : `/hebergement/brouillon`; + + const { id } = await $fetchBackend(url, { + method: hebergementId ? "PUT" : "POST", + body: hebergement, + credentials: "include", + }); + log.i("updateOrCreate - Done", { id }); + return id ?? hebergementId; + }, + async activate(hebergement, hebergementId) { + log.i("updateOrCreate - IN", { hebergement }); + const { id } = await $fetchBackend( + `/hebergement/${hebergementId}/activate`, + { + method: "PUT", + body: hebergement, + credentials: "include", + }, + ); + log.i("updateOrCreate - Done", { id }); + return id ?? hebergementId; + }, async updaloadFiles(hebergement) { if (hebergement.informationsLocaux.reglementationErp) { const fileDAS = diff --git a/packages/frontend-usagers/src/utils/eig.js b/packages/frontend-usagers/src/utils/eig.js index 00e82f4a3..d83e16d90 100644 --- a/packages/frontend-usagers/src/utils/eig.js +++ b/packages/frontend-usagers/src/utils/eig.js @@ -6,10 +6,10 @@ const isDeclarationligibleToEig = (d) => d.dateDebut <= dayjs().format("YYYY-MM-DD") && dayjs(d.dateFin).add(1, "week").format("YYYY-MM-DD") >= dayjs().format("YYYY-MM-DD") && - ![ - DeclarationSejour.statuts.BROUILLON, - DeclarationSejour.statuts.ABANDONNEE, - DeclarationSejour.statuts.ANNULEE, + [ + DeclarationSejour.statuts.VALIDEE_8J, + DeclarationSejour.statuts.SEJOUR_EN_COURS, + DeclarationSejour.statuts.TERMINEE, ].includes(d.statut); const canDelete = (statut) => eigModel.Statuts.BROUILLON === statut; diff --git a/packages/migrations/src/migrations/20240911122757_add-date-in-eig.js b/packages/migrations/src/migrations/20240911122757_add-date-in-eig.js new file mode 100644 index 000000000..dd97e69f3 --- /dev/null +++ b/packages/migrations/src/migrations/20240911122757_add-date-in-eig.js @@ -0,0 +1,26 @@ +/** + * @param { import("knex").Knex } knex + * @returns {Knex.Raw<TResult>} + */ +exports.up = function (knex) { + return knex.raw( + ` +ALTER TABLE FRONT.EIG +ADD COLUMN date DATE; +UPDATE FRONT.EIG eig SET date = (select date_debut from front.demande_sejour where id = eig.demande_sejour_id); +ALTER TABLE FRONT.EIG +ALTER COLUMN date SET NOT NULL; + `, + ); +}; + +/** + * @param { import("knex").Knex } knex + * @returns {Knex.Raw<TResult>} + */ +exports.down = function (knex) { + return knex.raw(` +ALTER TABLE FRONT.EIG +DROP COLUMN date; + `); +}; diff --git a/packages/migrations/src/migrations/20240917123037_add-read-by-dreets-and-ddets-to-eig.js b/packages/migrations/src/migrations/20240917123037_add-read-by-dreets-and-ddets-to-eig.js new file mode 100644 index 000000000..0d8e14fed --- /dev/null +++ b/packages/migrations/src/migrations/20240917123037_add-read-by-dreets-and-ddets-to-eig.js @@ -0,0 +1,39 @@ +/** + * @param { import("knex").Knex } knex + * @returns {Knex.Raw<TResult>} + */ +exports.up = function (knex) { + return knex.raw( + ` +ALTER TABLE FRONT.EIG +ADD COLUMN READ_BY_DREETS BOOLEAN DEFAULT FALSE, +ADD COLUMN READ_BY_DDETS BOOLEAN DEFAULT FALSE; + +UPDATE FRONT.EIG +SET + READ_BY_DREETS = TRUE, + READ_BY_DDETS = TRUE +WHERE + STATUT_ID = ( + SELECT + ID + FROM + FRONT.EIG_STATUT + WHERE + STATUT = 'LU' +); + `, + ); +}; + +/** + * @param { import("knex").Knex } knex + * @returns {Knex.Raw<TResult>} + */ +exports.down = function (knex) { + return knex.raw(` +ALTER TABLE FRONT.EIG +DROP COLUMN READ_BY_DREETS, +DROP COLUMN READ_BY_DEETS; + `); +}; diff --git a/packages/migrations/src/migrations/20240925083938_add-is-active-to-eig-type.js b/packages/migrations/src/migrations/20240925083938_add-is-active-to-eig-type.js new file mode 100644 index 000000000..dc3958372 --- /dev/null +++ b/packages/migrations/src/migrations/20240925083938_add-is-active-to-eig-type.js @@ -0,0 +1,27 @@ +/** + * @param { import("knex").Knex } knex + * @returns { Promise<void> } + */ +exports.up = function (knex) { + return knex.raw( + ` +ALTER TABLE FRONT.EIG_TYPE +ADD COLUMN IS_ACTIVE BOOLEAN DEFAULT TRUE ; + +UPDATE FRONT.EIG_TYPE +SET IS_ACTIVE = FALSE +WHERE TYPE = 'VIOLS' ; +`, + ); +}; + +/** + * @param { import("knex").Knex } knex + * @returns { Promise<void> } + */ +exports.down = function (knex) { + return knex.raw(` +ALTER TABLE FRONT.EIG_TYPE +DROP COLUMN IS_ACTIVE ; + `); +}; diff --git a/packages/migrations/src/migrations/20241119135647_vehiculesAdaptes-type.js b/packages/migrations/src/migrations/20241119135647_vehiculesAdaptes-type.js new file mode 100644 index 000000000..9ce2e49be --- /dev/null +++ b/packages/migrations/src/migrations/20241119135647_vehiculesAdaptes-type.js @@ -0,0 +1,38 @@ +/** + * @param { import("knex").Knex } knex + * @returns { Promise<void> } + */ +exports.up = function (knex) { + return knex.raw( + ` +ALTER TABLE FRONT.HEBERGEMENT +RENAME COLUMN VEHICULES_ADAPTES TO VEHICULES_ADAPTES_DEPRECATED; + +ALTER TABLE FRONT.HEBERGEMENT +ADD COLUMN VEHICULES_ADAPTES BOOLEAN; + +UPDATE FRONT.HEBERGEMENT +SET + VEHICULES_ADAPTES = CASE + WHEN VEHICULES_ADAPTES_DEPRECATED = 'true' THEN TRUE + ELSE FALSE + END; + `, + ); +}; + +/** + * @param { import("knex").Knex } knex + * @returns { Promise<void> } + */ +exports.down = function (knex) { + return knex.raw( + ` +ALTER TABLE FRONT.HEBERGEMENT +DROP COLUMN VEHICULES_ADAPTES; + +ALTER TABLE FRONT.HEBERGEMENT +RENAME COLUMN VEHICULES_ADAPTES_DEPRECATED TO VEHICULES_ADAPTES; + `, + ); +}; diff --git a/packages/migrations/src/migrations/20241120101643_add-sejourItinerant-sejourEtranger-to-ds.js b/packages/migrations/src/migrations/20241120101643_add-sejourItinerant-sejourEtranger-to-ds.js new file mode 100644 index 000000000..41f600495 --- /dev/null +++ b/packages/migrations/src/migrations/20241120101643_add-sejourItinerant-sejourEtranger-to-ds.js @@ -0,0 +1,28 @@ +/** + * @param { import("knex").Knex } knex + * @returns { Promise<void> } + */ +exports.up = function (knex) { + return knex.raw(` + ALTER TABLE FRONT.demande_sejour + ADD COLUMN sejour_etranger BOOLEAN, + ADD COLUMN sejour_itinerant BOOLEAN; + + UPDATE FRONT.DEMANDE_SEJOUR + SET + SEJOUR_ETRANGER = (HEBERGEMENT -> 'sejourEtranger')::BOOLEAN, + SEJOUR_ITINERANT = (HEBERGEMENT -> 'sejourItinerant')::BOOLEAN ; + `); +}; + +/** + * @param { import("knex").Knex } knex + * @returns { Promise<void> } + */ +exports.down = function (knex) { + return knex.raw(` +ALTER TABLE FRONT.DEMANDE_SEJOUR +DROP COLUMN SEJOUR_ETRANGER, +DROP COLUMN SEJOUR_ITINERANT; + `); +}; diff --git a/packages/migrations/src/migrations/20241204092236_add_hebergement_statut.js b/packages/migrations/src/migrations/20241204092236_add_hebergement_statut.js new file mode 100644 index 000000000..0a7b45966 --- /dev/null +++ b/packages/migrations/src/migrations/20241204092236_add_hebergement_statut.js @@ -0,0 +1,50 @@ +/** + * @param { import("knex").Knex } knex + * @returns { Promise<void> } + */ +exports.up = function (knex) { + return knex.raw(` +CREATE TABLE front.hebergement_statut ( + id serial NOT NULL, + value VARCHAR(100), + CONSTRAINT pk_hebergement_statut PRIMARY KEY (id) +); + +INSERT INTO + front.hebergement_statut (value) +VALUES + ('brouillon'), + ('actif'), + ('desactive'); + +ALTER TABLE front.hebergement +ADD COLUMN statut_id INTEGER REFERENCES front.hebergement_statut (id); + +UPDATE front.hebergement +SET + statut_id = ( + SELECT + id + FROM + front.hebergement_statut + WHERE + value = 'actif' + ) ; + +GRANT ALL ON TABLE front.hebergement_statut TO vao_u; +GRANT ALL ON sequence front.hebergement_statut_id_seq TO vao_u; + `); +}; + +/** + * @param { import("knex").Knex } knex + * @returns { Promise<void> } + */ +exports.down = function (knex) { + return knex.raw(` +ALTER TABLE front.hebergement +DROP COLUMN statut_id; + +DROP TABLE front.hebergement_statut; + `); +}; diff --git a/packages/migrations/src/migrations/20241210100043_update_documents_with_user_id.js b/packages/migrations/src/migrations/20241210100043_update_documents_with_user_id.js new file mode 100644 index 000000000..9d728913d --- /dev/null +++ b/packages/migrations/src/migrations/20241210100043_update_documents_with_user_id.js @@ -0,0 +1,25 @@ +/** + * @param { import("knex").Knex } knex + * @returns { Promise<void> } + */ +exports.up = async function (knex) { + await knex.schema.alterTable("doc.documents", (table) => { + table + .integer("user_id") + .nullable() + .references("id") + .inTable("front.users") + .onDelete("SET NULL"); + }); +}; + +/** + * @param { import("knex").Knex } knex + * @returns { Promise<void> } + */ +exports.down = async function (knex) { + await knex.schema.alterTable("doc.documents", (table) => { + table.dropForeign(["user_id"]); + table.dropColumn("user_id"); + }); +}; diff --git a/packages/shared/src/components/Hebergement.vue b/packages/shared/src/components/Hebergement.vue index daf86a03b..4cf661fc1 100644 --- a/packages/shared/src/components/Hebergement.vue +++ b/packages/shared/src/components/Hebergement.vue @@ -37,7 +37,7 @@ ? 'Nouvelle adresse de l\'hébergement *' : 'Adresse de l\'hébergement *' " - :initial-adress="initHebergement.coordonnees?.adresse.label" + :initial-adress="initHebergement.coordonnees?.adresse?.label" :adresse="adresse" :error-message="adresseErrorMessage" :on-addresse-change="onAdresseChange" @@ -59,6 +59,7 @@ :error-message="numTelephone1ErrorMessage" hint="Le numéro de téléphone saisi doit être valide. Exemple : 0612345678" placeholder="" + required :disabled="isDisabled" @update:model-value="onNumTelephone1Change" /> @@ -87,7 +88,6 @@ :error-message="emailErrorMessage" hint="Format attendu : nom@domaine.fr" placeholder="" - required :disabled="isDisabled" @update:model-value="onEmailChange" /> @@ -522,32 +522,55 @@ @update:model-value="onExcursionChange" /> </div> - <div class="fr-fieldset__element"> - <div class="fr-grid-row"> - <div v-if="!props.isDownloading" class="fr-col-4"> - <div class="fr-input-group"> - <nuxt-link :to="backRoute" class="back-button"> - <DsfrButton type="button" secondary>Retour</DsfrButton> - </nuxt-link> - </div> - </div> - <div v-if="isSaveVisible" class="fr-col-4"> - <div class="fr-input-group"> - <DsfrButton - v-if="!props.isDownloading" - id="next-step" - :disabled="!meta.valid" - type="button" - @click="submit" - >{{ labelNext }} - </DsfrButton> - <is-downloading - :is-downloading="props.isDownloading" - :message="props.message" - /> - </div> - </div> + <div class="button-container"> + <nuxt-link :to="backRoute" class="back-button"> + <DsfrButton type="button" secondary>Retour</DsfrButton> + </nuxt-link> + <div + v-if="isSaveVisible && !props.isDownloading" + class="crud-button-container" + > + <DsfrButton + v-if=" + props.modeBrouillonActivated && + (!hebergementStatut || + hebergementStatut === hebergementUtils.statut.BROUILLON) + " + id="next-step" + type="button" + @click="submitBrouillon" + >Enregistrer le brouillon + </DsfrButton> + <DsfrButton + v-if=" + props.modeBrouillonActivated && + hebergementStatut && + hebergementStatut === hebergementUtils.statut.BROUILLON + " + id="next-step" + :disabled="!meta.valid" + type="button" + @click="activate" + >Activer l'hebergement + </DsfrButton> + <DsfrButton + v-if=" + !hebergementStatut || + hebergementStatut === hebergementUtils.statut.ACTIF + " + id="next-step" + :disabled="!meta.valid" + type="button" + @click="submit" + >{{ + hebergementId ? "Modifier l'hebergement" : "Ajouter l'hebergement" + }} + </DsfrButton> </div> + <is-downloading + :is-downloading="props.isDownloading" + :message="props.message" + /> </div> </DsfrFieldset> </div> @@ -558,10 +581,10 @@ import { useField, useForm } from "vee-validate"; import * as yup from "yup"; import dayjs from "dayjs"; import { - DsfrInputGroup, DsfrButton, - DsfrRadioButtonSet, DsfrCheckboxSet, + DsfrInputGroup, + DsfrRadioButtonSet, } from "@gouvminint/vue-dsfr"; import IsDownloading from "./IsDownloading.vue"; import hebergementUtils from "../utils/hebergement"; @@ -570,7 +593,7 @@ import createLogger from "../utils/createLogger"; const toaster = useToaster(); -const emit = defineEmits(["cancel", "submit"]); +const emit = defineEmits(["cancel", "submit", "submit-brouillon", "activate"]); const ouiNonOptions = [ { @@ -589,12 +612,12 @@ const props = defineProps({ default: () => ({}), }, isDisabled: { type: Boolean, default: false }, - labelNext: { type: String, default: "Ajouter hébergement" }, isDownloading: { type: Boolean, default: false }, message: { type: String, required: false, default: null }, isSaveVisible: { type: Boolean, default: false }, defaultBackRoute: { type: String, required: true }, cdnUrl: { type: String, required: true }, + modeBrouillonActivated: { type: Boolean, default: false }, }); const logger = createLogger("vao-shared"); @@ -602,6 +625,9 @@ const log = logger("components/hebergement"); const validationSchema = yup.object(hebergementUtils.schema); +const hebergementId = computed(() => props.initHebergement.id); +const hebergementStatut = computed(() => props.initHebergement.statut); + const initialValues = { nom: props.initHebergement.nom ?? null, coordonnees: { @@ -853,9 +879,8 @@ function verifFormatFile(file, toasterMessage) { } } -function submit() { - // Vérification du format des fichiers avant enregistrement - if ( +const formatFilesOk = computed( + () => (reglementationErp.value === true && verifFormatFile( fileDernierArreteAutorisationMaire, @@ -869,8 +894,22 @@ function submit() { verifFormatFile( fileReponseExploitantOuProprietaire, "La réponse de l'exploitant/propriétaire", - )) - ) { + )), +); + +function activate() { + if (formatFilesOk.value) { + emit("activate", { ...toRaw(values) }); + } +} + +function submitBrouillon() { + emit("submit-brouillon", { ...toRaw(values) }); +} + +function submit() { + // Vérification du format des fichiers avant enregistrement + if (formatFilesOk.value) { log.i("submit", { ...toRaw(values) }); emit("submit", { ...toRaw(values) }); } @@ -881,4 +920,18 @@ function submit() { .back-button { background-image: none; } + +.button-container { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0.5rem; +} + +.crud-button-container { + display: flex; + align-items: center; + justify-content: center; + gap: 1rem; +} </style> diff --git a/packages/shared/src/components/RangeDatePicker.vue b/packages/shared/src/components/RangeDatePicker.vue new file mode 100644 index 000000000..98bb26ba3 --- /dev/null +++ b/packages/shared/src/components/RangeDatePicker.vue @@ -0,0 +1,83 @@ +<template> + <div> + <span class="fr-label">{{ label }}</span> + <VDatePicker + is-range + :model-value="modelValue" + timezone="UTC" + @update:model-value="$emit('update:modelValue', $event)" + > + <template #default="{ togglePopover, inputValue }"> + <div + class="flex rounded-lg border border-gray-300 dark:border-gray-600 overflow-hidden date-range-input" + > + <DsfrButton + class="date-range-input-button" + title="Choix de la date de l'eig" + icon="fr-icon-calendar-line" + size="medium" + tertiary + no-outline + type="button" + @click="togglePopover" + ><span v-if="inputValue.start" class="date-range-input-label"> + {{ inputValue.start }} -> {{ inputValue.end }}</span + > + <span v-else class="date-range-input-label-unselect"> + {{ placeholder }}</span + > + </DsfrButton> + <DsfrButton + label="Réinitialiser le choix des dates" + icon="fr-icon-delete-line" + icon-only + tertiary + no-outline + size="small" + type="button" + @click="$emit('update:modelValue', null)" + /> + </div> + </template> + </VDatePicker> + </div> +</template> + +<script setup> +defineEmits(["update:modelValue"]); +defineProps({ + label: { type: String, required: true }, + placeholder: { type: String, default: "Sélectionner une date" }, + modelValue: { type: Object, default: null }, +}); +</script> + +<style scoped> +.date-range-input { + background-color: var(--background-contrast-grey); + border-radius: 0.25rem 0.25rem 0 0; + box-shadow: inset 0 -2px 0 0 var(--border-plain-grey); + color: var(--text-default-grey); + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; + padding-bottom: 3px; + width: fit-content; +} + +.date-range-input-button { + width: 22rem; +} + +.date-range-input-label { + color: black; + font-weight: normal; +} + +.date-range-input-label-unselect { + font-weight: normal; + color: var(--text-default-grey); + font-style: italic; +} +</style> diff --git a/packages/shared/src/components/eig/EigStatusBadge.vue b/packages/shared/src/components/eig/EigStatusBadge.vue index e6d868788..be591100c 100644 --- a/packages/shared/src/components/eig/EigStatusBadge.vue +++ b/packages/shared/src/components/eig/EigStatusBadge.vue @@ -1,10 +1,25 @@ <template> <DsfrBadge + v-if="props.statut === eigModel.Statuts.BROUILLON" :small="small" - :type="type" + type="new" :label="props.statut" class="pointer" /> + <div v-else class="container"> + <DsfrBadge + :small="small" + :type="dreets.isRead ? 'success' : 'new'" + :label="`${dreets.isRead ? 'Lu' : 'Non lu'} par la DREETS ${dreets.territoireCode}`" + class="pointer" + /> + <DsfrBadge + :small="small" + :type="ddets.isRead ? 'success' : 'new'" + :label="`${ddets.isRead ? 'Lu' : 'Non lu'} par la DDETS ${ddets.territoireCode}`" + class="pointer" + /> + </div> </template> <script setup> @@ -18,18 +33,17 @@ const props = defineProps({ validator: (value) => Object.values(eigModel.Statuts).includes(value), }, small: { default: true, type: Boolean }, -}); - -const type = computed(() => { - switch (props.statut) { - case eigModel.Statuts.BROUILLON: - return "new"; - case eigModel.Statuts.ENVOYE: - return "success"; - case eigModel.Statuts.LU: - return "info"; - default: - return "union"; - } + dreets: { type: Object, required: true }, + ddets: { type: Object, required: true }, }); </script> + +<style scoped> +.container { + display: flex; + flex-direction: column; + align-items: start; + justify-content: start; + gap: 10px; +} +</style> diff --git a/packages/shared/src/components/eig/EigTypeListe.vue b/packages/shared/src/components/eig/EigTypeListe.vue new file mode 100644 index 000000000..9f7659db7 --- /dev/null +++ b/packages/shared/src/components/eig/EigTypeListe.vue @@ -0,0 +1,21 @@ +<template> + <div class="container"> + <span v-for="(type, idx) in types" :key="idx" role="listitem"> + {{ type }} + </span> + </div> +</template> + +<script setup> +defineProps({ + types: { type: Array, default: () => [] }, +}); +</script> + +<style scoped> +.container { + display: flex; + flex-direction: column; + align-items: flex-start; +} +</style> diff --git a/packages/shared/src/components/eig/Summary.vue b/packages/shared/src/components/eig/Summary.vue index 838d83fdc..7b388f749 100644 --- a/packages/shared/src/components/eig/Summary.vue +++ b/packages/shared/src/components/eig/Summary.vue @@ -1,9 +1,12 @@ <template> <div class="fr-col-7"> <div v-for="detail in currentEigValues" :key="detail.label"> - <strong>{{ detail.label }} : </strong - ><span v-if="typeof detail.value === 'string'">{{ detail.value }}</span> - <div v-if="Array.isArray(detail.value)"> + <strong>{{ detail.label }} : </strong> + <a v-if="!!detail.href" :href="detail.href">{{ detail.value }}</a> + <span v-else-if="typeof detail.value === 'string'">{{ + detail.value + }}</span> + <div v-else-if="Array.isArray(detail.value)"> <ul> <li v-for="item in detail.value" :key="item">{{ item }}</li> </ul> @@ -16,23 +19,33 @@ import dayjs from "dayjs"; import { mapEigToLabel } from "../../utils/eigUtils"; -const props = defineProps({ eig: { type: Object, required: true } }); +const props = defineProps({ + eig: { type: Object, required: true }, + env: { + type: String, + required: true, + validator: (value) => ["BO", "USAGER"].includes(value), + }, +}); const currentEigValues = computed(() => [ + { + label: "Organisme", + value: props.eig.raisonSociale ?? `${props.eig?.prenom} ${props.eig?.nom}`, + }, { label: "Déclaration", + href: props.eig?.declarationId + ? `/${props.env === "BO" ? "sejours" : "demande-sejour"}/${props.eig.declarationId}` + : "", value: props.eig?.idFonctionnelle ?? "", }, { - label: "Séjour", + label: "Nom du séjour", value: props.eig?.libelle ?? "", }, { - label: "Organisme", - value: props.eig.raisonSociale ?? `${props.eig?.prenom} ${props.eig?.nom}`, - }, - { - label: "Date (début / fin)", + label: "Date du séjour", value: props.eig.dateDebut && props.eig.dateFin ? `${dayjs(props.eig.dateDebut).format("DD/MM/YYYY")} - ${dayjs(props.eig.dateFin).format("DD/MM/YYYY")}` @@ -47,12 +60,24 @@ const currentEigValues = computed(() => [ value: props.eig?.adresses ?? [], }, { - label: "type d'événements", + label: "Date de l'incident", + value: props.eig?.date ? dayjs(props.eig?.date).format("DD/MM/YYYY") : "", + }, + { + label: "Département où a eu lieu l'incident", + value: `${props.eig?.departement ?? ""} ${props.eig?.departementLibelle ? " - " + props.eig?.departementLibelle : ""}`, + }, + { + label: "type(s) d'événement(s)", value: props.eig?.types?.map( (t) => mapEigToLabel[t.type] + (t.precision ? " : " + t.precision : ""), ) ?? [], }, + { + label: "Personnel présent lors de l'évènement", + value: props.eig?.personnel?.map((p) => `${p.prenom} ${p.nom}`) ?? [], + }, ]); </script> diff --git a/packages/shared/src/components/index.js b/packages/shared/src/components/index.js index b8c573352..d3081a03d 100644 --- a/packages/shared/src/components/index.js +++ b/packages/shared/src/components/index.js @@ -8,6 +8,7 @@ import DsfrTabsV2 from "./DsfrTabsV2.vue"; import TableWithBackendPagination from "./Table/TableWithBackendPagination.vue"; import ValidationModal from "./ValidationModal.vue"; import EigStatusBadge from "./eig/EigStatusBadge.vue"; +import EigTypeListe from "./eig/EigTypeListe.vue"; import Summary from "./eig/Summary.vue"; import IsDownloading from "./IsDownloading.vue"; import Hebergement from "./Hebergement.vue"; @@ -16,6 +17,7 @@ import CardNumber from "./CardNumber.vue"; import PasswordInput from "./PasswordInput.vue"; import TableFull from "./Table/TableFull.vue"; import MultiSelectOption from "./MultiSelectOption.vue"; +import RangeDatePicker from "./RangeDatePicker.vue"; import MessageHover from "./messages/MessageHover.vue"; import MessageEtat from "./messages/MessageEtat.vue"; export { @@ -37,6 +39,8 @@ export { PasswordInput, TableFull, MultiSelectOption, + EigTypeListe, + RangeDatePicker, MessageHover, MessageEtat, }; diff --git a/packages/shared/src/models/eig.js b/packages/shared/src/models/eig.js index f6d15af94..dd8e8f913 100644 --- a/packages/shared/src/models/eig.js +++ b/packages/shared/src/models/eig.js @@ -58,4 +58,8 @@ const UpdateTypes = { EMAIL_AUTRES_DESTINATAIRES: "EMAIL_AUTRES_DESTINATAIRES", }; -export { Statuts, Categorie, Types, UpdateTypes }; +const isTypeActive = (type) => { + return ![Types[Categorie.VICTIMES].VIOLS].includes(type); +}; + +export { Statuts, Categorie, Types, UpdateTypes, isTypeActive }; diff --git a/packages/shared/src/schema/eig.js b/packages/shared/src/schema/eig.js index 7362ae99c..6ed16fae7 100644 --- a/packages/shared/src/schema/eig.js +++ b/packages/shared/src/schema/eig.js @@ -10,6 +10,7 @@ const selectionSejourSchema = { .integer("Ce champ doit contenir un nombre entier") .required(), departement: yup.string().required("ce champ est obligatoire"), + date: yup.date().typeError("La date n'est pas au format attendu").required(), }; const eigTypeBase = yup diff --git a/packages/shared/src/utils/hebergement.js b/packages/shared/src/utils/hebergement.js index e0e6cf64f..8fbf37664 100644 --- a/packages/shared/src/utils/hebergement.js +++ b/packages/shared/src/utils/hebergement.js @@ -45,6 +45,12 @@ const accessibiliteOptions = [ { label: "Autre (précisez ci-dessous)", value: "commentaires" }, ]; +const statut = { + BROUILLON: "brouillon", + ACTIF: "actif", + DESACTIVE: "desactive", +}; + const numTelephoneRegex = /^(\+33|0|0033)[1-9][0-9]{8}$/i; const coordonneesSchema = { @@ -236,4 +242,5 @@ export default { informationsLocauxSchema, informationsTransportSchema, schema, + statut, }; diff --git a/yarn.lock b/yarn.lock index a633f7dc1..e44e87250 100644 --- a/yarn.lock +++ b/yarn.lock @@ -611,11 +611,25 @@ "@babel/highlight" "^7.24.7" picocolors "^1.0.0" +"@babel/code-frame@^7.25.9", "@babel/code-frame@^7.26.0": + version "7.26.2" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.26.2.tgz#4b5fab97d33338eff916235055f0ebc21e573a85" + integrity sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ== + dependencies: + "@babel/helper-validator-identifier" "^7.25.9" + js-tokens "^4.0.0" + picocolors "^1.0.0" + "@babel/compat-data@^7.25.2": version "7.25.4" resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.4.tgz" integrity sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ== +"@babel/compat-data@^7.25.9": + version "7.26.2" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.26.2.tgz#278b6b13664557de95b8f35b90d96785850bb56e" + integrity sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg== + "@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.18.5", "@babel/core@^7.23.0", "@babel/core@^7.23.7", "@babel/core@^7.23.9", "@babel/core@^7.24.7", "@babel/core@^7.25.2": version "7.25.2" resolved "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz" @@ -637,6 +651,27 @@ json5 "^2.2.3" semver "^6.3.1" +"@babel/core@^7.25.7": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.26.0.tgz#d78b6023cc8f3114ccf049eb219613f74a747b40" + integrity sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.26.0" + "@babel/generator" "^7.26.0" + "@babel/helper-compilation-targets" "^7.25.9" + "@babel/helper-module-transforms" "^7.26.0" + "@babel/helpers" "^7.26.0" + "@babel/parser" "^7.26.0" + "@babel/template" "^7.25.9" + "@babel/traverse" "^7.25.9" + "@babel/types" "^7.26.0" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + "@babel/generator@^7.25.0", "@babel/generator@^7.25.6", "@babel/generator@^7.7.2": version "7.25.6" resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.25.6.tgz" @@ -647,6 +682,17 @@ "@jridgewell/trace-mapping" "^0.3.25" jsesc "^2.5.1" +"@babel/generator@^7.25.9", "@babel/generator@^7.26.0": + version "7.26.2" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.26.2.tgz#87b75813bec87916210e5e01939a4c823d6bb74f" + integrity sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw== + dependencies: + "@babel/parser" "^7.26.2" + "@babel/types" "^7.26.0" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + jsesc "^3.0.2" + "@babel/helper-annotate-as-pure@^7.24.7": version "7.24.7" resolved "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz" @@ -665,6 +711,17 @@ lru-cache "^5.1.1" semver "^6.3.1" +"@babel/helper-compilation-targets@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz#55af025ce365be3cdc0c1c1e56c6af617ce88875" + integrity sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ== + dependencies: + "@babel/compat-data" "^7.25.9" + "@babel/helper-validator-option" "^7.25.9" + browserslist "^4.24.0" + lru-cache "^5.1.1" + semver "^6.3.1" + "@babel/helper-create-class-features-plugin@^7.24.7", "@babel/helper-create-class-features-plugin@^7.25.0": version "7.25.4" resolved "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.4.tgz" @@ -694,6 +751,14 @@ "@babel/traverse" "^7.24.7" "@babel/types" "^7.24.7" +"@babel/helper-module-imports@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz#e7f8d20602ebdbf9ebbea0a0751fb0f2a4141715" + integrity sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw== + dependencies: + "@babel/traverse" "^7.25.9" + "@babel/types" "^7.25.9" + "@babel/helper-module-transforms@^7.25.2": version "7.25.2" resolved "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz" @@ -704,6 +769,15 @@ "@babel/helper-validator-identifier" "^7.24.7" "@babel/traverse" "^7.25.2" +"@babel/helper-module-transforms@^7.26.0": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz#8ce54ec9d592695e58d84cd884b7b5c6a2fdeeae" + integrity sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw== + dependencies: + "@babel/helper-module-imports" "^7.25.9" + "@babel/helper-validator-identifier" "^7.25.9" + "@babel/traverse" "^7.25.9" + "@babel/helper-optimise-call-expression@^7.24.7": version "7.24.7" resolved "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.24.7.tgz" @@ -746,16 +820,31 @@ resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz" integrity sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ== +"@babel/helper-string-parser@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz#1aabb72ee72ed35789b4bbcad3ca2862ce614e8c" + integrity sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA== + "@babel/helper-validator-identifier@^7.24.5", "@babel/helper-validator-identifier@^7.24.7": version "7.24.7" resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz" integrity sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w== +"@babel/helper-validator-identifier@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz#24b64e2c3ec7cd3b3c547729b8d16871f22cbdc7" + integrity sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ== + "@babel/helper-validator-option@^7.24.8": version "7.24.8" resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz" integrity sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q== +"@babel/helper-validator-option@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz#86e45bd8a49ab7e03f276577f96179653d41da72" + integrity sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw== + "@babel/helpers@^7.25.0": version "7.25.6" resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.6.tgz" @@ -764,6 +853,14 @@ "@babel/template" "^7.25.0" "@babel/types" "^7.25.6" +"@babel/helpers@^7.26.0": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.26.0.tgz#30e621f1eba5aa45fe6f4868d2e9154d884119a4" + integrity sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw== + dependencies: + "@babel/template" "^7.25.9" + "@babel/types" "^7.26.0" + "@babel/highlight@^7.24.7": version "7.24.7" resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz" @@ -781,6 +878,13 @@ dependencies: "@babel/types" "^7.25.6" +"@babel/parser@^7.25.9", "@babel/parser@^7.26.0", "@babel/parser@^7.26.2": + version "7.26.2" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.2.tgz#fd7b6f487cfea09889557ef5d4eeb9ff9a5abd11" + integrity sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ== + dependencies: + "@babel/types" "^7.26.0" + "@babel/plugin-proposal-decorators@^7.23.0": version "7.24.7" resolved "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.24.7.tgz" @@ -927,11 +1031,23 @@ "@babel/helper-skip-transparent-expression-wrappers" "^7.24.7" "@babel/plugin-syntax-typescript" "^7.24.7" +"@babel/runtime@^7.21.0": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.0.tgz#8600c2f595f277c60815256418b85356a65173c1" + integrity sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/standalone@^7.23.8": version "7.25.6" resolved "https://registry.npmjs.org/@babel/standalone/-/standalone-7.25.6.tgz" integrity sha512-Kf2ZcZVqsKbtYhlA7sP0z5A3q5hmCVYMKMWRWNK/5OVwHIve3JY1djVRmIVAx8FMueLIfZGKQDIILK2w8zO4mg== +"@babel/standalone@^7.25.7": + version "7.26.2" + resolved "https://registry.yarnpkg.com/@babel/standalone/-/standalone-7.26.2.tgz#a1fdf2d477a1f3d2828f0551b5dc14c44d4e127f" + integrity sha512-i2VbegsRfwa9yq3xmfDX3tG2yh9K0cCqwpSyVG2nPxifh0EOnucAZUeO/g4lW2Zfg03aPJNtPfxQbDHzXc7H+w== + "@babel/template@^7.25.0", "@babel/template@^7.3.3": version "7.25.0" resolved "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz" @@ -941,6 +1057,15 @@ "@babel/parser" "^7.25.0" "@babel/types" "^7.25.0" +"@babel/template@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.25.9.tgz#ecb62d81a8a6f5dc5fe8abfc3901fc52ddf15016" + integrity sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg== + dependencies: + "@babel/code-frame" "^7.25.9" + "@babel/parser" "^7.25.9" + "@babel/types" "^7.25.9" + "@babel/traverse@^7.24.7", "@babel/traverse@^7.24.8", "@babel/traverse@^7.25.0", "@babel/traverse@^7.25.2", "@babel/traverse@^7.25.3", "@babel/traverse@^7.25.4", "@babel/traverse@^7.25.6": version "7.25.6" resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.6.tgz" @@ -954,6 +1079,19 @@ debug "^4.3.1" globals "^11.1.0" +"@babel/traverse@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.9.tgz#a50f8fe49e7f69f53de5bea7e413cd35c5e13c84" + integrity sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw== + dependencies: + "@babel/code-frame" "^7.25.9" + "@babel/generator" "^7.25.9" + "@babel/parser" "^7.25.9" + "@babel/template" "^7.25.9" + "@babel/types" "^7.25.9" + debug "^4.3.1" + globals "^11.1.0" + "@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.23.6", "@babel/types@^7.24.7", "@babel/types@^7.24.8", "@babel/types@^7.25.0", "@babel/types@^7.25.2", "@babel/types@^7.25.4", "@babel/types@^7.25.6", "@babel/types@^7.3.3": version "7.25.6" resolved "https://registry.npmjs.org/@babel/types/-/types-7.25.6.tgz" @@ -963,6 +1101,14 @@ "@babel/helper-validator-identifier" "^7.24.7" to-fast-properties "^2.0.0" +"@babel/types@^7.25.7", "@babel/types@^7.25.9", "@babel/types@^7.26.0": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.0.tgz#deabd08d6b753bc8e0f198f8709fb575e31774ff" + integrity sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA== + dependencies: + "@babel/helper-string-parser" "^7.25.9" + "@babel/helper-validator-identifier" "^7.25.9" + "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz" @@ -2158,6 +2304,32 @@ unimport "^3.11.1" untyped "^1.4.2" +"@nuxt/kit@^3.12.3": + version "3.14.1592" + resolved "https://registry.yarnpkg.com/@nuxt/kit/-/kit-3.14.1592.tgz#f959a269424c1ee313585a46112e474b6ccab3bc" + integrity sha512-r9r8bISBBisvfcNgNL3dSIQHSBe0v5YkX5zwNblIC2T0CIEgxEVoM5rq9O5wqgb5OEydsHTtT2hL57vdv6VT2w== + dependencies: + "@nuxt/schema" "3.14.1592" + c12 "^2.0.1" + consola "^3.2.3" + defu "^6.1.4" + destr "^2.0.3" + globby "^14.0.2" + hash-sum "^2.0.0" + ignore "^6.0.2" + jiti "^2.4.0" + klona "^2.0.6" + knitwork "^1.1.0" + mlly "^1.7.3" + pathe "^1.1.2" + pkg-types "^1.2.1" + scule "^1.3.0" + semver "^7.6.3" + ufo "^1.5.4" + unctx "^2.3.1" + unimport "^3.13.2" + untyped "^1.5.1" + "@nuxt/schema@3.13.1", "@nuxt/schema@^3.13.1": version "3.13.1" resolved "https://registry.npmjs.org/@nuxt/schema/-/schema-3.13.1.tgz" @@ -2176,6 +2348,25 @@ unimport "^3.11.1" untyped "^1.4.2" +"@nuxt/schema@3.14.1592": + version "3.14.1592" + resolved "https://registry.yarnpkg.com/@nuxt/schema/-/schema-3.14.1592.tgz#38c5c0af51d0b95e011db6c332f578aac97c8c82" + integrity sha512-A1d/08ueX8stTXNkvGqnr1eEXZgvKn+vj6s7jXhZNWApUSqMgItU4VK28vrrdpKbjIPwq2SwhnGOHUYvN9HwCQ== + dependencies: + c12 "^2.0.1" + compatx "^0.1.8" + consola "^3.2.3" + defu "^6.1.4" + hookable "^5.5.3" + pathe "^1.1.2" + pkg-types "^1.2.1" + scule "^1.3.0" + std-env "^3.8.0" + ufo "^1.5.4" + uncrypto "^0.1.3" + unimport "^3.13.2" + untyped "^1.5.1" + "@nuxt/telemetry@^2.5.4": version "2.6.0" resolved "https://registry.npmjs.org/@nuxt/telemetry/-/telemetry-2.6.0.tgz" @@ -2675,6 +2866,11 @@ resolved "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.25.tgz" integrity sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ== +"@popperjs/core@^2.11.8": + version "2.11.8" + resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f" + integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A== + "@prisma/instrumentation@5.19.1": version "5.19.1" resolved "https://registry.npmjs.org/@prisma/instrumentation/-/instrumentation-5.19.1.tgz" @@ -2765,6 +2961,15 @@ estree-walker "^2.0.2" picomatch "^2.3.1" +"@rollup/pluginutils@^5.1.3": + version "5.1.3" + resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-5.1.3.tgz#3001bf1a03f3ad24457591f2c259c8e514e0dbdf" + integrity sha512-Pnsb6f32CD2W3uCaLZIzDmeFyQ2b8UWMFI7xtwUezpcGBDVDW6y9XgAWIlARiGAo6eNF5FK5aQTr0LFyNyqq5A== + dependencies: + "@types/estree" "^1.0.0" + estree-walker "^2.0.2" + picomatch "^4.0.2" + "@rollup/rollup-android-arm-eabi@4.22.4": version "4.22.4" resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.4.tgz#8b613b9725e8f9479d142970b106b6ae878610d5" @@ -2845,6 +3050,15 @@ resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.22.4.tgz#3dd5d53e900df2a40841882c02e56f866c04d202" integrity sha512-j8pPKp53/lq9lMXN57S8cFz0MynJk8OWNuUnXct/9KCpKU7DgU3bYMJhwWmcqC0UU29p8Lr0/7KEVcaM6bf47Q== +"@samk-dev/nuxt-vcalendar@^1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@samk-dev/nuxt-vcalendar/-/nuxt-vcalendar-1.0.4.tgz#8fe82e54ea7d6f3b8447ec285dc92d99f925e28c" + integrity sha512-vl2VzELpqSheJmuG1ZehggieEb2E8YRkw+DDVl2UrLBj6z1ll3CHL0Kjrjg5Bg1UylmQO3yBdpTyjESP/tSj/Q== + dependencies: + "@nuxt/kit" "^3.12.3" + "@popperjs/core" "^2.11.8" + v-calendar "^3.1.2" + "@sentry-internal/browser-utils@8.31.0": version "8.31.0" resolved "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-8.31.0.tgz" @@ -3739,6 +3953,11 @@ resolved "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz" integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== +"@types/lodash@^4.14.165": + version "4.17.13" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.13.tgz#786e2d67cfd95e32862143abe7463a7f90c300eb" + integrity sha512-lfx+dftrEZcdBPczf9d0Qv0x+j/rfNCMuC6OcfXmO8gkfeNAY88PgKUbvG56whcN23gc27yenwF6oJZXGFpYxg== + "@types/luxon@~3.4.0": version "3.4.2" resolved "https://registry.npmjs.org/@types/luxon/-/luxon-3.4.2.tgz" @@ -3807,6 +4026,11 @@ pg-protocol "*" pg-types "^2.2.0" +"@types/resize-observer-browser@^0.1.7": + version "0.1.11" + resolved "https://registry.yarnpkg.com/@types/resize-observer-browser/-/resize-observer-browser-0.1.11.tgz#d3c98d788489d8376b7beac23863b1eebdd3c13c" + integrity sha512-cNw5iH8JkMkb3QkCoe7DaZiawbDQEUX8t7iuQaRTyLOyQCR2h+ibBD4GJt7p5yhUHrlOeL7ZtbxNHeipqNsBzQ== + "@types/resolve@1.20.2": version "1.20.2" resolved "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz" @@ -4380,6 +4604,11 @@ acorn@^7.1.1: resolved "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== +acorn@^8.14.0: + version "8.14.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.0.tgz#063e2c70cac5fb4f6467f0b11152e04c682795b0" + integrity sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA== + agent-base@6: version "6.0.2" resolved "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz" @@ -4887,6 +5116,16 @@ browserslist@^4.0.0, browserslist@^4.23.1, browserslist@^4.23.3: node-releases "^2.0.18" update-browserslist-db "^1.1.0" +browserslist@^4.24.0: + version "4.24.2" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.2.tgz#f5845bc91069dbd55ee89faf9822e1d885d16580" + integrity sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg== + dependencies: + caniuse-lite "^1.0.30001669" + electron-to-chromium "^1.5.41" + node-releases "^2.0.18" + update-browserslist-db "^1.1.1" + bser@2.1.1: version "2.1.1" resolved "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz" @@ -4974,6 +5213,24 @@ c12@^1.11.1, c12@^1.11.2: pkg-types "^1.2.0" rc9 "^2.1.2" +c12@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/c12/-/c12-2.0.1.tgz#5702d280b31a08abba39833494c9b1202f0f5aec" + integrity sha512-Z4JgsKXHG37C6PYUtIxCfLJZvo6FyhHJoClwwb9ftUkLpPSkuYqn6Tr+vnaN8hymm0kIbcg6Ey3kv/Q71k5w/A== + dependencies: + chokidar "^4.0.1" + confbox "^0.1.7" + defu "^6.1.4" + dotenv "^16.4.5" + giget "^1.2.3" + jiti "^2.3.0" + mlly "^1.7.1" + ohash "^1.1.4" + pathe "^1.1.2" + perfect-debounce "^1.0.0" + pkg-types "^1.2.0" + rc9 "^2.1.2" + cac@^6.7.14: version "6.7.14" resolved "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz" @@ -5020,6 +5277,11 @@ caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001646: resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001660.tgz" integrity sha512-GacvNTTuATm26qC74pt+ad1fW15mlQ/zuTzzY1ZoIzECTP8HURDfF43kNxPgf7H1jmelCBQTTbBNxdSXOA7Bqg== +caniuse-lite@^1.0.30001669: + version "1.0.30001685" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001685.tgz#2d10d36c540a9a5d47ad6ab9e1ed5f61fdeadd8c" + integrity sha512-e/kJN1EMyHQzgcMEEgoo+YTCO1NGCmIYHk5Qk8jT6AazWemS5QFKJ5ShCJlH3GZrNIdZofcNCEwZqbMjjKzmnA== + caseless@~0.12.0: version "0.12.0" resolved "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz" @@ -5067,7 +5329,7 @@ chokidar@^3.5.1, chokidar@^3.5.2, chokidar@^3.5.3, chokidar@^3.6.0: optionalDependencies: fsevents "~2.3.2" -chokidar@^4.0.0: +chokidar@^4.0.0, chokidar@^4.0.1: version "4.0.1" resolved "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz" integrity sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA== @@ -5325,6 +5587,11 @@ confbox@^0.1.7: resolved "https://registry.npmjs.org/confbox/-/confbox-0.1.7.tgz" integrity sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA== +confbox@^0.1.8: + version "0.1.8" + resolved "https://registry.yarnpkg.com/confbox/-/confbox-0.1.8.tgz#820d73d3b3c82d9bd910652c5d4d599ef8ff8b06" + integrity sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w== + consola@^3.2.3: version "3.2.3" resolved "https://registry.npmjs.org/consola/-/consola-3.2.3.tgz" @@ -5685,6 +5952,18 @@ data-view-byte-offset@^1.0.0: es-errors "^1.3.0" is-data-view "^1.0.1" +date-fns-tz@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/date-fns-tz/-/date-fns-tz-2.0.1.tgz#0a9b2099031c0d74120b45de9fd23192e48ea495" + integrity sha512-fJCG3Pwx8HUoLhkepdsP7Z5RsucUi+ZBOxyM5d0ZZ6c4SdYustq0VMmOu6Wf7bli+yS/Jwp91TOCqn9jMcVrUA== + +date-fns@^2.16.1: + version "2.30.0" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.30.0.tgz#f367e644839ff57894ec6ac480de40cae4b0f4d0" + integrity sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw== + dependencies: + "@babel/runtime" "^7.21.0" + dayjs@^1.11.10, dayjs@^1.11.11, dayjs@~1.11.11: version "1.11.13" resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz" @@ -6037,6 +6316,11 @@ electron-to-chromium@^1.5.4: resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.19.tgz" integrity sha512-kpLJJi3zxTR1U828P+LIUDZ5ohixyo68/IcYOHLqnbTPr/wdgn4i1ECvmALN9E16JPA6cvCG5UG79gVwVdEK5w== +electron-to-chromium@^1.5.41: + version "1.5.67" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.67.tgz#66ebd2be4a77469ac2760ef5e9e460ba9a43a845" + integrity sha512-nz88NNBsD7kQSAGGJyp8hS6xSPtWwqNogA0mjtc2nUYeEf3nURK9qpV18TuBdDmEDgVWotS8Wkzf+V52dSQ/LQ== + emittery@^0.13.1: version "0.13.1" resolved "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz" @@ -6304,7 +6588,7 @@ esbuild@^0.23.1: "@esbuild/win32-ia32" "0.23.1" "@esbuild/win32-x64" "0.23.1" -escalade@^3.1.1, escalade@^3.1.2: +escalade@^3.1.1, escalade@^3.1.2, escalade@^3.2.0: version "3.2.0" resolved "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz" integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== @@ -6911,6 +7195,11 @@ fdir@^6.2.0, fdir@^6.3.0: resolved "https://registry.npmjs.org/fdir/-/fdir-6.3.0.tgz" integrity sha512-QOnuT+BOtivR77wYvCWHfGt9s4Pz1VIMbD463vegT5MLqNXy8rYFT/lPVEqf/bhYeT6qmqrNHhsX+rWwe3rOCQ== +fdir@^6.4.2: + version "6.4.2" + resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.4.2.tgz#ddaa7ce1831b161bc3657bb99cb36e1622702689" + integrity sha512-KnhMXsKSPZlAhp7+IjUkRZKPb4fUyccpDrdFXbi4QL1qkmFh9kVY09Yox+n4MaOb3lHZ1Tv829C3oaaXoMYPDQ== + file-entry-cache@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz" @@ -7628,6 +7917,11 @@ ignore@^5.2.0, ignore@^5.2.4, ignore@^5.3.1, ignore@^5.3.2: resolved "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz" integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== +ignore@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-6.0.2.tgz#77cccb72a55796af1b6d2f9eb14fa326d24f4283" + integrity sha512-InwqeHHN2XpumIkMvpl/DCJVrAHgCsG5+cn1XlnLWGwtZBm8QJfSusItfrwx81CTp5agNZqpKU2J/ccC5nGT4A== + image-meta@^0.2.1: version "0.2.1" resolved "https://registry.npmjs.org/image-meta/-/image-meta-0.2.1.tgz" @@ -8520,6 +8814,11 @@ jiti@^1.19.1, jiti@^1.21.0, jiti@^1.21.6: resolved "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz" integrity sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w== +jiti@^2.3.0, jiti@^2.3.1, jiti@^2.4.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/jiti/-/jiti-2.4.1.tgz#4de9766ccbfa941d9b6390d2b159a4b295a52e6b" + integrity sha512-yPBThwecp1wS9DmoA4x4KR2h3QoslacnDR8ypuFM962kI4/456Iy1oHx2RAgh4jfZNdn0bctsdadceiBUgpU1g== + js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" @@ -8530,6 +8829,11 @@ js-tokens@^9.0.0: resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.0.tgz" integrity sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ== +js-tokens@^9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-9.0.1.tgz#2ec43964658435296f6761b34e10671c2d9527f4" + integrity sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ== + js-yaml@^3.13.1: version "3.14.1" resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz" @@ -8834,6 +9138,14 @@ local-pkg@^0.5.0: mlly "^1.4.2" pkg-types "^1.0.3" +local-pkg@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/local-pkg/-/local-pkg-0.5.1.tgz#69658638d2a95287534d4c2fff757980100dbb6d" + integrity sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ== + dependencies: + mlly "^1.7.3" + pkg-types "^1.2.1" + locate-path@^5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz" @@ -8945,7 +9257,7 @@ lodash.upperfirst@^4.3.1: resolved "https://registry.npmjs.org/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz" integrity sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg== -lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.21: +lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21: version "4.17.21" resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -8999,6 +9311,13 @@ magic-string@^0.30.0, magic-string@^0.30.10, magic-string@^0.30.11, magic-string dependencies: "@jridgewell/sourcemap-codec" "^1.5.0" +magic-string@^0.30.14: + version "0.30.14" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.14.tgz#e9bb29870b81cfc1ec3cc656552f5a7fcbf19077" + integrity sha512-5c99P1WKTed11ZC0HMJOj6CDIue6F8ySu+bJL+85q1zBEIY8IklrJ1eiKC2NDRh3Ct3FcvmJPyQHb9erXMTJNw== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.0" + magicast@^0.3.5: version "0.3.5" resolved "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz" @@ -9251,6 +9570,16 @@ mlly@^1.3.0, mlly@^1.4.2, mlly@^1.6.1, mlly@^1.7.1: pkg-types "^1.1.1" ufo "^1.5.3" +mlly@^1.7.2, mlly@^1.7.3: + version "1.7.3" + resolved "https://registry.yarnpkg.com/mlly/-/mlly-1.7.3.tgz#d86c0fcd8ad8e16395eb764a5f4b831590cee48c" + integrity sha512-xUsx5n/mN0uQf4V548PKQ+YShA4/IW0KI1dZhrNrPCLG+xizETbHTkOa1f8/xut9JRPp8kQuMnz0oqwkTiLo/A== + dependencies: + acorn "^8.14.0" + pathe "^1.1.2" + pkg-types "^1.2.1" + ufo "^1.5.4" + module-details-from-path@^1.0.3: version "1.0.3" resolved "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.3.tgz" @@ -9747,6 +10076,11 @@ ohash@^1.1.3: resolved "https://registry.npmjs.org/ohash/-/ohash-1.1.3.tgz" integrity sha512-zuHHiGTYTA1sYJ/wZN+t5HKZaH23i4yI1HMwbuXm24Nid7Dv0KcuRlKoNKS9UNfAVSBlnGLcuQrnOKWOZoEGaw== +ohash@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/ohash/-/ohash-1.1.4.tgz#ae8d83014ab81157d2c285abf7792e2995fadd72" + integrity sha512-FlDryZAahJmEF3VR3w1KogSEdWX3WhA5GPakFx4J81kEAiHyLMpdLLElS8n8dfNadMgAne/MywcvmogzscVt4g== + on-finished@2.4.1: version "2.4.1" resolved "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz" @@ -10161,7 +10495,7 @@ picocolors@^1.0.0, picocolors@^1.0.1: resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz" integrity sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw== -picocolors@^1.1.1: +picocolors@^1.1.0, picocolors@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== @@ -10210,6 +10544,15 @@ pkg-types@^1.0.3, pkg-types@^1.1.1, pkg-types@^1.2.0: mlly "^1.7.1" pathe "^1.1.2" +pkg-types@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/pkg-types/-/pkg-types-1.2.1.tgz#6ac4e455a5bb4b9a6185c1c79abd544c901db2e5" + integrity sha512-sQoqa8alT3nHjGuTjuKgOnvjo4cljkufdtLMnO2LBP/wRwuDlo1tkaEdMxCRhyGRPacv/ztlZgDPm2b7FAmEvw== + dependencies: + confbox "^0.1.8" + mlly "^1.7.2" + pathe "^1.1.2" + playwright-core@1.47.0: version "1.47.0" resolved "https://registry.npmjs.org/playwright-core/-/playwright-core-1.47.0.tgz" @@ -10818,6 +11161,11 @@ refa@^0.12.0, refa@^0.12.1: dependencies: "@eslint-community/regexpp" "^4.8.0" +regenerator-runtime@^0.14.0: + version "0.14.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f" + integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw== + regexp-ast-analysis@^0.7.0, regexp-ast-analysis@^0.7.1: version "0.7.1" resolved "https://registry.npmjs.org/regexp-ast-analysis/-/regexp-ast-analysis-0.7.1.tgz" @@ -11506,6 +11854,11 @@ std-env@^3.7.0: resolved "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz" integrity sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg== +std-env@^3.8.0: + version "3.8.0" + resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.8.0.tgz#b56ffc1baf1a29dcc80a3bdf11d7fca7c315e7d5" + integrity sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w== + streamsearch@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz" @@ -11678,6 +12031,13 @@ strip-literal@^2.1.0: dependencies: js-tokens "^9.0.0" +strip-literal@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/strip-literal/-/strip-literal-2.1.1.tgz#26906e65f606d49f748454a08084e94190c2e5ad" + integrity sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q== + dependencies: + js-tokens "^9.0.1" + strnum@^1.0.5: version "1.0.5" resolved "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz" @@ -11901,6 +12261,14 @@ tinyglobby@0.2.5: fdir "^6.2.0" picomatch "^4.0.2" +tinyglobby@^0.2.10: + version "0.2.10" + resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.10.tgz#e712cf2dc9b95a1f5c5bbd159720e15833977a0f" + integrity sha512-Zc+8eJlFMvgatPZTl6A9L/yht8QqdmUNtURHaKZLmKBE12hNPSrqNkUp2cs3M/UKmNVVAMFQYSjYIVHDjW5zew== + dependencies: + fdir "^6.4.2" + picomatch "^4.0.2" + tinyglobby@^0.2.6: version "0.2.6" resolved "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.6.tgz" @@ -12256,6 +12624,26 @@ unimport@^3.11.1, unimport@^3.7.2: strip-literal "^2.1.0" unplugin "^1.12.2" +unimport@^3.13.2: + version "3.14.2" + resolved "https://registry.yarnpkg.com/unimport/-/unimport-3.14.2.tgz#e6af29595ee9f0e26b4ac7e52fa1ad0e8be8a2d6" + integrity sha512-FSxhbAylGGanyuTb3K0Ka3T9mnsD0+cRKbwOS11Li4Lh2whWS091e32JH4bIHrTckxlW9GnExAglADlxXjjzFw== + dependencies: + "@rollup/pluginutils" "^5.1.3" + acorn "^8.14.0" + escape-string-regexp "^5.0.0" + estree-walker "^3.0.3" + local-pkg "^0.5.1" + magic-string "^0.30.14" + mlly "^1.7.3" + pathe "^1.1.2" + picomatch "^4.0.2" + pkg-types "^1.2.1" + scule "^1.3.0" + strip-literal "^2.1.1" + tinyglobby "^0.2.10" + unplugin "^1.16.0" + union-value@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz" @@ -12327,6 +12715,14 @@ unplugin@^1.10.0, unplugin@^1.12.0, unplugin@^1.12.2, unplugin@^1.12.3, unplugin acorn "^8.12.1" webpack-virtual-modules "^0.6.2" +unplugin@^1.16.0: + version "1.16.0" + resolved "https://registry.yarnpkg.com/unplugin/-/unplugin-1.16.0.tgz#ca0f248bf8798cd752dd02e5b381223b737cef72" + integrity sha512-5liCNPuJW8dqh3+DM6uNM2EI3MLLpCKp/KY+9pB5M2S2SR2qvvDHhKgBOaTWEbZTAws3CXfB0rKTIolWKL05VQ== + dependencies: + acorn "^8.14.0" + webpack-virtual-modules "^0.6.2" + unstorage@^1.10.2: version "1.12.0" resolved "https://registry.npmjs.org/unstorage/-/unstorage-1.12.0.tgz" @@ -12365,6 +12761,19 @@ untyped@^1.4.2: mri "^1.2.0" scule "^1.2.0" +untyped@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/untyped/-/untyped-1.5.1.tgz#2ccf3ee09419d59a44c21a192877ab45aa98361a" + integrity sha512-reBOnkJBFfBZ8pCKaeHgfZLcehXtM6UTxc+vqs1JvCps0c4amLNp3fhdGBZwYp+VLyoY9n3X5KOP7lCyWBUX9A== + dependencies: + "@babel/core" "^7.25.7" + "@babel/standalone" "^7.25.7" + "@babel/types" "^7.25.7" + defu "^6.1.4" + jiti "^2.3.1" + mri "^1.2.0" + scule "^1.3.0" + unwasm@^0.3.9: version "0.3.9" resolved "https://registry.npmjs.org/unwasm/-/unwasm-0.3.9.tgz" @@ -12385,6 +12794,14 @@ update-browserslist-db@^1.1.0: escalade "^3.1.2" picocolors "^1.0.1" +update-browserslist-db@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz#80846fba1d79e82547fb661f8d141e0945755fe5" + integrity sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A== + dependencies: + escalade "^3.2.0" + picocolors "^1.1.0" + uqr@^0.1.2: version "0.1.2" resolved "https://registry.npmjs.org/uqr/-/uqr-0.1.2.tgz" @@ -12422,6 +12839,18 @@ uuid@^9.0.1: resolved "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz" integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA== +v-calendar@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/v-calendar/-/v-calendar-3.1.2.tgz#fb47320a5469973454f030d850134eebcd2307eb" + integrity sha512-QDWrnp4PWCpzUblctgo4T558PrHgHzDtQnTeUNzKxfNf29FkCeFpwGd9bKjAqktaa2aJLcyRl45T5ln1ku34kg== + dependencies: + "@types/lodash" "^4.14.165" + "@types/resize-observer-browser" "^0.1.7" + date-fns "^2.16.1" + date-fns-tz "^2.0.0" + lodash "^4.17.20" + vue-screen-utils "^1.0.0-beta.13" + v8-to-istanbul@^9.0.1: version "9.3.0" resolved "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz" @@ -12641,6 +13070,11 @@ vue-router@^4.4.3, vue-router@^4.4.4, vue-router@^4.4.5, vue-router@latest: dependencies: "@vue/devtools-api" "^6.6.4" +vue-screen-utils@^1.0.0-beta.13: + version "1.0.0-beta.13" + resolved "https://registry.yarnpkg.com/vue-screen-utils/-/vue-screen-utils-1.0.0-beta.13.tgz#0c739e19f6ffbffab63184aba7b6d710b6a63681" + integrity sha512-EJ/8TANKhFj+LefDuOvZykwMr3rrLFPLNb++lNBqPOpVigT2ActRg6icH9RFQVm4nHwlHIHSGm5OY/Clar9yIg== + vue@^3.5.0, vue@^3.5.4: version "3.5.4" resolved "https://registry.npmjs.org/vue/-/vue-3.5.4.tgz"