From 4e4e976e79a97abd5aa65e23857850b28e85dfc2 Mon Sep 17 00:00:00 2001 From: Hubert MONCENIS Date: Fri, 20 Sep 2024 16:57:52 +0200 Subject: [PATCH 1/3] =?UTF-8?q?=E2=9C=A8=20Ajout=20des=20notifications=20p?= =?UTF-8?q?our=20un=20changement=20de=20statut=20d'une=20demande=20de=20re?= =?UTF-8?q?cours?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bootstrap/src/setupElimin\303\251.ts" | 21 ++++- .../applications/notifications/src/index.ts | 1 + .../recours.notification.ts" | 84 +++++++++++++++++++ 3 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 "packages/applications/notifications/src/subscribers/\303\251limin\303\251/recours.notification.ts" diff --git "a/packages/applications/bootstrap/src/setupElimin\303\251.ts" "b/packages/applications/bootstrap/src/setupElimin\303\251.ts" index f44cfe0f28..9b2c255943 100644 --- "a/packages/applications/bootstrap/src/setupElimin\303\251.ts" +++ "b/packages/applications/bootstrap/src/setupElimin\303\251.ts" @@ -8,7 +8,11 @@ import { RecoursAdapter, } from '@potentiel-infrastructure/domain-adapters'; import { RecoursProjector, ÉliminéProjector } from '@potentiel-applications/projectors'; -import { SendEmail, ÉliminéNotification } from '@potentiel-applications/notifications'; +import { + RecoursNotification, + SendEmail, + ÉliminéNotification, +} from '@potentiel-applications/notifications'; import { AttestationSaga } from '@potentiel-applications/document-builder'; type SetupÉliminéDependenices = { @@ -31,6 +35,7 @@ export const setupEliminé = async ({ sendEmail }: SetupÉliminéDependenices) = ÉliminéNotification.register({ sendEmail }); RecoursProjector.register(); + RecoursNotification.register({ sendEmail }); const unsubscribeRecoursProjector = await subscribe({ name: 'projector', @@ -50,6 +55,18 @@ export const setupEliminé = async ({ sendEmail }: SetupÉliminéDependenices) = streamCategory: 'recours', }); + const unsubscribeRecoursNotification = await subscribe({ + name: 'notifications', + streamCategory: 'éliminé', + eventType: ['RecoursDemandé-V1', 'RecoursAnnulé-V1', 'RecoursAccordé-V1', 'RecoursRejeté-V1'], + eventHandler: async (event) => { + await mediator.publish({ + type: 'System.Notification.Recours', + data: event, + }); + }, + }); + const unsubscribeÉliminéProjector = await subscribe<ÉliminéProjector.SubscriptionEvent>({ name: 'projector', eventType: ['ÉliminéNotifié-V1', 'RebuildTriggered'], @@ -88,6 +105,8 @@ export const setupEliminé = async ({ sendEmail }: SetupÉliminéDependenices) = return async () => { await unsubscribeRecoursProjector(); + await unsubscribeRecoursNotification(); + await unsubscribeÉliminéProjector(); await unsubscribeÉliminéNotification(); await unsubscribeÉliminéSaga(); diff --git a/packages/applications/notifications/src/index.ts b/packages/applications/notifications/src/index.ts index 0f065799ef..3da3237e83 100644 --- a/packages/applications/notifications/src/index.ts +++ b/packages/applications/notifications/src/index.ts @@ -5,5 +5,6 @@ export * as TâchePlanifiéeNotification from './subscribers/lauréat/tâchePlan export * as LauréatNotification from './subscribers/lauréat/lauréat.notification'; export * as ÉliminéNotification from './subscribers/éliminé/éliminé.notification'; export * as PériodeNotification from './subscribers/période/période.notification'; +export * as RecoursNotification from './subscribers/éliminé/recours.notification'; export { SendEmail, EmailPayload } from './sendEmail'; diff --git "a/packages/applications/notifications/src/subscribers/\303\251limin\303\251/recours.notification.ts" "b/packages/applications/notifications/src/subscribers/\303\251limin\303\251/recours.notification.ts" new file mode 100644 index 0000000000..6db2d05023 --- /dev/null +++ "b/packages/applications/notifications/src/subscribers/\303\251limin\303\251/recours.notification.ts" @@ -0,0 +1,84 @@ +import { Message, MessageHandler, mediator } from 'mediateur'; + +import { Option } from '@potentiel-libraries/monads'; +import { Event } from '@potentiel-infrastructure/pg-event-sourcing'; +import { Recours } from '@potentiel-domain/elimine'; +import { IdentifiantProjet } from '@potentiel-domain/common'; +import { Routes } from '@potentiel-applications/routes'; +import { + CandidatureAdapter, + récupérerPorteursParIdentifiantProjetAdapter, +} from '@potentiel-infrastructure/domain-adapters'; + +import { EmailPayload, SendEmail } from '../../sendEmail'; + +export type SubscriptionEvent = Recours.RecoursEvent & Event; + +export type Execute = Message<'System.Notification.Recours', SubscriptionEvent>; + +const templateId = 6310637; + +const getStatutByEventType = (type: SubscriptionEvent['type']) => { + switch (type) { + case 'RecoursDemandé-V1': + return 'demandée'; + case 'RecoursAnnulé-V1': + return 'annulée'; + case 'RecoursAccordé-V1': + return 'accordée'; + case 'RecoursRejeté-V1': + return 'rejetée'; + } +}; + +async function getEmailPayload(event: SubscriptionEvent): Promise { + const identifiantProjet = IdentifiantProjet.convertirEnValueType(event.payload.identifiantProjet); + + const projet = await CandidatureAdapter.récupérerProjetAdapter(identifiantProjet.formatter()); + const porteurs = await récupérerPorteursParIdentifiantProjetAdapter(identifiantProjet); + + if (Option.isNone(projet) || porteurs.length === 0 || !process.env.DGEC_EMAIL) { + return; + } + + const nomProjet = projet.nom; + const départementProjet = projet.localité.département; + const appelOffre = projet.appelOffre; + const période = projet.période; + + const admins = [ + { + email: process.env.DGEC_EMAIL, + fullName: 'DGEC', + }, + ]; + + const { BASE_URL } = process.env; + + return { + templateId, + messageSubject: `Potentiel - Demande de recours ${getStatutByEventType(event.type)} pour le projet ${nomProjet} (${appelOffre} période ${période})`, + recipients: [...porteurs, ...admins], + variables: { + nom_projet: nomProjet, + departement_projet: départementProjet, + statut: getStatutByEventType(event.type), + demande_recours_url: `${BASE_URL}${Routes.Recours.détail(identifiantProjet.formatter())}`, + }, + }; +} + +export type RegisterRecoursNotificationDependencies = { + sendEmail: SendEmail; +}; + +export const register = ({ sendEmail }: RegisterRecoursNotificationDependencies) => { + const handler: MessageHandler = async (event) => { + const payload = await getEmailPayload(event); + if (payload) { + await sendEmail(payload); + } + }; + + mediator.register('System.Notification.Recours', handler); +}; From 92f24af4566158a94d7c551b35b3bba0a83e38f8 Mon Sep 17 00:00:00 2001 From: Hubert MONCENIS Date: Mon, 23 Sep 2024 11:12:42 +0200 Subject: [PATCH 2/3] =?UTF-8?q?=F0=9F=8E=A8=20Modification=20de=20la=20cl?= =?UTF-8?q?=C3=A9=20pour=20la=20notif=20recours=20(ajout=20=C3=A9limin?= =?UTF-8?q?=C3=A9)=20+=20simplification=20de=20la=20m=C3=A9thode=20avec=20?= =?UTF-8?q?avec=20redirect=5Furl=20en=20fonction=20du=20statut?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bootstrap/src/setupElimin\303\251.ts" | 4 +- .../recours.notification.ts" | 83 +++++++++---------- .../infrastructure/email/src/sendEmail.ts | 2 +- 3 files changed, 43 insertions(+), 46 deletions(-) diff --git "a/packages/applications/bootstrap/src/setupElimin\303\251.ts" "b/packages/applications/bootstrap/src/setupElimin\303\251.ts" index 9b2c255943..33344c8b8e 100644 --- "a/packages/applications/bootstrap/src/setupElimin\303\251.ts" +++ "b/packages/applications/bootstrap/src/setupElimin\303\251.ts" @@ -57,14 +57,14 @@ export const setupEliminé = async ({ sendEmail }: SetupÉliminéDependenices) = const unsubscribeRecoursNotification = await subscribe({ name: 'notifications', - streamCategory: 'éliminé', eventType: ['RecoursDemandé-V1', 'RecoursAnnulé-V1', 'RecoursAccordé-V1', 'RecoursRejeté-V1'], eventHandler: async (event) => { await mediator.publish({ - type: 'System.Notification.Recours', + type: 'System.Notification.Éliminé.Recours', data: event, }); }, + streamCategory: 'recours', }); const unsubscribeÉliminéProjector = await subscribe<ÉliminéProjector.SubscriptionEvent>({ diff --git "a/packages/applications/notifications/src/subscribers/\303\251limin\303\251/recours.notification.ts" "b/packages/applications/notifications/src/subscribers/\303\251limin\303\251/recours.notification.ts" index 6db2d05023..34126f0703 100644 --- "a/packages/applications/notifications/src/subscribers/\303\251limin\303\251/recours.notification.ts" +++ "b/packages/applications/notifications/src/subscribers/\303\251limin\303\251/recours.notification.ts" @@ -10,11 +10,11 @@ import { récupérerPorteursParIdentifiantProjetAdapter, } from '@potentiel-infrastructure/domain-adapters'; -import { EmailPayload, SendEmail } from '../../sendEmail'; +import { SendEmail } from '../../sendEmail'; export type SubscriptionEvent = Recours.RecoursEvent & Event; -export type Execute = Message<'System.Notification.Recours', SubscriptionEvent>; +export type Execute = Message<'System.Notification.Éliminé.Recours', SubscriptionEvent>; const templateId = 6310637; @@ -31,54 +31,51 @@ const getStatutByEventType = (type: SubscriptionEvent['type']) => { } }; -async function getEmailPayload(event: SubscriptionEvent): Promise { - const identifiantProjet = IdentifiantProjet.convertirEnValueType(event.payload.identifiantProjet); - - const projet = await CandidatureAdapter.récupérerProjetAdapter(identifiantProjet.formatter()); - const porteurs = await récupérerPorteursParIdentifiantProjetAdapter(identifiantProjet); - - if (Option.isNone(projet) || porteurs.length === 0 || !process.env.DGEC_EMAIL) { - return; - } - - const nomProjet = projet.nom; - const départementProjet = projet.localité.département; - const appelOffre = projet.appelOffre; - const période = projet.période; - - const admins = [ - { - email: process.env.DGEC_EMAIL, - fullName: 'DGEC', - }, - ]; - - const { BASE_URL } = process.env; - - return { - templateId, - messageSubject: `Potentiel - Demande de recours ${getStatutByEventType(event.type)} pour le projet ${nomProjet} (${appelOffre} période ${période})`, - recipients: [...porteurs, ...admins], - variables: { - nom_projet: nomProjet, - departement_projet: départementProjet, - statut: getStatutByEventType(event.type), - demande_recours_url: `${BASE_URL}${Routes.Recours.détail(identifiantProjet.formatter())}`, - }, - }; -} - export type RegisterRecoursNotificationDependencies = { sendEmail: SendEmail; }; export const register = ({ sendEmail }: RegisterRecoursNotificationDependencies) => { const handler: MessageHandler = async (event) => { - const payload = await getEmailPayload(event); - if (payload) { - await sendEmail(payload); + const identifiantProjet = IdentifiantProjet.convertirEnValueType( + event.payload.identifiantProjet, + ); + + const projet = await CandidatureAdapter.récupérerProjetAdapter(identifiantProjet.formatter()); + const porteurs = await récupérerPorteursParIdentifiantProjetAdapter(identifiantProjet); + + if (Option.isNone(projet) || porteurs.length === 0 || !process.env.DGEC_EMAIL) { + return; } + const { BASE_URL } = process.env; + + const admins = [ + { + email: process.env.DGEC_EMAIL, + fullName: 'DGEC', + }, + ]; + const nomProjet = projet.nom; + const départementProjet = projet.localité.département; + const appelOffre = projet.appelOffre; + const période = projet.période; + const statut = getStatutByEventType(event.type); + + await sendEmail({ + templateId, + messageSubject: `Potentiel - Demande de recours ${statut} pour le projet ${nomProjet} (${appelOffre} période ${période})`, + recipients: [...porteurs, ...admins], + variables: { + nom_projet: nomProjet, + departement_projet: départementProjet, + statut, + redirect_url: + statut === 'annulée' + ? `${BASE_URL}${Routes.Projet.details(identifiantProjet.formatter())}` + : `${BASE_URL}${Routes.Recours.détail(identifiantProjet.formatter())}`, + }, + }); }; - mediator.register('System.Notification.Recours', handler); + mediator.register('System.Notification.Éliminé.Recours', handler); }; diff --git a/packages/infrastructure/email/src/sendEmail.ts b/packages/infrastructure/email/src/sendEmail.ts index bef8d491d6..e26ca2b2b2 100644 --- a/packages/infrastructure/email/src/sendEmail.ts +++ b/packages/infrastructure/email/src/sendEmail.ts @@ -44,6 +44,6 @@ export const sendEmail: SendEmail = async (sendEmailArgs) => { getLogger().info('Email sent', sendEmailArgs); } else { - getLogger().info('Emailing mode set to logging-only so no email was sent', sendEmailArgs); + getLogger().info('📨 Emailing mode set to logging-only so no email was sent', sendEmailArgs); } }; From 23dd893b83a2517b6a60b731c59f498c3603ba6d Mon Sep 17 00:00:00 2001 From: Hubert MONCENIS Date: Mon, 23 Sep 2024 15:54:48 +0200 Subject: [PATCH 3/3] =?UTF-8?q?=F0=9F=8E=A8=20Envoi=20de=20l'email=20?= =?UTF-8?q?=C3=A0=20destination=20des=20utilisateurs=20CRE=20quand=20le=20?= =?UTF-8?q?recours=20est=20accord=C3=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../recours.notification.ts" | 48 +++++++++++++------ 1 file changed, 33 insertions(+), 15 deletions(-) diff --git "a/packages/applications/notifications/src/subscribers/\303\251limin\303\251/recours.notification.ts" "b/packages/applications/notifications/src/subscribers/\303\251limin\303\251/recours.notification.ts" index 34126f0703..830a60e5af 100644 --- "a/packages/applications/notifications/src/subscribers/\303\251limin\303\251/recours.notification.ts" +++ "b/packages/applications/notifications/src/subscribers/\303\251limin\303\251/recours.notification.ts" @@ -1,4 +1,5 @@ import { Message, MessageHandler, mediator } from 'mediateur'; +import { match } from 'ts-pattern'; import { Option } from '@potentiel-libraries/monads'; import { Event } from '@potentiel-infrastructure/pg-event-sourcing'; @@ -7,8 +8,10 @@ import { IdentifiantProjet } from '@potentiel-domain/common'; import { Routes } from '@potentiel-applications/routes'; import { CandidatureAdapter, + listerUtilisateursAdapter, récupérerPorteursParIdentifiantProjetAdapter, } from '@potentiel-infrastructure/domain-adapters'; +import { Role } from '@potentiel-domain/utilisateur'; import { SendEmail } from '../../sendEmail'; @@ -16,19 +19,9 @@ export type SubscriptionEvent = Recours.RecoursEvent & Event; export type Execute = Message<'System.Notification.Éliminé.Recours', SubscriptionEvent>; -const templateId = 6310637; - -const getStatutByEventType = (type: SubscriptionEvent['type']) => { - switch (type) { - case 'RecoursDemandé-V1': - return 'demandée'; - case 'RecoursAnnulé-V1': - return 'annulée'; - case 'RecoursAccordé-V1': - return 'accordée'; - case 'RecoursRejeté-V1': - return 'rejetée'; - } +const templateId = { + changementStatutRecours: 6310637, + recoursAccordéCRE: 6189222, }; export type RegisterRecoursNotificationDependencies = { @@ -59,10 +52,15 @@ export const register = ({ sendEmail }: RegisterRecoursNotificationDependencies) const départementProjet = projet.localité.département; const appelOffre = projet.appelOffre; const période = projet.période; - const statut = getStatutByEventType(event.type); + const statut = match(event.type) + .with('RecoursDemandé-V1', () => 'demandée') + .with('RecoursAnnulé-V1', () => 'annulée') + .with('RecoursAccordé-V1', () => 'accordée') + .with('RecoursRejeté-V1', () => 'rejetée') + .exhaustive(); await sendEmail({ - templateId, + templateId: templateId.changementStatutRecours, messageSubject: `Potentiel - Demande de recours ${statut} pour le projet ${nomProjet} (${appelOffre} période ${période})`, recipients: [...porteurs, ...admins], variables: { @@ -75,6 +73,26 @@ export const register = ({ sendEmail }: RegisterRecoursNotificationDependencies) : `${BASE_URL}${Routes.Recours.détail(identifiantProjet.formatter())}`, }, }); + + if (event.type === 'RecoursAccordé-V1') { + const utilisateursCre = await listerUtilisateursAdapter([Role.cre.nom]); + const recipients = utilisateursCre.map(({ email, nomComplet }) => ({ + email, + fullName: nomComplet, + })); + + if (recipients.length > 0) { + await sendEmail({ + templateId: templateId.recoursAccordéCRE, + messageSubject: `Potentiel - Un recours a été accepté pour le projet ${nomProjet} (${appelOffre} période ${période})`, + recipients, + variables: { + nom_projet: nomProjet, + modification_request_url: `${BASE_URL}${Routes.Recours.détail(identifiantProjet.formatter())}`, + }, + }); + } + } }; mediator.register('System.Notification.Éliminé.Recours', handler);