fix: Alternate reschedule behaviour for past bookings - Don't allow new booking (#23736)

* fix: Past booking redirect

* fix: rename ENV_PAST_BOOKING_RESCHEDULE_CHANGE to ENV_PAST_BOOKING_RESCHEDULE_CHANGE_TEAM_IDS for clarity

- Renamed environment variable to better reflect that it contains comma-separated team IDs
- Updated all references in constants.ts, implementation file, tests, and turbo.json
- Addresses PR comment feedback about misleading variable name

Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Hariom Balhara
2025-09-17 12:47:45 +01:00
committed by GitHub
co-authored by Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
parent 9ee06b410e
commit 39917ebfa6
6 changed files with 554 additions and 49 deletions
@@ -4,14 +4,12 @@ import { URLSearchParams } from "url";
import { z } from "zod";
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
import { getFullName } from "@calcom/features/form-builder/utils";
import { determineReschedulePreventionRedirect } from "@calcom/features/bookings/lib/reschedule/determineReschedulePreventionRedirect";
import { buildEventUrlFromBooking } from "@calcom/lib/bookings/buildEventUrlFromBooking";
import { getDefaultEvent } from "@calcom/lib/defaultEvents";
import { getSafe } from "@calcom/lib/getSafe";
import { maybeGetBookingUidFromSeat } from "@calcom/lib/server/maybeGetBookingUidFromSeat";
import { UserRepository } from "@calcom/lib/server/repository/user";
import prisma, { bookingMinimalSelect } from "@calcom/prisma";
import { BookingStatus } from "@calcom/prisma/enums";
const querySchema = z.object({
uid: z.string(),
@@ -63,6 +61,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
allowReschedulingCancelledBookings: true,
team: {
select: {
id: true,
parentId: true,
slug: true,
},
@@ -98,7 +97,6 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
notFound: true,
} as const;
}
const eventType = booking.eventType ? booking.eventType : getDefaultEvent(dynamicEventSlugRef);
const userRepo = new UserRepository(prisma);
@@ -112,59 +110,36 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
profileEnrichedBookingUser: enrichedBookingUser,
});
const isForcedRescheduleForCancelledBooking = allowRescheduleForCancelledBooking;
// If booking is already REJECTED, we can't reschedule this booking. Take the user to the booking page which would show it's correct status and other details.
// If the booking is CANCELLED and allowRescheduleForCancelledBooking is false, we redirect the user to the original event link.
// A booking that has been rescheduled to a new booking will also have a status of CANCELLED
const isDisabledRescheduling = booking.eventType?.disableRescheduling;
// This comes from query param and thus is considered forced
const canBookThroughCancelledBookingRescheduleLink = booking.eventType?.allowReschedulingCancelledBookings;
const isNonRescheduleableBooking =
booking.status === BookingStatus.CANCELLED || booking.status === BookingStatus.REJECTED;
if (isDisabledRescheduling) {
return {
redirect: {
destination: `/booking/${uid}`,
permanent: false,
},
};
}
if (isNonRescheduleableBooking && !isForcedRescheduleForCancelledBooking) {
const canReschedule =
booking.status === BookingStatus.CANCELLED && canBookThroughCancelledBookingRescheduleLink;
return {
redirect: {
destination: canReschedule ? eventUrl : `/booking/${uid}`,
permanent: false,
},
};
}
if (!booking?.eventType && !booking?.dynamicEventSlugRef) {
// TODO: Show something in UI to let user know that this booking is not rescheduleable
return {
notFound: true,
} as {
notFound: true;
};
} as const;
}
const isBookingInPast = booking.endTime && new Date(booking.endTime) < new Date();
if (isBookingInPast && !eventType.allowReschedulingPastBookings) {
const destinationUrlSearchParams = new URLSearchParams();
const responses = bookingSeat ? getSafe<string>(bookingSeat.data, ["responses"]) : booking.responses;
const name = getFullName(getSafe<string | { firstName: string; lastName?: string }>(responses, ["name"]));
const email = getSafe<string>(responses, ["email"]);
// Check if reschedule should be prevented based on booking status and event type settings
const reschedulePreventionRedirectUrl = determineReschedulePreventionRedirect({
booking: {
uid,
status: booking.status,
endTime: booking.endTime,
responses: booking.responses,
eventType: {
disableRescheduling: !!eventType?.disableRescheduling,
allowReschedulingPastBookings: eventType.allowReschedulingPastBookings,
allowBookingFromCancelledBookingReschedule: !!eventType.allowReschedulingCancelledBookings,
teamId: eventType.team?.id ?? null,
},
},
eventUrl,
forceRescheduleForCancelledBooking: allowRescheduleForCancelledBooking,
bookingSeat,
});
if (name) destinationUrlSearchParams.set("name", name);
if (email) destinationUrlSearchParams.set("email", email);
const searchParamsString = destinationUrlSearchParams.toString();
if (reschedulePreventionRedirectUrl) {
return {
redirect: {
destination: searchParamsString ? `${eventUrl}?${searchParamsString}` : eventUrl,
destination: reschedulePreventionRedirectUrl,
permanent: false,
},
};
@@ -0,0 +1,412 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import * as constants from "@calcom/lib/constants";
import { BookingStatus } from "@calcom/prisma/client";
import type { JsonValue } from "@calcom/types/Json";
import {
determineReschedulePreventionRedirect,
type ReschedulePreventionRedirectInput,
type ReschedulePreventionRedirectResult,
} from "./determineReschedulePreventionRedirect";
// Mock the constants module
vi.mock("@calcom/lib/constants", async () => {
const actual = (await vi.importActual("@calcom/lib/constants")) as typeof import("@calcom/lib/constants");
return {
...actual,
ENV_PAST_BOOKING_RESCHEDULE_CHANGE_TEAM_IDS: undefined, // Default to undefined, will be overridden in tests
};
});
const createTestBooking = (overrides?: {
uid?: string;
status?: BookingStatus;
endTime?: Date | null;
responses?: JsonValue;
eventType?: {
disableRescheduling?: boolean | null;
allowReschedulingPastBookings?: boolean | null;
allowBookingFromCancelledBookingReschedule?: boolean | null;
teamId?: number | null;
} | null;
dynamicEventSlugRef?: string | null;
}) => ({
uid: overrides?.uid || "test-booking-uid",
status: overrides?.status || BookingStatus.ACCEPTED,
endTime: overrides?.endTime !== undefined ? overrides.endTime : futureDate(5),
responses: overrides?.responses || {
name: "John Doe",
email: "john.doe@example.com",
},
eventType:
overrides?.eventType !== undefined
? overrides.eventType
: // Default values from DB
{
disableRescheduling: false,
allowReschedulingPastBookings: false,
allowBookingFromCancelledBookingReschedule: false,
teamId: null,
},
dynamicEventSlugRef: overrides?.dynamicEventSlugRef !== undefined ? overrides.dynamicEventSlugRef : null,
});
const createReschedulePreventionRedirectInput = (overrides?: {
booking?: ReturnType<typeof createTestBooking>;
eventUrl?: string;
forceRescheduleForCancelledBooking?: boolean;
}): ReschedulePreventionRedirectInput => ({
booking: overrides?.booking || createTestBooking(),
eventUrl: overrides?.eventUrl || "https://example.com/event",
forceRescheduleForCancelledBooking: overrides?.forceRescheduleForCancelledBooking || false,
});
const daysAgo = (days: number) => new Date(Date.now() - days * 24 * 60 * 60 * 1000);
const futureDate = (days: number) => new Date(Date.now() + days * 24 * 60 * 60 * 1000);
const expectRedirectToBookingDetailsPage = (
result: ReschedulePreventionRedirectResult,
bookingUid: string
) => {
expect(result).toBe(`/booking/${bookingUid}`);
};
const expectRedirectToEventBookingUrl = (result: ReschedulePreventionRedirectResult, eventUrl: string) => {
expect(result).toBe(eventUrl);
};
const expectRedirectToEventBookingPageWithParams = ({
result,
eventUrl,
params,
}: {
result: ReschedulePreventionRedirectResult;
eventUrl: string;
params: Record<string, string>;
}) => {
console.log("expectRedirectToEventBookingPageWithParams", { result, eventUrl, params });
if (!result) {
throw new Error("We expected a redirect result");
}
const actualRedirectUrlObject = new URL(result);
Object.entries(params).forEach(([key, value]) => {
expect(actualRedirectUrlObject.searchParams.get(key)).toBe(value);
});
expect(`${actualRedirectUrlObject.origin + actualRedirectUrlObject.pathname}`).toBe(eventUrl);
};
const expectToNotPreventReschedule = (result: ReschedulePreventionRedirectResult) => {
expect(result).toBeNull();
};
describe("determineReschedulePreventionRedirect", () => {
beforeEach(() => {
vi.useFakeTimers();
vi.setSystemTime(new Date("2024-01-15T10:00:00Z"));
// Reset mocked constant to default before each test
vi.mocked(constants).ENV_PAST_BOOKING_RESCHEDULE_CHANGE_TEAM_IDS = undefined;
});
afterEach(() => {
vi.useRealTimers();
vi.clearAllMocks();
});
describe("when booking status is accepted and booking is in future", () => {
it("should not prevent reschedule if disableRescheduling is false", () => {
const testData = {
booking: createTestBooking({
status: BookingStatus.ACCEPTED,
endTime: futureDate(5),
}),
};
const input = createReschedulePreventionRedirectInput(testData);
const result = determineReschedulePreventionRedirect(input);
expectToNotPreventReschedule(result);
});
it("should redirect to booking details/status page when rescheduling is disabled(disableRescheduling is true)", () => {
const input = createReschedulePreventionRedirectInput({
booking: createTestBooking({
status: BookingStatus.ACCEPTED,
endTime: futureDate(5),
eventType: {
disableRescheduling: true,
},
}),
});
const result = determineReschedulePreventionRedirect(input);
expectRedirectToBookingDetailsPage(result, input.booking.uid);
});
});
describe("when booking status is cancelled", () => {
it("should redirect to booking details/status page by default", () => {
const input = createReschedulePreventionRedirectInput({
booking: createTestBooking({
status: BookingStatus.CANCELLED,
}),
});
const result = determineReschedulePreventionRedirect(input);
expectRedirectToBookingDetailsPage(result, input.booking.uid);
});
it("should redirect to new booking page when `allowBookingFromCancelledBookingReschedule` is true", () => {
const testData = {
booking: createTestBooking({
status: BookingStatus.CANCELLED,
eventType: {
allowBookingFromCancelledBookingReschedule: true,
},
}),
eventUrl: "https://example.com/event",
};
const input = createReschedulePreventionRedirectInput(testData);
const result = determineReschedulePreventionRedirect(input);
expectRedirectToEventBookingUrl(result, testData.eventUrl);
});
it("should not prevent reschedule when `forceRescheduleForCancelledBooking` is true regardless of `allowBookingFromCancelledBookingReschedule`", () => {
const inputWhenallowBookingFromCancelledBookingRescheduleIsFalse =
createReschedulePreventionRedirectInput({
booking: createTestBooking({
status: BookingStatus.CANCELLED,
eventType: {
allowBookingFromCancelledBookingReschedule: false,
},
}),
forceRescheduleForCancelledBooking: true,
});
const resultWhenallowBookingFromCancelledBookingRescheduleIsFalse =
determineReschedulePreventionRedirect(inputWhenallowBookingFromCancelledBookingRescheduleIsFalse);
expectToNotPreventReschedule(resultWhenallowBookingFromCancelledBookingRescheduleIsFalse);
const inputWhenallowBookingFromCancelledBookingRescheduleIsTrue =
createReschedulePreventionRedirectInput({
booking: createTestBooking({
status: BookingStatus.CANCELLED,
eventType: {
allowBookingFromCancelledBookingReschedule: true,
},
}),
forceRescheduleForCancelledBooking: true,
});
const resultWhenallowBookingFromCancelledBookingRescheduleIsTrue =
determineReschedulePreventionRedirect(inputWhenallowBookingFromCancelledBookingRescheduleIsTrue);
expectToNotPreventReschedule(resultWhenallowBookingFromCancelledBookingRescheduleIsTrue);
});
});
describe("when booking status is rejected", () => {
it("should redirect to booking details/status page by default", () => {
const input = createReschedulePreventionRedirectInput({
booking: createTestBooking({
status: BookingStatus.REJECTED,
}),
});
const result = determineReschedulePreventionRedirect(input);
expectRedirectToBookingDetailsPage(result, input.booking.uid);
});
it("should redirect to booking details/status page even when `allowBookingFromCancelledBookingReschedule` is true as that config doesn't apply to rejected bookings", () => {
const input = createReschedulePreventionRedirectInput({
booking: createTestBooking({
status: BookingStatus.REJECTED,
eventType: {
allowBookingFromCancelledBookingReschedule: true,
},
}),
});
const result = determineReschedulePreventionRedirect(input);
expectRedirectToBookingDetailsPage(result, input.booking.uid);
});
it("Current Behavior: Current behaviour is to not prevent reschedule. But EXPECTED(should redirect to booking details page even when `forceRescheduleForCancelledBooking` as that config doesn't apply to rejected bookings). ", () => {
const inputWhenallowBookingFromCancelledBookingRescheduleIsFalse =
createReschedulePreventionRedirectInput({
booking: createTestBooking({
status: BookingStatus.REJECTED,
eventType: {
allowBookingFromCancelledBookingReschedule: false,
},
}),
forceRescheduleForCancelledBooking: true,
});
const resultWhenallowBookingFromCancelledBookingRescheduleIsFalse =
determineReschedulePreventionRedirect(inputWhenallowBookingFromCancelledBookingRescheduleIsFalse);
// expectRedirectToBookingDetailsPage(
// resultWhenallowBookingFromCancelledBookingRescheduleIsFalse,
// inputWhenallowBookingFromCancelledBookingRescheduleIsFalse.booking.uid
// );
expectToNotPreventReschedule(resultWhenallowBookingFromCancelledBookingRescheduleIsFalse);
const inputWhenallowBookingFromCancelledBookingRescheduleIsTrue =
createReschedulePreventionRedirectInput({
booking: createTestBooking({
status: BookingStatus.REJECTED,
eventType: {
allowBookingFromCancelledBookingReschedule: true,
},
}),
forceRescheduleForCancelledBooking: true,
});
const resultWhenallowBookingFromCancelledBookingRescheduleIsTrue =
determineReschedulePreventionRedirect(inputWhenallowBookingFromCancelledBookingRescheduleIsTrue);
// expectRedirectToBookingDetailsPage(
// resultWhenallowBookingFromCancelledBookingRescheduleIsTrue,
// inputWhenallowBookingFromCancelledBookingRescheduleIsTrue.booking.uid
// );
expectToNotPreventReschedule(resultWhenallowBookingFromCancelledBookingRescheduleIsTrue);
});
});
describe("when booking is in the past - Default behaviour(without ENV_PAST_BOOKING_RESCHEDULE_CHANGE_TEAM_IDS)", () => {
it("should redirect to new booking page with prefilled params when allowReschedulingPastBookings is false", () => {
const input = createReschedulePreventionRedirectInput({
booking: createTestBooking({
endTime: daysAgo(5),
responses: {
name: "John Doe",
email: "john.doe@example.com",
},
eventType: {
allowReschedulingPastBookings: false,
},
}),
});
const result = determineReschedulePreventionRedirect(input);
expectRedirectToEventBookingPageWithParams({
result,
eventUrl: "https://example.com/event",
params: {
name: "John Doe",
email: "john.doe@example.com",
},
});
});
it("should not prevent reschedule when allowReschedulingPastBookings is true", () => {
const input = createReschedulePreventionRedirectInput({
booking: createTestBooking({
endTime: daysAgo(5),
eventType: {
disableRescheduling: false,
allowReschedulingPastBookings: true,
allowBookingFromCancelledBookingReschedule: false,
},
}),
});
const result = determineReschedulePreventionRedirect(input);
expectToNotPreventReschedule(result);
});
});
describe("when booking is in the past - New behaviour(based on ENV_PAST_BOOKING_RESCHEDULE_CHANGE_TEAM_IDS)", () => {
beforeEach(() => {
vi.mocked(constants).ENV_PAST_BOOKING_RESCHEDULE_CHANGE_TEAM_IDS = "123,456,789";
});
it("should redirect to booking status page instead of event URL when the event's teamId is in the environment variable", () => {
const input = createReschedulePreventionRedirectInput({
booking: createTestBooking({
uid: "team-booking-uid",
status: BookingStatus.ACCEPTED,
endTime: daysAgo(3),
eventType: {
teamId: 456, // This team ID is in the environment variable
},
}),
});
const result = determineReschedulePreventionRedirect(input);
expectRedirectToBookingDetailsPage(result, input.booking.uid);
});
it("should redirect to event URL when the event's teamId is not in the environment variable", () => {
const input = createReschedulePreventionRedirectInput({
booking: createTestBooking({
uid: "non-team-booking-uid",
status: BookingStatus.ACCEPTED,
endTime: daysAgo(3),
eventType: {
disableRescheduling: false,
allowReschedulingPastBookings: false,
allowBookingFromCancelledBookingReschedule: false,
teamId: 999, // This team ID is NOT in the environment variable
},
}),
});
const result = determineReschedulePreventionRedirect(input);
// Should redirect to eventUrl with name/email params (fallback behavior)
expectRedirectToEventBookingPageWithParams({
result,
eventUrl: "https://example.com/event",
params: {
name: "John Doe",
email: "john.doe@example.com",
},
});
});
it("should redirect to event URL when booking has no team (individual event)", () => {
const input = createReschedulePreventionRedirectInput({
booking: createTestBooking({
uid: "no-team-booking-uid",
status: BookingStatus.ACCEPTED,
endTime: daysAgo(3),
eventType: {
disableRescheduling: false,
allowReschedulingPastBookings: false,
allowBookingFromCancelledBookingReschedule: false,
teamId: null, // No team ID
},
}),
});
const result = determineReschedulePreventionRedirect(input);
// Should redirect to eventUrl with name/email params (fallback behavior)
expectRedirectToEventBookingPageWithParams({
result,
eventUrl: "https://example.com/event",
params: {
name: "John Doe",
email: "john.doe@example.com",
},
});
});
it("should not prevent reschedule when allowReschedulingPastBookings is true even if the teamId is in the environment variable", () => {
const input = createReschedulePreventionRedirectInput({
booking: createTestBooking({
uid: "allowed-past-reschedule-uid",
status: BookingStatus.ACCEPTED,
endTime: daysAgo(3),
eventType: {
allowReschedulingPastBookings: true, // Past reschedule explicitly allowed
teamId: 456, // Team ID is in environment variable
},
}),
});
const result = determineReschedulePreventionRedirect(input);
expectToNotPreventReschedule(result);
});
});
});
@@ -0,0 +1,111 @@
import { URLSearchParams } from "url";
import { getFullName } from "@calcom/features/form-builder/utils";
import { ENV_PAST_BOOKING_RESCHEDULE_CHANGE_TEAM_IDS } from "@calcom/lib/constants";
import { getSafe } from "@calcom/lib/getSafe";
import { BookingStatus } from "@calcom/prisma/enums";
import type { JsonValue } from "@calcom/types/Json";
export type ReschedulePreventionRedirectInput = {
booking: {
uid: string;
status: BookingStatus;
endTime: Date | null;
responses?: JsonValue;
eventType: {
disableRescheduling: boolean;
allowReschedulingPastBookings: boolean;
allowBookingFromCancelledBookingReschedule: boolean;
teamId: number | null;
};
};
eventUrl: string;
forceRescheduleForCancelledBooking?: boolean;
bookingSeat?: {
data: JsonValue;
booking: {
uid: string;
id: number;
};
};
};
export type ReschedulePreventionRedirectResult = string | null;
/**
*
* Parses the PAST_BOOKING_RESCHEDULE_NO_BOOKING_BEHAVIOUR environment variable and checks if the given team ID has the changed behaviour for past booking reschedule enabled
*
* The behaviour is that it does not allow allowing booking through the reschedule link of a past booking by default. If allowReschedulingPastBookings is true, then this behaviour isn't applicable
*/
function isPastBookingRescheduleBehaviourToPreventBooking(teamId: number | null | undefined): boolean {
if (!teamId) {
return false;
}
if (!ENV_PAST_BOOKING_RESCHEDULE_CHANGE_TEAM_IDS) {
return false;
}
const configuredTeamIds = ENV_PAST_BOOKING_RESCHEDULE_CHANGE_TEAM_IDS.split(",")
.map((id) => id.trim())
.filter((id) => id !== "")
.map((id) => parseInt(id, 10))
.filter((id) => !isNaN(id));
return configuredTeamIds.includes(teamId);
}
/**
* Determines the appropriate redirect URL for a reschedule request based on booking status and event type settings
* Returns the redirect URL string if a redirect is needed, null if reschedule should proceed normally
*/
export function determineReschedulePreventionRedirect(
input: ReschedulePreventionRedirectInput
): ReschedulePreventionRedirectResult {
const { booking, eventUrl, forceRescheduleForCancelledBooking, bookingSeat } = input;
const isDisabledRescheduling = booking.eventType.disableRescheduling;
if (isDisabledRescheduling) {
return `/booking/${booking.uid}`;
}
const isNonRescheduleableBooking =
booking.status === BookingStatus.CANCELLED || booking.status === BookingStatus.REJECTED;
const isForcedRescheduleForCancelledBooking = forceRescheduleForCancelledBooking;
if (isNonRescheduleableBooking && !isForcedRescheduleForCancelledBooking) {
const canBookThroughCancelledBookingRescheduleLink =
booking.eventType.allowBookingFromCancelledBookingReschedule;
const allowedToBeBookedThroughCancelledBookingRescheduleLink =
booking.status === BookingStatus.CANCELLED && canBookThroughCancelledBookingRescheduleLink;
return allowedToBeBookedThroughCancelledBookingRescheduleLink ? eventUrl : `/booking/${booking.uid}`;
}
const isBookingInPast = booking.endTime && new Date(booking.endTime) < new Date();
if (isBookingInPast && !booking.eventType.allowReschedulingPastBookings) {
// Check if this team should apply the redirect behavior for past bookings
const isNewPastBookingRescheduleBehaviour = isPastBookingRescheduleBehaviourToPreventBooking(
booking.eventType.teamId
);
if (isNewPastBookingRescheduleBehaviour) {
return `/booking/${booking.uid}`;
}
const destinationUrlSearchParams = new URLSearchParams();
const responses = bookingSeat ? getSafe<string>(bookingSeat.data, ["responses"]) : booking.responses;
const name = getFullName(getSafe<string | { firstName: string; lastName?: string }>(responses, ["name"]));
const email = getSafe<string>(responses, ["email"]);
if (name) destinationUrlSearchParams.set("name", name);
if (email) destinationUrlSearchParams.set("email", email);
const searchParamsString = destinationUrlSearchParams.toString();
return searchParamsString ? `${eventUrl}?${searchParamsString}` : eventUrl;
}
// Allow reschedule to proceed - default behaviour
return null;
}
+5
View File
@@ -249,3 +249,8 @@ export const RETELL_AI_TEST_EVENT_TYPE_MAP = (() => {
return null;
}
})();
// Environment variable for configuring past booking reschedule behavior per team. A comma separated list of team IDs(e.g. '1,2,3')
/* This is an internal environment variable and is not meant to be used by the self-hosters. It is planned to be removed later by either having it as an option in Event Type or by some other customer configurable approaches*/
export const ENV_PAST_BOOKING_RESCHEDULE_CHANGE_TEAM_IDS =
process.env._CAL_INTERNAL_PAST_BOOKING_RESCHEDULE_CHANGE_TEAM_IDS;
+1
View File
@@ -92,6 +92,7 @@ const commons = {
disableRescheduling: false,
onlyShowFirstAvailableSlot: false,
allowReschedulingPastBookings: false,
allowReschedulingCancelledBookings: false,
hideOrganizerEmail: false,
showOptimizedSlots: false,
id: 0,
+2 -1
View File
@@ -279,7 +279,8 @@
"ATOMS_E2E_APPLE_ID",
"ATOMS_E2E_APPLE_CONNECT_APP_SPECIFIC_PASSCODE",
"INTERCOM_API_TOKEN",
"NEXT_PUBLIC_INTERCOM_APP_ID"
"NEXT_PUBLIC_INTERCOM_APP_ID",
"_CAL_INTERNAL_PAST_BOOKING_RESCHEDULE_CHANGE_TEAM_IDS"
],
"tasks": {
"@calcom/prisma#build": {