fix(office365): handle seated bookings per-attendee to prevent misleading Outlook cancellation emails#29490
fix(office365): handle seated bookings per-attendee to prevent misleading Outlook cancellation emails#29490Maheshkumarjena wants to merge 20 commits into
Conversation
…cellation Split CalendarService.updateEvent() into two PATCH calls for seated events: 1. Event details only (no attendees) - silent update 2. Attendees only - Graph API notifies only the removed attendee Also pass seatsPerTimeSlot explicitly in cancelAttendeeSeat.ts so the seated code path triggers correctly in CalendarService.
|
Hey there and thank you for opening this pull request! 👋🏼 We require pull request titles to follow the Conventional Commits specification and it looks like your proposed title needs to be adjusted. Details: |
|
Welcome to Cal.diy, @Maheshkumarjena! Thanks for opening this pull request. A few things to keep in mind:
A maintainer will review your PR soon. Thanks for contributing! |
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (4)
🚧 Files skipped from review as they are similar to previous changes (4)
📝 WalkthroughWalkthroughThis PR enhances Office365 calendar management for seated bookings by introducing infrastructure for persisting seat-specific calendar references and implementing two-phase updates with retry logic. New utilities enable safe storage of per-integration reference metadata within booking seat records. The Office365 calendar service now splits attendee updates into conditional single- or two-step PATCH requests with exponential-backoff retry behavior. EventManager gains methods for creating per-attendee Office365 events and excluding calendar types during attendee updates. Seat create/cancel/reschedule flows and booking cancellation are updated to treat Office365 references separately. Email templates support configurable attendee visibility. 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/features/bookings/lib/handleSeats/reschedule/attendee/attendeeRescheduleSeatedBooking.ts (1)
77-114:⚠️ Potential issue | 🟠 Major | 🏗️ Heavy liftRescheduling never rewrites the attendee's Office365 seat event.
bookingSeatis moved tonewTimeSlotBookingwith its existing metadata, but both attendee update calls now exclude Office365 and there is no replacement create/delete flow here. That leaves the seat's Office365 reference pointing at the old slot after reschedule.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/features/bookings/lib/handleSeats/reschedule/attendee/attendeeRescheduleSeatedBooking.ts` around lines 77 - 114, Rescheduling does not update the Office365 seat event reference, leaving the bookingSeat's calendar pointer pointing to the old slot; modify the reschedule flow to clear and recreate/update Office365 references: when moving seatAttendee/bookingSeat to newTimeSlotBooking (symbols: seatAttendee, bookingSeat, newTimeSlotBooking) either include Office365 fields in the prisma.attendee.update/prisma.bookingSeat.update (set old calendar IDs to null) and then invoke the calendar create flow, or call the calendar API to delete the old Office365 event and create/update the new one via eventManager (e.g., use eventManager.updateCalendarAttendees or a dedicated create/delete method) so the Office365 event is removed from the old slot and recreated/linked to the newTimeSlotBooking; ensure evt/copyEvent and seatedCalendarAttendeeUpdateOptions are passed with the correct iCalUID and attendee info to recreate the seat event.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/app-store/office365calendar/lib/CalendarService.ts`:
- Around line 344-367: The PATCH calls in the seating flow always use
`${endpoint}/calendar/events/${uid}` and ignore event.externalCalendarId; update
the request URL construction in the branches that handle seats (the
onlyUpdateCalendarAttendees branch and the seatsPerTimeSlot branch where fetcher
is called) to choose the calendar-aware endpoint: if
translatedEvent.externalCalendarId (or event.externalCalendarId) exists, call
fetcher against `${endpoint}/calendar/${externalCalendarId}/events/${uid}` (same
change needed for the corresponding delete path referenced around the later
block), preserving existing body, retry/backoff logic (MAX_RETRIES, BACKOFF_MS)
and calls to handleErrorsJson; ensure you reference
`onlyUpdateCalendarAttendees`, `translatedEvent.attendees`,
`translatedEvent.externalCalendarId`, `fetcher`, `endpoint`, and `uid` when
making the URL change so create/update/delete use the same calendar-specific
path.
In `@packages/features/bookings/lib/EventManager.ts`:
- Around line 898-910: The per-seat Office365 loop currently skips destinations
when getCredential(...) returns undefined; change it to mirror the fallback used
in createAllCalendarEvents so missing credentials don't silently no-op. After
calling getCredential(...) for the destination, if credential is undefined,
attempt the same fallback resolution used in createAllCalendarEvents (i.e., look
up a matching credential in this.calendarCredentials by credentialId or
delegationCredentialId or call the same helper used there), then proceed to
await createOffice365CalendarEvent(...) with the resolved credential; ensure you
reference office365DestinationCalendars, getCredential,
createOffice365CalendarEvent, createAllCalendarEvents and
this.calendarCredentials when applying the fallback.
In `@packages/features/bookings/lib/handleSeats/cancel/cancelAttendeeSeat.ts`:
- Around line 85-111: The current loop only reads Office365 refs from
seatReference.metadata (seatOffice365CalendarReferences) and then removes all
booking-level Office365 refs from sharedReferencesToUpdate, which drops Outlook
updates for pre-migration bookings; fix by falling back to booking-level
Office365 references when seatOffice365CalendarReferences is empty: after
computing seatOffice365CalendarReferences (via getSeatCalendarReferences)
compute bookingLevelOffice365 = bookingToDelete.references.filter(r => r.type
=== OFFICE365_CALENDAR_TYPE) and set refsToProcess =
seatOffice365CalendarReferences.length ? seatOffice365CalendarReferences :
bookingLevelOffice365, then iterate refsToProcess in place of
seatOffice365CalendarReferences when calling
getDelegationCredentialOrFindRegularCredential/getCalendar/deleteEvent; adjust
construction of sharedReferencesToUpdate to only exclude Office365 refs that
were actually processed (i.e., exclude refs present in refsToProcess) so
pre-migration booking-level refs are not dropped when used as a fallback.
---
Outside diff comments:
In
`@packages/features/bookings/lib/handleSeats/reschedule/attendee/attendeeRescheduleSeatedBooking.ts`:
- Around line 77-114: Rescheduling does not update the Office365 seat event
reference, leaving the bookingSeat's calendar pointer pointing to the old slot;
modify the reschedule flow to clear and recreate/update Office365 references:
when moving seatAttendee/bookingSeat to newTimeSlotBooking (symbols:
seatAttendee, bookingSeat, newTimeSlotBooking) either include Office365 fields
in the prisma.attendee.update/prisma.bookingSeat.update (set old calendar IDs to
null) and then invoke the calendar create flow, or call the calendar API to
delete the old Office365 event and create/update the new one via eventManager
(e.g., use eventManager.updateCalendarAttendees or a dedicated create/delete
method) so the Office365 event is removed from the old slot and recreated/linked
to the newTimeSlotBooking; ensure evt/copyEvent and
seatedCalendarAttendeeUpdateOptions are passed with the correct iCalUID and
attendee info to recreate the seat event.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 48ecbc07-dcc7-41a7-a2f2-dd1e4a8fb254
📒 Files selected for processing (12)
packages/app-store/office365calendar/lib/CalendarService.tspackages/emails/email-manager.test.tspackages/emails/templates/attendee-scheduled-email.tspackages/features/bookings/lib/EventManager.tspackages/features/bookings/lib/handleCancelBooking.tspackages/features/bookings/lib/handleSeats/cancel/cancelAttendeeSeat.tspackages/features/bookings/lib/handleSeats/create/createNewSeat.tspackages/features/bookings/lib/handleSeats/lib/seatCalendarReferences.test.tspackages/features/bookings/lib/handleSeats/lib/seatCalendarReferences.tspackages/features/bookings/lib/handleSeats/reschedule/attendee/attendeeRescheduleSeatedBooking.tspackages/features/bookings/lib/service/RegularBookingService.tspackages/types/Calendar.d.ts
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/features/bookings/lib/service/RegularBookingService.ts (1)
2459-2500:⚠️ Potential issue | 🟠 Major | 🏗️ Heavy liftDon't silently lose the per-seat Office365 reference write.
cancelAttendeeSeatnow depends onbookingSeat.metadatafor Office365 cleanup, but this write happens after the booking reference insert and any failure is only logged. That leaves a created booking in a partial state where later single-seat cancellation can't find the attendee-specific Outlook event to delete. Please make the booking reference insert plus seat metadata update atomic, or fail this flow when the seat metadata write doesn't persist.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/features/bookings/lib/service/RegularBookingService.ts` around lines 2459 - 2500, The seat-specific Office365 reference write can fail silently because the booking update (deps.prismaClient.booking.update) and the per-seat update (deps.prismaClient.bookingSeat.update using withSeatCalendarReferences and office365SeatReferences) are executed separately; wrap both the booking.references createMany and the conditional bookingSeat.update into a single atomic Prisma transaction (e.g., deps.prismaClient.$transaction) inside RegularBookingService so that either both persist or the whole operation rolls back and you throw the error instead of only logging it; ensure you include the booking.update (including referencesToCreate) and the bookingSeat.update together, propagate the thrown error out of the catch, and remove the silent-only logging behavior so failures prevent partial state needed by cancelAttendeeSeat.
🧹 Nitpick comments (4)
packages/features/bookings/lib/handleSeats/cancel/cancelAttendeeSeat.ts (1)
24-31: ⚡ Quick winThese new comments should capture intent, not mechanics.
They mostly narrate what the code already says. As per coding guidelines, "Only add code comments that explain why, not what" and "Never add comments that simply restate what the code does".
Also applies to: 118-123, 152-158
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/features/bookings/lib/handleSeats/cancel/cancelAttendeeSeat.ts` around lines 24 - 31, The docblocks and inline comments in cancelAttendeeSeat (and the comment blocks around lines 118-123 and 152-158) are restating implementation details rather than documenting intent; replace them with brief intent-focused comments that explain why the function/blocks exist, the assumptions/invariants they rely on, and important side effects (e.g., why cancelAttendeeSeat must update external integrations/webhooks, expected ordering or transactional guarantees, error handling boundaries, and any business rules about seat state changes), and remove any comments that merely paraphrase the code to keep docs high-level and purposeful.packages/features/bookings/lib/service/RegularBookingService.ts (1)
490-498: ⚡ Quick winThese comments are documenting behavior, not rationale.
They read like restatements of nearby code. As per coding guidelines, "Only add code comments that explain why, not what" and "Never add comments that simply restate what the code does".
Also applies to: 2475-2480
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/features/bookings/lib/service/RegularBookingService.ts` around lines 490 - 498, The JSDoc on the RegularBookingService method that "Creates or reschedules a regular booking..." is restating code rather than explaining the rationale—remove or replace it with a short comment that documents why the method exists or important design/behavior decisions (e.g., why integration references are recorded, constraints around rescheduling, or business rules enforced) rather than what the code does; apply the same treatment to the similar comment block around the other occurrence referenced (the block at 2475-2480). Target the comment attached to the RegularBookingService handler and any adjacent JSDoc blocks that duplicate implementation detail.packages/app-store/office365calendar/lib/CalendarService.ts (1)
321-327: ⚡ Quick winTrim these new comments down to rationale only.
These blocks restate the implementation more than the reason for it, which makes an already dense path harder to scan. As per coding guidelines, "Only add code comments that explain why, not what" and "Never add comments that simply restate what the code does".
Also applies to: 390-398, 413-420
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/app-store/office365calendar/lib/CalendarService.ts` around lines 321 - 327, Trim the JSDoc blocks to only convey the rationale for the behavior (why the method handles seated-attendee-only updates specially), removing any sentences that simply restate the implementation or describe what the code does; specifically update the comment block above the Office365 event update method in CalendarService (the JSDoc at 321-327) and the other two blocks at 390-398 and 413-420 so they no longer duplicate parameter/return descriptions or implementation details but instead briefly explain the reasoning/intent behind the special-case handling and any non-obvious decisions.packages/emails/templates/attendee-scheduled-email.ts (1)
17-24: ⚡ Quick winPlease keep these comments at the “why” level.
Both additions mostly restate the constructor/filter behavior. As per coding guidelines, "Only add code comments that explain why, not what" and "Never add comments that simply restate what the code does".
Also applies to: 42-47
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/emails/templates/attendee-scheduled-email.ts` around lines 17 - 24, The JSDoc blocks in packages/emails/templates/attendee-scheduled-email.ts (the attendee scheduled email builder comment describing parameters calEvent, attendee, showAttendees and the similar block later) are restating what the constructor/filter does; replace them with a concise "why" comment that explains the design intent (e.g., why seated attendee visibility is applied and when showAttendees override exists), remove the redundant "what" descriptions of parameters and behavior, and keep the rationale tied to the attendee scheduled email generation and seated-attendee visibility logic so future readers understand the decision instead of duplicated implementation detail.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@packages/features/bookings/lib/service/RegularBookingService.ts`:
- Around line 2459-2500: The seat-specific Office365 reference write can fail
silently because the booking update (deps.prismaClient.booking.update) and the
per-seat update (deps.prismaClient.bookingSeat.update using
withSeatCalendarReferences and office365SeatReferences) are executed separately;
wrap both the booking.references createMany and the conditional
bookingSeat.update into a single atomic Prisma transaction (e.g.,
deps.prismaClient.$transaction) inside RegularBookingService so that either both
persist or the whole operation rolls back and you throw the error instead of
only logging it; ensure you include the booking.update (including
referencesToCreate) and the bookingSeat.update together, propagate the thrown
error out of the catch, and remove the silent-only logging behavior so failures
prevent partial state needed by cancelAttendeeSeat.
---
Nitpick comments:
In `@packages/app-store/office365calendar/lib/CalendarService.ts`:
- Around line 321-327: Trim the JSDoc blocks to only convey the rationale for
the behavior (why the method handles seated-attendee-only updates specially),
removing any sentences that simply restate the implementation or describe what
the code does; specifically update the comment block above the Office365 event
update method in CalendarService (the JSDoc at 321-327) and the other two blocks
at 390-398 and 413-420 so they no longer duplicate parameter/return descriptions
or implementation details but instead briefly explain the reasoning/intent
behind the special-case handling and any non-obvious decisions.
In `@packages/emails/templates/attendee-scheduled-email.ts`:
- Around line 17-24: The JSDoc blocks in
packages/emails/templates/attendee-scheduled-email.ts (the attendee scheduled
email builder comment describing parameters calEvent, attendee, showAttendees
and the similar block later) are restating what the constructor/filter does;
replace them with a concise "why" comment that explains the design intent (e.g.,
why seated attendee visibility is applied and when showAttendees override
exists), remove the redundant "what" descriptions of parameters and behavior,
and keep the rationale tied to the attendee scheduled email generation and
seated-attendee visibility logic so future readers understand the decision
instead of duplicated implementation detail.
In `@packages/features/bookings/lib/handleSeats/cancel/cancelAttendeeSeat.ts`:
- Around line 24-31: The docblocks and inline comments in cancelAttendeeSeat
(and the comment blocks around lines 118-123 and 152-158) are restating
implementation details rather than documenting intent; replace them with brief
intent-focused comments that explain why the function/blocks exist, the
assumptions/invariants they rely on, and important side effects (e.g., why
cancelAttendeeSeat must update external integrations/webhooks, expected ordering
or transactional guarantees, error handling boundaries, and any business rules
about seat state changes), and remove any comments that merely paraphrase the
code to keep docs high-level and purposeful.
In `@packages/features/bookings/lib/service/RegularBookingService.ts`:
- Around line 490-498: The JSDoc on the RegularBookingService method that
"Creates or reschedules a regular booking..." is restating code rather than
explaining the rationale—remove or replace it with a short comment that
documents why the method exists or important design/behavior decisions (e.g.,
why integration references are recorded, constraints around rescheduling, or
business rules enforced) rather than what the code does; apply the same
treatment to the similar comment block around the other occurrence referenced
(the block at 2475-2480). Target the comment attached to the
RegularBookingService handler and any adjacent JSDoc blocks that duplicate
implementation detail.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 0fecd827-f3e3-4705-ba38-ff44b0eac67b
📒 Files selected for processing (9)
packages/app-store/office365calendar/lib/CalendarService.tspackages/emails/templates/attendee-scheduled-email.tspackages/features/bookings/lib/EventManager.tspackages/features/bookings/lib/handleCancelBooking.tspackages/features/bookings/lib/handleSeats/cancel/cancelAttendeeSeat.tspackages/features/bookings/lib/handleSeats/create/createNewSeat.tspackages/features/bookings/lib/handleSeats/lib/seatCalendarReferences.tspackages/features/bookings/lib/handleSeats/reschedule/attendee/attendeeRescheduleSeatedBooking.tspackages/features/bookings/lib/service/RegularBookingService.ts
🚧 Files skipped from review as they are similar to previous changes (5)
- packages/features/bookings/lib/handleSeats/create/createNewSeat.ts
- packages/features/bookings/lib/handleSeats/reschedule/attendee/attendeeRescheduleSeatedBooking.ts
- packages/features/bookings/lib/handleSeats/lib/seatCalendarReferences.ts
- packages/features/bookings/lib/EventManager.ts
- packages/features/bookings/lib/handleCancelBooking.ts
…nd pre-migration fallback for seated bookings
What does this PR do?
Fixes Outlook/Microsoft 365 behavior for seated bookings where Cal.com previously mutated one shared Outlook event for all attendees, causing two issues:
Root cause: Outlook/Exchange treats attendee-list changes on a shared organizer event as organizer-level updates. For seated bookings, seat-level changes should not appear as a full meeting cancellation or significant change.
Changes made:
BookingSeatinstead of shared across the booking.Visual Demo (For contributors especially)
Video Demo (if applicable):
Before: Attendee-level changes trigger misleading Outlook cancellations/updates for other attendees. Later attendees receive invite content referencing the first attendee.
before.fix.video_1.mp4
After: Each attendee receives their own Outlook invite. Existing attendees are unaffected when another joins or leaves. Canceling one seat only affects that attendee's event.
final.fix.with.everything.functional_1.mp4
Image Demo (if applicable):
N/A
Mandatory Tasks (DO NOT REMOVE)
How should this be tested?
Environment variables required:
Do not commit real values.
Setup: Configure the official Office365 calendar integration and create a seated booking event type connected to it.
Manual test steps:
Automated tests:
Checklist