Files
calendar/packages/features/ee/round-robin/utils/validateRoundRobinSlotAvailability.ts
T
b286244aab fix: api v2 logic for slots reservation endpoint (#23222)
* init logic for handling reservation for round robin event

* remove unused comments

* add function to get already reserved slots

* fixup: add correct logic for round robin slots reservation

* fixup: implement PR feedback

* fixup: update logic to handle fixed and non fixed round robin hosts

* update slots repository

* fixup: implement PR feedback

* fixup: implement PR feedback

* add tests for round robin slot reservation with non fixed hosts

* add tests for round robin slot reservation with fixed and non fixed hosts

* fix: implement PR feedback

* fix: e2e test

* fix: move validateRoundRobinSlotAvailability to core libraries

* fix: merge conflicts

* fix: merge conflicts

* update slots.ts for platform libraries

* fix: import logic from platform libraries

* cleanup

* fix: import path

* fix: missing import

* fix: test code and handle error thrown

---------

Co-authored-by: supalarry <laurisskraucis@gmail.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
2025-10-10 12:35:21 +02:00

91 lines
2.6 KiB
TypeScript

import { DateTime } from "luxon";
import { HttpError } from "@calcom/lib/http-error";
import { prisma } from "@calcom/prisma";
import type { Host } from "@calcom/prisma/client";
export async function validateRoundRobinSlotAvailability(
eventTypeId: number,
startDate: DateTime,
endDate: DateTime,
hosts: Host[]
) {
const fixedHosts = hosts.filter((host) => host.isFixed === true);
const nonFixedHosts = hosts.filter((host) => host.isFixed === false);
if (fixedHosts.length > 0) {
await validateFixedHostsAvailability(eventTypeId, startDate, endDate, fixedHosts);
} else {
await validateNonFixedHostsAvailability(eventTypeId, startDate, endDate, nonFixedHosts);
}
}
async function validateFixedHostsAvailability(
eventTypeId: number,
startDate: DateTime,
endDate: DateTime,
hosts: Host[]
) {
const existingBooking = await prisma.booking.findFirst({
where: {
eventTypeId,
startTime: startDate.toJSDate(),
endTime: endDate.toJSDate(),
},
select: { attendees: true, userId: true, status: true },
});
const existingSlotReservation = await prisma.selectedSlots.count({
where: {
eventTypeId,
slotUtcStartDate: startDate.toISO() ?? "",
slotUtcEndDate: endDate.toISO() ?? "",
// Only consider non-expired reservations
releaseAt: { gt: DateTime.utc().toJSDate() },
},
});
const hasHostAsAttendee = hosts.some(
(host) =>
existingBooking?.attendees.some((attendee) => attendee.id === host.userId) ||
existingBooking?.userId === host.userId
);
if (existingSlotReservation > 0) {
throw new HttpError({
statusCode: 422,
message: `Can't reserve the slot because the round robin event type has no available hosts left at this time slot.`,
});
}
if (hasHostAsAttendee) {
throw new HttpError({
statusCode: 422,
message: `Can't reserve a slot if the event is already booked.`,
});
}
}
async function validateNonFixedHostsAvailability(
eventTypeId: number,
startDate: DateTime,
endDate: DateTime,
hosts: Host[]
) {
const existingSlotReservations = await prisma.selectedSlots.count({
where: {
eventTypeId,
slotUtcStartDate: startDate.toISO() ?? "",
slotUtcEndDate: endDate.toISO() ?? "",
// Only consider non-expired reservations
releaseAt: { gt: DateTime.utc().toJSDate() },
},
});
if (existingSlotReservations === hosts.length) {
throw new HttpError({
statusCode: 422,
message: `Can't reserve the slot because the round robin event type has no available hosts left at this time slot.`,
});
}
}