diff --git a/.env.dev.example.env b/.env.dev.example.env index 9a7c94a3d..4ba5d5ff6 100644 --- a/.env.dev.example.env +++ b/.env.dev.example.env @@ -47,6 +47,7 @@ BACKEND_URL="http://localhost:3010" BACKEND_CRON_REQUEST_DS8J15J_NOTIFY_CRON="0 22 * * *" BACKEND_CRON_REQUEST_DS8J15J_DEADLINE_REMIND="15" BACKEND_CRON_UPDATE_STATUT_DS_CRON="* * * * *" +BACKEND_CRON_REQUEST_ACTIONS_BO_CRON="0 9,12 * * 1-5" # Tokens TOKEN_SECRET="-----BEGIN EC PRIVATE KEY-----\nMIHcAgEBBEIAobMQpD5H2nAL7LrYLsxTxmE70HB6F3sWeAeq3DXdLQ/5zGFcV36e\nI9VdYyed8fzYq6t+RP42M7fDUzLAKpn46cKgBwYFK4EEACOhgYkDgYYABAFcZNGM\nL+ba9RXURG9yEUKzZsVt19jN+1xbjL1EIRC6IsvZeK58yd/Y924WtpklMSqMI6Fx\nSmGOpELXEe9BICNJHAG1x3KH0SUWN+gKC1mRbriSFd9HhrtRY7AgUdD6TR7H7un0\nxrJnFvr9iC1K+E6linASudSivbUhP8QzHt2k5JsTsQ==\n-----END EC PRIVATE KEY-----" diff --git a/.kontinuous/env/dev/templates/backend.configmap.yaml b/.kontinuous/env/dev/templates/backend.configmap.yaml index 2c511f22f..2f4a2167c 100644 --- a/.kontinuous/env/dev/templates/backend.configmap.yaml +++ b/.kontinuous/env/dev/templates/backend.configmap.yaml @@ -17,3 +17,4 @@ data: BACKEND_CRON_REQUEST_DS8J15J_NOTIFY_CRON: "*/15 * * * *" BACKEND_CRON_REQUEST_DS8J15J_DEADLINE_REMIND: "90" BACKEND_CRON_UPDATE_STATUT_DS_CRON: "*/15 * * * *" + BACKEND_CRON_REQUEST_ACTIONS_BO_CRON: "0 9,12 * * 1-5" \ No newline at end of file diff --git a/.kontinuous/env/preprod/templates/backend.configmap.yaml b/.kontinuous/env/preprod/templates/backend.configmap.yaml index 008bce3f7..e3b110828 100644 --- a/.kontinuous/env/preprod/templates/backend.configmap.yaml +++ b/.kontinuous/env/preprod/templates/backend.configmap.yaml @@ -16,4 +16,5 @@ data: TMP_DIRECTORY: "/tmp" BACKEND_CRON_REQUEST_DS8J15J_NOTIFY_CRON: "*/15 * * * *" BACKEND_CRON_REQUEST_DS8J15J_DEADLINE_REMIND: "90" - BACKEND_CRON_UPDATE_STATUT_DS_CRON: "*/15 * * * *" \ No newline at end of file + BACKEND_CRON_UPDATE_STATUT_DS_CRON: "*/15 * * * *" + BACKEND_CRON_REQUEST_ACTIONS_BO_CRON: "0 9,12 * * 1-5" \ No newline at end of file diff --git a/.kontinuous/env/prod/templates/backend.configmap.yaml b/.kontinuous/env/prod/templates/backend.configmap.yaml index 12357e846..222dde1e0 100644 --- a/.kontinuous/env/prod/templates/backend.configmap.yaml +++ b/.kontinuous/env/prod/templates/backend.configmap.yaml @@ -17,4 +17,5 @@ data: TMP_DIRECTORY: "/tmp" BACKEND_CRON_REQUEST_DS8J15J_NOTIFY_CRON: "0 22 * * *" BACKEND_CRON_REQUEST_DS8J15J_DEADLINE_REMIND: "15" - BACKEND_CRON_UPDATE_STATUT_DS_CRON: "0 02 * * *" \ No newline at end of file + BACKEND_CRON_UPDATE_STATUT_DS_CRON: "0 02 * * *" + BACKEND_CRON_REQUEST_ACTIONS_BO_CRON: "0 8 * * 1" \ No newline at end of file diff --git a/packages/backend/src/config.js b/packages/backend/src/config.js index 40ade5b2a..38c6340a4 100644 --- a/packages/backend/src/config.js +++ b/packages/backend/src/config.js @@ -29,6 +29,10 @@ module.exports = { process.env.BACKEND_CRON_REQUEST_DS8J15J_DEADLINE_REMIND, name: "REQUEST_DS8J15J", }, + notifyactionsbo: { + cron: process.env.BACKEND_CRON_REQUEST_ACTIONS_BO_CRON, + name: "REQUEST_ACTIONS_BO", + }, update: { cron: process.env.BACKEND_CRON_UPDATE_STATUT_DS_CRON, name: "UPDATE_STATUT_DS", diff --git a/packages/backend/src/crons/demandes-sejours/index.js b/packages/backend/src/crons/demandes-sejours/index.js index 559ff2ae0..c8e4c2817 100644 --- a/packages/backend/src/crons/demandes-sejours/index.js +++ b/packages/backend/src/crons/demandes-sejours/index.js @@ -1,2 +1,3 @@ +module.exports.notifyRappelActionsBO = require("./notifyRappelActionsBO").job; module.exports.notifyRappelds8j15j = require("./notifyRappelds8j15j").job; module.exports.updateStatutDS = require("./updateStatutDS").job; diff --git a/packages/backend/src/crons/demandes-sejours/notifyRappelActionsBO.js b/packages/backend/src/crons/demandes-sejours/notifyRappelActionsBO.js new file mode 100644 index 000000000..1fdec5163 --- /dev/null +++ b/packages/backend/src/crons/demandes-sejours/notifyRappelActionsBO.js @@ -0,0 +1,266 @@ +const { CronJob } = require("cron"); +const AppError = require("../../utils/error"); + +const run = require("../run"); +const logger = require("../../utils/logger"); +const pool = require("../../utils/pgpool").getPool(); +const { statuts } = require("../../helpers/ds-statuts"); +const Send = require("../../services/mail").mailService.send; +const sendTemplate = require("../../helpers/mail"); + +const { + senderEmail, + frontUsagersDomain, + frontBODomain, +} = require("../../config"); + +const { notifyRappelActionsBO } = require("."); + +const { name, cron } = require("../../config").crons.request.notifyactionsbo; + +const log = logger(module.filename); + +const generateRappelQuery = ( + statutsArray, + additionalColumns = "", + additionalJoins = "", + additionalGroupBy = "", + 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, + ds.id_fonctionnelle, + ds.date_debut, + ds.statut, + ds.libelle as titre, + TO_CHAR(ds.date_debut, 'DD/MM/YYYY') as date_debut, + use.mail, + ${additionalColumns} + ((ds.date_debut - ((10) * INTERVAL '1 day'))::date <= now()::date) as isalerte, + string_agg(com.label, ', ' ORDER BY he.date_debut_hebergement::date ASC) AS Communes + FROM + hebergement_exploded he + INNER JOIN front.demande_sejour ds ON ds.id = he.id + INNER JOIN geo.communes com ON com.code_insee = he.code_insee AND com.date_fin is null + ${additionalJoins} + WHERE (ds.date_debut)::date>=now()::date + AND ds.statut IN (${statutsArray}) + GROUP BY + ds.id, + ds.id_fonctionnelle, + ds.date_debut, + ds.statut, + ds.libelle, + use.mail, + ${additionalGroupBy} + isalerte + ORDER BY + ${additionalOrderBy} + isalerte DESC, + ds.date_debut ASC; +`; + +const query = { + fetchRappelDSBO: generateRappelQuery( + `'${statuts.TRANSMISE}','${statuts.EN_COURS}','${statuts.TRANSMISE_8J}','${statuts.EN_COURS_8J}'`, + ``, + ` INNER JOIN geo.territoires ter ON ter.code = ds.departement_suivi INNER JOIN back.users use ON ter.code = use.ter_code `, + ``, + `use.mail,`, + ), + fetchRappelDSFUsager: generateRappelQuery( + `'${statuts.ATTENTE_8_JOUR}','${statuts.A_MODIFIER}','${statuts.A_MODIFIER_8J}'`, + `((ds.responsable_sejour::jsonb)->>'email')::text as mailresp, mail || ';' || ((ds.responsable_sejour::jsonb)->>'email')::text as mails,`, + ` INNER JOIN front.user_organisme uso ON uso.org_id = ds.organisme_id INNER JOIN front.users use ON use.id = uso.use_id `, + `mailresp,`, + `mail,`, + ), +}; + +const action = async () => { + log.i(`notifyRappelActionsBO - IN`); + // Création des contenus pour les envoies BackOffice + const { rowCount: countRappelActionsBO, rows: rowsRappelActionsBO } = + await pool.query(query.fetchRappelDSBO); + createContent({ + isBO: true, + rowCount: countRappelActionsBO, + rows: rowsRappelActionsBO, + }); + // Création des contenus pour les envoies FrontUsagers + const { + rowCount: countRappelActionsFUsager, + rows: rowsRappelActionsFUsager, + } = await pool.query(query.fetchRappelDSFUsager); + createContent({ + isBO: false, + rowCount: countRappelActionsFUsager, + rows: rowsRappelActionsFUsager, + }); + + log.i(`notifyRappelds8j15j - DONE`); +}; +async function createContent({ isBO, rowCount, rows }) { + if (rowCount === 0) return; + const mails = getUniqueMails({ isBO, rows }); + for (const mail of mails) { + const emailContent = []; + emailContent.push(...generateInitialEmailContent()); + const listeDsAvecAlerte = filterRowsWithAlert({ isBO, mail, rows }); + emailContent.push(...appendContentForAlert({ listeDsAvecAlerte })); + const listeDsSansAlerte = filterRowsWithoutAlert({ isBO, mail, rows }); + emailContent.push(...appendContentForNonAlert({ listeDsSansAlerte })); + try { + await sendMail({ emailContent, isBO, mail }); + } catch (error) { + log.w(error); + } + } +} + +function getUniqueMails({ isBO, rows }) { + return [ + ...new Set( + rows + .filter((ds) => (isBO ? ds.mail : ds.mails)) + .map((ds) => (isBO ? ds.mail : ds.mails)) + ), + ]; +} + +function generateInitialEmailContent() { + return [ + "

Bonjour,

", + "

Vous trouverez ci-dessous la liste des déclarations VAO sur lesquelles une action de votre part est attendue,

", + ]; +} + +function filterRowsWithAlert({ isBO, rows, mail }) { + return rows.filter( + (ds) => ds.isalerte && (isBO ? ds.mail === mail : ds.mails === mail) + ); +} + +function filterRowsWithoutAlert({ isBO, rows, mail }) { + return rows.filter( + (ds) => !ds.isalerte && (isBO ? ds.mail === mail : ds.mails === mail) + ); +} + +function appendContentForAlert({ listeDsAvecAlerte }) { + const newContent = []; + if (listeDsAvecAlerte.length > 0) { + newContent.push( + "

DECLARATIONS NECESSITANT UNE ACTION URGENTE DE VOTRE PART dont la date de début de séjour est à moins de 10 jours", + ); + newContent.push("

"); + } + return newContent; +} + +function appendContentForNonAlert({ listeDsSansAlerte }) { + const newContent = []; + if (listeDsSansAlerte.length > 0) { + newContent.push( + "

AUTRES DECLARATIONS DE SEJOUR NECESSITANT UNE ACTION DE VOTRE PART", + ); + newContent.push("

"); + } + return newContent; +} + +async function sendMail({ emailContent, mail, isBO }) { + await Send( + await sendNotificationMail({ + content: emailContent.join("\n"), + email: mail.split(";"), + isBO, + }), + ); +} + +function createDSContent({ ds }) { + const content = []; + content.push(`

${ds.id_fonctionnelle} - ${ds.communes}
`); + content.push(`Statut de la déclaration : ${ds.statut}
`); + content.push(`Date de début du séjour : ${ds.date_debut}

`); + return content; +} + +async function sendNotificationMail({ content, email, isBO }) { + log.i("sendNotificationMail - In", { + email, + }); + const textDeFin = []; + let uriAppListeSejours = ""; + if (isBO) { + uriAppListeSejours = frontBODomain + "/sejours"; + } else { + uriAppListeSejours = frontUsagersDomain + "/demande-sejour/liste"; + textDeFin.push( + `

Si vous avez des difficultés pour traiter vos déclarations, vous vous rappelons que vous pouvez contacter le support utilisateur.

`, + ); + textDeFin.push( + `

De plus, vous avez toujours la possibilité d’annuler des déclarations de séjours qui ne sont plus d’actualité pour garder votre tableau à jour.

`, + ); + } + textDeFin.push( + `Vous pouvez accéder à la liste des déclarations de votre département en cliquant ici`, + ); + + if (!email) { + const message = `Le paramètre email manque à la requête`; + log.w(`sendNotificationMail - ${message}`); + throw new AppError(message); + } + const html = sendTemplate.getBody( + ``, + [ + { + p: [ + ` + ${content} + `, + textDeFin.join("\n"), + ], + type: "p", + }, + ], + "L'équipe du SI VAO

Ce courriel est un message automatique, merci de ne pas répondre.", + ); + const params = { + from: senderEmail, + html: html, + notifyRappelActionsBO, + replyTo: senderEmail, + subject: `Séjours VAO – Récapitulatif des déclarations de séjour en attente de traitement de votre part`, + to: email, + }; + log.d("sendNotificationMail post email", { + params, + }); + return params; +} + +const job = new CronJob(cron, run(name, action)); + +module.exports.job = job; +module.exports.action = action; diff --git a/packages/backend/src/crons/index.js b/packages/backend/src/crons/index.js index 558ebe709..a930882c4 100644 --- a/packages/backend/src/crons/index.js +++ b/packages/backend/src/crons/index.js @@ -2,12 +2,16 @@ const logger = require("../utils/logger"); const log = logger(module.filename); +const { + notifyRappelActionsBO: UsagerNotifyActionsBO, +} = require("./demandes-sejours"); const { notifyRappelds8j15j: UsagerNotify } = require("./demandes-sejours"); const { updateStatutDS: UpdateStatut } = require("./demandes-sejours"); module.exports.stop = function stop() { log.i("Stopping crons..."); + UsagerNotifyActionsBO.stop(); UsagerNotify.stop(); UpdateStatut.stop(); @@ -17,6 +21,7 @@ module.exports.stop = function stop() { module.exports.start = function start() { log.i("Starting crons..."); + UsagerNotifyActionsBO.start(); UsagerNotify.start(); UpdateStatut.start(); diff --git a/packages/backend/src/services/DemandeSejour.js b/packages/backend/src/services/DemandeSejour.js index c22c254da..a636a5a7a 100644 --- a/packages/backend/src/services/DemandeSejour.js +++ b/packages/backend/src/services/DemandeSejour.js @@ -1011,7 +1011,6 @@ module.exports.getByDepartementCodes = async ( } log.d({ paramsWithPagination, queryWithPagination }); - const response = await pool.query(queryWithPagination, paramsWithPagination); if (limit === null || response.rowCount < limit) {