refactor: handle emails and sms side effects (#23578)
* refactor: handle emails and sms side effects * fix: typo and import part * fix: types import path * fix: rename * refactor: class with logger dependency * fix: replace structured clone with cloneDeep * fix: await send reschedule round robin notifs * fixup! fix: await send reschedule round robin notifs * fix: avoid oversharing data * refactor and fix tests * chore: bump platform lib * chore: bump platform lib * chore: use logger with event details * chore: add try catches * refactor: remove private methods to send sms and emails * chore: address code review * chore: bump platform lib * chore: address code review
This commit is contained in:
1 parent
184d1eb751
commit
60a8841439
4 files changed
+367
-184
No files matched your search
@@ -38,7 +38,7 @@
|
||||
"@axiomhq/winston": "^1.2.0",
|
||||
"@calcom/platform-constants": "*",
|
||||
"@calcom/platform-enums": "*",
|
||||
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.340",
|
||||
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.344",
|
||||
"@calcom/platform-types": "*",
|
||||
"@calcom/platform-utils": "*",
|
||||
"@calcom/prisma": "*",
|
||||
|
||||
@@ -0,0 +1,325 @@
|
||||
import { default as cloneDeep } from "lodash/cloneDeep";
|
||||
import type { Logger } from "tslog";
|
||||
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import {
|
||||
allowDisablingHostConfirmationEmails,
|
||||
allowDisablingAttendeeConfirmationEmails,
|
||||
} from "@calcom/ee/workflows/lib/allowDisablingStandardEmails";
|
||||
import type { Workflow as WorkflowType } from "@calcom/ee/workflows/lib/types";
|
||||
import {
|
||||
sendRoundRobinRescheduledEmailsAndSMS,
|
||||
sendRoundRobinScheduledEmailsAndSMS,
|
||||
sendRoundRobinCancelledEmailsAndSMS,
|
||||
sendRescheduledEmailsAndSMS,
|
||||
sendScheduledEmailsAndSMS,
|
||||
sendOrganizerRequestEmail,
|
||||
sendAttendeeRequestEmailAndSMS,
|
||||
} from "@calcom/emails";
|
||||
import type { BookingType } from "@calcom/features/bookings/lib/handleNewBooking/originalRescheduledBookingUtils";
|
||||
import type { EventNameObjectType } from "@calcom/lib/event";
|
||||
import { getPiiFreeCalendarEvent } from "@calcom/lib/piiFreeData";
|
||||
import { safeStringify } from "@calcom/lib/safeStringify";
|
||||
import { getTranslation } from "@calcom/lib/server/i18n";
|
||||
import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat";
|
||||
import type { DestinationCalendar, Prisma, User } from "@calcom/prisma/client";
|
||||
import type { SchedulingType } from "@calcom/prisma/enums";
|
||||
import type { EventTypeMetadata } from "@calcom/prisma/zod-utils";
|
||||
import type { AdditionalInformation, CalendarEvent, Person } from "@calcom/types/Calendar";
|
||||
|
||||
export const BookingActionMap = {
|
||||
confirmed: "BOOKING_CONFIRMED",
|
||||
rescheduled: "BOOKING_RESCHEDULED",
|
||||
requested: "BOOKING_REQUESTED",
|
||||
} as const;
|
||||
|
||||
type EmailAndSmsPayload = {
|
||||
evt: CalendarEvent;
|
||||
eventType: {
|
||||
metadata?: EventTypeMetadata;
|
||||
schedulingType: SchedulingType | null;
|
||||
};
|
||||
};
|
||||
|
||||
type RescheduleEmailAndSmsPayload = EmailAndSmsPayload & {
|
||||
rescheduleReason?: string;
|
||||
additionalInformation: AdditionalInformation;
|
||||
additionalNotes: string | null | undefined;
|
||||
iCalUID: string;
|
||||
users: (Pick<User, "id" | "name" | "timeZone" | "locale" | "email"> & {
|
||||
destinationCalendar: DestinationCalendar | null;
|
||||
isFixed?: boolean;
|
||||
})[];
|
||||
changedOrganizer?: boolean;
|
||||
isRescheduledByBooker: boolean;
|
||||
originalRescheduledBooking: NonNullable<BookingType>;
|
||||
};
|
||||
|
||||
type ConfirmedEmailAndSmsPayload = EmailAndSmsPayload & {
|
||||
workflows: WorkflowType[];
|
||||
eventNameObject: EventNameObjectType;
|
||||
additionalInformation: AdditionalInformation;
|
||||
additionalNotes: string | null | undefined;
|
||||
customInputs: Prisma.JsonObject | null | undefined;
|
||||
};
|
||||
|
||||
type RequestedEmailAndSmsPayload = EmailAndSmsPayload & {
|
||||
attendees?: Person[];
|
||||
additionalNotes?: string | null;
|
||||
};
|
||||
|
||||
type RescheduledSideEffectsPayload = {
|
||||
action: typeof BookingActionMap.rescheduled;
|
||||
data: RescheduleEmailAndSmsPayload;
|
||||
};
|
||||
|
||||
type ConfirmedSideEffectsPayload = {
|
||||
action: typeof BookingActionMap.confirmed;
|
||||
data: ConfirmedEmailAndSmsPayload;
|
||||
};
|
||||
|
||||
type RequestedSideEffectsPayload = {
|
||||
action: typeof BookingActionMap.requested;
|
||||
data: RequestedEmailAndSmsPayload;
|
||||
};
|
||||
|
||||
export type EmailsAndSmsSideEffectsPayload =
|
||||
| RescheduledSideEffectsPayload
|
||||
| RequestedSideEffectsPayload
|
||||
| ConfirmedSideEffectsPayload;
|
||||
|
||||
export interface IBookingEmailSmsHandler {
|
||||
logger: Logger<unknown>;
|
||||
}
|
||||
|
||||
export class BookingEmailSmsHandler {
|
||||
private readonly log: Logger<unknown>;
|
||||
|
||||
constructor(dependencies: IBookingEmailSmsHandler) {
|
||||
this.log = dependencies.logger.getSubLogger({ prefix: ["BookingEmailSmsHandler"] });
|
||||
}
|
||||
|
||||
public async send(payload: EmailsAndSmsSideEffectsPayload) {
|
||||
const { action, data } = payload;
|
||||
|
||||
if (action === BookingActionMap.rescheduled) {
|
||||
if (data.eventType.schedulingType === "ROUND_ROBIN") return this._handleRoundRobinRescheduled(data);
|
||||
return this._handleRescheduled(data);
|
||||
}
|
||||
|
||||
if (action === BookingActionMap.confirmed) return this._handleConfirmed(data);
|
||||
if (action === BookingActionMap.requested) return this._handleRequested(data);
|
||||
|
||||
this.log.warn("Unknown email/SMS action requested.", { action });
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles notifications for a RESCHEDULED booking.
|
||||
*/
|
||||
private async _handleRescheduled(data: RescheduleEmailAndSmsPayload) {
|
||||
const {
|
||||
evt,
|
||||
eventType: { metadata },
|
||||
rescheduleReason,
|
||||
additionalNotes,
|
||||
additionalInformation,
|
||||
} = data;
|
||||
|
||||
await sendRescheduledEmailsAndSMS(
|
||||
{
|
||||
...evt,
|
||||
additionalInformation,
|
||||
additionalNotes,
|
||||
cancellationReason: `$RCH$${rescheduleReason || ""}`,
|
||||
},
|
||||
metadata
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles notifications for a RESCHEDULED RR booking.
|
||||
*/
|
||||
private async _handleRoundRobinRescheduled(data: RescheduleEmailAndSmsPayload) {
|
||||
const {
|
||||
evt,
|
||||
eventType: { metadata },
|
||||
originalRescheduledBooking,
|
||||
rescheduleReason,
|
||||
additionalNotes,
|
||||
changedOrganizer,
|
||||
additionalInformation,
|
||||
users,
|
||||
isRescheduledByBooker,
|
||||
iCalUID,
|
||||
} = data;
|
||||
const copyEvent = cloneDeep(evt);
|
||||
const copyEventAdditionalInfo = {
|
||||
...copyEvent,
|
||||
additionalInformation,
|
||||
additionalNotes,
|
||||
cancellationReason: `$RCH$${rescheduleReason || ""}`,
|
||||
};
|
||||
const cancelledRRHostEvt = cloneDeep(copyEventAdditionalInfo);
|
||||
this.log.debug("Emails: Sending rescheduled emails for booking confirmation");
|
||||
|
||||
const originalBookingMemberEmails: Person[] = [];
|
||||
|
||||
for (const user of originalRescheduledBooking.attendees) {
|
||||
const translate = await getTranslation(user.locale ?? "en", "common");
|
||||
originalBookingMemberEmails.push({
|
||||
name: user.name,
|
||||
email: user.email,
|
||||
timeZone: user.timeZone,
|
||||
phoneNumber: user.phoneNumber,
|
||||
language: { translate, locale: user.locale ?? "en" },
|
||||
});
|
||||
}
|
||||
if (originalRescheduledBooking.user) {
|
||||
const translate = await getTranslation(originalRescheduledBooking.user.locale ?? "en", "common");
|
||||
const originalOrganizer = originalRescheduledBooking.user;
|
||||
|
||||
originalBookingMemberEmails.push({
|
||||
...originalRescheduledBooking.user,
|
||||
username: originalRescheduledBooking.user.username ?? undefined,
|
||||
timeFormat: getTimeFormatStringFromUserTimeFormat(originalRescheduledBooking.user.timeFormat),
|
||||
name: originalRescheduledBooking.user.name || "",
|
||||
language: { translate, locale: originalRescheduledBooking.user.locale ?? "en" },
|
||||
});
|
||||
|
||||
if (changedOrganizer) {
|
||||
cancelledRRHostEvt.title = originalRescheduledBooking.title;
|
||||
cancelledRRHostEvt.startTime =
|
||||
dayjs(originalRescheduledBooking?.startTime).utc().format() || copyEventAdditionalInfo.startTime;
|
||||
cancelledRRHostEvt.endTime =
|
||||
dayjs(originalRescheduledBooking?.endTime).utc().format() || copyEventAdditionalInfo.endTime;
|
||||
cancelledRRHostEvt.organizer = {
|
||||
email: originalOrganizer.email,
|
||||
name: originalOrganizer.name || "",
|
||||
timeZone: originalOrganizer.timeZone,
|
||||
language: { translate, locale: originalOrganizer.locale || "en" },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const newBookingMemberEmails: Person[] = [
|
||||
...(copyEvent.team?.members || []),
|
||||
copyEvent.organizer,
|
||||
...copyEvent.attendees,
|
||||
];
|
||||
|
||||
const matchOriginalMemberWithNewMember = (originalMember: Person, newMember: Person) =>
|
||||
originalMember.email === newMember.email;
|
||||
|
||||
const newBookedMembers = newBookingMemberEmails.filter(
|
||||
(member) => !originalBookingMemberEmails.some((om) => matchOriginalMemberWithNewMember(om, member))
|
||||
);
|
||||
const cancelledMembers = originalBookingMemberEmails.filter(
|
||||
(member) => !newBookingMemberEmails.some((nm) => matchOriginalMemberWithNewMember(member, nm))
|
||||
);
|
||||
const rescheduledMembers = newBookingMemberEmails.filter((member) =>
|
||||
originalBookingMemberEmails.some((om) => matchOriginalMemberWithNewMember(om, member))
|
||||
);
|
||||
|
||||
const reassignedTo = users.find(
|
||||
(user) => !user.isFixed && newBookedMembers.some((member) => member.email === user.email)
|
||||
);
|
||||
|
||||
try {
|
||||
await Promise.all([
|
||||
sendRoundRobinRescheduledEmailsAndSMS(
|
||||
{ ...copyEventAdditionalInfo, iCalUID },
|
||||
rescheduledMembers,
|
||||
metadata
|
||||
),
|
||||
sendRoundRobinScheduledEmailsAndSMS({
|
||||
calEvent: copyEventAdditionalInfo,
|
||||
members: newBookedMembers,
|
||||
eventTypeMetadata: metadata,
|
||||
}),
|
||||
sendRoundRobinCancelledEmailsAndSMS(
|
||||
cancelledRRHostEvt,
|
||||
cancelledMembers,
|
||||
metadata,
|
||||
reassignedTo
|
||||
? {
|
||||
name: reassignedTo.name,
|
||||
email: reassignedTo.email,
|
||||
...(isRescheduledByBooker && { reason: "Booker Rescheduled" }),
|
||||
}
|
||||
: undefined
|
||||
),
|
||||
]);
|
||||
} catch (err) {
|
||||
this.log.error("Failed to send rescheduled round robin event related emails", err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles notifications for a newly CONFIRMED booking.
|
||||
*/
|
||||
private async _handleConfirmed(data: ConfirmedEmailAndSmsPayload) {
|
||||
const {
|
||||
evt,
|
||||
eventType: { metadata },
|
||||
workflows,
|
||||
eventNameObject,
|
||||
additionalInformation,
|
||||
additionalNotes,
|
||||
customInputs,
|
||||
} = data;
|
||||
|
||||
let isHostConfirmationEmailsDisabled = metadata?.disableStandardEmails?.confirmation?.host || false;
|
||||
if (isHostConfirmationEmailsDisabled) {
|
||||
isHostConfirmationEmailsDisabled = allowDisablingHostConfirmationEmails(workflows);
|
||||
}
|
||||
|
||||
let isAttendeeConfirmationEmailDisabled =
|
||||
metadata?.disableStandardEmails?.confirmation?.attendee || false;
|
||||
if (isAttendeeConfirmationEmailDisabled) {
|
||||
isAttendeeConfirmationEmailDisabled = allowDisablingAttendeeConfirmationEmails(workflows);
|
||||
}
|
||||
|
||||
try {
|
||||
await sendScheduledEmailsAndSMS(
|
||||
{ ...evt, additionalInformation, additionalNotes, customInputs },
|
||||
eventNameObject,
|
||||
isHostConfirmationEmailsDisabled,
|
||||
isAttendeeConfirmationEmailDisabled,
|
||||
metadata
|
||||
);
|
||||
} catch (err) {
|
||||
this.log.error("Failed to send scheduled event related emails", err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles notifications when a booking REQUEST is made (requires confirmation).
|
||||
*/
|
||||
private async _handleRequested(data: RequestedEmailAndSmsPayload) {
|
||||
const {
|
||||
evt,
|
||||
eventType: { metadata },
|
||||
attendees,
|
||||
additionalNotes,
|
||||
} = data;
|
||||
if (!attendees?.length) {
|
||||
this.log.error("Requested action called without attendee details.");
|
||||
return;
|
||||
}
|
||||
this.log.debug(
|
||||
"Action: BOOKING_REQUESTED. Sending request emails.",
|
||||
safeStringify({ calEvent: getPiiFreeCalendarEvent(evt) })
|
||||
);
|
||||
|
||||
const eventWithNotes = { ...evt, additionalNotes };
|
||||
|
||||
try {
|
||||
await Promise.all([
|
||||
sendOrganizerRequestEmail(eventWithNotes, metadata),
|
||||
sendAttendeeRequestEmailAndSMS(eventWithNotes, attendees[0], metadata),
|
||||
]);
|
||||
} catch (err) {
|
||||
this.log.error("Failed to send requested event related emails", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { DestinationCalendar, User } from "@prisma/client";
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { cloneDeep } from "lodash";
|
||||
import short, { uuid } from "short-uuid";
|
||||
import { v5 as uuidv5 } from "uuid";
|
||||
|
||||
@@ -14,15 +13,6 @@ import {
|
||||
import { getAppFromSlug } from "@calcom/app-store/utils";
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { scheduleMandatoryReminder } from "@calcom/ee/workflows/lib/reminders/scheduleMandatoryReminder";
|
||||
import {
|
||||
sendAttendeeRequestEmailAndSMS,
|
||||
sendOrganizerRequestEmail,
|
||||
sendRescheduledEmailsAndSMS,
|
||||
sendRoundRobinCancelledEmailsAndSMS,
|
||||
sendRoundRobinRescheduledEmailsAndSMS,
|
||||
sendRoundRobinScheduledEmailsAndSMS,
|
||||
sendScheduledEmailsAndSMS,
|
||||
} from "@calcom/emails";
|
||||
import getICalUID from "@calcom/emails/lib/getICalUID";
|
||||
import { CalendarEventBuilder } from "@calcom/features/CalendarEventBuilder";
|
||||
import type { BookingDataSchemaGetter } from "@calcom/features/bookings/lib/dto/types";
|
||||
@@ -32,10 +22,6 @@ import { handleWebhookTrigger } from "@calcom/features/bookings/lib/handleWebhoo
|
||||
import { isEventTypeLoggingEnabled } from "@calcom/features/bookings/lib/isEventTypeLoggingEnabled";
|
||||
import type { CacheService } from "@calcom/features/calendar-cache/lib/getShouldServeCache";
|
||||
import AssignmentReasonRecorder from "@calcom/features/ee/round-robin/assignmentReason/AssignmentReasonRecorder";
|
||||
import {
|
||||
allowDisablingAttendeeConfirmationEmails,
|
||||
allowDisablingHostConfirmationEmails,
|
||||
} from "@calcom/features/ee/workflows/lib/allowDisablingStandardEmails";
|
||||
import type { FeaturesRepository } from "@calcom/features/flags/features.repository";
|
||||
import { getFullName } from "@calcom/features/form-builder/utils";
|
||||
import { UsersRepository } from "@calcom/features/users/users.repository";
|
||||
@@ -101,12 +87,12 @@ import type {
|
||||
AppsStatus,
|
||||
CalendarEvent,
|
||||
CalEventResponses,
|
||||
Person,
|
||||
} from "@calcom/types/Calendar";
|
||||
import type { CredentialForCalendarService } from "@calcom/types/Credential";
|
||||
import type { EventResult, PartialReference } from "@calcom/types/EventManager";
|
||||
|
||||
import type { EventPayloadType, EventTypeInfo } from "../../webhooks/lib/sendPayload";
|
||||
import { BookingActionMap, BookingEmailSmsHandler } from "./BookingEmailSmsHandler";
|
||||
import { getAllCredentialsIncludeServiceAccountKey } from "./getAllCredentialsForUsersOnEvent/getAllCredentials";
|
||||
import { refreshCredentials } from "./getAllCredentialsForUsersOnEvent/refreshCredentials";
|
||||
import getBookingDataSchema from "./getBookingDataSchema";
|
||||
@@ -503,6 +489,7 @@ async function handler(
|
||||
});
|
||||
|
||||
const loggerWithEventDetails = createLoggerWithEventDetails(eventTypeId, reqBody.user, eventTypeSlug);
|
||||
const emailsAndSmsHandler = new BookingEmailSmsHandler({ logger: loggerWithEventDetails });
|
||||
|
||||
await checkIfBookerEmailIsBlocked({ loggedInUserId: userId, bookerEmail });
|
||||
|
||||
@@ -996,7 +983,7 @@ async function handler(
|
||||
|
||||
// Filter out host groups that have no hosts in them
|
||||
const nonEmptyHostGroups = Object.fromEntries(
|
||||
Object.entries(hostGroups).filter(([groupId, hosts]) => hosts.length > 0)
|
||||
Object.entries(hostGroups).filter(([_groupId, hosts]) => hosts.length > 0)
|
||||
);
|
||||
// If there are RR hosts, we need to find a lucky user
|
||||
if (
|
||||
@@ -1660,7 +1647,7 @@ async function handler(
|
||||
|
||||
let videoCallUrl;
|
||||
|
||||
//this is the actual rescheduling logic
|
||||
// this is the actual rescheduling logic
|
||||
if (!eventType.seatsPerTimeSlot && originalRescheduledBooking?.uid) {
|
||||
log.silly("Rescheduling booking", originalRescheduledBooking.uid);
|
||||
// cancel workflow reminders from previous rescheduled booking
|
||||
@@ -1845,133 +1832,22 @@ async function handler(
|
||||
|
||||
evt.appsStatus = handleAppsStatus(results, booking, reqAppsStatus);
|
||||
|
||||
if (noEmail !== true && isConfirmedByDefault) {
|
||||
const copyEvent = cloneDeep(evt);
|
||||
const copyEventAdditionalInfo = {
|
||||
...copyEvent,
|
||||
additionalInformation: metadata,
|
||||
additionalNotes, // Resets back to the additionalNote input and not the override value
|
||||
cancellationReason: `$RCH$${rescheduleReason ? rescheduleReason : ""}`, // Removable code prefix to differentiate cancellation from rescheduling for email
|
||||
};
|
||||
const cancelledRRHostEvt = cloneDeep(copyEventAdditionalInfo);
|
||||
loggerWithEventDetails.debug("Emails: Sending rescheduled emails for booking confirmation");
|
||||
|
||||
/*
|
||||
handle emails for round robin
|
||||
- if booked rr host is the same, then rescheduling email
|
||||
- if new rr host is booked, then cancellation email to old host and confirmation email to new host
|
||||
*/
|
||||
if (eventType.schedulingType === SchedulingType.ROUND_ROBIN) {
|
||||
const originalBookingMemberEmails: Person[] = [];
|
||||
|
||||
for (const user of originalRescheduledBooking.attendees) {
|
||||
const translate = await getTranslation(user.locale ?? "en", "common");
|
||||
originalBookingMemberEmails.push({
|
||||
name: user.name,
|
||||
email: user.email,
|
||||
timeZone: user.timeZone,
|
||||
phoneNumber: user.phoneNumber,
|
||||
language: { translate, locale: user.locale ?? "en" },
|
||||
});
|
||||
}
|
||||
if (originalRescheduledBooking.user) {
|
||||
const translate = await getTranslation(originalRescheduledBooking.user.locale ?? "en", "common");
|
||||
const originalOrganizer = originalRescheduledBooking.user;
|
||||
|
||||
originalBookingMemberEmails.push({
|
||||
...originalRescheduledBooking.user,
|
||||
username: originalRescheduledBooking.user.username ?? undefined,
|
||||
timeFormat: getTimeFormatStringFromUserTimeFormat(originalRescheduledBooking.user.timeFormat),
|
||||
name: originalRescheduledBooking.user.name || "",
|
||||
language: { translate, locale: originalRescheduledBooking.user.locale ?? "en" },
|
||||
});
|
||||
|
||||
if (changedOrganizer) {
|
||||
cancelledRRHostEvt.title = originalRescheduledBooking.title;
|
||||
cancelledRRHostEvt.startTime =
|
||||
dayjs(originalRescheduledBooking?.startTime).utc().format() ||
|
||||
copyEventAdditionalInfo.startTime;
|
||||
cancelledRRHostEvt.endTime =
|
||||
dayjs(originalRescheduledBooking?.endTime).utc().format() || copyEventAdditionalInfo.endTime;
|
||||
cancelledRRHostEvt.organizer = {
|
||||
email: originalOrganizer.email,
|
||||
name: originalOrganizer.name || "",
|
||||
timeZone: originalOrganizer.timeZone,
|
||||
language: { translate, locale: originalOrganizer.locale || "en" },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const newBookingMemberEmails: Person[] =
|
||||
copyEvent.team?.members
|
||||
.map((member) => member)
|
||||
.concat(copyEvent.organizer)
|
||||
.concat(copyEvent.attendees) || [];
|
||||
|
||||
const matchOriginalMemberWithNewMember = (originalMember: Person, newMember: Person) => {
|
||||
return originalMember.email === newMember.email;
|
||||
};
|
||||
|
||||
// scheduled Emails
|
||||
const newBookedMembers = newBookingMemberEmails.filter(
|
||||
(member) =>
|
||||
!originalBookingMemberEmails.find((originalMember) =>
|
||||
matchOriginalMemberWithNewMember(originalMember, member)
|
||||
)
|
||||
);
|
||||
// cancelled Emails
|
||||
const cancelledMembers = originalBookingMemberEmails.filter(
|
||||
(member) =>
|
||||
!newBookingMemberEmails.find((newMember) => matchOriginalMemberWithNewMember(member, newMember))
|
||||
);
|
||||
// rescheduled Emails
|
||||
const rescheduledMembers = newBookingMemberEmails.filter((member) =>
|
||||
originalBookingMemberEmails.find((orignalMember) =>
|
||||
matchOriginalMemberWithNewMember(orignalMember, member)
|
||||
)
|
||||
);
|
||||
|
||||
if (!isDryRun) {
|
||||
sendRoundRobinRescheduledEmailsAndSMS(
|
||||
{ ...copyEventAdditionalInfo, iCalUID },
|
||||
rescheduledMembers,
|
||||
eventType.metadata
|
||||
);
|
||||
sendRoundRobinScheduledEmailsAndSMS({
|
||||
calEvent: copyEventAdditionalInfo,
|
||||
members: newBookedMembers,
|
||||
eventTypeMetadata: 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) {
|
||||
// send normal rescheduled emails (non round robin event, where organizers stay the same)
|
||||
await sendRescheduledEmailsAndSMS(
|
||||
{
|
||||
...copyEvent,
|
||||
additionalInformation: metadata,
|
||||
additionalNotes, // Resets back to the additionalNote input and not the override value
|
||||
cancellationReason: `$RCH$${rescheduleReason ? rescheduleReason : ""}`, // Removable code prefix to differentiate cancellation from rescheduling for email
|
||||
},
|
||||
eventType?.metadata
|
||||
);
|
||||
}
|
||||
}
|
||||
if (!noEmail && isConfirmedByDefault && !isDryRun) {
|
||||
await emailsAndSmsHandler.send({
|
||||
action: BookingActionMap.rescheduled,
|
||||
data: {
|
||||
evt,
|
||||
eventType,
|
||||
additionalInformation: metadata,
|
||||
additionalNotes,
|
||||
iCalUID,
|
||||
originalRescheduledBooking,
|
||||
rescheduleReason,
|
||||
isRescheduledByBooker: reqBody.rescheduledBy === bookerEmail,
|
||||
users,
|
||||
changedOrganizer,
|
||||
},
|
||||
});
|
||||
}
|
||||
// If it's not a reschedule, doesn't require confirmation and there's no price,
|
||||
// Create a booking
|
||||
@@ -2076,43 +1952,23 @@ async function handler(
|
||||
});
|
||||
}
|
||||
}
|
||||
if (noEmail !== true) {
|
||||
let isHostConfirmationEmailsDisabled = false;
|
||||
let isAttendeeConfirmationEmailDisabled = false;
|
||||
|
||||
isHostConfirmationEmailsDisabled =
|
||||
eventType.metadata?.disableStandardEmails?.confirmation?.host || false;
|
||||
isAttendeeConfirmationEmailDisabled =
|
||||
eventType.metadata?.disableStandardEmails?.confirmation?.attendee || false;
|
||||
|
||||
if (isHostConfirmationEmailsDisabled) {
|
||||
isHostConfirmationEmailsDisabled = allowDisablingHostConfirmationEmails(workflows);
|
||||
}
|
||||
|
||||
if (isAttendeeConfirmationEmailDisabled) {
|
||||
isAttendeeConfirmationEmailDisabled = allowDisablingAttendeeConfirmationEmails(workflows);
|
||||
}
|
||||
|
||||
loggerWithEventDetails.debug(
|
||||
"Emails: Sending scheduled emails for booking confirmation",
|
||||
safeStringify({
|
||||
calEvent: getPiiFreeCalendarEvent(evt),
|
||||
})
|
||||
);
|
||||
|
||||
if (!noEmail) {
|
||||
if (!isDryRun && !(eventType.seatsPerTimeSlot && rescheduleUid)) {
|
||||
await sendScheduledEmailsAndSMS(
|
||||
{
|
||||
...evt,
|
||||
await emailsAndSmsHandler.send({
|
||||
action: BookingActionMap.confirmed,
|
||||
data: {
|
||||
eventType: {
|
||||
metadata: eventType.metadata,
|
||||
schedulingType: eventType.schedulingType,
|
||||
},
|
||||
eventNameObject,
|
||||
workflows,
|
||||
evt,
|
||||
additionalInformation,
|
||||
additionalNotes,
|
||||
customInputs,
|
||||
},
|
||||
eventNameObject,
|
||||
isHostConfirmationEmailsDisabled,
|
||||
isAttendeeConfirmationEmailDisabled,
|
||||
eventType.metadata
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2142,8 +1998,10 @@ async function handler(
|
||||
})
|
||||
);
|
||||
if (!isDryRun) {
|
||||
await sendOrganizerRequestEmail({ ...evt, additionalNotes }, eventType.metadata);
|
||||
await sendAttendeeRequestEmailAndSMS({ ...evt, additionalNotes }, attendeesList[0], eventType.metadata);
|
||||
await emailsAndSmsHandler.send({
|
||||
action: BookingActionMap.requested,
|
||||
data: { evt, attendees: attendeesList, eventType, additionalNotes },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2708,7 +2708,7 @@ __metadata:
|
||||
"@axiomhq/winston": ^1.2.0
|
||||
"@calcom/platform-constants": "*"
|
||||
"@calcom/platform-enums": "*"
|
||||
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.340"
|
||||
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.343"
|
||||
"@calcom/platform-types": "*"
|
||||
"@calcom/platform-utils": "*"
|
||||
"@calcom/prisma": "*"
|
||||
@@ -3767,13 +3767,13 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@calcom/platform-libraries@npm:@calcom/platform-libraries@0.0.340":
|
||||
version: 0.0.340
|
||||
resolution: "@calcom/platform-libraries@npm:0.0.340"
|
||||
"@calcom/platform-libraries@npm:@calcom/platform-libraries@0.0.343":
|
||||
version: 0.0.343
|
||||
resolution: "@calcom/platform-libraries@npm:0.0.343"
|
||||
dependencies:
|
||||
"@calcom/features": "*"
|
||||
"@calcom/lib": "*"
|
||||
checksum: 19c2f125fc6b3074314365a19894e86a709243ccf29a5315468ff5e8f9767a4b2cc618cd5e4797c37f1e46c8a4f6937b3063b85b5f26157ba65d84ab664d7298
|
||||
checksum: 332910a95336b720999e24c6eaf1e7780a507347e4610dbbeff023d69edc2a47f83f4c3014763fbcd23bb92a34d9a61c83d400c71d4501466b5b5f36add4173d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
||||
Reference in new issue
Block a user