diff --git a/packages/features/bookings/lib/getBookingDataSchema.ts b/packages/features/bookings/lib/getBookingDataSchema.ts index cbd58d4232ee3e..25f379f0b28ea2 100644 --- a/packages/features/bookings/lib/getBookingDataSchema.ts +++ b/packages/features/bookings/lib/getBookingDataSchema.ts @@ -17,4 +17,6 @@ const getBookingDataSchema = ({ ); }; +export type TgetBookingDataSchema = z.infer>; + export default getBookingDataSchema; diff --git a/packages/features/bookings/lib/handleNewBooking.ts b/packages/features/bookings/lib/handleNewBooking.ts index 72c115be5938df..b17e66745da42d 100644 --- a/packages/features/bookings/lib/handleNewBooking.ts +++ b/packages/features/bookings/lib/handleNewBooking.ts @@ -1,11 +1,9 @@ -import type { App, DestinationCalendar, EventTypeCustomInput } from "@prisma/client"; -import { Prisma } from "@prisma/client"; +import type { DestinationCalendar } from "@prisma/client"; +import type { Prisma } from "@prisma/client"; // eslint-disable-next-line no-restricted-imports import { cloneDeep } from "lodash"; import type { NextApiRequest } from "next"; -import type { TFunction } from "next-i18next"; import short, { uuid } from "short-uuid"; -import type { Logger } from "tslog"; import { v5 as uuidv5 } from "uuid"; import type z from "zod"; @@ -16,12 +14,9 @@ import { OrganizerDefaultConferencingAppType, getLocationValueForDB, } from "@calcom/app-store/locations"; -import type { EventTypeAppsList } from "@calcom/app-store/utils"; import { getAppFromSlug } from "@calcom/app-store/utils"; import EventManager from "@calcom/core/EventManager"; import { getEventName } from "@calcom/core/event"; -import { getBusyTimesForLimitChecks } from "@calcom/core/getBusyTimes"; -import { getUsersAvailability } from "@calcom/core/getUserAvailability"; import dayjs from "@calcom/dayjs"; import { scheduleMandatoryReminder } from "@calcom/ee/workflows/lib/reminders/scheduleMandatoryReminder"; import { @@ -35,7 +30,6 @@ import { } from "@calcom/emails"; import getICalUID from "@calcom/emails/lib/getICalUID"; import { getBookingFieldsWithSystemFields } from "@calcom/features/bookings/lib/getBookingFields"; -import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses"; import { handleWebhookTrigger } from "@calcom/features/bookings/lib/handleWebhookTrigger"; import { isEventTypeLoggingEnabled } from "@calcom/features/bookings/lib/isEventTypeLoggingEnabled"; import { @@ -50,7 +44,6 @@ import { deleteWebhookScheduledTriggers, scheduleTrigger, } from "@calcom/features/webhooks/lib/scheduleTrigger"; -import { isPrismaObjOrUndefined, parseBookingLimit, parseDurationLimit } from "@calcom/lib"; import { getVideoCallUrlFromCalEvent } from "@calcom/lib/CalEventParser"; import { getUTCOffsetByTimezone } from "@calcom/lib/date-fns"; import { getDefaultEvent, getUsernameList } from "@calcom/lib/defaultEvents"; @@ -76,10 +69,8 @@ import prisma, { userSelect } from "@calcom/prisma"; import type { BookingReference } from "@calcom/prisma/client"; import { BookingStatus, SchedulingType, WebhookTriggerEvents } from "@calcom/prisma/enums"; import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential"; -import { - bookingCreateSchemaLegacyPropsForApi, - userMetadata as userMetadataSchema, -} from "@calcom/prisma/zod-utils"; +import type { bookingCreateSchemaLegacyPropsForApi } from "@calcom/prisma/zod-utils"; +import { userMetadata as userMetadataSchema } from "@calcom/prisma/zod-utils"; import { deleteAllWorkflowReminders, getAllWorkflowsFromEventType, @@ -91,507 +82,35 @@ import type { IntervalLimit, Person, } from "@calcom/types/Calendar"; -import type { CredentialPayload } from "@calcom/types/Credential"; import type { EventResult, PartialReference } from "@calcom/types/EventManager"; import type { EventTypeInfo } from "../../webhooks/lib/sendPayload"; -import { checkForConflicts } from "./conflictChecker/checkForConflicts"; import { getAllCredentials } from "./getAllCredentialsForUsersOnEvent/getAllCredentials"; import { refreshCredentials } from "./getAllCredentialsForUsersOnEvent/refreshCredentials"; import getBookingDataSchema from "./getBookingDataSchema"; import { checkIfBookerEmailIsBlocked } from "./handleNewBooking/checkIfBookerEmailIsBlocked"; +import { createBooking } from "./handleNewBooking/createBooking"; +import { ensureAvailableUsers } from "./handleNewBooking/ensureAvailableUsers"; +import { getBookingData } from "./handleNewBooking/getBookingData"; import { getEventTypesFromDB } from "./handleNewBooking/getEventTypesFromDB"; import type { getEventTypeResponse } from "./handleNewBooking/getEventTypesFromDB"; +import { getOriginalRescheduledBooking } from "./handleNewBooking/getOriginalRescheduledBooking"; import { getRequiresConfirmationFlags } from "./handleNewBooking/getRequiresConfirmationFlags"; import { handleAppsStatus } from "./handleNewBooking/handleAppsStatus"; -import { handleCustomInputs } from "./handleNewBooking/handleCustomInputs"; import { loadUsers } from "./handleNewBooking/loadUsers"; +import type { + Invitee, + IEventTypePaymentCredentialType, + IsFixedAwareUser, + BookingType, + Booking, +} from "./handleNewBooking/types"; import handleSeats from "./handleSeats/handleSeats"; import type { BookingSeat } from "./handleSeats/types"; const translator = short(); const log = logger.getSubLogger({ prefix: ["[api] book:user"] }); -type User = Prisma.UserGetPayload; -type BookingType = Prisma.PromiseReturnType; -export type Booking = Prisma.PromiseReturnType; -export type NewBookingEventType = Awaited> | getEventTypeResponse; - -// Work with Typescript to require reqBody.end -type ReqBodyWithoutEnd = z.infer>; -type ReqBodyWithEnd = ReqBodyWithoutEnd & { end: string }; -export type Invitee = { - email: string; - name: string; - firstName: string; - lastName: string; - timeZone: string; - language: { - translate: TFunction; - locale: string; - }; -}[]; -export type OrganizerUser = Awaited>[number] & { - isFixed?: boolean; - metadata?: Prisma.JsonValue; -}; -export type OriginalRescheduledBooking = Awaited>; - -type AwaitedBookingData = Awaited>; -export type RescheduleReason = AwaitedBookingData["rescheduleReason"]; -export type NoEmail = AwaitedBookingData["noEmail"]; -export type AdditionalNotes = AwaitedBookingData["notes"]; -export type ReqAppsStatus = AwaitedBookingData["appsStatus"]; -export type SmsReminderNumber = AwaitedBookingData["smsReminderNumber"]; -export type EventTypeId = AwaitedBookingData["eventTypeId"]; -export type ReqBodyMetadata = ReqBodyWithEnd["metadata"]; - -export type IsConfirmedByDefault = ReturnType["isConfirmedByDefault"]; -export type PaymentAppData = ReturnType; - -export interface IEventTypePaymentCredentialType { - appId: EventTypeAppsList; - app: { - categories: App["categories"]; - dirName: string; - }; - key: Prisma.JsonValue; -} - -type IsFixedAwareUser = User & { - isFixed: boolean; - credentials: CredentialPayload[]; - organization: { slug: string }; - priority?: number; -}; - -export async function ensureAvailableUsers( - eventType: getEventTypeResponse & { - users: IsFixedAwareUser[]; - }, - input: { dateFrom: string; dateTo: string; timeZone: string; originalRescheduledBooking?: BookingType }, - loggerWithEventDetails: Logger -) { - const availableUsers: IsFixedAwareUser[] = []; - const getStartDateTimeUtc = (startDateTimeInput: string, timeZone?: string) => { - return timeZone === "Etc/GMT" - ? dayjs.utc(startDateTimeInput) - : dayjs(startDateTimeInput).tz(timeZone).utc(); - }; - - const startDateTimeUtc = getStartDateTimeUtc(input.dateFrom, input.timeZone); - const endDateTimeUtc = - input.timeZone === "Etc/GMT" ? dayjs.utc(input.dateTo) : dayjs(input.dateTo).tz(input.timeZone).utc(); - - const duration = dayjs(input.dateTo).diff(input.dateFrom, "minute"); - const originalBookingDuration = input.originalRescheduledBooking - ? dayjs(input.originalRescheduledBooking.endTime).diff( - dayjs(input.originalRescheduledBooking.startTime), - "minutes" - ) - : undefined; - - const bookingLimits = parseBookingLimit(eventType?.bookingLimits); - const durationLimits = parseDurationLimit(eventType?.durationLimits); - let busyTimesFromLimitsBookingsAllUsers: Awaited> = []; - - if (eventType && (bookingLimits || durationLimits)) { - busyTimesFromLimitsBookingsAllUsers = await getBusyTimesForLimitChecks({ - userIds: eventType.users.map((u) => u.id), - eventTypeId: eventType.id, - startDate: startDateTimeUtc.format(), - endDate: endDateTimeUtc.format(), - rescheduleUid: input.originalRescheduledBooking?.uid ?? null, - bookingLimits, - durationLimits, - }); - } - - ( - await getUsersAvailability({ - users: eventType.users, - query: { - ...input, - eventTypeId: eventType.id, - duration: originalBookingDuration, - returnDateOverrides: false, - dateFrom: startDateTimeUtc.format(), - dateTo: endDateTimeUtc.format(), - }, - initialData: { - eventType, - rescheduleUid: input.originalRescheduledBooking?.uid ?? null, - busyTimesFromLimitsBookings: busyTimesFromLimitsBookingsAllUsers, - }, - }) - ).forEach(({ oooExcludedDateRanges: dateRanges, busy: bufferedBusyTimes }, index) => { - const user = eventType.users[index]; - - log.debug( - "calendarBusyTimes==>>>", - JSON.stringify({ bufferedBusyTimes, dateRanges, isRecurringEvent: eventType.recurringEvent }) - ); - - if (!dateRanges.length) { - loggerWithEventDetails.error( - `User does not have availability at this time.`, - safeStringify({ - startDateTimeUtc, - endDateTimeUtc, - input, - }) - ); - return; - } - - let foundConflict = false; - - let dateRangeForBooking = false; - - //check if event time is within the date range - for (const dateRange of dateRanges) { - if ( - (startDateTimeUtc.isAfter(dateRange.start) || startDateTimeUtc.isSame(dateRange.start)) && - (endDateTimeUtc.isBefore(dateRange.end) || endDateTimeUtc.isSame(dateRange.end)) - ) { - dateRangeForBooking = true; - break; - } - } - - if (!dateRangeForBooking) { - loggerWithEventDetails.error( - `No date range for booking.`, - safeStringify({ - startDateTimeUtc, - endDateTimeUtc, - input, - }) - ); - return; - } - - try { - foundConflict = checkForConflicts(bufferedBusyTimes, startDateTimeUtc, duration); - } catch (error) { - loggerWithEventDetails.error("Unable set isAvailableToBeBooked. Using true. ", error); - } - // no conflicts found, add to available users. - if (!foundConflict) { - availableUsers.push(user); - } - }); - - if (!availableUsers.length) { - loggerWithEventDetails.error( - `No available users found.`, - safeStringify({ - startDateTimeUtc, - endDateTimeUtc, - input, - }) - ); - throw new Error(ErrorCode.NoAvailableUsersFound); - } - return availableUsers; -} - -async function getOriginalRescheduledBooking(uid: string, seatsEventType?: boolean) { - return prisma.booking.findFirst({ - where: { - uid: uid, - status: { - in: [BookingStatus.ACCEPTED, BookingStatus.CANCELLED, BookingStatus.PENDING], - }, - }, - include: { - attendees: { - select: { - name: true, - email: true, - locale: true, - timeZone: true, - ...(seatsEventType && { bookingSeat: true, id: true }), - }, - }, - user: { - select: { - id: true, - name: true, - email: true, - locale: true, - timeZone: true, - destinationCalendar: true, - credentials: { - select: { - id: true, - userId: true, - key: true, - type: true, - teamId: true, - appId: true, - invalid: true, - user: { - select: { - email: true, - }, - }, - }, - }, - }, - }, - destinationCalendar: true, - payment: true, - references: true, - workflowReminders: true, - }, - }); -} - -export async function getBookingData({ - req, - eventType, - schema, -}: { - req: NextApiRequest; - eventType: getEventTypeResponse; - schema: T; -}) { - const reqBody = await schema.parseAsync(req.body); - const reqBodyWithEnd = (reqBody: ReqBodyWithoutEnd): reqBody is ReqBodyWithEnd => { - // Use the event length to auto-set the event end time. - if (!Object.prototype.hasOwnProperty.call(reqBody, "end")) { - reqBody.end = dayjs.utc(reqBody.start).add(eventType.length, "minutes").format(); - } - return true; - }; - if (!reqBodyWithEnd(reqBody)) { - throw new Error(ErrorCode.RequestBodyWithouEnd); - } - // reqBody.end is no longer an optional property. - if (reqBody.customInputs) { - // Check if required custom inputs exist - handleCustomInputs(eventType.customInputs as EventTypeCustomInput[], reqBody.customInputs); - const reqBodyWithLegacyProps = bookingCreateSchemaLegacyPropsForApi.parse(reqBody); - return { - ...reqBody, - name: reqBodyWithLegacyProps.name, - email: reqBodyWithLegacyProps.email, - guests: reqBodyWithLegacyProps.guests, - location: reqBodyWithLegacyProps.location || "", - smsReminderNumber: reqBodyWithLegacyProps.smsReminderNumber, - notes: reqBodyWithLegacyProps.notes, - rescheduleReason: reqBodyWithLegacyProps.rescheduleReason, - // So TS doesn't complain about unknown properties - calEventUserFieldsResponses: undefined, - calEventResponses: undefined, - customInputs: undefined, - }; - } - if (!reqBody.responses) { - throw new Error("`responses` must not be nullish"); - } - const responses = reqBody.responses; - - const { userFieldsResponses: calEventUserFieldsResponses, responses: calEventResponses } = - getCalEventResponses({ - bookingFields: eventType.bookingFields, - responses, - }); - return { - ...reqBody, - name: responses.name, - email: responses.email, - guests: responses.guests ? responses.guests : [], - location: responses.location?.optionValue || responses.location?.value || "", - smsReminderNumber: responses.smsReminderNumber, - notes: responses.notes || "", - calEventUserFieldsResponses, - rescheduleReason: responses.rescheduleReason, - calEventResponses, - // So TS doesn't complain about unknown properties - customInputs: undefined, - }; -} - -async function createBooking({ - originalRescheduledBooking, - evt, - eventTypeId, - eventTypeSlug, - reqBodyUser, - reqBodyMetadata, - reqBodyRecurringEventId, - uid, - responses, - isConfirmedByDefault, - smsReminderNumber, - organizerUser, - rescheduleReason, - eventType, - bookerEmail, - paymentAppData, - changedOrganizer, -}: { - originalRescheduledBooking: OriginalRescheduledBooking; - evt: CalendarEvent; - eventType: NewBookingEventType; - eventTypeId: EventTypeId; - eventTypeSlug: AwaitedBookingData["eventTypeSlug"]; - reqBodyUser: ReqBodyWithEnd["user"]; - reqBodyMetadata: ReqBodyWithEnd["metadata"]; - reqBodyRecurringEventId: ReqBodyWithEnd["recurringEventId"]; - uid: short.SUUID; - responses: ReqBodyWithEnd["responses"] | null; - isConfirmedByDefault: IsConfirmedByDefault; - smsReminderNumber: AwaitedBookingData["smsReminderNumber"]; - organizerUser: Awaited>[number] & { - isFixed?: boolean; - metadata?: Prisma.JsonValue; - }; - rescheduleReason: Awaited>["rescheduleReason"]; - bookerEmail: Awaited>["email"]; - paymentAppData: ReturnType; - changedOrganizer: boolean; -}) { - if (originalRescheduledBooking) { - evt.title = originalRescheduledBooking?.title || evt.title; - evt.description = originalRescheduledBooking?.description || evt.description; - evt.location = originalRescheduledBooking?.location || evt.location; - evt.location = changedOrganizer ? evt.location : originalRescheduledBooking?.location || evt.location; - } - - const eventTypeRel = !eventTypeId - ? {} - : { - connect: { - id: eventTypeId, - }, - }; - - const dynamicEventSlugRef = !eventTypeId ? eventTypeSlug : null; - const dynamicGroupSlugRef = !eventTypeId ? (reqBodyUser as string).toLowerCase() : null; - - const attendeesData = evt.attendees.map((attendee) => { - //if attendee is team member, it should fetch their locale not booker's locale - //perhaps make email fetch request to see if his locale is stored, else - return { - name: attendee.name, - email: attendee.email, - timeZone: attendee.timeZone, - locale: attendee.language.locale, - }; - }); - - if (evt.team?.members) { - attendeesData.push( - ...evt.team.members.map((member) => ({ - email: member.email, - name: member.name, - timeZone: member.timeZone, - locale: member.language.locale, - })) - ); - } - - const newBookingData: Prisma.BookingCreateInput = { - uid, - userPrimaryEmail: evt.organizer.email, - responses: responses === null || evt.seatsPerTimeSlot ? Prisma.JsonNull : responses, - title: evt.title, - startTime: dayjs.utc(evt.startTime).toDate(), - endTime: dayjs.utc(evt.endTime).toDate(), - description: evt.seatsPerTimeSlot ? null : evt.additionalNotes, - customInputs: isPrismaObjOrUndefined(evt.customInputs), - status: isConfirmedByDefault ? BookingStatus.ACCEPTED : BookingStatus.PENDING, - location: evt.location, - eventType: eventTypeRel, - smsReminderNumber, - metadata: reqBodyMetadata, - attendees: { - createMany: { - data: attendeesData, - }, - }, - dynamicEventSlugRef, - dynamicGroupSlugRef, - iCalUID: evt.iCalUID ?? "", - user: { - connect: { - id: organizerUser.id, - }, - }, - destinationCalendar: - evt.destinationCalendar && evt.destinationCalendar.length > 0 - ? { - connect: { id: evt.destinationCalendar[0].id }, - } - : undefined, - }; - - if (reqBodyRecurringEventId) { - newBookingData.recurringEventId = reqBodyRecurringEventId; - } - if (originalRescheduledBooking) { - newBookingData.metadata = { - ...(typeof originalRescheduledBooking.metadata === "object" && originalRescheduledBooking.metadata), - }; - newBookingData["paid"] = originalRescheduledBooking.paid; - newBookingData["fromReschedule"] = originalRescheduledBooking.uid; - if (originalRescheduledBooking.uid) { - newBookingData.cancellationReason = rescheduleReason; - } - if (newBookingData.attendees?.createMany?.data) { - // Reschedule logic with booking with seats - if (eventType?.seatsPerTimeSlot && bookerEmail) { - newBookingData.attendees.createMany.data = attendeesData.filter( - (attendee) => attendee.email === bookerEmail - ); - } - } - if (originalRescheduledBooking.recurringEventId) { - newBookingData.recurringEventId = originalRescheduledBooking.recurringEventId; - } - } - - const createBookingObj = { - include: { - user: { - select: { email: true, name: true, timeZone: true, username: true }, - }, - attendees: true, - payment: true, - references: true, - }, - data: newBookingData, - }; - - if (originalRescheduledBooking?.paid && originalRescheduledBooking?.payment) { - const bookingPayment = originalRescheduledBooking?.payment?.find((payment) => payment.success); - - if (bookingPayment) { - createBookingObj.data.payment = { - connect: { id: bookingPayment.id }, - }; - } - } - - if (typeof paymentAppData.price === "number" && paymentAppData.price > 0) { - /* Validate if there is any payment app credential for this user */ - await prisma.credential.findFirstOrThrow({ - where: { - appId: paymentAppData.appId, - ...(paymentAppData.credentialId ? { id: paymentAppData.credentialId } : { userId: organizerUser.id }), - }, - select: { - id: true, - }, - }); - } - - return prisma.booking.create(createBookingObj); -} - export function getCustomInputsResponses( reqBody: { responses?: Record; @@ -667,53 +186,6 @@ function getICalSequence(originalRescheduledBooking: BookingType | null) { return originalRescheduledBooking.iCalSequence + 1; } -export const findBookingQuery = async (bookingId: number) => { - const foundBooking = await prisma.booking.findUnique({ - where: { - id: bookingId, - }, - select: { - uid: true, - location: true, - startTime: true, - endTime: true, - title: true, - description: true, - status: true, - responses: true, - metadata: true, - user: { - select: { - name: true, - email: true, - timeZone: true, - username: true, - }, - }, - eventType: { - select: { - title: true, - description: true, - currency: true, - length: true, - lockTimeZoneToggleOnBookingPage: true, - requiresConfirmation: true, - requiresBookerEmailVerification: true, - price: true, - }, - }, - }, - }); - - // This should never happen but it's just typescript safe - if (!foundBooking) { - throw new Error("Internal Error. Couldn't find booking"); - } - - // Don't leak any sensitive data - return foundBooking; -}; - type BookingDataSchemaGetter = | typeof getBookingDataSchema | typeof import("@calcom/features/bookings/lib/getBookingDataSchemaForApi").default; diff --git a/packages/features/bookings/lib/handleNewBooking/createBooking.ts b/packages/features/bookings/lib/handleNewBooking/createBooking.ts new file mode 100644 index 00000000000000..6c1ad5c3019f5e --- /dev/null +++ b/packages/features/bookings/lib/handleNewBooking/createBooking.ts @@ -0,0 +1,267 @@ +import { Prisma } from "@prisma/client"; +import type short from "short-uuid"; + +import dayjs from "@calcom/dayjs"; +import { isPrismaObjOrUndefined } from "@calcom/lib"; +import prisma from "@calcom/prisma"; +import { BookingStatus } from "@calcom/prisma/enums"; +import type { CalendarEvent } from "@calcom/types/Calendar"; + +import type { TgetBookingDataSchema } from "../getBookingDataSchema"; +import type { + EventTypeId, + AwaitedBookingData, + NewBookingEventType, + IsConfirmedByDefault, + PaymentAppData, + OriginalRescheduledBooking, + AwaitedLoadUsers, +} from "./types"; + +type ReqBodyWithEnd = TgetBookingDataSchema & { end: string }; + +type CreateBookingParams = { + originalRescheduledBooking: OriginalRescheduledBooking; + evt: CalendarEvent; + eventType: NewBookingEventType; + eventTypeId: EventTypeId; + eventTypeSlug: AwaitedBookingData["eventTypeSlug"]; + reqBodyUser: ReqBodyWithEnd["user"]; + reqBodyMetadata: ReqBodyWithEnd["metadata"]; + reqBodyRecurringEventId: ReqBodyWithEnd["recurringEventId"]; + uid: short.SUUID; + responses: ReqBodyWithEnd["responses"] | null; + isConfirmedByDefault: IsConfirmedByDefault; + smsReminderNumber: AwaitedBookingData["smsReminderNumber"]; + organizerUser: AwaitedLoadUsers[number] & { + isFixed?: boolean; + metadata?: Prisma.JsonValue; + }; + rescheduleReason: AwaitedBookingData["rescheduleReason"]; + bookerEmail: AwaitedBookingData["email"]; + paymentAppData: PaymentAppData; + changedOrganizer: boolean; +}; + +function updateEventDetails( + evt: CalendarEvent, + originalRescheduledBooking: OriginalRescheduledBooking | null, + changedOrganizer: boolean +) { + if (originalRescheduledBooking) { + evt.title = originalRescheduledBooking?.title || evt.title; + evt.description = originalRescheduledBooking?.description || evt.description; + evt.location = originalRescheduledBooking?.location || evt.location; + evt.location = changedOrganizer ? evt.location : originalRescheduledBooking?.location || evt.location; + } +} + +export async function createBooking({ + originalRescheduledBooking, + evt, + eventTypeId, + eventTypeSlug, + reqBodyUser, + reqBodyMetadata, + reqBodyRecurringEventId, + uid, + responses, + isConfirmedByDefault, + smsReminderNumber, + organizerUser, + rescheduleReason, + eventType, + bookerEmail, + paymentAppData, + changedOrganizer, +}: CreateBookingParams) { + updateEventDetails(evt, originalRescheduledBooking, changedOrganizer); + + const newBookingData = buildNewBookingData({ + uid, + evt, + responses, + isConfirmedByDefault, + reqBodyMetadata, + smsReminderNumber, + eventTypeSlug, + organizerUser, + reqBodyRecurringEventId, + originalRescheduledBooking, + bookerEmail, + rescheduleReason, + eventType, + eventTypeId, + reqBodyUser, + }); + + return await saveBooking(newBookingData, originalRescheduledBooking, paymentAppData, organizerUser); +} + +async function saveBooking( + newBookingData: Prisma.BookingCreateInput, + originalRescheduledBooking: OriginalRescheduledBooking, + paymentAppData: PaymentAppData, + organizerUser: CreateBookingParams["organizerUser"] +) { + const createBookingObj = { + include: { + user: { + select: { email: true, name: true, timeZone: true, username: true }, + }, + attendees: true, + payment: true, + references: true, + }, + data: newBookingData, + }; + + if (originalRescheduledBooking?.paid && originalRescheduledBooking?.payment) { + const bookingPayment = originalRescheduledBooking.payment.find((payment) => payment.success); + if (bookingPayment) { + createBookingObj.data.payment = { connect: { id: bookingPayment.id } }; + } + } + + if (typeof paymentAppData.price === "number" && paymentAppData.price > 0) { + await prisma.credential.findFirstOrThrow({ + where: { + appId: paymentAppData.appId, + ...(paymentAppData.credentialId ? { id: paymentAppData.credentialId } : { userId: organizerUser.id }), + }, + select: { id: true }, + }); + } + + return prisma.booking.create(createBookingObj); +} + +function getEventTypeRel(eventTypeId: EventTypeId) { + return eventTypeId ? { connect: { id: eventTypeId } } : {}; +} + +function getAttendeesData(evt: Pick) { + //if attendee is team member, it should fetch their locale not booker's locale + //perhaps make email fetch request to see if his locale is stored, else + const attendees = evt.attendees.map((attendee) => ({ + name: attendee.name, + email: attendee.email, + timeZone: attendee.timeZone, + locale: attendee.language.locale, + })); + + if (evt.team?.members) { + attendees.push( + ...evt.team.members.map((member) => ({ + email: member.email, + name: member.name, + timeZone: member.timeZone, + locale: member.language.locale, + })) + ); + } + + return attendees; +} + +function buildNewBookingData(params: { + uid: short.SUUID; + evt: CalendarEvent; + responses: ReqBodyWithEnd["responses"] | null; + isConfirmedByDefault: IsConfirmedByDefault; + reqBodyMetadata: ReqBodyWithEnd["metadata"]; + smsReminderNumber: AwaitedBookingData["smsReminderNumber"]; + eventTypeSlug: AwaitedBookingData["eventTypeSlug"]; + organizerUser: CreateBookingParams["organizerUser"]; + reqBodyRecurringEventId: ReqBodyWithEnd["recurringEventId"]; + originalRescheduledBooking: OriginalRescheduledBooking | null; + bookerEmail: AwaitedBookingData["email"]; + rescheduleReason: AwaitedBookingData["rescheduleReason"]; + eventType: NewBookingEventType; + eventTypeId: EventTypeId; + reqBodyUser: ReqBodyWithEnd["user"]; +}): Prisma.BookingCreateInput { + const { + uid, + evt, + responses, + isConfirmedByDefault, + reqBodyMetadata, + smsReminderNumber, + eventTypeSlug, + organizerUser, + reqBodyRecurringEventId, + originalRescheduledBooking, + bookerEmail, + rescheduleReason, + eventType, + eventTypeId, + reqBodyUser, + } = params; + + const attendeesData = getAttendeesData(evt); + const eventTypeRel = getEventTypeRel(eventTypeId); + + const newBookingData: Prisma.BookingCreateInput = { + uid, + userPrimaryEmail: evt.organizer.email, + responses: responses === null || evt.seatsPerTimeSlot ? Prisma.JsonNull : responses, + title: evt.title, + startTime: dayjs.utc(evt.startTime).toDate(), + endTime: dayjs.utc(evt.endTime).toDate(), + description: evt.seatsPerTimeSlot ? null : evt.additionalNotes, + customInputs: isPrismaObjOrUndefined(evt.customInputs), + status: isConfirmedByDefault ? BookingStatus.ACCEPTED : BookingStatus.PENDING, + location: evt.location, + eventType: eventTypeRel, + smsReminderNumber, + metadata: reqBodyMetadata, + attendees: { + createMany: { + data: attendeesData, + }, + }, + dynamicEventSlugRef: !eventTypeId ? eventTypeSlug : null, + dynamicGroupSlugRef: !eventTypeId ? (reqBodyUser as string).toLowerCase() : null, + iCalUID: evt.iCalUID ?? "", + user: { + connect: { + id: organizerUser.id, + }, + }, + destinationCalendar: + evt.destinationCalendar && evt.destinationCalendar.length > 0 + ? { + connect: { id: evt.destinationCalendar[0].id }, + } + : undefined, + }; + + if (reqBodyRecurringEventId) { + newBookingData.recurringEventId = reqBodyRecurringEventId; + } + + if (originalRescheduledBooking) { + newBookingData.metadata = { + ...(typeof originalRescheduledBooking.metadata === "object" && originalRescheduledBooking.metadata), + }; + newBookingData.paid = originalRescheduledBooking.paid; + newBookingData.fromReschedule = originalRescheduledBooking.uid; + if (originalRescheduledBooking.uid) { + newBookingData.cancellationReason = rescheduleReason; + } + // Reschedule logic with booking with seats + if (newBookingData.attendees?.createMany?.data && eventType?.seatsPerTimeSlot && bookerEmail) { + newBookingData.attendees.createMany.data = attendeesData.filter( + (attendee) => attendee.email === bookerEmail + ); + } + if (originalRescheduledBooking.recurringEventId) { + newBookingData.recurringEventId = originalRescheduledBooking.recurringEventId; + } + } + + return newBookingData; +} + +export type Booking = Prisma.PromiseReturnType; diff --git a/packages/features/bookings/lib/handleNewBooking/ensureAvailableUsers.ts b/packages/features/bookings/lib/handleNewBooking/ensureAvailableUsers.ts new file mode 100644 index 00000000000000..028c076203e7fd --- /dev/null +++ b/packages/features/bookings/lib/handleNewBooking/ensureAvailableUsers.ts @@ -0,0 +1,155 @@ +import type { Logger } from "tslog"; + +import { getBusyTimesForLimitChecks } from "@calcom/core/getBusyTimes"; +import { getUsersAvailability } from "@calcom/core/getUserAvailability"; +import dayjs from "@calcom/dayjs"; +import type { Dayjs } from "@calcom/dayjs"; +import { parseBookingLimit, parseDurationLimit } from "@calcom/lib"; +import { ErrorCode } from "@calcom/lib/errorCodes"; +import { safeStringify } from "@calcom/lib/safeStringify"; + +import { checkForConflicts } from "../conflictChecker/checkForConflicts"; +import type { getEventTypeResponse } from "./getEventTypesFromDB"; +import type { IsFixedAwareUser, BookingType } from "./types"; + +type DateRange = { + start: Dayjs; + end: Dayjs; +}; + +const getDateTimeInUtc = (timeInput: string, timeZone?: string) => { + return timeZone === "Etc/GMT" ? dayjs.utc(timeInput) : dayjs(timeInput).tz(timeZone).utc(); +}; + +const getOriginalBookingDuration = (originalBooking?: BookingType) => { + return originalBooking + ? dayjs(originalBooking.endTime).diff(dayjs(originalBooking.startTime), "minutes") + : undefined; +}; + +const hasDateRangeForBooking = ( + dateRanges: DateRange[], + startDateTimeUtc: dayjs.Dayjs, + endDateTimeUtc: dayjs.Dayjs +) => { + let dateRangeForBooking = false; + + for (const dateRange of dateRanges) { + if ( + (startDateTimeUtc.isAfter(dateRange.start) || startDateTimeUtc.isSame(dateRange.start)) && + (endDateTimeUtc.isBefore(dateRange.end) || endDateTimeUtc.isSame(dateRange.end)) + ) { + dateRangeForBooking = true; + break; + } + } + + return dateRangeForBooking; +}; + +export async function ensureAvailableUsers( + eventType: getEventTypeResponse & { + users: IsFixedAwareUser[]; + }, + input: { dateFrom: string; dateTo: string; timeZone: string; originalRescheduledBooking?: BookingType }, + loggerWithEventDetails: Logger +) { + const availableUsers: IsFixedAwareUser[] = []; + + const startDateTimeUtc = getDateTimeInUtc(input.dateFrom, input.timeZone); + const endDateTimeUtc = getDateTimeInUtc(input.dateTo, input.timeZone); + + const duration = dayjs(input.dateTo).diff(input.dateFrom, "minute"); + const originalBookingDuration = getOriginalBookingDuration(input.originalRescheduledBooking); + + const bookingLimits = parseBookingLimit(eventType?.bookingLimits); + const durationLimits = parseDurationLimit(eventType?.durationLimits); + + const busyTimesFromLimitsBookingsAllUsers: Awaited> = + eventType && (bookingLimits || durationLimits) + ? await getBusyTimesForLimitChecks({ + userIds: eventType.users.map((u) => u.id), + eventTypeId: eventType.id, + startDate: startDateTimeUtc.format(), + endDate: endDateTimeUtc.format(), + rescheduleUid: input.originalRescheduledBooking?.uid ?? null, + bookingLimits, + durationLimits, + }) + : []; + + const usersAvailability = await getUsersAvailability({ + users: eventType.users, + query: { + ...input, + eventTypeId: eventType.id, + duration: originalBookingDuration, + returnDateOverrides: false, + dateFrom: startDateTimeUtc.format(), + dateTo: endDateTimeUtc.format(), + }, + initialData: { + eventType, + rescheduleUid: input.originalRescheduledBooking?.uid ?? null, + busyTimesFromLimitsBookings: busyTimesFromLimitsBookingsAllUsers, + }, + }); + + usersAvailability.forEach(({ oooExcludedDateRanges: dateRanges, busy: bufferedBusyTimes }, index) => { + const user = eventType.users[index]; + + loggerWithEventDetails.debug( + "calendarBusyTimes==>>>", + JSON.stringify({ bufferedBusyTimes, dateRanges, isRecurringEvent: eventType.recurringEvent }) + ); + + if (!dateRanges.length) { + loggerWithEventDetails.error( + `User does not have availability at this time.`, + safeStringify({ + startDateTimeUtc, + endDateTimeUtc, + input, + }) + ); + return; + } + + //check if event time is within the date range + if (!hasDateRangeForBooking(dateRanges, startDateTimeUtc, endDateTimeUtc)) { + loggerWithEventDetails.error( + `No date range for booking.`, + safeStringify({ + startDateTimeUtc, + endDateTimeUtc, + input, + }) + ); + return; + } + + try { + const foundConflict = checkForConflicts(bufferedBusyTimes, startDateTimeUtc, duration); + // no conflicts found, add to available users. + if (!foundConflict) { + availableUsers.push(user); + } + } catch (error) { + loggerWithEventDetails.error("Unable set isAvailableToBeBooked. Using true. ", error); + } + }); + + if (!availableUsers.length) { + loggerWithEventDetails.error( + `No available users found.`, + safeStringify({ + startDateTimeUtc, + endDateTimeUtc, + input, + }) + ); + throw new Error(ErrorCode.NoAvailableUsersFound); + } + + return availableUsers; +} diff --git a/packages/features/bookings/lib/handleNewBooking/findBookingQuery.ts b/packages/features/bookings/lib/handleNewBooking/findBookingQuery.ts new file mode 100644 index 00000000000000..ec339e585f3174 --- /dev/null +++ b/packages/features/bookings/lib/handleNewBooking/findBookingQuery.ts @@ -0,0 +1,48 @@ +import prisma from "@calcom/prisma"; + +export const findBookingQuery = async (bookingId: number) => { + const foundBooking = await prisma.booking.findUnique({ + where: { + id: bookingId, + }, + select: { + uid: true, + location: true, + startTime: true, + endTime: true, + title: true, + description: true, + status: true, + responses: true, + metadata: true, + user: { + select: { + name: true, + email: true, + timeZone: true, + username: true, + }, + }, + eventType: { + select: { + title: true, + description: true, + currency: true, + length: true, + lockTimeZoneToggleOnBookingPage: true, + requiresConfirmation: true, + requiresBookerEmailVerification: true, + price: true, + }, + }, + }, + }); + + // This should never happen but it's just typescript safe + if (!foundBooking) { + throw new Error("Internal Error. Couldn't find booking"); + } + + // Don't leak any sensitive data + return foundBooking; +}; diff --git a/packages/features/bookings/lib/handleNewBooking/getBookingData.ts b/packages/features/bookings/lib/handleNewBooking/getBookingData.ts new file mode 100644 index 00000000000000..316630a1b85f75 --- /dev/null +++ b/packages/features/bookings/lib/handleNewBooking/getBookingData.ts @@ -0,0 +1,89 @@ +import type { EventTypeCustomInput } from "@prisma/client"; +import type { NextApiRequest } from "next"; +import type z from "zod"; + +import dayjs from "@calcom/dayjs"; +import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses"; +import { ErrorCode } from "@calcom/lib/errorCodes"; +import { bookingCreateSchemaLegacyPropsForApi } from "@calcom/prisma/zod-utils"; + +import type { TgetBookingDataSchema } from "../getBookingDataSchema"; +import { handleCustomInputs } from "./handleCustomInputs"; +import type { getEventTypeResponse } from "./types"; + +type ReqBodyWithEnd = TgetBookingDataSchema & { end: string }; + +export async function getBookingData({ + req, + eventType, + schema, +}: { + req: NextApiRequest; + eventType: getEventTypeResponse; + schema: T; +}) { + const reqBody = await schema.parseAsync(req.body); + const reqBodyWithEnd = (reqBody: TgetBookingDataSchema): reqBody is ReqBodyWithEnd => { + // Use the event length to auto-set the event end time. + if (!Object.prototype.hasOwnProperty.call(reqBody, "end")) { + reqBody.end = dayjs.utc(reqBody.start).add(eventType.length, "minutes").format(); + } + return true; + }; + if (!reqBodyWithEnd(reqBody)) { + throw new Error(ErrorCode.RequestBodyWithouEnd); + } + // reqBody.end is no longer an optional property. + if (reqBody.customInputs) { + // Check if required custom inputs exist + handleCustomInputs(eventType.customInputs as EventTypeCustomInput[], reqBody.customInputs); + const reqBodyWithLegacyProps = bookingCreateSchemaLegacyPropsForApi.parse(reqBody); + return { + ...reqBody, + name: reqBodyWithLegacyProps.name, + email: reqBodyWithLegacyProps.email, + guests: reqBodyWithLegacyProps.guests, + location: reqBodyWithLegacyProps.location || "", + smsReminderNumber: reqBodyWithLegacyProps.smsReminderNumber, + notes: reqBodyWithLegacyProps.notes, + rescheduleReason: reqBodyWithLegacyProps.rescheduleReason, + // So TS doesn't complain about unknown properties + calEventUserFieldsResponses: undefined, + calEventResponses: undefined, + customInputs: undefined, + }; + } + if (!reqBody.responses) { + throw new Error("`responses` must not be nullish"); + } + const responses = reqBody.responses; + + const { userFieldsResponses: calEventUserFieldsResponses, responses: calEventResponses } = + getCalEventResponses({ + bookingFields: eventType.bookingFields, + responses, + }); + return { + ...reqBody, + name: responses.name, + email: responses.email, + guests: responses.guests ? responses.guests : [], + location: responses.location?.optionValue || responses.location?.value || "", + smsReminderNumber: responses.smsReminderNumber, + notes: responses.notes || "", + calEventUserFieldsResponses, + rescheduleReason: responses.rescheduleReason, + calEventResponses, + // So TS doesn't complain about unknown properties + customInputs: undefined, + }; +} + +export type AwaitedBookingData = Awaited>; +export type RescheduleReason = AwaitedBookingData["rescheduleReason"]; +export type NoEmail = AwaitedBookingData["noEmail"]; +export type AdditionalNotes = AwaitedBookingData["notes"]; +export type ReqAppsStatus = AwaitedBookingData["appsStatus"]; +export type SmsReminderNumber = AwaitedBookingData["smsReminderNumber"]; +export type EventTypeId = AwaitedBookingData["eventTypeId"]; +export type ReqBodyMetadata = ReqBodyWithEnd["metadata"]; diff --git a/packages/features/bookings/lib/handleNewBooking/getOriginalRescheduledBooking.ts b/packages/features/bookings/lib/handleNewBooking/getOriginalRescheduledBooking.ts new file mode 100644 index 00000000000000..55930a095f4a26 --- /dev/null +++ b/packages/features/bookings/lib/handleNewBooking/getOriginalRescheduledBooking.ts @@ -0,0 +1,60 @@ +import type { Prisma } from "@prisma/client"; + +import prisma from "@calcom/prisma"; +import { BookingStatus } from "@calcom/prisma/enums"; + +export async function getOriginalRescheduledBooking(uid: string, seatsEventType?: boolean) { + return prisma.booking.findFirst({ + where: { + uid: uid, + status: { + in: [BookingStatus.ACCEPTED, BookingStatus.CANCELLED, BookingStatus.PENDING], + }, + }, + include: { + attendees: { + select: { + name: true, + email: true, + locale: true, + timeZone: true, + ...(seatsEventType && { bookingSeat: true, id: true }), + }, + }, + user: { + select: { + id: true, + name: true, + email: true, + locale: true, + timeZone: true, + destinationCalendar: true, + credentials: { + select: { + id: true, + userId: true, + key: true, + type: true, + teamId: true, + appId: true, + invalid: true, + user: { + select: { + email: true, + }, + }, + }, + }, + }, + }, + destinationCalendar: true, + payment: true, + references: true, + workflowReminders: true, + }, + }); +} + +export type BookingType = Prisma.PromiseReturnType; + +export type OriginalRescheduledBooking = Awaited>; diff --git a/packages/features/bookings/lib/handleNewBooking/getRequiresConfirmationFlags.ts b/packages/features/bookings/lib/handleNewBooking/getRequiresConfirmationFlags.ts index 23a2df53f956c6..e2c4f9891f1b97 100644 --- a/packages/features/bookings/lib/handleNewBooking/getRequiresConfirmationFlags.ts +++ b/packages/features/bookings/lib/handleNewBooking/getRequiresConfirmationFlags.ts @@ -69,3 +69,5 @@ function determineIsConfirmedByDefault( ): boolean { return (!requiresConfirmation && price === 0) || userReschedulingIsOwner; } + +export type IsConfirmedByDefault = ReturnType["isConfirmedByDefault"]; diff --git a/packages/features/bookings/lib/handleNewBooking/handleAppsStatus.ts b/packages/features/bookings/lib/handleNewBooking/handleAppsStatus.ts index 29792e43dea11d..67fdb38f27e0e9 100644 --- a/packages/features/bookings/lib/handleNewBooking/handleAppsStatus.ts +++ b/packages/features/bookings/lib/handleNewBooking/handleAppsStatus.ts @@ -1,7 +1,7 @@ import type { AdditionalInformation, AppsStatus } from "@calcom/types/Calendar"; import type { EventResult } from "@calcom/types/EventManager"; -import type { ReqAppsStatus, Booking } from "../handleNewBooking"; +import type { ReqAppsStatus, Booking } from "./types"; export function handleAppsStatus( results: EventResult[], diff --git a/packages/features/bookings/lib/handleNewBooking/loadUsers.ts b/packages/features/bookings/lib/handleNewBooking/loadUsers.ts index 7e60d290a14555..a7117bec7e8482 100644 --- a/packages/features/bookings/lib/handleNewBooking/loadUsers.ts +++ b/packages/features/bookings/lib/handleNewBooking/loadUsers.ts @@ -8,7 +8,7 @@ import { UserRepository } from "@calcom/lib/server/repository/user"; import prisma, { userSelect } from "@calcom/prisma"; import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential"; -import type { NewBookingEventType } from "../handleNewBooking"; +import type { NewBookingEventType } from "./types"; const log = logger.getSubLogger({ prefix: ["[loadUsers]:handleNewBooking "] }); @@ -85,3 +85,5 @@ export const findUsersByUsername = async ({ }; }); }; + +export type AwaitedLoadUsers = Awaited>; diff --git a/packages/features/bookings/lib/handleNewBooking/types.ts b/packages/features/bookings/lib/handleNewBooking/types.ts new file mode 100644 index 00000000000000..385816a633f24c --- /dev/null +++ b/packages/features/bookings/lib/handleNewBooking/types.ts @@ -0,0 +1,81 @@ +import type { App } from "@prisma/client"; +import type { Prisma } from "@prisma/client"; +import type { TFunction } from "next-i18next"; + +import type { EventTypeAppsList } from "@calcom/app-store/utils"; +import type { AwaitedGetDefaultEvent } from "@calcom/lib/defaultEvents"; +import type { PaymentAppData } from "@calcom/lib/getPaymentAppData"; +import type { userSelect } from "@calcom/prisma"; +import type { CredentialPayload } from "@calcom/types/Credential"; + +import type { Booking } from "./createBooking"; +import type { + AwaitedBookingData, + RescheduleReason, + NoEmail, + AdditionalNotes, + ReqAppsStatus, + SmsReminderNumber, + EventTypeId, + ReqBodyMetadata, +} from "./getBookingData"; +import type { getEventTypeResponse } from "./getEventTypesFromDB"; +import type { BookingType, OriginalRescheduledBooking } from "./getOriginalRescheduledBooking"; +import type { getRequiresConfirmationFlags } from "./getRequiresConfirmationFlags"; +import type { AwaitedLoadUsers } from "./loadUsers"; + +type User = Prisma.UserGetPayload; + +export type OrganizerUser = AwaitedLoadUsers[number] & { + isFixed?: boolean; + metadata?: Prisma.JsonValue; +}; + +export type Invitee = { + email: string; + name: string; + firstName: string; + lastName: string; + timeZone: string; + language: { + translate: TFunction; + locale: string; + }; +}[]; + +export interface IEventTypePaymentCredentialType { + appId: EventTypeAppsList; + app: { + categories: App["categories"]; + dirName: string; + }; + key: Prisma.JsonValue; +} + +export type IsFixedAwareUser = User & { + isFixed: boolean; + credentials: CredentialPayload[]; + organization: { slug: string }; + priority?: number; +}; + +export type NewBookingEventType = AwaitedGetDefaultEvent | getEventTypeResponse; + +export type IsConfirmedByDefault = ReturnType["isConfirmedByDefault"]; + +export type { + AwaitedBookingData, + RescheduleReason, + NoEmail, + AdditionalNotes, + ReqAppsStatus, + SmsReminderNumber, + EventTypeId, + ReqBodyMetadata, + PaymentAppData, + BookingType, + Booking, + OriginalRescheduledBooking, + AwaitedLoadUsers, + getEventTypeResponse, +}; diff --git a/packages/features/bookings/lib/handleSeats/create/createNewSeat.ts b/packages/features/bookings/lib/handleSeats/create/createNewSeat.ts index 7b19d2848e05ee..d764c67c306023 100644 --- a/packages/features/bookings/lib/handleSeats/create/createNewSeat.ts +++ b/packages/features/bookings/lib/handleSeats/create/createNewSeat.ts @@ -15,8 +15,8 @@ import { handlePayment } from "@calcom/lib/payment/handlePayment"; import prisma from "@calcom/prisma"; import { BookingStatus } from "@calcom/prisma/enums"; -import type { IEventTypePaymentCredentialType } from "../../handleNewBooking"; -import { findBookingQuery } from "../../handleNewBooking"; +import { findBookingQuery } from "../../handleNewBooking/findBookingQuery"; +import type { IEventTypePaymentCredentialType } from "../../handleNewBooking/types"; import type { SeatedBooking, NewSeatedBookingObject, HandleSeatsResultBooking } from "../types"; const createNewSeat = async ( diff --git a/packages/features/bookings/lib/handleSeats/lib/lastAttendeeDeleteBooking.ts b/packages/features/bookings/lib/handleSeats/lib/lastAttendeeDeleteBooking.ts index 4c1dc53dfa8691..4c1eb418cf9935 100644 --- a/packages/features/bookings/lib/handleSeats/lib/lastAttendeeDeleteBooking.ts +++ b/packages/features/bookings/lib/handleSeats/lib/lastAttendeeDeleteBooking.ts @@ -8,7 +8,7 @@ import { BookingStatus } from "@calcom/prisma/enums"; import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential"; import type { CalendarEvent } from "@calcom/types/Calendar"; -import type { OriginalRescheduledBooking } from "../../handleNewBooking"; +import type { OriginalRescheduledBooking } from "../../handleNewBooking/types"; /* Check if the original booking has no more attendees, if so delete the booking and any calendar or video integrations */ diff --git a/packages/features/bookings/lib/handleSeats/reschedule/attendee/attendeeRescheduleSeatedBooking.ts b/packages/features/bookings/lib/handleSeats/reschedule/attendee/attendeeRescheduleSeatedBooking.ts index c75867059785bc..e340319666ff76 100644 --- a/packages/features/bookings/lib/handleSeats/reschedule/attendee/attendeeRescheduleSeatedBooking.ts +++ b/packages/features/bookings/lib/handleSeats/reschedule/attendee/attendeeRescheduleSeatedBooking.ts @@ -7,7 +7,7 @@ import { getTranslation } from "@calcom/lib/server/i18n"; import prisma from "@calcom/prisma"; import type { Person, CalendarEvent } from "@calcom/types/Calendar"; -import { findBookingQuery } from "../../../handleNewBooking"; +import { findBookingQuery } from "../../../handleNewBooking/findBookingQuery"; import lastAttendeeDeleteBooking from "../../lib/lastAttendeeDeleteBooking"; import type { RescheduleSeatedBookingObject, SeatAttendee, NewTimeSlotBooking } from "../../types"; diff --git a/packages/features/bookings/lib/handleSeats/reschedule/owner/combineTwoSeatedBookings.ts b/packages/features/bookings/lib/handleSeats/reschedule/owner/combineTwoSeatedBookings.ts index d4b5b1ab19e097..7efe855e038b2b 100644 --- a/packages/features/bookings/lib/handleSeats/reschedule/owner/combineTwoSeatedBookings.ts +++ b/packages/features/bookings/lib/handleSeats/reschedule/owner/combineTwoSeatedBookings.ts @@ -10,7 +10,8 @@ import prisma from "@calcom/prisma"; import { BookingStatus } from "@calcom/prisma/enums"; import type { createLoggerWithEventDetails } from "../../../handleNewBooking"; -import { addVideoCallDataToEvent, findBookingQuery } from "../../../handleNewBooking"; +import { addVideoCallDataToEvent } from "../../../handleNewBooking"; +import { findBookingQuery } from "../../../handleNewBooking/findBookingQuery"; import type { SeatedBooking, RescheduleSeatedBookingObject, NewTimeSlotBooking } from "../../types"; const combineTwoSeatedBookings = async ( diff --git a/packages/features/bookings/lib/handleSeats/reschedule/owner/moveSeatedBookingToNewTimeSlot.ts b/packages/features/bookings/lib/handleSeats/reschedule/owner/moveSeatedBookingToNewTimeSlot.ts index 9bf8b35bd9dcd2..93dc6f4342660d 100644 --- a/packages/features/bookings/lib/handleSeats/reschedule/owner/moveSeatedBookingToNewTimeSlot.ts +++ b/packages/features/bookings/lib/handleSeats/reschedule/owner/moveSeatedBookingToNewTimeSlot.ts @@ -6,9 +6,11 @@ import { sendRescheduledEmails } from "@calcom/emails"; import prisma from "@calcom/prisma"; import type { AdditionalInformation, AppsStatus } from "@calcom/types/Calendar"; -import { addVideoCallDataToEvent, findBookingQuery } from "../../../handleNewBooking"; -import type { Booking, createLoggerWithEventDetails } from "../../../handleNewBooking"; +import { addVideoCallDataToEvent } from "../../../handleNewBooking"; +import type { createLoggerWithEventDetails } from "../../../handleNewBooking"; +import { findBookingQuery } from "../../../handleNewBooking/findBookingQuery"; import { handleAppsStatus } from "../../../handleNewBooking/handleAppsStatus"; +import type { Booking } from "../../../handleNewBooking/types"; import type { SeatedBooking, RescheduleSeatedBookingObject } from "../../types"; const moveSeatedBookingToNewTimeSlot = async ( diff --git a/packages/features/bookings/lib/handleSeats/types.d.ts b/packages/features/bookings/lib/handleSeats/types.d.ts index 7f79068146700d..e4497d39d11616 100644 --- a/packages/features/bookings/lib/handleSeats/types.d.ts +++ b/packages/features/bookings/lib/handleSeats/types.d.ts @@ -4,7 +4,7 @@ import type z from "zod"; import type { Workflow } from "@calcom/features/ee/workflows/lib/types"; import type { AppsStatus } from "@calcom/types/Calendar"; -import type { Booking, NewBookingEventType, OriginalRescheduledBooking } from "../handleNewBooking"; +import type { Booking, NewBookingEventType, OriginalRescheduledBooking } from "../handleNewBooking/types"; export type BookingSeat = Prisma.BookingSeatGetPayload<{ include: { booking: true; attendee: true } }> | null; diff --git a/packages/features/instant-meeting/handleInstantMeeting.ts b/packages/features/instant-meeting/handleInstantMeeting.ts index 523b0e34cb66e3..49707c8cc81449 100644 --- a/packages/features/instant-meeting/handleInstantMeeting.ts +++ b/packages/features/instant-meeting/handleInstantMeeting.ts @@ -8,7 +8,8 @@ import { createInstantMeetingWithCalVideo } from "@calcom/core/videoClient"; import dayjs from "@calcom/dayjs"; import getBookingDataSchema from "@calcom/features/bookings/lib/getBookingDataSchema"; import { getBookingFieldsWithSystemFields } from "@calcom/features/bookings/lib/getBookingFields"; -import { getBookingData, getCustomInputsResponses } from "@calcom/features/bookings/lib/handleNewBooking"; +import { getCustomInputsResponses } from "@calcom/features/bookings/lib/handleNewBooking"; +import { getBookingData } from "@calcom/features/bookings/lib/handleNewBooking/getBookingData"; import { getEventTypesFromDB } from "@calcom/features/bookings/lib/handleNewBooking/getEventTypesFromDB"; import { getFullName } from "@calcom/features/form-builder/utils"; import { sendGenericWebhookPayload } from "@calcom/features/webhooks/lib/sendPayload"; diff --git a/packages/lib/defaultEvents.ts b/packages/lib/defaultEvents.ts index b7d2e8053e3753..ddf079903575f1 100644 --- a/packages/lib/defaultEvents.ts +++ b/packages/lib/defaultEvents.ts @@ -168,3 +168,5 @@ export const getUsernameList = (users: string | string[] | undefined): string[] }; export default defaultEvents; + +export type AwaitedGetDefaultEvent = Awaited>; diff --git a/packages/lib/getPaymentAppData.ts b/packages/lib/getPaymentAppData.ts index 0064321e7f9e1e..0c7de5402e7bf7 100644 --- a/packages/lib/getPaymentAppData.ts +++ b/packages/lib/getPaymentAppData.ts @@ -53,3 +53,5 @@ export default function getPaymentAppData( } ); } + +export type PaymentAppData = ReturnType;