From a2c13e576e145300dc688d22d1f43f6668d105bd Mon Sep 17 00:00:00 2001 From: Lauris Skraucis Date: Fri, 30 May 2025 12:23:51 +0200 Subject: [PATCH] refactor: v2 dont allow rescheduling cancelled,rescheduled bookings (#21643) --- .../controllers/e2e/user-bookings.e2e-spec.ts | 140 ++++++++++++++++++ .../2024-08-13/services/bookings.service.ts | 20 +++ 2 files changed, 160 insertions(+) diff --git a/apps/api/v2/src/ee/bookings/2024-08-13/controllers/e2e/user-bookings.e2e-spec.ts b/apps/api/v2/src/ee/bookings/2024-08-13/controllers/e2e/user-bookings.e2e-spec.ts index 592e2aedaa..f06465cc20 100644 --- a/apps/api/v2/src/ee/bookings/2024-08-13/controllers/e2e/user-bookings.e2e-spec.ts +++ b/apps/api/v2/src/ee/bookings/2024-08-13/controllers/e2e/user-bookings.e2e-spec.ts @@ -2399,6 +2399,146 @@ describe("Bookings Endpoints 2024-08-13", () => { }); }); + describe("cant't reschedule cancelled booking", () => { + it("should not be able to reschedule cancelled booking", async () => { + const cancelledBooking = await bookingsRepositoryFixture.create({ + status: "CANCELLED", + user: { + connect: { + id: user.id, + }, + }, + startTime: new Date(Date.UTC(2050, 0, 8, 13, 0, 0)), + endTime: new Date(Date.UTC(2050, 0, 8, 14, 0, 0)), + title: "peer coding lets goo", + uid: `cancelled-booking-${randomString()}`, + eventType: { + connect: { + id: eventTypeId, + }, + }, + location: "integrations:daily", + customInputs: {}, + metadata: {}, + responses: { + name: "Oldie", + email: "oldie@gmail.com", + }, + attendees: { + create: { + email: "oldie@gmail.com", + name: "Oldie", + locale: "lv", + timeZone: "Europe/Rome", + }, + }, + }); + + const body: RescheduleBookingInput_2024_08_13 = { + start: new Date(Date.UTC(2040, 0, 9, 14, 0, 0)).toISOString(), + reschedulingReason: "Flying to mars that day", + }; + + const response = await request(app.getHttpServer()) + .post(`/v2/bookings/${cancelledBooking.uid}/reschedule`) + .send(body) + .set(CAL_API_VERSION_HEADER, VERSION_2024_08_13) + .expect(400); + + expect(response.body.error.message).toEqual( + `Can't reschedule booking with uid=${cancelledBooking.uid} because it has been cancelled. Please provide uid of a booking that is not cancelled.` + ); + await bookingsRepositoryFixture.deleteById(cancelledBooking.id); + }); + + it("should not be able to reschedule rescheduled booking", async () => { + const rescheduledBooking = await bookingsRepositoryFixture.create({ + status: "CANCELLED", + rescheduled: true, + user: { + connect: { + id: user.id, + }, + }, + startTime: new Date(Date.UTC(2050, 0, 8, 13, 0, 0)), + endTime: new Date(Date.UTC(2050, 0, 8, 14, 0, 0)), + title: "peer coding lets goo", + uid: `cancelled-booking-${randomString()}`, + eventType: { + connect: { + id: eventTypeId, + }, + }, + location: "integrations:daily", + customInputs: {}, + metadata: {}, + responses: { + name: "Oldie", + email: "oldie@gmail.com", + }, + attendees: { + create: { + email: "oldie@gmail.com", + name: "Oldie", + locale: "lv", + timeZone: "Europe/Rome", + }, + }, + }); + + const newBooking = await bookingsRepositoryFixture.create({ + status: "ACCEPTED", + user: { + connect: { + id: user.id, + }, + }, + fromReschedule: rescheduledBooking.uid, + startTime: new Date(Date.UTC(2050, 0, 8, 13, 0, 0)), + endTime: new Date(Date.UTC(2050, 0, 8, 14, 0, 0)), + title: "peer coding lets goo", + uid: `new-booking-${randomString()}`, + eventType: { + connect: { + id: eventTypeId, + }, + }, + location: "integrations:daily", + customInputs: {}, + metadata: {}, + responses: { + name: "Oldie", + email: "oldie@gmail.com", + }, + attendees: { + create: { + email: "oldie@gmail.com", + name: "Oldie", + locale: "lv", + timeZone: "Europe/Rome", + }, + }, + }); + + const body: RescheduleBookingInput_2024_08_13 = { + start: new Date(Date.UTC(2040, 0, 9, 14, 0, 0)).toISOString(), + reschedulingReason: "Flying to mars that day", + }; + + const response = await request(app.getHttpServer()) + .post(`/v2/bookings/${rescheduledBooking.uid}/reschedule`) + .send(body) + .set(CAL_API_VERSION_HEADER, VERSION_2024_08_13) + .expect(400); + + expect(response.body.error.message).toEqual( + `Can't reschedule booking with uid=${rescheduledBooking.uid} because it has been cancelled and rescheduled already to booking with uid=${newBooking.uid}. You probably want to reschedule ${newBooking.uid} instead by passing it within the request URL.` + ); + await bookingsRepositoryFixture.deleteById(rescheduledBooking.id); + await bookingsRepositoryFixture.deleteById(newBooking.id); + }); + }); + function responseDataIsRecurranceBooking(data: any): data is RecurringBookingOutput_2024_08_13 { return ( !Array.isArray(data) && diff --git a/apps/api/v2/src/ee/bookings/2024-08-13/services/bookings.service.ts b/apps/api/v2/src/ee/bookings/2024-08-13/services/bookings.service.ts index 5bebe6abd1..877ecab424 100644 --- a/apps/api/v2/src/ee/bookings/2024-08-13/services/bookings.service.ts +++ b/apps/api/v2/src/ee/bookings/2024-08-13/services/bookings.service.ts @@ -637,6 +637,7 @@ export class BookingsService_2024_08_13 { async rescheduleBooking(request: Request, bookingUid: string, body: RescheduleBookingInput) { try { + await this.canRescheduleBooking(bookingUid); const bookingRequest = await this.inputService.createRescheduleBookingRequest( request, bookingUid, @@ -691,6 +692,25 @@ export class BookingsService_2024_08_13 { } } + async canRescheduleBooking(bookingUid: string) { + const booking = await this.bookingsRepository.getByUid(bookingUid); + if (!booking) { + throw new Error(`Booking with uid=${bookingUid} was not found in the database`); + } + if (booking.status === "CANCELLED" && !booking.rescheduled) { + throw new BadRequestException( + `Can't reschedule booking with uid=${bookingUid} because it has been cancelled. Please provide uid of a booking that is not cancelled.` + ); + } + if (booking.status === "CANCELLED" && booking.rescheduled) { + const rescheduledTo = await this.bookingsRepository.getByFromReschedule(bookingUid); + throw new BadRequestException( + `Can't reschedule booking with uid=${bookingUid} because it has been cancelled and rescheduled already to booking with uid=${rescheduledTo?.uid}. You probably want to reschedule ${rescheduledTo?.uid} instead by passing it within the request URL.` + ); + } + return booking; + } + async cancelBooking(request: Request, bookingUid: string, body: CancelBookingInput) { if (this.inputService.isCancelSeatedBody(body)) { const seat = await this.bookingSeatRepository.getByReferenceUid(body.seatUid);