Skip to content

Commit

Permalink
send second assessment reminder
Browse files Browse the repository at this point in the history
  • Loading branch information
celineung committed Jan 20, 2025
1 parent 937258a commit 98a67c4
Show file tree
Hide file tree
Showing 8 changed files with 205 additions and 54 deletions.
2 changes: 1 addition & 1 deletion back/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
"trigger-delete-old-discussion-messages": "ts-node src/scripts/triggerDeleteOldDiscussionMessages",
"trigger-refresh-materialized-views": "ts-node src/scripts/triggerRefreshMaterializedViews.ts",
"trigger-resync-old-conventions-to-pe": "ts-node src/scripts/triggerResyncOldConventionsToPe.ts",
"trigger-assessment-first-reminders": "ts-node src/scripts/triggerAssessmentReminder.ts",
"trigger-assessment-reminders": "ts-node src/scripts/triggerAssessmentReminder.ts",
"trigger-sending-emails-with-assessment-creation-link": "ts-node src/scripts/triggerSendingEmailsWithAssessmentCreationLink.ts",
"trigger-sending-beneficiary-assessment-emails": "ts-node src/scripts/triggerSendingBeneficiaryPdfAssessmentEmails.ts",
"trigger-suggest-edit-form-establishment-every-6-months": "ts-node src/scripts/triggerSuggestEditFormEstablishmentEvery6Months.ts",
Expand Down
2 changes: 1 addition & 1 deletion back/scalingo/cron.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
"size": "M"
},
{
"command": "15 21 * * * pnpm back run trigger-assessment-first-reminders",
"command": "15 21 * * * pnpm back run trigger-assessment-reminders",
"size": "M"
},
{
Expand Down
79 changes: 46 additions & 33 deletions back/src/domains/establishment/use-cases/AssessmentReminder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
ConventionReadDto,
Email,
errors,
executeInSequence,
frontRoutes,
immersionFacileNoReplyEmailSender,
} from "shared";
Expand All @@ -23,10 +24,12 @@ import { TimeGateway } from "../../core/time-gateway/ports/TimeGateway";
import { UnitOfWork } from "../../core/unit-of-work/ports/UnitOfWork";

type AssessmentReminderOutput = {
numberOfFirstReminders: number;
numberOfReminders: number;
};

export type AssessmentReminderMode = "3daysAfterConventionEnd";
export type AssessmentReminderMode =
| "3daysAfterConventionEnd"
| "10daysAfterConventionEnd";

export type AssessmentReminder = ReturnType<typeof makeAssessmentReminder>;
export const makeAssessmentReminder = createTransactionalUseCase<
Expand All @@ -41,7 +44,9 @@ export const makeAssessmentReminder = createTransactionalUseCase<
>(
{
name: "AssessmentReminder",
inputSchema: z.object({ mode: z.enum(["3daysAfterConventionEnd"]) }),
inputSchema: z.object({
mode: z.enum(["3daysAfterConventionEnd", "10daysAfterConventionEnd"]),
}),
},
async ({ inputParams: params, uow, deps }) => {
const now = deps.timeGateway.now();
Expand All @@ -52,36 +57,36 @@ export const makeAssessmentReminder = createTransactionalUseCase<
conventionRepository: uow.conventionRepository,
});

await Promise.all(
conventionIdsToRemind.map(async (conventionId) => {
const convention =
await uow.conventionQueries.getConventionById(conventionId);
if (!convention)
throw errors.convention.notFound({ conventionId: conventionId });
await sendAssessmentReminders({
uow,
recipientEmails: convention.agencyValidatorEmails,
convention,
now,
generateConventionMagicLinkUrl: deps.generateConventionMagicLinkUrl,
saveNotificationAndRelatedEvent: deps.saveNotificationAndRelatedEvent,
role: "validator",
});
await sendAssessmentReminders({
uow,
recipientEmails: convention.agencyCounsellorEmails,
convention,
now,
generateConventionMagicLinkUrl: deps.generateConventionMagicLinkUrl,
saveNotificationAndRelatedEvent: deps.saveNotificationAndRelatedEvent,
role: "counsellor",
});
}),
);

return Promise.resolve({
numberOfFirstReminders: conventionIdsToRemind.length,
await executeInSequence(conventionIdsToRemind, async (conventionId) => {
const convention =
await uow.conventionQueries.getConventionById(conventionId);
if (!convention)
throw errors.convention.notFound({ conventionId: conventionId });
await sendAssessmentReminders({
uow,
mode: params.mode,
recipientEmails: convention.agencyValidatorEmails,
convention,
now,
generateConventionMagicLinkUrl: deps.generateConventionMagicLinkUrl,
saveNotificationAndRelatedEvent: deps.saveNotificationAndRelatedEvent,
role: "validator",
});
await sendAssessmentReminders({
uow,
mode: params.mode,
recipientEmails: convention.agencyCounsellorEmails,
convention,
now,
generateConventionMagicLinkUrl: deps.generateConventionMagicLinkUrl,
saveNotificationAndRelatedEvent: deps.saveNotificationAndRelatedEvent,
role: "counsellor",
});
});

return {
numberOfReminders: conventionIdsToRemind.length,
};
},
);

Expand Down Expand Up @@ -111,11 +116,13 @@ const getConventionIdsToRemind = async ({
};

const createNotification = ({
mode,
convention,
recipientEmail,
establishmentContactEmail,
assessmentCreationLink,
}: {
mode: AssessmentReminderMode;
convention: ConventionReadDto;
recipientEmail: Email;
establishmentContactEmail: Email;
Expand All @@ -129,7 +136,10 @@ const createNotification = ({
},
kind: "email",
templatedContent: {
kind: "ASSESSMENT_AGENCY_FIRST_REMINDER",
kind:
mode === "3daysAfterConventionEnd"
? "ASSESSMENT_AGENCY_FIRST_REMINDER"
: "ASSESSMENT_AGENCY_SECOND_REMINDER",
params: {
beneficiaryFirstName: convention.signatories.beneficiary.firstName,
beneficiaryLastName: convention.signatories.beneficiary.lastName,
Expand All @@ -147,6 +157,7 @@ const createNotification = ({

const sendAssessmentReminders = async ({
uow,
mode,
saveNotificationAndRelatedEvent,
convention,
recipientEmails,
Expand All @@ -155,6 +166,7 @@ const sendAssessmentReminders = async ({
role,
}: {
uow: UnitOfWork;
mode: AssessmentReminderMode;
saveNotificationAndRelatedEvent: SaveNotificationAndRelatedEvent;
convention: ConventionReadDto;
recipientEmails: Email[];
Expand All @@ -171,6 +183,7 @@ const sendAssessmentReminders = async ({
now,
});
const notification = createNotification({
mode,
convention,
recipientEmail: recipientEmail,
establishmentContactEmail:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,11 @@ describe("AssessmentReminder", () => {
];
await uow.conventionRepository.save(convention);

const { numberOfFirstReminders } = await assessmentReminder.execute({
const { numberOfReminders } = await assessmentReminder.execute({
mode: "3daysAfterConventionEnd",
});

expect(numberOfFirstReminders).toBe(1);
expect(numberOfReminders).toBe(1);
expectObjectInArrayToMatch(uow.notificationRepository.notifications, [
{
templatedContent: {
Expand Down Expand Up @@ -110,4 +110,66 @@ describe("AssessmentReminder", () => {
{ topic: "NotificationAdded" },
]);
});

it("send second assessment reminder", async () => {
const now = timeGateway.now();
const conventionEndDate = subDays(now, 10);
const agency = new AgencyDtoBuilder().build();
const convention = new ConventionDtoBuilder()
.withStatus("ACCEPTED_BY_VALIDATOR")
.withDateEnd(conventionEndDate.toISOString())
.withAgencyId(agency.id)
.build();
const validator = new InclusionConnectedUserBuilder()
.withId("10000000-0000-0000-0000-000000000003")
.withEmail("[email protected]")
.buildUser();
await uow.userRepository.save(validator);
uow.agencyRepository.agencies = [
toAgencyWithRights(agency, {
[validator.id]: {
isNotifiedByEmail: true,
roles: ["validator"],
},
}),
];
await uow.conventionRepository.save(convention);

const { numberOfReminders } = await assessmentReminder.execute({
mode: "10daysAfterConventionEnd",
});

expect(numberOfReminders).toBe(1);
expectObjectInArrayToMatch(uow.notificationRepository.notifications, [
{
templatedContent: {
kind: "ASSESSMENT_AGENCY_SECOND_REMINDER",
params: {
conventionId: convention.id,
internshipKind: convention.internshipKind,
businessName: convention.businessName,
establishmentContactEmail:
convention.signatories.establishmentRepresentative.email,
beneficiaryFirstName: convention.signatories.beneficiary.firstName,
beneficiaryLastName: convention.signatories.beneficiary.lastName,
assessmentCreationLink: fakeGenerateMagicLinkUrlFn({
id: convention.id,
email: validator.email,
role: "validator",
targetRoute: frontRoutes.assessment,
now,
}),
},
recipients: [validator.email],
sender: {
email: "[email protected]",
name: "Immersion Facilitée",
},
},
},
]);
expectObjectInArrayToMatch(uow.outboxRepository.events, [
{ topic: "NotificationAdded" },
]);
});
});
58 changes: 42 additions & 16 deletions back/src/scripts/triggerAssessmentReminder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,28 +22,54 @@ const triggerAssessmentReminder = async () => {
3600 * 24 * 30,
);

return makeAssessmentReminder({
uowPerformer: createUowPerformer(config, createGetPgPoolFn(config))
.uowPerformer,
deps: {
timeGateway,
saveNotificationAndRelatedEvent: makeSaveNotificationAndRelatedEvent(
new UuidV4Generator(),
const { numberOfReminders: numberOfFirstReminders } =
await makeAssessmentReminder({
uowPerformer: createUowPerformer(config, createGetPgPoolFn(config))
.uowPerformer,
deps: {
timeGateway,
),
generateConventionMagicLinkUrl: makeGenerateConventionMagicLinkUrl(
config,
generateConventionJwt,
),
},
}).execute({ mode: "3daysAfterConventionEnd" });
saveNotificationAndRelatedEvent: makeSaveNotificationAndRelatedEvent(
new UuidV4Generator(),
timeGateway,
),
generateConventionMagicLinkUrl: makeGenerateConventionMagicLinkUrl(
config,
generateConventionJwt,
),
},
}).execute({ mode: "3daysAfterConventionEnd" });

const { numberOfReminders: numberOfSecondReminders } =
await makeAssessmentReminder({
uowPerformer: createUowPerformer(config, createGetPgPoolFn(config))
.uowPerformer,
deps: {
timeGateway,
saveNotificationAndRelatedEvent: makeSaveNotificationAndRelatedEvent(
new UuidV4Generator(),
timeGateway,
),
generateConventionMagicLinkUrl: makeGenerateConventionMagicLinkUrl(
config,
generateConventionJwt,
),
},
}).execute({ mode: "10daysAfterConventionEnd" });

return {
numberOfFirstReminders,
numberOfSecondReminders,
};
};

handleCRONScript(
"assessmentReminder",
config,
triggerAssessmentReminder,
({ numberOfFirstReminders }) =>
[`Total of first reminders : ${numberOfFirstReminders}`].join("\n"),
({ numberOfFirstReminders, numberOfSecondReminders }) =>
[
`Total of first reminders : ${numberOfFirstReminders}`,
`Total if second reminders: ${numberOfSecondReminders}`,
].join("\n"),
logger,
);
9 changes: 9 additions & 0 deletions front/src/app/pages/admin/EmailPreviewTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,15 @@ export const defaultEmailValueByEmailKind: {
establishmentContactEmail: "ESTABLISHMENT_CONTACT_EMAIL",
internshipKind: "immersion",
},
ASSESSMENT_AGENCY_SECOND_REMINDER: {
assessmentCreationLink: "ASSESSMENT_CREATION_LINK",
beneficiaryFirstName: "BENEFICIARY_FIRST_NAME",
beneficiaryLastName: "BENEFICIARY_LAST_NAME",
businessName: "BUSINESS_NAME",
conventionId: "CONVENTION_ID",
establishmentContactEmail: "ESTABLISHMENT_CONTACT_EMAIL",
internshipKind: "immersion",
},
ASSESSMENT_AGENCY_NOTIFICATION: {
agencyLogoUrl: defaultEmailPreviewUrl,
assessmentCreationLink: "ASSESSMENT_CREATION_LINK",
Expand Down
9 changes: 9 additions & 0 deletions shared/src/email/EmailParamsByEmailType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,15 @@ export type EmailParamsByEmailType = {
establishmentContactEmail: Email;
internshipKind: InternshipKind;
};
ASSESSMENT_AGENCY_SECOND_REMINDER: {
assessmentCreationLink: string;
beneficiaryFirstName: string;
beneficiaryLastName: string;
businessName: string;
conventionId: ConventionId;
establishmentContactEmail: Email;
internshipKind: InternshipKind;
};
ASSESSMENT_BENEFICIARY_NOTIFICATION: {
conventionId: ConventionId;
beneficiaryFirstName: string;
Expand Down
34 changes: 33 additions & 1 deletion shared/src/email/emailTemplatesByName.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export const emailTemplatesByName =
ASSESSMENT_AGENCY_FIRST_REMINDER: {
niceName:
"Bilan - Prescripteurs - Relance à 3 jours après la fin de l’immersion",
tags: ["bilan_prescripteur_formulaireBilan_premierRappel"],
tags: ["bilan_prescripteur_formulaireBilan_J+3"],
createEmailVariables: ({
assessmentCreationLink,
beneficiaryFirstName,
Expand Down Expand Up @@ -69,6 +69,38 @@ export const emailTemplatesByName =
`,
}),
},
ASSESSMENT_AGENCY_SECOND_REMINDER: {
niceName:
"Bilan - Prescripteurs - Relance à 10 jours après la fin de l’immersion",
tags: ["bilan_prescripteur_formulaireBilan_J+10"],
createEmailVariables: ({
assessmentCreationLink,
beneficiaryFirstName,
beneficiaryLastName,
businessName,
conventionId,
establishmentContactEmail,
internshipKind,
}) => ({
subject: `Immersion Facilitée - Urgent : Bilan toujours non complété pour l'immersion de ${beneficiaryFirstName}`,
greetings: greetingsWithConventionId(conventionId),
content: `
Malgré une première relance, le bilan de l’immersion de ${beneficiaryFirstName} ${beneficiaryLastName} chez ${businessName} reste incomplet.
1. <a href= "mailto:${establishmentContactEmail}" target="_blank">Relancer l'entreprise</a> (${establishmentContactEmail}) pour qu’elle remplisse le bilan.
2. Les contacter par téléphone et les accompagner pour le compléter ensemble.
`,
buttons: [
{
label: "Formulaire de bilan",
url: assessmentCreationLink,
},
],
subContent: `
${defaultSignature(internshipKind)}
`,
}),
},
ASSESSMENT_AGENCY_NOTIFICATION: {
niceName: "Bilan - Prescripteurs - Lien de création du bilan",
tags: ["bilan_prescripteur_formulaireBilan"],
Expand Down

0 comments on commit 98a67c4

Please sign in to comment.