diff --git a/apps/api/v2/src/modules/slots/slots-2024-09-04/controllers/e2e/reschedule-uid-slots.controller.e2e-spec.ts b/apps/api/v2/src/modules/slots/slots-2024-09-04/controllers/e2e/reschedule-uid-slots.controller.e2e-spec.ts new file mode 100644 index 0000000000..d10a4a8506 --- /dev/null +++ b/apps/api/v2/src/modules/slots/slots-2024-09-04/controllers/e2e/reschedule-uid-slots.controller.e2e-spec.ts @@ -0,0 +1,343 @@ +import { bootstrap } from "@/app"; +import { AppModule } from "@/app.module"; +import { SchedulesModule_2024_06_11 } from "@/ee/schedules/schedules_2024_06_11/schedules.module"; +import { SchedulesService_2024_06_11 } from "@/ee/schedules/schedules_2024_06_11/services/schedules.service"; +import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard"; +import { PrismaModule } from "@/modules/prisma/prisma.module"; +import { + expectedSlotsUTC, + expectedSlotsUTCRange, +} from "@/modules/slots/slots-2024-09-04/controllers/e2e/expected-slots"; +import { GetSlotsOutput_2024_09_04 } from "@/modules/slots/slots-2024-09-04/outputs/get-slots.output"; +import { SlotsModule_2024_09_04 } from "@/modules/slots/slots-2024-09-04/slots.module"; +import { TokensModule } from "@/modules/tokens/tokens.module"; +import { UsersModule } from "@/modules/users/users.module"; +import { INestApplication } from "@nestjs/common"; +import { NestExpressApplication } from "@nestjs/platform-express"; +import { Test } from "@nestjs/testing"; +import { User, Booking } from "@prisma/client"; +import * as request from "supertest"; +import { BookingsRepositoryFixture } from "test/fixtures/repository/bookings.repository.fixture"; +import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture"; +import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture"; +import { randomString } from "test/utils/randomString"; + +import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_09_04 } from "@calcom/platform-constants"; +import { CreateScheduleInput_2024_06_11 } from "@calcom/platform-types"; + +describe("Slots 2024-09-04 Endpoints", () => { + describe("rescheduleUid functionality", () => { + let app: INestApplication; + + let userRepositoryFixture: UserRepositoryFixture; + let schedulesService: SchedulesService_2024_06_11; + let eventTypesRepositoryFixture: EventTypesRepositoryFixture; + let bookingsRepositoryFixture: BookingsRepositoryFixture; + + const userEmail = `slots-reschedule-uid-${randomString()}@example.com`; + const bookedStartTime = "2050-09-05T11:00:00.000Z"; + let user: User; + let eventTypeId: number; + let eventTypeSlug: string; + let existingBooking: Booking; + + beforeAll(async () => { + const moduleRef = await Test.createTestingModule({ + imports: [ + AppModule, + PrismaModule, + UsersModule, + TokensModule, + SchedulesModule_2024_06_11, + SlotsModule_2024_09_04, + ], + }) + .overrideGuard(PermissionsGuard) + .useValue({ + canActivate: () => true, + }) + .compile(); + + userRepositoryFixture = new UserRepositoryFixture(moduleRef); + schedulesService = moduleRef.get(SchedulesService_2024_06_11); + eventTypesRepositoryFixture = new EventTypesRepositoryFixture(moduleRef); + bookingsRepositoryFixture = new BookingsRepositoryFixture(moduleRef); + + user = await userRepositoryFixture.create({ + email: userEmail, + name: userEmail, + username: userEmail, + }); + + const userSchedule: CreateScheduleInput_2024_06_11 = { + name: `reschedule-uid-schedule-${randomString()}`, + timeZone: "Europe/Rome", + isDefault: true, + }; + // note(Lauris): this creates default schedule monday to friday from 9AM to 5PM in Europe/Rome timezone + await schedulesService.createUserSchedule(user.id, userSchedule); + + const event = await eventTypesRepositoryFixture.create( + { + title: "reschedule test meeting", + slug: `reschedule-uid-event-type-${randomString()}`, + length: 60, + }, + user.id + ); + eventTypeId = event.id; + eventTypeSlug = event.slug; + + // Create an existing booking that will be used for reschedule testing + existingBooking = await bookingsRepositoryFixture.create({ + uid: `reschedule-booking-uid-${randomString()}`, + title: "existing booking for reschedule", + startTime: bookedStartTime, + endTime: "2050-09-05T12:00:00.000Z", + eventType: { + connect: { + id: eventTypeId, + }, + }, + metadata: {}, + responses: { + name: "reschedule tester", + email: "reschedule@example.com", + guests: [], + }, + user: { + connect: { + id: user.id, + }, + }, + }); + + app = moduleRef.createNestApplication(); + bootstrap(app as NestExpressApplication); + + await app.init(); + }); + + describe("bookingUidToReschedule parameter validation", () => { + it("should accept bookingUidToReschedule as optional string parameter", async () => { + const response = await request(app.getHttpServer()) + .get( + `/v2/slots?eventTypeId=${eventTypeId}&start=2050-09-05&end=2050-09-09&bookingUidToReschedule=${existingBooking.uid}` + ) + .set(CAL_API_VERSION_HEADER, VERSION_2024_09_04) + .expect(200); + + const responseBody: GetSlotsOutput_2024_09_04 = response.body; + expect(responseBody.status).toEqual(SUCCESS_STATUS); + expect(responseBody.data).toBeDefined(); + }); + + it("should accept numeric bookingUidToReschedule parameter and convert to string", async () => { + const response = await request(app.getHttpServer()) + .get( + `/v2/slots?eventTypeId=${eventTypeId}&start=2050-09-05&end=2050-09-09&bookingUidToReschedule=12345` + ) + .set(CAL_API_VERSION_HEADER, VERSION_2024_09_04) + .expect(200); + + // Note: The API accepts numeric bookingUidToReschedule and converts it to string + // This is expected behavior for query parameters + const responseBody: GetSlotsOutput_2024_09_04 = response.body; + expect(responseBody.status).toEqual(SUCCESS_STATUS); + }); + + it("should handle empty bookingUidToReschedule parameter gracefully", async () => { + const response = await request(app.getHttpServer()) + .get(`/v2/slots?eventTypeId=${eventTypeId}&start=2050-09-05&end=2050-09-09&bookingUidToReschedule=`) + .set(CAL_API_VERSION_HEADER, VERSION_2024_09_04) + .expect(200); + + const responseBody: GetSlotsOutput_2024_09_04 = response.body; + expect(responseBody.status).toEqual(SUCCESS_STATUS); + }); + }); + + describe("bookingUidToReschedule slot availability behavior", () => { + it("should exclude booked slot when bookingUidToReschedule is not provided", async () => { + const response = await request(app.getHttpServer()) + .get(`/v2/slots?eventTypeId=${eventTypeId}&start=2050-09-05&end=2050-09-09`) + .set(CAL_API_VERSION_HEADER, VERSION_2024_09_04) + .expect(200); + + const responseBody: GetSlotsOutput_2024_09_04 = response.body; + expect(responseBody.status).toEqual(SUCCESS_STATUS); + const slots = responseBody.data; + + expect(slots).toBeDefined(); + const days = Object.keys(slots); + expect(days.length).toEqual(5); + + // Check that the booked slot time is NOT available + const slotsForBookedDay = slots["2050-09-05"]; + expect(slotsForBookedDay).toBeDefined(); + + // Verify the booked slot is excluded + const bookedSlotExists = slotsForBookedDay.some((slot: any) => slot.start === bookedStartTime); + expect(bookedSlotExists).toBe(false); + + // Verify we still have slots for that day (just not the booked one) + expect(slotsForBookedDay.length).toBeGreaterThan(0); + }); + + it("should include booked slot when matching bookingUidToReschedule is provided", async () => { + const response = await request(app.getHttpServer()) + .get( + `/v2/slots?eventTypeId=${eventTypeId}&start=2050-09-05&end=2050-09-09&bookingUidToReschedule=${existingBooking.uid}` + ) + .set(CAL_API_VERSION_HEADER, VERSION_2024_09_04) + .expect(200); + + const responseBody: GetSlotsOutput_2024_09_04 = response.body; + expect(responseBody.status).toEqual(SUCCESS_STATUS); + const slots = responseBody.data; + + expect(slots).toBeDefined(); + const days = Object.keys(slots); + expect(days.length).toEqual(5); + + // Check that the booked slot time IS available when rescheduling + const slotsForBookedDay = slots["2050-09-05"]; + expect(slotsForBookedDay).toBeDefined(); + + // Verify the booked slot is now included due to bookingUidToReschedule + const bookedSlotExists = slotsForBookedDay.some((slot: any) => slot.start === bookedStartTime); + expect(bookedSlotExists).toBe(true); + }); + + it("should work with non-existent bookingUidToReschedule without errors", async () => { + const nonExistentUid = `non-existent-${randomString()}`; + const response = await request(app.getHttpServer()) + .get( + `/v2/slots?eventTypeId=${eventTypeId}&start=2050-09-05&end=2050-09-09&bookingUidToReschedule=${nonExistentUid}` + ) + .set(CAL_API_VERSION_HEADER, VERSION_2024_09_04) + .expect(200); + + const responseBody: GetSlotsOutput_2024_09_04 = response.body; + expect(responseBody.status).toEqual(SUCCESS_STATUS); + const slots = responseBody.data; + + expect(slots).toBeDefined(); + + // Should behave like normal slots query when bookingUidToReschedule doesn't match any booking + const slotsForBookedDay = slots["2050-09-05"]; + expect(slotsForBookedDay).toBeDefined(); + + // Verify the booked slot is excluded (same as without bookingUidToReschedule) + const bookedSlotExists = slotsForBookedDay.some((slot: any) => slot.start === bookedStartTime); + expect(bookedSlotExists).toBe(false); + }); + }); + + describe("bookingUidToReschedule with different query types", () => { + it("should work with bookingUidToReschedule using event type slug query", async () => { + const response = await request(app.getHttpServer()) + .get( + `/v2/slots?eventTypeSlug=${eventTypeSlug}&username=${user.username}&start=2050-09-05&end=2050-09-09&bookingUidToReschedule=${existingBooking.uid}` + ) + .set(CAL_API_VERSION_HEADER, VERSION_2024_09_04) + .expect(200); + + const responseBody: GetSlotsOutput_2024_09_04 = response.body; + expect(responseBody.status).toEqual(SUCCESS_STATUS); + const slots = responseBody.data; + + expect(slots).toBeDefined(); + expect(slots).toEqual(expectedSlotsUTC); + }); + + it("should work with bookingUidToReschedule in range format", async () => { + const response = await request(app.getHttpServer()) + .get( + `/v2/slots?eventTypeId=${eventTypeId}&start=2050-09-05&end=2050-09-09&format=range&bookingUidToReschedule=${existingBooking.uid}` + ) + .set(CAL_API_VERSION_HEADER, VERSION_2024_09_04) + .expect(200); + + const responseBody: GetSlotsOutput_2024_09_04 = response.body; + expect(responseBody.status).toEqual(SUCCESS_STATUS); + const slots = responseBody.data; + + expect(slots).toBeDefined(); + expect(slots).toEqual(expectedSlotsUTCRange); + + // Verify range format structure + const daySlots = slots["2050-09-05"]; + if (daySlots && daySlots.length > 0) { + expect(daySlots[0]).toHaveProperty("start"); + expect(daySlots[0]).toHaveProperty("end"); + } + }); + + it("should work with bookingUidToReschedule and timezone parameter", async () => { + const response = await request(app.getHttpServer()) + .get( + `/v2/slots?eventTypeId=${eventTypeId}&start=2050-09-05&end=2050-09-09&timeZone=Europe/Rome&bookingUidToReschedule=${existingBooking.uid}` + ) + .set(CAL_API_VERSION_HEADER, VERSION_2024_09_04) + .expect(200); + + const responseBody: GetSlotsOutput_2024_09_04 = response.body; + expect(responseBody.status).toEqual(SUCCESS_STATUS); + const slots = responseBody.data; + + expect(slots).toBeDefined(); + const days = Object.keys(slots); + expect(days.length).toEqual(5); + + // Verify timezone conversion works with bookingUidToReschedule + const daySlots = slots["2050-09-05"]; + if (daySlots && daySlots.length > 0) { + // Should contain timezone info (+02:00 for Europe/Rome) + expect(daySlots[0].start).toContain("+02:00"); + + // Verify rescheduleUid functionality: the booked slot should be available + const bookedSlotTimeRome = "2050-09-05T13:00:00.000+02:00"; // 11:00 UTC = 13:00 Rome + const bookedSlotExists = daySlots.some((slot: any) => slot.start === bookedSlotTimeRome); + expect(bookedSlotExists).toBe(true); + } + }); + }); + + describe("bookingUidToReschedule edge cases", () => { + it("should handle bookingUidToReschedule with special characters", async () => { + const specialUid = `special-uid-${randomString()}-with-dashes`; + const response = await request(app.getHttpServer()) + .get( + `/v2/slots?eventTypeId=${eventTypeId}&start=2050-09-05&end=2050-09-09&bookingUidToReschedule=${specialUid}` + ) + .set(CAL_API_VERSION_HEADER, VERSION_2024_09_04) + .expect(200); + + const responseBody: GetSlotsOutput_2024_09_04 = response.body; + expect(responseBody.status).toEqual(SUCCESS_STATUS); + }); + + it("should handle very long bookingUidToReschedule", async () => { + const longUid = `very-long-uid-${randomString()}-${"x".repeat(100)}`; + const response = await request(app.getHttpServer()) + .get( + `/v2/slots?eventTypeId=${eventTypeId}&start=2050-09-05&end=2050-09-09&bookingUidToReschedule=${longUid}` + ) + .set(CAL_API_VERSION_HEADER, VERSION_2024_09_04) + .expect(200); + + const responseBody: GetSlotsOutput_2024_09_04 = response.body; + expect(responseBody.status).toEqual(SUCCESS_STATUS); + }); + }); + + afterAll(async () => { + // Clean up test data + await bookingsRepositoryFixture.deleteById(existingBooking.id); + await bookingsRepositoryFixture.deleteAllBookings(user.id, user.email); + await userRepositoryFixture.deleteByEmail(userEmail); + await app.close(); + }); + }); +}); diff --git a/apps/api/v2/src/modules/slots/slots-2024-09-04/controllers/slots.controller.ts b/apps/api/v2/src/modules/slots/slots-2024-09-04/controllers/slots.controller.ts index a60cac778a..33fbb200ae 100644 --- a/apps/api/v2/src/modules/slots/slots-2024-09-04/controllers/slots.controller.ts +++ b/apps/api/v2/src/modules/slots/slots-2024-09-04/controllers/slots.controller.ts @@ -83,6 +83,7 @@ export class SlotsController_2024_09_04 { - timeZone: Time zone in which the available slots should be returned. Defaults to UTC. - duration: Only use for event types that allow multiple durations or for dynamic event types. If not passed for multiple duration event types defaults to default duration. For dynamic event types defaults to 30 aka each returned slot is 30 minutes long. So duration=60 means that returned slots will be each 60 minutes long. - format: Format of the slots. By default return is an object where each key is date and value is array of slots as string. If you want to get start and end of each slot use "range" as value. + - bookingUidToReschedule: When rescheduling an existing booking, provide the booking's unique identifier to exclude its time slot from busy time calculations. This ensures the original booking time appears as available for rescheduling. `, }) @ApiQuery({ @@ -173,6 +174,13 @@ export class SlotsController_2024_09_04 { 2024-08-13 (will have hours 00:00:00 aka at very beginning of the date) or you can specify hours manually like 2024-08-13T09:00:00Z.`, example: "2050-09-05", }) + @ApiQuery({ + name: "bookingUidToReschedule", + required: false, + description: + "The unique identifier of the booking being rescheduled. When provided will ensure that the original booking time appears within the returned available slots when rescheduling.", + example: "abc123def456", + }) @DocsResponse({ status: 200, description: `A map of available slots indexed by date, where each date is associated with an array of time slots. If format=range is specified, each slot will be an object with start and end properties denoting start and end of the slot. diff --git a/apps/api/v2/src/modules/slots/slots-2024-09-04/services/slots-input.service.ts b/apps/api/v2/src/modules/slots/slots-2024-09-04/services/slots-input.service.ts index 3f3a0be0d0..ec4e7a72a9 100644 --- a/apps/api/v2/src/modules/slots/slots-2024-09-04/services/slots-input.service.ts +++ b/apps/api/v2/src/modules/slots/slots-2024-09-04/services/slots-input.service.ts @@ -46,6 +46,7 @@ export class SlotsInputService_2024_09_04 { const usernameList = "usernames" in query ? query.usernames : []; const timeZone = query.timeZone; const orgSlug = "organizationSlug" in query ? query.organizationSlug : null; + const rescheduleUid = query.bookingUidToReschedule || null; return { isTeamEvent, @@ -57,6 +58,7 @@ export class SlotsInputService_2024_09_04 { usernameList, timeZone, orgSlug, + rescheduleUid, }; } diff --git a/apps/api/v2/swagger/documentation.json b/apps/api/v2/swagger/documentation.json index 3d35b87293..20906bda66 100644 --- a/apps/api/v2/swagger/documentation.json +++ b/apps/api/v2/swagger/documentation.json @@ -11122,7 +11122,7 @@ "get": { "operationId": "SlotsController_2024_09_04_getAvailableSlots", "summary": "Find out when is an event type ready to be booked.", - "description": "\n There are 4 ways to get available slots for event type of an individual user:\n\n 1. By event type id. Event type id can be of user and team event types. Example '/v2/slots?eventTypeId=10&start=2050-09-05&end=2050-09-06&timeZone=Europe/Rome'\n\n 2. By event type slug + username. Example '/v2/slots?eventTypeSlug=intro&username=bob&start=2050-09-05&end=2050-09-06'\n\n 3. By event type slug + username + organization slug when searching within an organization. Example '/v2/slots?organizationSlug=org-slug&eventTypeSlug=intro&username=bob&start=2050-09-05&end=2050-09-06'\n\n 4. By usernames only (used for dynamic event type - there is no specific event but you want to know when 2 or more people are available). Example '/v2/slots?usernames=alice,bob&username=bob&organizationSlug=org-slug&start=2050-09-05&end=2050-09-06'. As you see you also need to provide the slug of the organization to which each user in the 'usernames' array belongs.\n\n And 3 ways to get available slots for team event type:\n\n 1. By team event type id. Example '/v2/slots?eventTypeId=10&start=2050-09-05&end=2050-09-06&timeZone=Europe/Rome'\n\n 2. By team event type slug + team slug. Example '/v2/slots?eventTypeSlug=intro&teamSlug=team-slug&start=2050-09-05&end=2050-09-06'\n\n 3. By team event type slug + team slug + organization slug when searching within an organization. Example '/v2/slots?organizationSlug=org-slug&eventTypeSlug=intro&teamSlug=team-slug&start=2050-09-05&end=2050-09-06'\n\n All of them require \"start\" and \"end\" query parameters which define the time range for which available slots should be checked.\n Optional parameters are:\n - timeZone: Time zone in which the available slots should be returned. Defaults to UTC.\n - duration: Only use for event types that allow multiple durations or for dynamic event types. If not passed for multiple duration event types defaults to default duration. For dynamic event types defaults to 30 aka each returned slot is 30 minutes long. So duration=60 means that returned slots will be each 60 minutes long.\n - format: Format of the slots. By default return is an object where each key is date and value is array of slots as string. If you want to get start and end of each slot use \"range\" as value.\n ", + "description": "\n There are 4 ways to get available slots for event type of an individual user:\n\n 1. By event type id. Event type id can be of user and team event types. Example '/v2/slots?eventTypeId=10&start=2050-09-05&end=2050-09-06&timeZone=Europe/Rome'\n\n 2. By event type slug + username. Example '/v2/slots?eventTypeSlug=intro&username=bob&start=2050-09-05&end=2050-09-06'\n\n 3. By event type slug + username + organization slug when searching within an organization. Example '/v2/slots?organizationSlug=org-slug&eventTypeSlug=intro&username=bob&start=2050-09-05&end=2050-09-06'\n\n 4. By usernames only (used for dynamic event type - there is no specific event but you want to know when 2 or more people are available). Example '/v2/slots?usernames=alice,bob&username=bob&organizationSlug=org-slug&start=2050-09-05&end=2050-09-06'. As you see you also need to provide the slug of the organization to which each user in the 'usernames' array belongs.\n\n And 3 ways to get available slots for team event type:\n\n 1. By team event type id. Example '/v2/slots?eventTypeId=10&start=2050-09-05&end=2050-09-06&timeZone=Europe/Rome'\n\n 2. By team event type slug + team slug. Example '/v2/slots?eventTypeSlug=intro&teamSlug=team-slug&start=2050-09-05&end=2050-09-06'\n\n 3. By team event type slug + team slug + organization slug when searching within an organization. Example '/v2/slots?organizationSlug=org-slug&eventTypeSlug=intro&teamSlug=team-slug&start=2050-09-05&end=2050-09-06'\n\n All of them require \"start\" and \"end\" query parameters which define the time range for which available slots should be checked.\n Optional parameters are:\n - timeZone: Time zone in which the available slots should be returned. Defaults to UTC.\n - duration: Only use for event types that allow multiple durations or for dynamic event types. If not passed for multiple duration event types defaults to default duration. For dynamic event types defaults to 30 aka each returned slot is 30 minutes long. So duration=60 means that returned slots will be each 60 minutes long.\n - format: Format of the slots. By default return is an object where each key is date and value is array of slots as string. If you want to get start and end of each slot use \"range\" as value.\n - bookingUidToReschedule: When rescheduling an existing booking, provide the booking's unique identifier to exclude its time slot from busy time calculations. This ensures the original booking time appears as available for rescheduling.\n ", "parameters": [ { "name": "cal-api-version", @@ -11134,6 +11134,14 @@ "default": "2024-09-04" } }, + { + "name": "bookingUidToReschedule", + "required": false, + "in": "query", + "description": "The unique identifier of the booking being rescheduled. When provided will ensure that the original booking time appears within the returned available slots when rescheduling.", + "example": "abc123def456", + "schema": {} + }, { "name": "start", "required": true, diff --git a/docs/api-reference/v2/openapi.json b/docs/api-reference/v2/openapi.json index 91b1fa7de2..e5b519381f 100644 --- a/docs/api-reference/v2/openapi.json +++ b/docs/api-reference/v2/openapi.json @@ -10614,7 +10614,7 @@ "get": { "operationId": "SlotsController_2024_09_04_getAvailableSlots", "summary": "Find out when is an event type ready to be booked.", - "description": "\n There are 4 ways to get available slots for event type of an individual user:\n\n 1. By event type id. Event type id can be of user and team event types. Example '/v2/slots?eventTypeId=10&start=2050-09-05&end=2050-09-06&timeZone=Europe/Rome'\n\n 2. By event type slug + username. Example '/v2/slots?eventTypeSlug=intro&username=bob&start=2050-09-05&end=2050-09-06'\n\n 3. By event type slug + username + organization slug when searching within an organization. Example '/v2/slots?organizationSlug=org-slug&eventTypeSlug=intro&username=bob&start=2050-09-05&end=2050-09-06'\n\n 4. By usernames only (used for dynamic event type - there is no specific event but you want to know when 2 or more people are available). Example '/v2/slots?usernames=alice,bob&username=bob&organizationSlug=org-slug&start=2050-09-05&end=2050-09-06'. As you see you also need to provide the slug of the organization to which each user in the 'usernames' array belongs.\n\n And 3 ways to get available slots for team event type:\n\n 1. By team event type id. Example '/v2/slots?eventTypeId=10&start=2050-09-05&end=2050-09-06&timeZone=Europe/Rome'\n\n 2. By team event type slug + team slug. Example '/v2/slots?eventTypeSlug=intro&teamSlug=team-slug&start=2050-09-05&end=2050-09-06'\n\n 3. By team event type slug + team slug + organization slug when searching within an organization. Example '/v2/slots?organizationSlug=org-slug&eventTypeSlug=intro&teamSlug=team-slug&start=2050-09-05&end=2050-09-06'\n\n All of them require \"start\" and \"end\" query parameters which define the time range for which available slots should be checked.\n Optional parameters are:\n - timeZone: Time zone in which the available slots should be returned. Defaults to UTC.\n - duration: Only use for event types that allow multiple durations or for dynamic event types. If not passed for multiple duration event types defaults to default duration. For dynamic event types defaults to 30 aka each returned slot is 30 minutes long. So duration=60 means that returned slots will be each 60 minutes long.\n - format: Format of the slots. By default return is an object where each key is date and value is array of slots as string. If you want to get start and end of each slot use \"range\" as value.\n ", + "description": "\n There are 4 ways to get available slots for event type of an individual user:\n\n 1. By event type id. Event type id can be of user and team event types. Example '/v2/slots?eventTypeId=10&start=2050-09-05&end=2050-09-06&timeZone=Europe/Rome'\n\n 2. By event type slug + username. Example '/v2/slots?eventTypeSlug=intro&username=bob&start=2050-09-05&end=2050-09-06'\n\n 3. By event type slug + username + organization slug when searching within an organization. Example '/v2/slots?organizationSlug=org-slug&eventTypeSlug=intro&username=bob&start=2050-09-05&end=2050-09-06'\n\n 4. By usernames only (used for dynamic event type - there is no specific event but you want to know when 2 or more people are available). Example '/v2/slots?usernames=alice,bob&username=bob&organizationSlug=org-slug&start=2050-09-05&end=2050-09-06'. As you see you also need to provide the slug of the organization to which each user in the 'usernames' array belongs.\n\n And 3 ways to get available slots for team event type:\n\n 1. By team event type id. Example '/v2/slots?eventTypeId=10&start=2050-09-05&end=2050-09-06&timeZone=Europe/Rome'\n\n 2. By team event type slug + team slug. Example '/v2/slots?eventTypeSlug=intro&teamSlug=team-slug&start=2050-09-05&end=2050-09-06'\n\n 3. By team event type slug + team slug + organization slug when searching within an organization. Example '/v2/slots?organizationSlug=org-slug&eventTypeSlug=intro&teamSlug=team-slug&start=2050-09-05&end=2050-09-06'\n\n All of them require \"start\" and \"end\" query parameters which define the time range for which available slots should be checked.\n Optional parameters are:\n - timeZone: Time zone in which the available slots should be returned. Defaults to UTC.\n - duration: Only use for event types that allow multiple durations or for dynamic event types. If not passed for multiple duration event types defaults to default duration. For dynamic event types defaults to 30 aka each returned slot is 30 minutes long. So duration=60 means that returned slots will be each 60 minutes long.\n - format: Format of the slots. By default return is an object where each key is date and value is array of slots as string. If you want to get start and end of each slot use \"range\" as value.\n - bookingUidToReschedule: When rescheduling an existing booking, provide the booking's unique identifier to exclude its time slot from busy time calculations. This ensures the original booking time appears as available for rescheduling.\n ", "parameters": [ { "name": "cal-api-version", @@ -10626,6 +10626,14 @@ "default": "2024-09-04" } }, + { + "name": "bookingUidToReschedule", + "required": false, + "in": "query", + "description": "The unique identifier of the booking being rescheduled. When provided will ensure that the original booking time appears within the returned available slots when rescheduling.", + "example": "abc123def456", + "schema": {} + }, { "name": "start", "required": true, diff --git a/packages/platform/types/slots/slots-2024-09-04/inputs/get-slots.input.ts b/packages/platform/types/slots/slots-2024-09-04/inputs/get-slots.input.ts index 9168242a22..e22265fc31 100644 --- a/packages/platform/types/slots/slots-2024-09-04/inputs/get-slots.input.ts +++ b/packages/platform/types/slots/slots-2024-09-04/inputs/get-slots.input.ts @@ -78,6 +78,16 @@ export class GetAvailableSlotsInput_2024_09_04 { enum: SlotFormat, }) format?: SlotFormat; + + @IsString() + @IsOptional() + @ApiPropertyOptional({ + type: String, + description: + "The unique identifier of the booking being rescheduled. When provided will ensure that the original booking time appears within the returned available slots when rescheduling.", + example: "abc123def456", + }) + bookingUidToReschedule?: string; } export const ById_2024_09_04_type = "byEventTypeId";