-
Notifications
You must be signed in to change notification settings - Fork 12k
fix: round-robin reserve per host capacity #24520
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -76,35 +76,85 @@ export const reserveSlotHandler = async ({ ctx, input }: ReserveSlotOptions) => | |
|
|
||
| if (eventType && shouldReserveSlot && !reservedBySomeoneElse && !_isDryRun) { | ||
| try { | ||
| await Promise.all( | ||
| // FIXME: In case of team event, users doesn't have assignees, those are in hosts. users just have the creator of the event which is wrong. | ||
| // Also, we must not block all the users' slots, we must use routedTeamMemberIds if set like we do in getSchedule. | ||
| // We could even improve it by identifying the next person being booked now that we have a queue of assignees. | ||
| eventType.users.map((user) => | ||
| prisma.selectedSlots.upsert({ | ||
| where: { selectedSlotUnique: { userId: user.id, slotUtcStartDate, slotUtcEndDate, uid } }, | ||
| update: { | ||
| slotUtcStartDate, | ||
| slotUtcEndDate, | ||
| releaseAt, | ||
| eventTypeId, | ||
| }, | ||
| create: { | ||
| userId: user.id, | ||
| eventTypeId, | ||
| slotUtcStartDate, | ||
| slotUtcEndDate, | ||
| uid, | ||
| releaseAt, | ||
| isSeat: eventType.seatsPerTimeSlot !== null, | ||
| }, | ||
| }) | ||
| ) | ||
| ); | ||
| } catch { | ||
| // Get the event type details to check scheduling type | ||
| const eventTypeDetails = await prisma.eventType.findUnique({ | ||
| where: { id: eventTypeId }, | ||
| select: { schedulingType: true }, | ||
| }); | ||
|
|
||
| if (eventTypeDetails?.schedulingType === "ROUND_ROBIN") { | ||
| // Atomic operation: Create reservation first, then check capacity | ||
| await prisma.selectedSlots.upsert({ | ||
| where: { selectedSlotUnique: { userId: -1, slotUtcStartDate, slotUtcEndDate, uid } }, | ||
| update: { | ||
| slotUtcStartDate, | ||
| slotUtcEndDate, | ||
| releaseAt, | ||
| eventTypeId, | ||
| }, | ||
| create: { | ||
| userId: -1, // Generic reservation for Round-Robin | ||
| eventTypeId, | ||
| slotUtcStartDate, | ||
| slotUtcEndDate, | ||
| uid, | ||
| releaseAt, | ||
| isSeat: eventType.seatsPerTimeSlot !== null, | ||
| }, | ||
| }); | ||
|
|
||
| // Check capacity after creation to handle race conditions | ||
| const totalReservations = await prisma.selectedSlots.count({ | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Round-robin capacity counting is not scoped by eventTypeId, so reservations from other event types with the same time range will prematurely exhaust capacity for this event type. Prompt for AI agents |
||
| where: { | ||
| slotUtcStartDate, | ||
| slotUtcEndDate, | ||
| userId: -1, | ||
| releaseAt: { gt: new Date() }, | ||
| }, | ||
| }); | ||
|
|
||
| if (totalReservations > eventType.users.length) { | ||
| // Rollback: Delete the reservation we just created | ||
| await prisma.selectedSlots.delete({ | ||
| where: { selectedSlotUnique: { userId: -1, slotUtcStartDate, slotUtcEndDate, uid } }, | ||
| }); | ||
| throw new TRPCError({ | ||
| message: "Slot is fully booked", | ||
| code: "BAD_REQUEST", | ||
|
Comment on lines
+107
to
+123
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Round-Robin overbooking check needs to isolate by event type.
Please scope the count to the relevant event type (or another discriminator tied to the actual host pool) so we only compare against the capacity that belongs to this event type’s hosts. Right now this breaks RR booking for any org running multiple independent RR event types. 🤖 Prompt for AI Agents |
||
| }); | ||
| } | ||
| } else { | ||
| // For other scheduling types, create reservations for all users | ||
| await Promise.all( | ||
| eventType.users.map((user) => | ||
| prisma.selectedSlots.upsert({ | ||
| where: { selectedSlotUnique: { userId: user.id, slotUtcStartDate, slotUtcEndDate, uid } }, | ||
| update: { | ||
| slotUtcStartDate, | ||
| slotUtcEndDate, | ||
| releaseAt, | ||
| eventTypeId, | ||
| }, | ||
| create: { | ||
| userId: user.id, | ||
| eventTypeId, | ||
| slotUtcStartDate, | ||
| slotUtcEndDate, | ||
| uid, | ||
| releaseAt, | ||
| isSeat: eventType.seatsPerTimeSlot !== null, | ||
| }, | ||
| }) | ||
| ) | ||
| ); | ||
| } | ||
| } catch (error) { | ||
| if (error instanceof TRPCError) { | ||
| throw error; | ||
| } | ||
| throw new TRPCError({ | ||
| message: "Event type not found", | ||
| code: "NOT_FOUND", | ||
| message: "Failed to reserve slot", | ||
| code: "INTERNAL_SERVER_ERROR", | ||
| }); | ||
| } | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do not pull every Round-Robin hold into unrelated availability checks.
Adding
{ userId: -1 }without scoping by event type makes this query return all Round-Robin reservations across the entire table. Downstream consumers (e.g._getReservedSlotsAndCleanupExpired) now assume every returned row belongs to the current event type, so any other Round-Robin event that reserves the same timestamp instantly reduces capacity (or blocks bookings) for events whose hosts are completely unrelated. This regresses isolated teams: a booking for Team A at 10:00 with hosts {1,2} will prevent Team B with hosts {10,11} from reserving that same 10:00 slot because the global count trips the capacity guard.We need to keep the fix (one reservation per slot) while still isolating capacity per host group. At minimum the query must restrict the
userId: -1branch to the relevant eventTypeId(s) so unrelated pools stay independent; otherwise every Round-Robin event competes for the same shared counter. Please tighten this filter (or carry eventTypeId context into the repository) before shipping.🤖 Prompt for AI Agents