diff --git a/cypress/support/step_definitions/annual-review/scheduler/an_email_for_the_string_key_date_is_sent_to_eligible_providers.js b/cypress/support/step_definitions/annual-review/scheduler/an_email_for_the_string_key_date_is_sent_to_eligible_providers.js index 40e3ae4c2..a0587a2b0 100644 --- a/cypress/support/step_definitions/annual-review/scheduler/an_email_for_the_string_key_date_is_sent_to_eligible_providers.js +++ b/cypress/support/step_definitions/annual-review/scheduler/an_email_for_the_string_key_date_is_sent_to_eligible_providers.js @@ -17,7 +17,7 @@ Then("an email for the {string} key date is sent to eligible providers", async f { jsonData: { path: ["notes"], - equals: ["sendStartedProviderEmail"], + array_contains: ["sendStartedProviderEmail"], }, }, { diff --git a/src/scheduler/workers/processListsBeforeAndDuringStart/helpers.ts b/src/scheduler/workers/processListsBeforeAndDuringStart/helpers.ts index a35333560..81dff45c6 100644 --- a/src/scheduler/workers/processListsBeforeAndDuringStart/helpers.ts +++ b/src/scheduler/workers/processListsBeforeAndDuringStart/helpers.ts @@ -14,7 +14,7 @@ export function formatDate(date: Date = todayDateString) { return date.toLocaleString("en-gb", options); } -export function hasDataContainingReference( +export function isEmailSentBefore( event: Event | Audit | undefined, reminderType: ListAnnualReviewPostReminderType | ListItemAnnualReviewProviderReminderType ): boolean { @@ -38,7 +38,7 @@ export function hasDataContainingReference( if ("createdAt" in event) { reminderHasBeenSent = subsequentEmails?.includes?.((event.jsonData as ListEventJsonData)?.reminderType as string); } else { - reminderHasBeenSent = subsequentEmails?.includes?.((event.jsonData as Record)?.notes); + reminderHasBeenSent = subsequentEmails?.includes?.((event.jsonData as Record)?.notes[0]); } if (reminderHasBeenSent) { diff --git a/src/scheduler/workers/processListsBeforeAndDuringStart/main.ts b/src/scheduler/workers/processListsBeforeAndDuringStart/main.ts index 7f9e62cfc..3cb9ba2a2 100644 --- a/src/scheduler/workers/processListsBeforeAndDuringStart/main.ts +++ b/src/scheduler/workers/processListsBeforeAndDuringStart/main.ts @@ -14,7 +14,7 @@ import type { ListItemWithHistory, } from "shared/types"; import type { MilestoneTillAnnualReview } from "../../batch/helpers"; -import { formatDate, hasDataContainingReference } from "./helpers"; +import { formatDate, isEmailSentBefore } from "./helpers"; import { createAnnualReviewProviderUrl } from "scheduler/workers/createAnnualReviewProviderUrl"; import { sendAnnualReviewPostEmail, @@ -47,28 +47,26 @@ async function processPostEmailsForList( ); }); - const sendResult = await Promise.allSettled(postEmailPromises); - const emailSent = sendResult.find((result) => result.status === "fulfilled" && result.value); + try { + await Promise.any(postEmailPromises); + logger.info(`Annual review email sent to post contacts ${list.jsonData.users}`); - if (!emailSent) { + await prisma.audit.create({ + data: { + auditEvent: AuditEvent.REMINDER, + type: "list", + jsonData: { + eventName: "reminder", + annualReviewRef: list.jsonData.currentAnnualReview?.reference, + reminderType, + }, + }, + }); + } catch (e) { logger.error( `processPostEmailsForList: Unable to send annual review email to post contacts ${list.jsonData.users} for list ${list.id} ${milestoneTillAnnualReview} before annual review start` ); - return; } - logger.info(`Annual review email sent to post contacts ${list.jsonData.users}`); - - await prisma.audit.create({ - data: { - auditEvent: AuditEvent.REMINDER, - type: "list", - jsonData: { - eventName: "reminder", - annualReviewRef: list.jsonData.currentAnnualReview?.reference, - reminderType, - }, - }, - }); } async function sendAnnualReviewStartEmail(list: List, listItem: ListItemWithHistory) { @@ -97,7 +95,7 @@ async function processProviderEmailsForListItems(list: List, listItems: ListItem const { result: events } = await findAllReminderEvents({ annualReviewReference, itemId: listItem.id }); if (events?.length) { const event = events.pop(); - isEmailSent = hasDataContainingReference(event, "sendStartedProviderEmail"); + isEmailSent = isEmailSentBefore(event, "sendStartedProviderEmail"); } } @@ -167,7 +165,7 @@ export async function processList(list: List, listItemsForList: ListItemWithHist end: subDays(endOfDay(new Date(annualReviewKeyDates?.POST_ONE_WEEK ?? "")), 1), }) ) { - isEmailSent = hasDataContainingReference(latestAudit as Audit, "sendOneMonthPostEmail"); + isEmailSent = isEmailSentBefore(latestAudit as Audit, "sendOneMonthPostEmail"); if (!isEmailSent) { await processPostEmailsForList(list, "POST_ONE_MONTH", "sendOneMonthPostEmail"); } @@ -179,14 +177,14 @@ export async function processList(list: List, listItemsForList: ListItemWithHist end: subDays(endOfDay(new Date(annualReviewKeyDates?.POST_ONE_DAY ?? "")), 1), }) ) { - isEmailSent = hasDataContainingReference(latestAudit as Audit, "sendOneWeekPostEmail"); + isEmailSent = isEmailSentBefore(latestAudit as Audit, "sendOneWeekPostEmail"); if (!isEmailSent) { await processPostEmailsForList(list, "POST_ONE_WEEK", "sendOneWeekPostEmail"); } return; } if (isSameDay(today, new Date(annualReviewKeyDates?.POST_ONE_DAY ?? ""))) { - isEmailSent = hasDataContainingReference(latestAudit as Audit, "sendOneDayPostEmail"); + isEmailSent = isEmailSentBefore(latestAudit as Audit, "sendOneDayPostEmail"); if (!isEmailSent) { await processPostEmailsForList(list, "POST_ONE_DAY", "sendOneDayPostEmail"); } @@ -194,14 +192,14 @@ export async function processList(list: List, listItemsForList: ListItemWithHist } if (isSameDay(new Date(annualReviewKeyDates?.START ?? ""), today)) { // email posts to notify of annual review start - isEmailSent = hasDataContainingReference(latestAudit as Audit, "sendStartedPostEmail"); + isEmailSent = isEmailSentBefore(latestAudit as Audit, "sendStartedPostEmail"); if (!isEmailSent) { await processPostEmailsForList(list, "START", "sendStartedPostEmail"); } // update ListItem.isAnnualReview if today = the START milestone date // email providers to notify of annual review start - isEmailSent = hasDataContainingReference(latestEvent as Event, "sendStartedProviderEmail"); + isEmailSent = isEmailSentBefore(latestEvent as Event, "sendStartedProviderEmail"); if (isEmailSent) { logger.info(`Annual review started email has already been sent to providers for list ${list.id}`); return; diff --git a/src/server/models/listItem/listItem.ts b/src/server/models/listItem/listItem.ts index 82d320d21..b585b6466 100644 --- a/src/server/models/listItem/listItem.ts +++ b/src/server/models/listItem/listItem.ts @@ -6,7 +6,7 @@ import { listItemCreateInputFromWebhook } from "./listItemCreateInputFromWebhook import pgescape from "pg-escape"; import { prisma } from "server/models/db/prisma-client"; import { logger } from "server/services/logger"; -import { Status } from "@prisma/client"; +import { AuditEvent, Status } from "@prisma/client"; import { merge } from "lodash"; import { EVENTS } from "./listItemEvent"; import { subMonths } from "date-fns"; @@ -458,6 +458,17 @@ export async function deleteListItem(id: number, userId: User["id"]): Promise = T extends object + ? { + [P in keyof T]?: DeepPartial; + } + : T; + export function getNotifyClient() { if (config.isSmokeTest) { return new FakeNotifyClient(); @@ -20,8 +27,8 @@ export function getNotifyClient() { } class FakeNotifyClient { - sendEmail() { - return { id: "Created" }; + sendEmail(): { statusText: string; data: DeepPartial } { + return { statusText: "test", data: { id: "Created" } }; } } diff --git a/src/types.d.ts b/src/types.d.ts index e35bdd4e5..8d16473e4 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -120,7 +120,7 @@ declare module "notifications-node-client" { is_csv: boolean; } - type Response = Promise<{ status: number; data: T | ErrorResponse }>; + type Response = Promise<{ status: number; statusText: string; data: T | ErrorResponse }>; interface ErrorResponse { status_code: number;