betagouv/service-national-universel

View on GitHub
api/src/crons/clotureInscriptionReminder.js

Summary

Maintainability
A
35 mins
Test Coverage
/**
 * Ce CRON est lancé tous les jours à 14h.
 * Il vérifie si on est la veille de la fermeture des instructions d'une cohorte.
 * Si c'est le cas, il envoit à tous les référents départementaux pour qui il reste des jeunes en attente de validation une relance mail.
 */
const { capture } = require("../sentry");
const { ReferentModel } = require("../models");
const { sendTemplate } = require("../brevo");
const slack = require("../slack");
const config = require("config");
const { YOUNG_STATUS, REFERENT_ROLES, REFERENT_DEPARTMENT_SUBROLE, SENDINBLUE_TEMPLATES } = require("snu-lib");
const { YoungModel } = require("../models");
const { CohortModel } = require("../models");

// /!\ WARNING /!\ Only works if the instructionEndDate is set to midnight UTC +/- 30 minutes
const HOURS_BEFORE_END_REMINDER = 48 - 14; // à 14h la veille
const MS_BEFORE_END_REMINDER = HOURS_BEFORE_END_REMINDER * 60 * 60 * 1000; // la même chose en ms
const MS_THIRTY_MINUTES = 30 * 60 * 1000;

exports.handler = async () => {
  try {
    let countReferents = 0;
    // liste des cohortes pour lesquelles on est la veille de la fin de l'instruction
    const cohorts = await getCohortsEndingTomorrow();
    // pour chaque cohorte :
    for (const cohort of cohorts) {
      // ---  liste des départements ou il reste des jeunes en attente de validation
      const departments = await getDepatmentsWithWaitingYoungs(cohort);
      // --- Pour chaque département :
      for (const department of departments) {
        // --- --- Trouver les référent departementaux avec subrole secretariat ou manager_department
        const referents = await getReferentForDepartment(department._id);
        // --- --- Pour chaque référent :
        for (const referent of referents) {
          // --- --- --- envoyer la relance.
          await sendReminder(referent, department, cohort);
          countReferents++;
        }
      }
    }
    await slack.success({ title: "clotureInscriptionReminder", text: `${countReferents} referents reminded.` });
  } catch (e) {
    capture(e);
    await slack.error({ title: "clotureInscriptionReminder", text: JSON.stringify(e) });
    throw e;
  }
};

// la fonction est async pour prévoir le jour où on ira chercher l'information dans la collection cohort.
async function getCohortsEndingTomorrow() {
  const now = new Date();
  const month = now.getMonth() + 1;
  const todayDate = new Date(`${now.getFullYear()}-${month < 9 ? "0" + month : month}-${now.getDate()}T14:00:00.000Z`);
  const today = todayDate.valueOf();
  const sessions2023 = await CohortModel.find({});
  return sessions2023
    .filter((session) => {
      return (
        session.instructionEndDate &&
        session.instructionEndDate.valueOf() - MS_BEFORE_END_REMINDER <= today + MS_THIRTY_MINUTES &&
        session.instructionEndDate.valueOf() - MS_BEFORE_END_REMINDER >= today - MS_THIRTY_MINUTES
      );
    })
    .map((session) => session.name);
}

function getDepatmentsWithWaitingYoungs(cohort) {
  return YoungModel.aggregate([{ $match: { cohort, status: YOUNG_STATUS.WAITING_VALIDATION } }, { $group: { _id: "$department", count: { $sum: 1 } } }]);
}

function getReferentForDepartment(department) {
  return ReferentModel.find({
    role: REFERENT_ROLES.REFERENT_DEPARTMENT,
    subRole: { $in: [REFERENT_DEPARTMENT_SUBROLE.secretariat, REFERENT_DEPARTMENT_SUBROLE.manager_department] },
    department,
  });
}

function sendReminder(referent, department, cohort) {
  return sendTemplate(SENDINBLUE_TEMPLATES.referent.INSTRUCTION_END_REMINDER, {
    emailTo: [{ name: `${referent.firstName} ${referent.lastName}`, email: referent.email }],
    params: {
      cta: `${config.ADMIN_URL}/volontaire?STATUS=%5B"WAITING_VALIDATION"%5D&COHORT=%5B"${encodeURIComponent(cohort)}"%5D&DEPARTMENT=%5B"${encodeURIComponent(department._id)}"%5D`,
      department: department._id,
      cohort,
      pending_cases: department.count,
    },
  });
}