Files
calendar/packages/features/bookings/lib/handleNewRecurringBooking.ts
T
417a612aa3 refactor: extract NextRequest from handle new booking (#20082)
* extract NextRequest

* update api and tests

* booking limits tests

* fix more tests to use new approach

* update more tests with new format

* extract getOrgDomainConfig to not use req

* extract req from loadNewUsers and pass in hostname/forcedSlug

* fix instant meeting types and hostname fixes

* fix handleNewBookingReq

* fix type errors in tests

* make hostName and forcedSlug optional

* fix type err

* Revert "fix type err"

This reverts commit 9d5de9019d9dafe348c97b876baaa1d0675967e5.

* wip fix e2e

* fix: add missing headers

* migrate handle recurring event and also create tests specific to fn

* platform recurringbooking

* fix type

* hard code types on request object

* bump libraries

* fixup! bump libraries

* fix: accessing host if headers not passed

* fix: v2 recurring booking

* fix: accessing host if headers not passed

* chore: bump platform libraries

* fix tests

* push

* chore: bump libraries

* push lock changes

* bump libraries

---------

Co-authored-by: amrit <iamamrit27@gmail.com>
Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
Co-authored-by: Morgan Vernay <morgan@cal.com>
Co-authored-by: supalarry <laurisskraucis@gmail.com>
Co-authored-by: Lauris Skraucis <lauris.skraucis@gmail.com>
2025-04-02 17:57:58 -04:00

127 lines
4.6 KiB
TypeScript

import handleNewBooking from "@calcom/features/bookings/lib/handleNewBooking";
import type { BookingResponse } from "@calcom/features/bookings/types";
import { SchedulingType } from "@calcom/prisma/client";
import type { AppsStatus } from "@calcom/types/Calendar";
export type PlatformParams = {
platformClientId?: string;
platformCancelUrl?: string;
platformBookingUrl?: string;
platformRescheduleUrl?: string;
platformBookingLocation?: string;
};
export type BookingHandlerInput = {
bookingData: Record<string, any>[];
userId?: number;
// These used to come from headers but now we're passing them as params
hostname?: string;
forcedSlug?: string;
noEmail?: boolean;
} & PlatformParams;
export const handleNewRecurringBooking = async (input: BookingHandlerInput): Promise<BookingResponse[]> => {
const data = input.bookingData;
const createdBookings: BookingResponse[] = [];
const allRecurringDates: { start: string | undefined; end: string | undefined }[] = data.map((booking) => {
return { start: booking.start, end: booking.end };
});
const appsStatus: AppsStatus[] | undefined = undefined;
const numSlotsToCheckForAvailability = 2;
let thirdPartyRecurringEventId = null;
// for round robin, the first slot needs to be handled first to define the lucky user
const firstBooking = data[0];
const isRoundRobin = firstBooking.schedulingType === SchedulingType.ROUND_ROBIN;
let luckyUsers = undefined;
const handleBookingMeta = {
userId: input.userId,
platformClientId: input.platformClientId,
platformRescheduleUrl: input.platformRescheduleUrl,
platformCancelUrl: input.platformCancelUrl,
platformBookingUrl: input.platformBookingUrl,
platformBookingLocation: input.platformBookingLocation,
};
if (isRoundRobin) {
const recurringEventData = {
...firstBooking,
appsStatus,
allRecurringDates,
isFirstRecurringSlot: true,
thirdPartyRecurringEventId,
numSlotsToCheckForAvailability,
currentRecurringIndex: 0,
noEmail: input.noEmail !== undefined ? input.noEmail : false,
};
const firstBookingResult = await handleNewBooking({
bookingData: recurringEventData,
hostname: input.hostname || "",
forcedSlug: input.forcedSlug as string | undefined,
...handleBookingMeta,
});
luckyUsers = firstBookingResult.luckyUsers;
}
for (let key = isRoundRobin ? 1 : 0; key < data.length; key++) {
const booking = data[key];
// Disable AppStatus in Recurring Booking Email as it requires us to iterate backwards to be able to compute the AppsStatus for all the bookings except the very first slot and then send that slot's email with statuses
// It is also doubtful that how useful is to have the AppsStatus of all the bookings in the email.
// It is more important to iterate forward and check for conflicts for only first few bookings defined by 'numSlotsToCheckForAvailability'
// if (key === 0) {
// const calcAppsStatus: { [key: string]: AppsStatus } = createdBookings
// .flatMap((book) => (book.appsStatus !== undefined ? book.appsStatus : []))
// .reduce((prev, curr) => {
// if (prev[curr.type]) {
// prev[curr.type].failures += curr.failures;
// prev[curr.type].success += curr.success;
// } else {
// prev[curr.type] = curr;
// }
// return prev;
// }, {} as { [key: string]: AppsStatus });
// appsStatus = Object.values(calcAppsStatus);
// }
const recurringEventData = {
...booking,
appsStatus,
allRecurringDates,
isFirstRecurringSlot: key == 0,
thirdPartyRecurringEventId,
numSlotsToCheckForAvailability,
currentRecurringIndex: key,
noEmail: input.noEmail !== undefined ? input.noEmail : key !== 0,
luckyUsers,
};
const promiseEachRecurringBooking: ReturnType<typeof handleNewBooking> = handleNewBooking({
hostname: input.hostname || "",
forcedSlug: input.forcedSlug as string | undefined,
bookingData: recurringEventData,
...handleBookingMeta,
});
const eachRecurringBooking = await promiseEachRecurringBooking;
createdBookings.push(eachRecurringBooking);
if (!thirdPartyRecurringEventId) {
if (eachRecurringBooking.references && eachRecurringBooking.references.length > 0) {
for (const reference of eachRecurringBooking.references!) {
if (reference.thirdPartyRecurringEventId) {
thirdPartyRecurringEventId = reference.thirdPartyRecurringEventId;
break;
}
}
}
}
}
return createdBookings;
};