diff --git a/packages/features/bookings/lib/handleCancelBooking.ts b/packages/features/bookings/lib/handleCancelBooking.ts index 6a20fb5201..20eb6f7761 100644 --- a/packages/features/bookings/lib/handleCancelBooking.ts +++ b/packages/features/bookings/lib/handleCancelBooking.ts @@ -107,6 +107,7 @@ async function getBookingToDelete(id: number | undefined, uid: string | undefine bookingFields: true, seatsShowAttendees: true, metadata: true, + schedulingType: true, hosts: { select: { user: true, @@ -357,7 +358,13 @@ async function handler(req: CustomRequest) { smsReminderNumber: bookingToDelete.smsReminderNumber, evt: { ...evt, - ...{ eventType: { slug: bookingToDelete.eventType?.slug } }, + ...{ + eventType: { + slug: bookingToDelete.eventType?.slug, + schedulingType: bookingToDelete.eventType?.schedulingType, + hosts: bookingToDelete.eventType?.hosts, + }, + }, }, hideBranding: !!bookingToDelete.eventType?.owner?.hideBranding, }); diff --git a/packages/features/bookings/lib/handleConfirmation.ts b/packages/features/bookings/lib/handleConfirmation.ts index 3bc204e386..5414e503a5 100644 --- a/packages/features/bookings/lib/handleConfirmation.ts +++ b/packages/features/bookings/lib/handleConfirmation.ts @@ -20,6 +20,7 @@ import { getTeamIdFromEventType } from "@calcom/lib/getTeamIdFromEventType"; import logger from "@calcom/lib/logger"; import { safeStringify } from "@calcom/lib/safeStringify"; import type { PrismaClient } from "@calcom/prisma"; +import type { SchedulingType } from "@calcom/prisma/enums"; import { BookingStatus, WebhookTriggerEvents } from "@calcom/prisma/enums"; import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils"; import { getAllWorkflowsFromEventType } from "@calcom/trpc/server/routers/viewer/workflows/util"; @@ -135,6 +136,15 @@ export async function handleConfirmation(args: { eventType: { bookingFields: Prisma.JsonValue | null; slug: string; + schedulingType: SchedulingType | null; + hosts: { + user: { + email: string; + destinationCalendar?: { + primaryEmail: string | null; + } | null; + }; + }[]; owner: { hideBranding?: boolean | null; } | null; @@ -175,6 +185,21 @@ export async function handleConfirmation(args: { select: { slug: true, bookingFields: true, + schedulingType: true, + hosts: { + select: { + user: { + select: { + email: true, + destinationCalendar: { + select: { + primaryEmail: true, + }, + }, + }, + }, + }, + }, owner: { select: { hideBranding: true, @@ -220,11 +245,26 @@ export async function handleConfirmation(args: { select: { slug: true, bookingFields: true, + schedulingType: true, owner: { select: { hideBranding: true, }, }, + hosts: { + select: { + user: { + select: { + email: true, + destinationCalendar: { + select: { + primaryEmail: true, + }, + }, + }, + }, + }, + }, }, }, uid: true, @@ -249,7 +289,11 @@ export async function handleConfirmation(args: { const evtOfBooking = { ...evt, metadata: { videoCallUrl: meetingUrl }, - eventType: { slug: eventTypeSlug }, + eventType: { + slug: eventTypeSlug, + schedulingType: updatedBookings[index].eventType?.schedulingType, + hosts: updatedBookings[index].eventType?.hosts, + }, }; evtOfBooking.startTime = updatedBookings[index].startTime.toISOString(); evtOfBooking.endTime = updatedBookings[index].endTime.toISOString(); diff --git a/packages/features/bookings/lib/handleNewBooking.ts b/packages/features/bookings/lib/handleNewBooking.ts index 52b9e65b29..29bcb1a9f5 100644 --- a/packages/features/bookings/lib/handleNewBooking.ts +++ b/packages/features/bookings/lib/handleNewBooking.ts @@ -1741,7 +1741,11 @@ async function handler( loggerWithEventDetails.error("Error while creating booking references", JSON.stringify({ error })); } - const evtWithMetadata = { ...evt, metadata, eventType: { slug: eventType.slug } }; + const evtWithMetadata = { + ...evt, + metadata, + eventType: { slug: eventType.slug, schedulingType: eventType.schedulingType, hosts: eventType.hosts }, + }; if (!eventType.metadata?.disableStandardEmails?.all?.attendee) { await scheduleMandatoryReminder( diff --git a/packages/features/bookings/lib/handleNewBooking/test/workflow-notifications.test.ts b/packages/features/bookings/lib/handleNewBooking/test/workflow-notifications.test.ts index e2a523ea77..95fcc17243 100644 --- a/packages/features/bookings/lib/handleNewBooking/test/workflow-notifications.test.ts +++ b/packages/features/bookings/lib/handleNewBooking/test/workflow-notifications.test.ts @@ -392,8 +392,7 @@ describe("handleNewBooking", () => { }); expectWorkflowToBeTriggered({ - // emailsToReceive: [organizer.email].concat(otherTeamMembers.map(member => member.email)), - emailsToReceive: [organizer.email], + emailsToReceive: [organizer.email].concat(otherTeamMembers.map((member) => member.email)), emails, }); }, diff --git a/packages/features/bookings/lib/handleSeats/handleSeats.ts b/packages/features/bookings/lib/handleSeats/handleSeats.ts index b4cf2a661a..f12f11946c 100644 --- a/packages/features/bookings/lib/handleSeats/handleSeats.ts +++ b/packages/features/bookings/lib/handleSeats/handleSeats.ts @@ -104,7 +104,17 @@ const handleSeats = async (newSeatedBookingObject: NewSeatedBookingObject) => { await scheduleWorkflowReminders({ workflows, smsReminderNumber: smsReminderNumber || null, - calendarEvent: { ...evt, ...{ metadata, eventType: { slug: eventType.slug } } }, + calendarEvent: { + ...evt, + ...{ + metadata, + eventType: { + slug: eventType.slug, + schedulingType: eventType.schedulingType, + hosts: eventType.hosts, + }, + }, + }, isNotConfirmed: evt.requiresConfirmation || false, isRescheduleEvent: !!rescheduleUid, emailAttendeeSendToOverride: bookerEmail, diff --git a/packages/features/ee/workflows/api/scheduleEmailReminders.ts b/packages/features/ee/workflows/api/scheduleEmailReminders.ts index efdbe624f3..ec699d4f5e 100644 --- a/packages/features/ee/workflows/api/scheduleEmailReminders.ts +++ b/packages/features/ee/workflows/api/scheduleEmailReminders.ts @@ -9,7 +9,7 @@ import logger from "@calcom/lib/logger"; import { defaultHandler } from "@calcom/lib/server"; import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat"; import prisma from "@calcom/prisma"; -import { WorkflowActions, WorkflowMethods, WorkflowTemplates } from "@calcom/prisma/enums"; +import { SchedulingType, WorkflowActions, WorkflowMethods, WorkflowTemplates } from "@calcom/prisma/enums"; import { bookingMetadataSchema } from "@calcom/prisma/zod-utils"; import type { PartialWorkflowReminder } from "../lib/getWorkflowReminders"; @@ -119,6 +119,19 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { switch (reminder.workflowStep.action) { case WorkflowActions.EMAIL_HOST: sendTo = reminder.booking?.userPrimaryEmail ?? reminder.booking.user?.email; + const hosts = reminder?.booking?.eventType?.hosts + ?.filter((host) => + reminder.booking?.attendees.some((attendee) => attendee.email === host.user.email) + ) + .map(({ user }) => user.destinationCalendar?.primaryEmail ?? user.email); + const schedulingType = reminder.booking.eventType?.schedulingType; + + if ( + hosts && + (schedulingType === SchedulingType.COLLECTIVE || schedulingType === SchedulingType.ROUND_ROBIN) + ) { + sendTo = sendTo ? [sendTo, ...hosts] : hosts; + } break; case WorkflowActions.EMAIL_ATTENDEE: sendTo = reminder.booking.attendees[0].email; diff --git a/packages/features/ee/workflows/lib/getWorkflowReminders.ts b/packages/features/ee/workflows/lib/getWorkflowReminders.ts index ea07e4cafa..70ddde3e1a 100644 --- a/packages/features/ee/workflows/lib/getWorkflowReminders.ts +++ b/packages/features/ee/workflows/lib/getWorkflowReminders.ts @@ -27,7 +27,14 @@ type PartialBooking = | "attendees" | "userPrimaryEmail" | "smsReminderNumber" - > & { eventType: (Partial & { team: { parentId?: number } }) | null } & { + > & { + eventType: + | (Partial & { + team: { parentId?: number }; + hosts: { user: { email: string; destinationCalendar?: { primaryEmail: string } } }[] | undefined; + }) + | null; + } & { user: Partial | null; }) | null; @@ -159,6 +166,20 @@ export const select: Prisma.WorkflowReminderSelect = { bookingFields: true, title: true, slug: true, + hosts: { + select: { + user: { + select: { + email: true, + destinationCalendar: { + select: { + primaryEmail: true, + }, + }, + }, + }, + }, + }, recurringEvent: true, team: { select: { diff --git a/packages/features/ee/workflows/lib/reminders/reminderScheduler.ts b/packages/features/ee/workflows/lib/reminders/reminderScheduler.ts index 0f0aaf2ce1..23089618c9 100644 --- a/packages/features/ee/workflows/lib/reminders/reminderScheduler.ts +++ b/packages/features/ee/workflows/lib/reminders/reminderScheduler.ts @@ -6,7 +6,7 @@ import { import type { Workflow, WorkflowStep } from "@calcom/features/ee/workflows/lib/types"; import { checkSMSRateLimit } from "@calcom/lib/checkRateLimitAndThrowError"; import { SENDER_NAME } from "@calcom/lib/constants"; -import { WorkflowActions, WorkflowTriggerEvents } from "@calcom/prisma/enums"; +import { SchedulingType, WorkflowActions, WorkflowTriggerEvents } from "@calcom/prisma/enums"; import type { CalendarEvent } from "@calcom/types/Calendar"; import { scheduleEmailReminder } from "./emailReminderManager"; @@ -16,7 +16,11 @@ import { scheduleWhatsappReminder } from "./whatsappReminderManager"; export type ExtendedCalendarEvent = CalendarEvent & { metadata?: { videoCallUrl: string | undefined }; - eventType: { slug?: string }; + eventType: { + slug?: string; + schedulingType?: SchedulingType | null; + hosts?: { user: { email: string; destinationCalendar?: { primaryEmail: string | null } | null } }[]; + }; }; type ProcessWorkflowStepParams = { @@ -85,6 +89,13 @@ const processWorkflowStep = async ( break; case WorkflowActions.EMAIL_HOST: sendTo = [evt.organizer?.email || ""]; + + const schedulingType = evt.eventType.schedulingType; + const isTeamEvent = + schedulingType === SchedulingType.ROUND_ROBIN || schedulingType === SchedulingType.COLLECTIVE; + if (isTeamEvent && evt.team?.members) { + sendTo = sendTo.concat(evt.team.members.map((member) => member.email)); + } break; case WorkflowActions.EMAIL_ATTENDEE: const attendees = !!emailAttendeeSendToOverride diff --git a/packages/trpc/server/routers/viewer/workflows/activateEventType.handler.ts b/packages/trpc/server/routers/viewer/workflows/activateEventType.handler.ts index 9318d49591..e1ddb2e04a 100644 --- a/packages/trpc/server/routers/viewer/workflows/activateEventType.handler.ts +++ b/packages/trpc/server/routers/viewer/workflows/activateEventType.handler.ts @@ -4,7 +4,7 @@ import { scheduleWhatsappReminder } from "@calcom/features/ee/workflows/lib/remi import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat"; import { prisma } from "@calcom/prisma"; import { BookingStatus } from "@calcom/prisma/client"; -import { MembershipRole, WorkflowActions } from "@calcom/prisma/enums"; +import { MembershipRole, SchedulingType, WorkflowActions } from "@calcom/prisma/enums"; import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils"; import type { TrpcSessionUser } from "@calcom/trpc/server/trpc"; @@ -200,7 +200,26 @@ export const activateEventTypeHandler = async ({ ctx, input }: ActivateEventType }, include: { attendees: true, - eventType: true, + eventType: { + select: { + schedulingType: true, + slug: true, + hosts: { + select: { + user: { + select: { + email: true, + destinationCalendar: { + select: { + primaryEmail: true, + }, + }, + }, + }, + }, + }, + }, + }, user: true, }, }); @@ -232,6 +251,8 @@ export const activateEventTypeHandler = async ({ ctx, input }: ActivateEventType language: { locale: booking?.user?.locale || defaultLocale }, eventType: { slug: booking.eventType?.slug, + schedulingType: booking.eventType?.schedulingType, + hosts: booking.eventType?.hosts, }, }; for (const step of eventTypeWorkflow.steps) { @@ -245,6 +266,19 @@ export const activateEventTypeHandler = async ({ ctx, input }: ActivateEventType switch (step.action) { case WorkflowActions.EMAIL_HOST: sendTo = [bookingInfo.organizer?.email]; + const schedulingType = bookingInfo.eventType?.schedulingType; + const hosts = bookingInfo.eventType.hosts + ?.filter((host) => + bookingInfo.attendees.some((attendee) => host.user.email === attendee.email) + ) + .map(({ user }) => user.destinationCalendar?.primaryEmail ?? user.email); + if ( + (schedulingType === SchedulingType.ROUND_ROBIN || + schedulingType === SchedulingType.COLLECTIVE) && + hosts + ) { + sendTo = sendTo.concat(hosts); + } break; case WorkflowActions.EMAIL_ATTENDEE: sendTo = bookingInfo.attendees.map((attendee) => attendee.email); diff --git a/packages/trpc/server/routers/viewer/workflows/util.ts b/packages/trpc/server/routers/viewer/workflows/util.ts index 208db797ce..b7b3d720f6 100644 --- a/packages/trpc/server/routers/viewer/workflows/util.ts +++ b/packages/trpc/server/routers/viewer/workflows/util.ts @@ -31,6 +31,7 @@ import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat"; import prisma from "@calcom/prisma"; import type { Prisma, WorkflowStep } from "@calcom/prisma/client"; import type { TimeUnit } from "@calcom/prisma/enums"; +import { SchedulingType } from "@calcom/prisma/enums"; import { BookingStatus, MembershipRole, @@ -64,6 +65,21 @@ export const bookingSelect = { select: { slug: true, id: true, + schedulingType: true, + hosts: { + select: { + user: { + select: { + email: true, + destinationCalendar: { + select: { + primaryEmail: true, + }, + }, + }, + }, + }, + }, }, }, user: { @@ -732,6 +748,8 @@ export async function scheduleBookingReminders( language: { locale: booking?.user?.locale || defaultLocale }, eventType: { slug: booking.eventType?.slug, + schedulingType: booking.eventType?.schedulingType, + hosts: booking.eventType?.hosts, }, }; if ( @@ -744,6 +762,16 @@ export async function scheduleBookingReminders( switch (step.action) { case WorkflowActions.EMAIL_HOST: sendTo = [bookingInfo.organizer?.email]; + const schedulingType = bookingInfo.eventType.schedulingType; + const hosts = bookingInfo.eventType.hosts + ?.filter((host) => bookingInfo.attendees.some((attendee) => attendee.email === host.user.email)) + .map(({ user }) => user.destinationCalendar?.primaryEmail ?? user.email); + if ( + hosts && + (schedulingType === SchedulingType.ROUND_ROBIN || schedulingType === SchedulingType.COLLECTIVE) + ) { + sendTo = sendTo.concat(hosts); + } break; case WorkflowActions.EMAIL_ATTENDEE: sendTo = bookingInfo.attendees.map((attendee) => attendee.email); @@ -751,6 +779,7 @@ export async function scheduleBookingReminders( case WorkflowActions.EMAIL_ADDRESS: await verifyEmailSender(step.sendTo || "", userId, teamId); sendTo = [step.sendTo || ""]; + break; } await scheduleEmailReminder({ evt: bookingInfo,