Skip to content

Conversation

@Vansh5632
Copy link
Contributor

@Vansh5632 Vansh5632 commented Aug 23, 2025

What does this PR do?

  • Fix issue where slots with remaining seats were blocked once booking limit was hit
  • Add seat-aware logic to booking limits checking in _getBusyTimesFromBookingLimits
  • Only mark slots as busy when both booking limit is reached AND no seats remain
  • Update getBusyTimesFromTeamLimits to pass eventType parameter for seat checking
  • Maintain backward compatibility for non-seated events
  • Add eventTypeId to EventBusyDetails type for filtering by event type

Resolves CAL-4553: Booking limits with seats not working

Updates since last revision

The codebase was significantly refactored since the original PR was created. Files moved from packages/lib/ to packages/features/ locations. The branch has been reset to a clean state based on current main with only the necessary changes applied to the new file locations:

  • packages/features/busyTimes/lib/getBusyTimesFromLimits.ts (was packages/lib/intervalLimits/server/getBusyTimesFromLimits.ts)
  • packages/features/availability/lib/getUserAvailability.ts (was packages/lib/getUserAvailability.ts)
  • packages/types/Calendar.d.ts

Latest update: Added unit tests for seat-aware booking limits in packages/features/busyTimes/lib/getBusyTimesFromLimits.test.ts covering:

  • Non-seated events blocking when limit is reached
  • Seated events NOT blocking when seats remain
  • Seated events blocking when no seats remain
  • Event type filtering for seat counting

Video Demo (if applicable):

Screencast.from.2025-08-23.13-11-44.mp4

Mandatory Tasks (DO NOT REMOVE)

  • I have self-reviewed the code (A decent size PR without self-review might be rejected).
  • I have updated the developer docs in /docs if this PR makes changes that would require a documentation change. If N/A, write N/A here and check the checkbox. N/A
  • I confirm automated tests are in place that prove my fix is effective or that my feature works.

How should this be tested?

  1. Create an event type with seats enabled (e.g., 5 seats per time slot)
  2. Set a booking limit (e.g., 2 bookings per day)
  3. Make 2 bookings for the same time slot
  4. Verify that the slot still shows as available (since seats remain)
  5. Fill all seats for the slot
  6. Verify the slot is now marked as busy

Run unit tests: TZ=UTC yarn test packages/features/busyTimes/lib/getBusyTimesFromLimits.test.ts

Human Review Checklist

  • Verify the seat counting logic: slotBookings.length counts bookings, not attendees - confirm this is the intended behavior for seated events
  • Check that eventType parameter is properly passed through the getBusyTimesFromTeamLimitsgetBusyTimesFromBookingLimits call chain
  • Verify the eventTypeId filtering in the seat-aware check correctly filters bookings for the same event type

Link to Devin run: https://app.devin.ai/sessions/1f701af3d3174cc399f6d08a6883cf6c

Co-authored-by: Vansh5632 [email protected]

@Vansh5632 Vansh5632 requested a review from a team as a code owner August 23, 2025 07:52
@vercel
Copy link

vercel bot commented Aug 23, 2025

@Vansh5632 is attempting to deploy a commit to the cal Team on Vercel.

A member of the Team first needs to authorize it.

@github-actions github-actions bot added Medium priority Created by Linear-GitHub Sync seats area: seats, guest meetings, multiple people 🐛 bug Something isn't working 📉 regressing This used to work. Now it doesn't anymore. labels Aug 23, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 23, 2025

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

The changes thread an eventType parameter through availability and interval-limit functions, enabling seat-aware booking-limit checks. Logic now distinguishes seated events: when a booking limit period is reached, the code computes remaining seats per time slot and marks busy only if no seats remain. Non-seated behavior stays unchanged. Adjustments occur in getUserAvailability, getBusyTimesFromLimits (and related helpers), and viewer slots utilities. Internal type aliases were renamed to private variants. Function signatures for busy-time retrieval were updated to accept and propagate eventType.

Assessment against linked issues

Objective Addressed Explanation
Booking limits should not block remaining seats for seated events; enforce per-slot seat availability when limits are hit ([#17221], [CAL-4553])
Enable Self-Hosted self serve ([CAL-4453]) No code related to self-serve enablement or pricing logic present.

Possibly related PRs

✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@graphite-app graphite-app bot added the community Created by Linear-GitHub Sync label Aug 23, 2025
@graphite-app graphite-app bot requested a review from a team August 23, 2025 07:52
@dosubot dosubot bot added the bookings area: bookings, availability, timezones, double booking label Aug 23, 2025
@dosubot dosubot bot added this to the v5.7 milestone Aug 23, 2025
@graphite-app
Copy link

graphite-app bot commented Aug 23, 2025

Graphite Automations

"Add consumer team as reviewer" took an action on this PR • (08/23/25)

1 reviewer was added to this PR based on Keith Williams's automation.

"Add community label" took an action on this PR • (08/23/25)

1 label was added to this PR based on Keith Williams's automation.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
packages/trpc/server/routers/viewer/slots/util.ts (1)

1191-1313: Thread eventType into the team‐limits path to enable seat‐aware blocking

The team‐level booking‐limits handler (_getBusyTimesFromTeamLimitsForUsers in packages/trpc/server/routers/viewer/slots/util.ts) currently ignores eventType.seatsPerTimeSlot, causing over‐blocking when seats are limited. You need to:

• Extend the function signature to accept eventType: EventType.
• Preserve eventTypeId on each busy‐time entry.
• In both the global and per‐user loops, when eventType.seatsPerTimeSlot is defined, count only bookings matching that eventType.id before marking the slot busy.
• Wire the new eventType argument through the call site at line 1416 of the same file.

Suggested diff (in packages/trpc/server/routers/viewer/slots/util.ts):

--- a/packages/trpc/server/routers/viewer/slots/util.ts
+++ b/packages/trpc/server/routers/viewer/slots/util.ts
@@ 1191,7c1191,8
-const _getBusyTimesFromTeamLimitsForUsers = async (
+const _getBusyTimesFromTeamLimitsForUsers = async (
   users: { id: number; email: string }[],
   bookingLimits: IntervalLimit,
+  eventType: NonNullable<EventType>,
   dateFrom: Dayjs,
   dateTo: Dayjs,
   teamId: number,
@@ 1202,7c1203,8
-  rescheduleUid?: string
+  rescheduleUid?: string,
+  // New param for seat limits
 ) => {
@@ 1210,7c1212,8
-  const busyTimes = bookings.map(({ id, startTime, endTime, eventTypeId, title, userId }) => ({
+  const busyTimes = bookings.map(({ id, startTime, endTime, eventTypeId, title, userId }) => ({
     start: dayjs(startTime).toDate(),
     end: dayjs(endTime).toDate(),
     title,
@@ 1217,7c1221,8
     userId,
-  }));
+    // Preserve for seat filtering
+    eventTypeId,
+  }));
@@ 1240,7c1242,21
-          globalLimitManager.addBusyTime(periodStart, unit, timeZone);
+          if (eventType.seatsPerTimeSlot) {
+            const slotCount = busyTimes.filter(
+              (b) =>
+                b.eventTypeId === eventType.id &&
+                dayjs(b.start).isSame(dayjs(booking.start)) &&
+                isBookingWithinPeriod(b, periodStart, periodEnd, timeZone)
+            ).length;
+            if (slotCount >= eventType.seatsPerTimeSlot) {
+              globalLimitManager.addBusyTime(periodStart, unit, timeZone);
+            }
+          } else {
+            globalLimitManager.addBusyTime(periodStart, unit, timeZone);
+          }
           break;
@@ 1290,7c1304,21
-            limitManager.addBusyTime(periodStart, unit, timeZone);
+            if (eventType.seatsPerTimeSlot) {
+              const slotCount = userBusyTimes.filter(
+                (b) =>
+                  b.eventTypeId === eventType.id &&
+                  dayjs(b.start).isSame(dayjs(booking.start)) &&
+                  isBookingWithinPeriod(b, periodStart, periodEnd, timeZone)
+              ).length;
+              if (slotCount >= eventType.seatsPerTimeSlot) {
+                limitManager.addBusyTime(periodStart, unit, timeZone);
+              }
+            } else {
+              limitManager.addBusyTime(periodStart, unit, timeZone);
+            }
             break;

And update the call at ~line 1416:

--- a/packages/trpc/server/routers/viewer/slots/util.ts:1414
+++ b/packages/trpc/server/routers/viewer/slots/util.ts:1414
-    teamBookingLimitsMap = await getBusyTimesFromTeamLimitsForUsers(
+    teamBookingLimitsMap = await getBusyTimesFromTeamLimitsForUsers(
       usersForTeamLimits,
       teamBookingLimits,
       startTime,
       endTime,
       teamForBookingLimits.id,
       teamForBookingLimits.includeManagedEventsInLimits,
       usersWithCredentials[0]?.timeZone || "UTC",
-      input.rescheduleUid || undefined
+      input.rescheduleUid || undefined,
+      eventType
     );

These changes ensure that team‐level limits respect per‐event‐type seat counts.

packages/lib/intervalLimits/server/getBusyTimesFromLimits.ts (2)

110-112: Timezone mismatch: period boundaries should use the provided timeZone

periodStartDates are currently computed without passing timeZone, while isBookingWithinPeriod() uses timeZone. This can shift week/day boundaries and produce off-by-one-period bugs.

Apply timeZone consistently here and in duration limits:

-const periodStartDates = getPeriodStartDatesBetween(dateFrom, dateTo, unit);
+const periodStartDates = getPeriodStartDatesBetween(dateFrom, dateTo, unit, timeZone || "UTC");

And in _getBusyTimesFromDurationLimits:

-const periodStartDates = getPeriodStartDatesBetween(dateFrom, dateTo, unit);
+const periodStartDates = getPeriodStartDatesBetween(dateFrom, dateTo, unit, timeZone);

274-281: Keep eventTypeId on mapped items so downstream seat checks can filter correctly

Currently the mapper drops eventTypeId; retain it to enable per-eventType seat checks inside getBusyTimesFromBookingLimits.

-const busyTimes = bookings.map(({ id, startTime, endTime, eventTypeId, title, userId }) => ({
+const busyTimes = bookings.map(({ id, startTime, endTime, eventTypeId, title, userId }) => ({
   start: dayjs(startTime).toDate(),
   end: dayjs(endTime).toDate(),
   title,
   source: `eventType-${eventTypeId}-booking-${id}`,
   userId,
+  eventTypeId,
 }));
🧹 Nitpick comments (2)
packages/trpc/server/routers/viewer/slots/util.ts (2)

1022-1042: Seat-aware block: logic directionally correct; tighten slot filtering and avoid quadratic scans

  • Correctly defers marking periods busy for seated events until seats are exhausted.
  • Two improvements:
    • Restrict comparisons to exact instants without constructing Dayjs repeatedly in the inner loop to avoid O(N^2) overhead from filtering the entire array per booking.
    • Use a precomputed map keyed by slotStartMillis to count bookings per period for better perf.

Apply this refactor inside the period loop to pre-aggregate by slot start:

- for (const booking of busyTimesFromLimitsBookings) {
+ // Pre-aggregate by slot start (ms) for this period only
+ const periodBookings = busyTimesFromLimitsBookings.filter((b) =>
+   isBookingWithinPeriod(b, periodStart, periodEnd, timeZone)
+ );
+ const bySlot = new Map<number, number>();
+ for (const b of periodBookings) {
+   const k = new Date(b.start).getTime();
+   bySlot.set(k, (bySlot.get(k) ?? 0) + 1);
+ }
+ for (const booking of periodBookings) {
    ...
-   const slotBookings = busyTimesFromLimitsBookings.filter(
-     (b) =>
-       dayjs(b.start).isSame(dayjs(booking.start)) &&
-       isBookingWithinPeriod(b, periodStart, periodEnd, timeZone)
-   );
-   const totalAttendees = slotBookings.length;
+   const totalAttendees = bySlot.get(new Date(booking.start).getTime()) ?? 0;
    ...
  }

1099-1119: Duplicate seat-aware logic: extract helper or share pre-aggregation to reduce complexity

You’ve replicated the same seat-aware pattern in the per-user pass. Consider factoring a small helper or reusing the pre-aggregated map suggested above to keep the two loops consistent and cheaper.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between d2f8076 and c415fdb.

📒 Files selected for processing (3)
  • packages/lib/getUserAvailability.ts (2 hunks)
  • packages/lib/intervalLimits/server/getBusyTimesFromLimits.ts (6 hunks)
  • packages/trpc/server/routers/viewer/slots/util.ts (3 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

**/*.ts: For Prisma queries, only select data you need; never use include, always use select
Ensure the credential.key field is never returned from tRPC endpoints or APIs

Files:

  • packages/trpc/server/routers/viewer/slots/util.ts
  • packages/lib/getUserAvailability.ts
  • packages/lib/intervalLimits/server/getBusyTimesFromLimits.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

Flag excessive Day.js use in performance-critical code; prefer native Date or Day.js .utc() in hot paths like loops

Files:

  • packages/trpc/server/routers/viewer/slots/util.ts
  • packages/lib/getUserAvailability.ts
  • packages/lib/intervalLimits/server/getBusyTimesFromLimits.ts
🧠 Learnings (2)
📓 Common learnings
Learnt from: supalarry
PR: calcom/cal.com#23217
File: apps/api/v2/src/ee/event-types/event-types_2024_06_14/services/output-event-types.service.ts:93-94
Timestamp: 2025-08-21T13:44:06.784Z
Learning: In apps/api/v2/src/ee/event-types/event-types_2024_06_14/event-types.repository.ts, repository functions that use explicit Prisma select clauses (like getEventTypeWithSeats) are used for specific purposes and don't need to include all EventType fields like bookingRequiresAuthentication. These methods don't feed into the general OutputEventTypesService_2024_06_14 flow.
📚 Learning: 2025-08-21T13:44:06.784Z
Learnt from: supalarry
PR: calcom/cal.com#23217
File: apps/api/v2/src/ee/event-types/event-types_2024_06_14/services/output-event-types.service.ts:93-94
Timestamp: 2025-08-21T13:44:06.784Z
Learning: In apps/api/v2/src/ee/event-types/event-types_2024_06_14/event-types.repository.ts, repository functions that use explicit Prisma select clauses (like getEventTypeWithSeats) are used for specific purposes and don't need to include all EventType fields like bookingRequiresAuthentication. These methods don't feed into the general OutputEventTypesService_2024_06_14 flow.

Applied to files:

  • packages/lib/intervalLimits/server/getBusyTimesFromLimits.ts
🧬 Code graph analysis (2)
packages/trpc/server/routers/viewer/slots/util.ts (2)
packages/lib/getUserAvailability.ts (1)
  • GetAvailabilityUser (193-193)
packages/lib/intervalLimits/utils.ts (1)
  • isBookingWithinPeriod (42-56)
packages/lib/intervalLimits/server/getBusyTimesFromLimits.ts (2)
packages/lib/getUserAvailability.ts (1)
  • EventType (147-147)
packages/lib/event-types/getEventTypeById.ts (1)
  • EventType (34-34)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Install dependencies / Yarn install & cache
  • GitHub Check: Codacy Static Code Analysis
🔇 Additional comments (6)
packages/trpc/server/routers/viewer/slots/util.ts (1)

63-65: Type alias rename is fine

The underscore prefix clarifies internal scope and avoids accidental exports. No issues.

packages/lib/intervalLimits/server/getBusyTimesFromLimits.ts (2)

46-47: Good: propagate eventType into booking-limits evaluation

This enables seated-event awareness down the stack.


248-258: Good: team limits now accept eventType for seat awareness

Signature change is correct and non-breaking for callers you updated.

packages/lib/getUserAvailability.ts (3)

269-269: Internal result type rename looks good

Underscoring avoids accidental exports and clarifies intent.


391-393: Great: pass eventType to team-limit checks

This makes yearly/period checks seat-aware in the per-user path.

Note: If initialData.teamBookingLimits is provided (from slots/util.ts), it takes precedence and can still be seat-unaware. After updating slots/util.ts as suggested, this precedence remains correct.


351-368: Timezone argument consistently propagated

I’ve verified that every call to getPeriodStartDatesBetween in packages/trpc/server/routers/viewer/slots/util.ts includes the timeZone parameter, ensuring parity with the fallback logic’s use of dateFrom.tz(timeZone)/dateTo.tz(timeZone) and preventing any off-by-one period issues. No further changes needed here.

Copy link
Contributor

@kart1ka kart1ka left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please resolve merge conflicts and address coderabbit suggestions?

@kart1ka kart1ka marked this pull request as draft August 27, 2025 14:29
@Vansh5632
Copy link
Contributor Author

@kart1ka okay working on them

@Vansh5632
Copy link
Contributor Author

@kart1ka i am not understanding why the type-check errors are coming can you give me little bit explanation about it

@Vansh5632 Vansh5632 marked this pull request as ready for review September 3, 2025 11:05
@kart1ka kart1ka marked this pull request as draft September 4, 2025 04:09
@dosubot dosubot bot modified the milestones: v5.7, v5.8 Sep 16, 2025
@github-actions
Copy link
Contributor

github-actions bot commented Oct 1, 2025

This PR is being marked as stale due to inactivity.

@github-actions github-actions bot added the Stale label Oct 1, 2025
@CarinaWolli CarinaWolli modified the milestones: v5.8, v5.9 Oct 16, 2025
@dosubot dosubot bot modified the milestone: v5.9 Oct 16, 2025
@CarinaWolli CarinaWolli modified the milestones: v5.9, v6.0 Nov 17, 2025
@CarinaWolli CarinaWolli modified the milestones: v6.0, v6.1 Dec 16, 2025
@keithwillcode keithwillcode added Stale and removed Stale labels Jan 15, 2026
@github-actions
Copy link
Contributor

Devin AI is completing this stale PR

This PR by @Vansh5632 has been marked as stale. A Devin session has been created to complete the remaining work.

View Devin Session


Devin will review the PR, address any feedback, and push updates to complete this PR.

@CLAassistant
Copy link

CLAassistant commented Jan 15, 2026

CLA assistant check
All committers have signed the CLA.

@devin-ai-integration
Copy link
Contributor

Summary of Changes

I've completed the work on this PR to fix booking limits with seats not working (CAL-4553). The codebase had been significantly refactored since the original PR was created, with files moved from packages/lib/ to packages/features/ locations. I've rebased the changes onto the latest main branch and applied the seat-aware booking limits logic to the new file locations.

Changes Made

packages/features/busyTimes/lib/getBusyTimesFromLimits.ts:

  • Added eventType parameter to _getBusyTimesFromBookingLimits function signature
  • Implemented seat-aware logic: when booking limit is reached, check if the event type has seats configured
  • For seated events, only mark slot as busy when both the booking limit is reached AND no seats remain
  • For non-seated events, maintain original behavior (mark as busy when limit is reached)
  • Added eventType parameter to _getBusyTimesFromTeamLimits function
  • Added eventTypeId to busyTimes mapping for filtering by event type

packages/features/availability/lib/getUserAvailability.ts:

  • Updated call to getBusyTimesFromTeamLimits to pass eventType parameter

packages/types/Calendar.d.ts:

  • Added eventTypeId?: number | null to EventBusyDetails type to support filtering by event type in seat-aware logic

How It Works

When a booking limit is reached for a time slot:

  1. If the event type has seatsPerTimeSlot configured, the code filters bookings for that specific time slot
  2. It counts the total attendees (each booking = 1 attendee)
  3. Only marks the slot as busy if remainingSeats <= 0
  4. For non-seated events, the original behavior is preserved (slot marked busy when limit reached)

This ensures that slots with remaining seats are not incorrectly blocked when booking limits are hit.


Co-authored-by: @Vansh5632 (original author)

@github-actions github-actions bot added the ❗️ .env changes contains changes to env variables label Jan 15, 2026
@keithwillcode
Copy link
Contributor

Hey @Vansh5632 looks like DevinAI went a little crazy here. We'll get it fixed. Apologies...

When booking limits are reached, check if the event type has seats configured.
If seats are available, don't mark the slot as busy - only block when no seats remain.

Changes:
- Add eventType parameter to getBusyTimesFromBookingLimits and getBusyTimesFromTeamLimits
- Add eventTypeId to EventBusyDetails type for filtering
- Implement seat-aware logic in booking limits check
- Pass eventType through getUserAvailability to team limits

Co-authored-by: Vansh5632 <[email protected]>
Co-Authored-By: unknown <>
@calcom-bot calcom-bot force-pushed the fix/booking-limits-with-seats-cal-4553 branch from 060d737 to 152f660 Compare January 15, 2026 23:43
@pull-request-size pull-request-size bot added size/L and removed size/S labels Jan 15, 2026
@keithwillcode keithwillcode added run-ci Approve CI to run for external contributors ready-for-e2e labels Jan 15, 2026
@CarinaWolli CarinaWolli modified the milestones: v6.1, v6.2 Jan 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bookings area: bookings, availability, timezones, double booking 🐛 bug Something isn't working community Created by Linear-GitHub Sync ❗️ .env changes contains changes to env variables Medium priority Created by Linear-GitHub Sync ready-for-e2e 📉 regressing This used to work. Now it doesn't anymore. run-ci Approve CI to run for external contributors seats area: seats, guest meetings, multiple people size/L Stale

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[CAL-4553] Booking limits with seats not working

5 participants