fix: improve cancellation email of reschedule RR booking (#17768)
* fix: improve cancellation email of reschedule RR booking * fix failing test * fix: uses same lucky user when rescheduling RR booking also has a fixed host * chore * chore * better message in the cancelled email * fix * fix * add booker rescheduled in reassigned email to old host * test * add reassigned subtite * fix: use same lucky user when reschedule RR booking has a fixed host * fix: test * fix: subtitle --------- Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com>
This commit is contained in:
co-authored by
Anik Dhabal Babu
parent
a4915184cd
commit
afea724e4d
@@ -711,10 +711,12 @@ export function expectSuccessfulRoundRobinReschedulingEmails({
|
||||
emails,
|
||||
newOrganizer,
|
||||
prevOrganizer,
|
||||
bookerReschedule,
|
||||
}: {
|
||||
emails: Fixtures["emails"];
|
||||
newOrganizer: { email: string; name: string };
|
||||
prevOrganizer: { email: string; name: string };
|
||||
bookerReschedule?: boolean;
|
||||
}) {
|
||||
if (newOrganizer !== prevOrganizer) {
|
||||
vi.waitFor(() => {
|
||||
@@ -738,6 +740,19 @@ export function expectSuccessfulRoundRobinReschedulingEmails({
|
||||
`${prevOrganizer.email}`
|
||||
);
|
||||
});
|
||||
|
||||
// if booking is rescheduled by booker, old organizer should recieve reassigned emails
|
||||
if (bookerReschedule) {
|
||||
vi.waitFor(() => {
|
||||
expect(emails).toHaveEmail(
|
||||
{
|
||||
heading: "event_request_reassigned",
|
||||
to: `${prevOrganizer.email}`,
|
||||
},
|
||||
`${prevOrganizer.email}`
|
||||
);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
vi.waitFor(() => {
|
||||
// organizer should receive rescheduled emails
|
||||
|
||||
@@ -237,25 +237,44 @@ export const sendRoundRobinCancelledEmailsAndSMS = async (
|
||||
calEvent: CalendarEvent,
|
||||
members: Person[],
|
||||
eventTypeMetadata?: EventTypeMetadata,
|
||||
reassignedTo?: { name: string | null; email: string }
|
||||
reassignedTo?: { name: string | null; email: string; reason?: string }
|
||||
) => {
|
||||
if (eventTypeDisableHostEmail(eventTypeMetadata)) return;
|
||||
const calendarEvent = formatCalEvent(calEvent);
|
||||
const emailsAndSMSToSend: Promise<unknown>[] = [];
|
||||
const successfullyReScheduledSMS = new EventCancelledSMS(calEvent);
|
||||
for (const teamMember of members) {
|
||||
if (!reassignedTo) {
|
||||
emailsAndSMSToSend.push(
|
||||
sendEmail(() => new OrganizerCancelledEmail({ calEvent: calendarEvent, teamMember }))
|
||||
);
|
||||
} else {
|
||||
emailsAndSMSToSend.push(
|
||||
sendEmail(
|
||||
() =>
|
||||
new OrganizerReassignedEmail({ calEvent: calendarEvent, teamMember, reassigned: reassignedTo })
|
||||
)
|
||||
);
|
||||
emailsAndSMSToSend.push(
|
||||
sendEmail(
|
||||
() => new OrganizerCancelledEmail({ calEvent: calendarEvent, teamMember, reassigned: reassignedTo })
|
||||
)
|
||||
);
|
||||
|
||||
if (teamMember.phoneNumber) {
|
||||
emailsAndSMSToSend.push(successfullyReScheduledSMS.sendSMSToAttendee(teamMember));
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all(emailsAndSMSToSend);
|
||||
};
|
||||
|
||||
export const sendRoundRobinReassignedEmailsAndSMS = async (args: {
|
||||
calEvent: CalendarEvent;
|
||||
members: Person[];
|
||||
reassignedTo: { name: string | null; email: string };
|
||||
eventTypeMetadata?: EventTypeMetadata;
|
||||
}) => {
|
||||
const { calEvent, members, reassignedTo, eventTypeMetadata } = args;
|
||||
if (eventTypeDisableHostEmail(eventTypeMetadata)) return;
|
||||
const calendarEvent = formatCalEvent(calEvent);
|
||||
const emailsAndSMSToSend: Promise<unknown>[] = [];
|
||||
const successfullyReScheduledSMS = new EventCancelledSMS(calEvent);
|
||||
for (const teamMember of members) {
|
||||
emailsAndSMSToSend.push(
|
||||
sendEmail(
|
||||
() => new OrganizerReassignedEmail({ calEvent: calendarEvent, teamMember, reassigned: reassignedTo })
|
||||
)
|
||||
);
|
||||
|
||||
if (teamMember.phoneNumber) {
|
||||
emailsAndSMSToSend.push(successfullyReScheduledSMS.sendSMSToAttendee(teamMember));
|
||||
|
||||
@@ -107,6 +107,9 @@ export const BaseScheduledEmail = (
|
||||
}
|
||||
withSpacer
|
||||
/>
|
||||
{props.reassigned?.reason && (
|
||||
<Info label={t("reason")} description={props.reassigned.reason} withSpacer />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{props.reassigned && props.reassigned.byUser && (
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
import { OrganizerScheduledEmail } from "./OrganizerScheduledEmail";
|
||||
|
||||
export const OrganizerCancelledEmail = (props: React.ComponentProps<typeof OrganizerScheduledEmail>) => (
|
||||
<OrganizerScheduledEmail
|
||||
title="event_request_cancelled"
|
||||
headerType="xCircle"
|
||||
subject="event_cancelled_subject"
|
||||
callToAction={null}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
export const OrganizerCancelledEmail = (props: React.ComponentProps<typeof OrganizerScheduledEmail>) => {
|
||||
const t = props.teamMember?.language.translate || props.calEvent.organizer.language.translate;
|
||||
const title = props.reassigned ? "event_request_reassigned" : "event_request_cancelled";
|
||||
const subtitle = props.reassigned ? t("event_reassigned_subtitle") : "";
|
||||
const subject = props.reassigned ? "event_reassigned_subject" : "event_cancelled_subject";
|
||||
return (
|
||||
<OrganizerScheduledEmail
|
||||
title={title}
|
||||
subtitle={subtitle}
|
||||
headerType="xCircle"
|
||||
subject={subject}
|
||||
callToAction={null}
|
||||
reassigned={props.reassigned}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -4,10 +4,12 @@ import type { CalendarEvent, Person } from "@calcom/types/Calendar";
|
||||
import { renderEmail } from "../";
|
||||
import generateIcsFile, { GenerateIcsRole } from "../lib/generateIcsFile";
|
||||
import OrganizerScheduledEmail from "./organizer-scheduled-email";
|
||||
import type { Reassigned } from "./organizer-scheduled-email";
|
||||
|
||||
export default class OrganizerCancelledEmail extends OrganizerScheduledEmail {
|
||||
protected async getNodeMailerPayload(): Promise<Record<string, unknown>> {
|
||||
const toAddresses = [this.teamMember?.email || this.calEvent.organizer.email];
|
||||
const subject = this.reassigned ? "event_reassigned_subject" : "event_cancelled_subject";
|
||||
|
||||
return {
|
||||
icalEvent: generateIcsFile({
|
||||
@@ -17,19 +19,20 @@ export default class OrganizerCancelledEmail extends OrganizerScheduledEmail {
|
||||
}),
|
||||
from: `${EMAIL_FROM_NAME} <${this.getMailerOptions().from}>`,
|
||||
to: toAddresses.join(","),
|
||||
subject: `${this.t("event_cancelled_subject", {
|
||||
subject: `${this.t(subject, {
|
||||
title: this.calEvent.title,
|
||||
date: this.getFormattedDate(),
|
||||
})}`,
|
||||
html: await this.getHtml(this.calEvent, this.calEvent.organizer),
|
||||
html: await this.getHtml(this.calEvent, this.calEvent.organizer, this.reassigned),
|
||||
text: this.getTextBody("event_request_cancelled"),
|
||||
};
|
||||
}
|
||||
|
||||
async getHtml(calEvent: CalendarEvent, organizer: Person) {
|
||||
async getHtml(calEvent: CalendarEvent, organizer: Person, reassigned: Reassigned | undefined) {
|
||||
return await renderEmail("OrganizerCancelledEmail", {
|
||||
calEvent,
|
||||
attendee: organizer,
|
||||
reassigned,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,8 +80,8 @@ import { CreationSource } from "@calcom/prisma/enums";
|
||||
import {
|
||||
eventTypeAppMetadataOptionalSchema,
|
||||
eventTypeMetaDataSchemaWithTypedApps,
|
||||
userMetadata as userMetadataSchema,
|
||||
} from "@calcom/prisma/zod-utils";
|
||||
import { userMetadata as userMetadataSchema } from "@calcom/prisma/zod-utils";
|
||||
import { getAllWorkflowsFromEventType } from "@calcom/trpc/server/routers/viewer/workflows/util";
|
||||
import type {
|
||||
AdditionalInformation,
|
||||
@@ -1929,7 +1929,21 @@ async function handler(
|
||||
members: newBookedMembers,
|
||||
eventTypeMetadata: eventType.metadata,
|
||||
});
|
||||
sendRoundRobinCancelledEmailsAndSMS(cancelledRRHostEvt, cancelledMembers, eventType.metadata);
|
||||
const reassignedTo = users.find(
|
||||
(user) => !user.isFixed && newBookedMembers.some((member) => member.email === user.email)
|
||||
);
|
||||
sendRoundRobinCancelledEmailsAndSMS(
|
||||
cancelledRRHostEvt,
|
||||
cancelledMembers,
|
||||
eventType.metadata,
|
||||
!!reassignedTo
|
||||
? {
|
||||
name: reassignedTo.name,
|
||||
email: reassignedTo.email,
|
||||
...(reqBody.rescheduledBy === bookerEmail && { reason: "Booker Rescheduled" }),
|
||||
}
|
||||
: undefined
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (!isDryRun) {
|
||||
|
||||
@@ -2006,7 +2006,7 @@ describe("handleNewBooking", () => {
|
||||
});
|
||||
describe("Team event-type", () => {
|
||||
test(
|
||||
"should send correct schedule/cancellation emails to hosts when round robin is rescheduled to different host",
|
||||
"should send correct schedule/cancellation/reassigned emails to hosts when round robin is rescheduled to different host",
|
||||
async ({ emails }) => {
|
||||
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
|
||||
const booker = getBooker({
|
||||
@@ -2158,6 +2158,7 @@ describe("handleNewBooking", () => {
|
||||
prevOrganizer: roundRobinHost1,
|
||||
newOrganizer: roundRobinHost2,
|
||||
emails,
|
||||
bookerReschedule: true,
|
||||
});
|
||||
},
|
||||
timeout
|
||||
|
||||
@@ -463,9 +463,9 @@ describe("roundRobinManualReassignment test", () => {
|
||||
const roundRobinManualReassignment = (await import("./roundRobinManualReassignment")).default;
|
||||
await mockEventManagerReschedule();
|
||||
|
||||
const sendRoundRobinCancelledEmailsAndSMSSpy = vi.spyOn(
|
||||
const sendRoundRobinReassignedEmailsAndSMSSpy = vi.spyOn(
|
||||
await import("@calcom/emails"),
|
||||
"sendRoundRobinCancelledEmailsAndSMS"
|
||||
"sendRoundRobinReassignedEmailsAndSMS"
|
||||
);
|
||||
|
||||
const testDestinationCalendar = createTestDestinationCalendar();
|
||||
@@ -521,7 +521,7 @@ describe("roundRobinManualReassignment test", () => {
|
||||
reassignedById: 1,
|
||||
});
|
||||
|
||||
expect(sendRoundRobinCancelledEmailsAndSMSSpy).toHaveBeenCalledTimes(1);
|
||||
expect(sendRoundRobinReassignedEmailsAndSMSSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { cloneDeep } from "lodash";
|
||||
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import {
|
||||
sendRoundRobinCancelledEmailsAndSMS,
|
||||
sendRoundRobinReassignedEmailsAndSMS,
|
||||
sendRoundRobinScheduledEmailsAndSMS,
|
||||
sendRoundRobinUpdatedEmailsAndSMS,
|
||||
} from "@calcom/emails";
|
||||
@@ -373,9 +373,9 @@ export const roundRobinManualReassignment = async ({
|
||||
};
|
||||
|
||||
if (previousRRHost && emailsEnabled) {
|
||||
await sendRoundRobinCancelledEmailsAndSMS(
|
||||
cancelledEvt,
|
||||
[
|
||||
await sendRoundRobinReassignedEmailsAndSMS({
|
||||
calEvent: cancelledEvt,
|
||||
members: [
|
||||
{
|
||||
...previousRRHost,
|
||||
name: previousRRHost.name || "",
|
||||
@@ -384,9 +384,9 @@ export const roundRobinManualReassignment = async ({
|
||||
language: { translate: previousRRHostT, locale: previousRRHost.locale || "en" },
|
||||
},
|
||||
],
|
||||
eventType?.metadata as EventTypeMetadata,
|
||||
{ name: newUser.name, email: newUser.email }
|
||||
);
|
||||
reassignedTo: { name: newUser.name, email: newUser.email },
|
||||
eventTypeMetadata: eventType?.metadata as EventTypeMetadata,
|
||||
});
|
||||
}
|
||||
|
||||
if (hasOrganizerChanged) {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { cloneDeep } from "lodash";
|
||||
import { OrganizerDefaultConferencingAppType, getLocationValueForDB } from "@calcom/app-store/locations";
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import {
|
||||
sendRoundRobinCancelledEmailsAndSMS,
|
||||
sendRoundRobinReassignedEmailsAndSMS,
|
||||
sendRoundRobinScheduledEmailsAndSMS,
|
||||
sendRoundRobinUpdatedEmailsAndSMS,
|
||||
} from "@calcom/emails";
|
||||
@@ -420,9 +420,9 @@ export const roundRobinReassignment = async ({
|
||||
}
|
||||
|
||||
if (emailsEnabled) {
|
||||
await sendRoundRobinCancelledEmailsAndSMS(
|
||||
cancelledRRHostEvt,
|
||||
[
|
||||
await sendRoundRobinReassignedEmailsAndSMS({
|
||||
calEvent: cancelledRRHostEvt,
|
||||
members: [
|
||||
{
|
||||
...previousRRHost,
|
||||
name: previousRRHost.name || "",
|
||||
@@ -431,9 +431,9 @@ export const roundRobinReassignment = async ({
|
||||
language: { translate: previousRRHostT, locale: previousRRHost.locale || "en" },
|
||||
},
|
||||
],
|
||||
eventType?.metadata as EventTypeMetadata,
|
||||
{ name: reassignedRRHost.name, email: reassignedRRHost.email }
|
||||
);
|
||||
reassignedTo: { name: reassignedRRHost.name, email: reassignedRRHost.email },
|
||||
eventTypeMetadata: eventType?.metadata as EventTypeMetadata,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user