From e04a394e1c0953a2671d03a24a2e558ce650e873 Mon Sep 17 00:00:00 2001 From: Hariom Balhara <1780212+hariombalhara@users.noreply.github.com> Date: Mon, 9 Feb 2026 17:55:33 +0530 Subject: [PATCH] fix: (booking-audit) Remove IS_PRODUCTION gate and add feature flag check in producer (#26524) * fix: remove IS_PRODUCTION gate from BookingAuditProducer Remove the IS_PRODUCTION check that was preventing booking audits from being queued in production. Audits are still properly gated by: 1. Organization check: Audits are skipped for non-organization bookings (organizationId === null) 2. Feature flag: The BookingAuditTaskConsumer checks if the 'booking-audit' feature is enabled for the organization via featuresRepository.checkIfTeamHasFeature() The IS_PRODUCTION gate was intentionally added to prevent logs from being created in production while the action data versioning was being actively reviewed and finalized. Without proper versioning handling, the Booking History UI could crash when encountering unversioned data. Now that the versioning system is in place, this gate can be safely removed. Co-Authored-By: hariom@cal.com * fix: revert formatting, keep only IS_PRODUCTION removal Reverts the unintended formatting changes from the previous commit. Only removes the IS_PRODUCTION gate without changing indentation. Co-Authored-By: hariom@cal.com * feat: add booking-audit feature flag check in producer to avoid unnecessary task creation - Query booking-audit and booking-email-sms-tasker flags in parallel before fireBookingEvents - Pass isBookingAuditEnabled through BookingEventHandler to producer's queueTask method - Add conditional check in queueTask with debug log when skipping audit - Reuse pre-queried isBookingEmailSmsTaskerEnabled flag instead of querying again Co-Authored-By: hariom@cal.com * feat: make isBookingAuditEnabled required and pass it through all booking audit flows - Make isBookingAuditEnabled a required property in BookingAuditProducerService interface - Update all BookingEventHandler methods to require isBookingAuditEnabled - Add feature flag check in all flows that call booking audit: - handleSeats (seat booking/rescheduling) - RecurringBookingService (bulk bookings) - handleCancelBooking (booking cancellation) - handleConfirmation (booking acceptance) - roundRobinReassignment (automatic reassignment) - roundRobinManualReassignment (manual reassignment) - trpc handlers: addGuests, confirm, editLocation, requestReschedule - Skip queueing audit tasks when feature is disabled with debug logging Co-Authored-By: hariom@cal.com * fix: add featuresRepository dependency to RecurringBookingService DI module Co-Authored-By: hariom@cal.com * refactor: make isBookingAuditEnabled optional for non-main flows Keep feature flag check only in main flows (handleSeats, RegularBookingService, RecurringBookingService) which are frequently triggered. For other flows (handleCancelBooking, handleConfirmation, roundRobinReassignment, etc.), rely on the existing consumer-level check. Changes: - Revert feature flag check from non-main flows - Make isBookingAuditEnabled optional in interface for non-main flow methods - Keep isBookingAuditEnabled required for main flow methods (queueCreatedAudit, queueRescheduledAudit, queueSeatBookedAudit, queueSeatRescheduledAudit, queueBulkCreatedAudit, queueBulkRescheduledAudit) - Update BookingEventHandlerService to use required params for main flows and optional for non-main flows Co-Authored-By: hariom@cal.com * refactor: remove isBookingAuditEnabled from non-main flow methods Co-Authored-By: hariom@cal.com * feat: make isBookingAuditEnabled required in all BookingEventHandlerService methods - Add isBookingAuditEnabled as required parameter in all BookingAuditProducerService interface methods - Update BookingAuditTaskerProducerService to use simplified check (!params.isBookingAuditEnabled) - Update BookingEventHandlerService to require isBookingAuditEnabled in all methods - Update all callers to query booking-audit feature flag and pass isBookingAuditEnabled: - handleCancelBooking - handleConfirmation - roundRobinReassignment - roundRobinManualReassignment - addGuests.handler - confirm.handler - editLocation.handler - requestReschedule.handler - Inject featuresRepository in API V2's booking-location.service.ts Co-Authored-By: hariom@cal.com * test: update roundRobinReassignment tests to include isBookingAuditEnabled Co-Authored-By: hariom@cal.com * test: update roundRobinManualReassignment tests to include isBookingAuditEnabled Co-Authored-By: hariom@cal.com * fix: inject featuresRepository in API V2 RecurringBookingService Co-Authored-By: hariom@cal.com * fix: import PrismaWorkerModule for PrismaFeaturesRepository dependency Co-Authored-By: hariom@cal.com * fix: use booking's organization for feature flag check in addGuests handler Use booking.user?.profiles?.[0]?.organizationId instead of user.organizationId to check the booking-audit feature flag. This ensures the feature flag is checked against the booking's organization rather than the actor's organization, which is consistent with other handlers in this PR. Addresses Cubic AI review feedback (confidence 9/10). Co-Authored-By: unknown <> * Add comment * fix: use user.organizationId for feature flag check in addGuests handler Co-Authored-By: hariom@cal.com * fix: use booking's organizationId for feature flag check in addGuests handler Co-Authored-By: hariom@cal.com * fix: revert to user.organizationId for feature flag check in addGuests handler Co-Authored-By: hariom@cal.com * fix: add isBookingAuditEnabled to onNoShowUpdated calls after main merge Co-Authored-By: hariom@cal.com * fix: add isBookingAuditEnabled to onNoShowUpdated calls and update tests Co-Authored-By: hariom@cal.com --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../ee/bookings/2024-08-13/bookings.module.ts | 2 + .../services/booking-location.service.ts | 12 +- .../lib/modules/recurring-booking.module.ts | 6 +- .../lib/services/recurring-booking.service.ts | 5 +- .../BookingAuditProducerService.interface.ts | 24 +++- .../BookingAuditTaskerProducerService.ts | 52 ++++++- .../di/RecurringBookingService.module.ts | 2 + .../bookings/lib/handleCancelBooking.ts | 8 ++ .../bookings/lib/handleConfirmation.ts | 11 ++ .../bookings/lib/handleSeats/handleSeats.ts | 15 +- .../BookingEventHandlerService.ts | 81 +++++++---- .../lib/service/RecurringBookingService.ts | 8 ++ .../lib/service/RegularBookingService.ts | 133 ++++++++++-------- .../roundRobinManualReassignment.test.ts | 2 + .../roundRobinManualReassignment.ts | 6 + .../roundRobinReassignment.test.ts | 2 + .../ee/round-robin/roundRobinReassignment.ts | 7 + packages/features/handleMarkNoShow.ts | 6 + .../tasker/tasks/triggerNoShow/common.ts | 7 + .../triggerNoShow/triggerGuestNoShow.test.ts | 10 ++ .../triggerNoShow/triggerHostNoShow.test.ts | 11 ++ .../viewer/bookings/addGuests.handler.ts | 10 +- .../viewer/bookings/confirm.handler.ts | 11 ++ .../viewer/bookings/editLocation.handler.ts | 7 + .../bookings/requestReschedule.handler.ts | 7 + 25 files changed, 342 insertions(+), 103 deletions(-) diff --git a/apps/api/v2/src/ee/bookings/2024-08-13/bookings.module.ts b/apps/api/v2/src/ee/bookings/2024-08-13/bookings.module.ts index ff1f25c63e..3ed1af1e63 100644 --- a/apps/api/v2/src/ee/bookings/2024-08-13/bookings.module.ts +++ b/apps/api/v2/src/ee/bookings/2024-08-13/bookings.module.ts @@ -26,6 +26,7 @@ import { SchedulesModule_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/ import { InstantBookingModule } from "@/lib/modules/instant-booking.module"; import { RecurringBookingModule } from "@/lib/modules/recurring-booking.module"; import { RegularBookingModule } from "@/lib/modules/regular-booking.module"; +import { PrismaFeaturesRepository } from "@/lib/repositories/prisma-features.repository"; import { ApiKeysRepository } from "@/modules/api-keys/api-keys-repository"; import { AppsRepository } from "@/modules/apps/apps.repository"; import { BillingModule } from "@/modules/billing/billing.module"; @@ -105,6 +106,7 @@ import { BookingEventHandlerModule } from "@/lib/modules/booking-event-handler.m CalVideoOutputService, BookingPbacGuard, BookingLocationService_2024_08_13, + PrismaFeaturesRepository, ], controllers: [ BookingsController_2024_08_13, diff --git a/apps/api/v2/src/ee/bookings/2024-08-13/services/booking-location.service.ts b/apps/api/v2/src/ee/bookings/2024-08-13/services/booking-location.service.ts index 44318b530c..a9eff5ebb8 100644 --- a/apps/api/v2/src/ee/bookings/2024-08-13/services/booking-location.service.ts +++ b/apps/api/v2/src/ee/bookings/2024-08-13/services/booking-location.service.ts @@ -10,6 +10,7 @@ import { BookingsRepository_2024_08_13 } from "@/ee/bookings/2024-08-13/reposito import { BookingsService_2024_08_13 } from "@/ee/bookings/2024-08-13/services/bookings.service"; import { InputBookingsService_2024_08_13 } from "@/ee/bookings/2024-08-13/services/input.service"; import { EventTypesRepository_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/event-types.repository"; +import { PrismaFeaturesRepository } from "@/lib/repositories/prisma-features.repository"; import { BookingEventHandlerService } from "@/lib/services/booking-event-handler.service"; import { ApiAuthGuardUser } from "@/modules/auth/strategies/api-auth/api-auth.strategy"; import { EventTypeAccessService } from "@/modules/event-types/services/event-type-access.service"; @@ -26,7 +27,8 @@ export class BookingLocationService_2024_08_13 { private readonly inputService: InputBookingsService_2024_08_13, private readonly eventTypesRepository: EventTypesRepository_2024_06_14, private readonly eventTypeAccessService: EventTypeAccessService, - private readonly bookingEventHandlerService: BookingEventHandlerService + private readonly bookingEventHandlerService: BookingEventHandlerService, + private readonly featuresRepository: PrismaFeaturesRepository ) {} async updateBookingLocation( @@ -99,10 +101,15 @@ export class BookingLocationService_2024_08_13 { responses: updatedBookingResponses, }); + const organizationId = existingBookingHost.organizationId ?? null; + const isBookingAuditEnabled = organizationId + ? await this.featuresRepository.checkIfTeamHasFeature(organizationId, "booking-audit") + : false; + await this.bookingEventHandlerService.onLocationChanged({ bookingUid: existingBooking.uid, actor: makeUserActor(user.uuid), - organizationId: existingBookingHost.organizationId ?? null, + organizationId, source: "API_V2", auditData: { location: { @@ -110,6 +117,7 @@ export class BookingLocationService_2024_08_13 { new: bookingLocation, }, }, + isBookingAuditEnabled, }); return this.bookingsService.getBooking(updatedBooking.uid, user); diff --git a/apps/api/v2/src/lib/modules/recurring-booking.module.ts b/apps/api/v2/src/lib/modules/recurring-booking.module.ts index b3849bdd30..9a0a8614a4 100644 --- a/apps/api/v2/src/lib/modules/recurring-booking.module.ts +++ b/apps/api/v2/src/lib/modules/recurring-booking.module.ts @@ -7,8 +7,10 @@ import { Scope } from "@nestjs/common"; import { BookingAuditProducerService } from "@/lib/services/booking-audit-producer.service"; import { HashedLinkService } from "@/lib/services/hashed-link.service"; import { TaskerService } from "@/lib/services/tasker.service"; +import { PrismaFeaturesRepository } from "@/lib/repositories/prisma-features.repository"; +import { PrismaWorkerModule } from "@/modules/prisma/prisma-worker.module"; @Module({ - imports: [RegularBookingModule], + imports: [RegularBookingModule, PrismaWorkerModule], providers: [ RecurringBookingService, BookingEventHandlerService, @@ -24,6 +26,8 @@ import { TaskerService } from "@/lib/services/tasker.service"; BookingAuditProducerService, TaskerService, /** Required by BookingEventHandlerService - Ends **/ + /** Required by RecurringBookingService **/ + PrismaFeaturesRepository, ], exports: [RecurringBookingService], }) diff --git a/apps/api/v2/src/lib/services/recurring-booking.service.ts b/apps/api/v2/src/lib/services/recurring-booking.service.ts index 79888bdf6e..ffa418a9f8 100644 --- a/apps/api/v2/src/lib/services/recurring-booking.service.ts +++ b/apps/api/v2/src/lib/services/recurring-booking.service.ts @@ -1,5 +1,6 @@ import { RegularBookingService } from "@/lib/services/regular-booking.service"; import { BookingEventHandlerService } from "@/lib/services/booking-event-handler.service"; +import { PrismaFeaturesRepository } from "@/lib/repositories/prisma-features.repository"; import { Injectable } from "@nestjs/common"; import { RecurringBookingService as BaseRecurringBookingService } from "@calcom/platform-libraries/bookings"; @@ -8,11 +9,13 @@ import { RecurringBookingService as BaseRecurringBookingService } from "@calcom/ export class RecurringBookingService extends BaseRecurringBookingService { constructor( regularBookingService: RegularBookingService, - bookingEventHandler: BookingEventHandlerService + bookingEventHandler: BookingEventHandlerService, + featuresRepository: PrismaFeaturesRepository ) { super({ regularBookingService, bookingEventHandler, + featuresRepository, }); } } diff --git a/packages/features/booking-audit/lib/service/BookingAuditProducerService.interface.ts b/packages/features/booking-audit/lib/service/BookingAuditProducerService.interface.ts index 239d6d0d1b..1a4aa0c232 100644 --- a/packages/features/booking-audit/lib/service/BookingAuditProducerService.interface.ts +++ b/packages/features/booking-audit/lib/service/BookingAuditProducerService.interface.ts @@ -38,6 +38,7 @@ export interface BookingAuditProducerService { operationId?: string | null; data: z.infer; context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise; queueRescheduledAudit(params: { @@ -48,6 +49,7 @@ export interface BookingAuditProducerService { operationId?: string | null; data: z.infer; context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise; queueAcceptedAudit(params: { @@ -58,6 +60,7 @@ export interface BookingAuditProducerService { operationId?: string | null; data: z.infer; context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise; queueCancelledAudit(params: { @@ -68,6 +71,7 @@ export interface BookingAuditProducerService { operationId?: string | null; data: z.infer; context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise; queueRescheduleRequestedAudit(params: { @@ -78,6 +82,7 @@ export interface BookingAuditProducerService { operationId?: string | null; data: z.infer; context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise; queueAttendeeAddedAudit(params: { @@ -88,6 +93,7 @@ export interface BookingAuditProducerService { operationId?: string | null; data: z.infer; context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise; queueNoShowUpdatedAudit(params: { @@ -98,6 +104,7 @@ export interface BookingAuditProducerService { operationId?: string | null; data: z.infer; context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise; queueRejectedAudit(params: { @@ -108,6 +115,7 @@ export interface BookingAuditProducerService { operationId?: string | null; data: z.infer; context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise; queueAttendeeRemovedAudit(params: { @@ -118,6 +126,7 @@ export interface BookingAuditProducerService { operationId?: string | null; data: z.infer; context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise; queueReassignmentAudit(params: { @@ -128,6 +137,7 @@ export interface BookingAuditProducerService { operationId?: string | null; data: z.infer; context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise; queueLocationChangedAudit(params: { @@ -138,6 +148,7 @@ export interface BookingAuditProducerService { operationId?: string | null; data: z.infer; context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise; queueSeatBookedAudit(params: { @@ -148,6 +159,7 @@ export interface BookingAuditProducerService { operationId?: string | null; data: z.infer; context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise; queueSeatRescheduledAudit(params: { @@ -158,11 +170,9 @@ export interface BookingAuditProducerService { operationId?: string | null; data: z.infer; context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise; - /** - * Queues a bulk accepted audit task for multiple bookings - */ queueBulkAcceptedAudit(params: { bookings: Array<{ bookingUid: string; @@ -173,11 +183,9 @@ export interface BookingAuditProducerService { source: ActionSource; operationId?: string | null; context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise; - /** - * Queues a bulk cancelled audit task for multiple bookings - */ queueBulkCancelledAudit(params: { bookings: Array<{ bookingUid: string; @@ -188,6 +196,7 @@ export interface BookingAuditProducerService { source: ActionSource; operationId?: string | null; context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise; queueBulkCreatedAudit(params: { @@ -199,6 +208,7 @@ export interface BookingAuditProducerService { organizationId: number | null; source: ActionSource; operationId?: string | null; + isBookingAuditEnabled: boolean; }): Promise; queueBulkRescheduledAudit(params: { @@ -210,6 +220,7 @@ export interface BookingAuditProducerService { organizationId: number | null; source: ActionSource; operationId?: string | null; + isBookingAuditEnabled: boolean; }): Promise; queueBulkRejectedAudit(params: { @@ -222,5 +233,6 @@ export interface BookingAuditProducerService { source: ActionSource; operationId?: string | null; context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise; } diff --git a/packages/features/booking-audit/lib/service/BookingAuditTaskerProducerService.ts b/packages/features/booking-audit/lib/service/BookingAuditTaskerProducerService.ts index c800a5c9c5..669c362dac 100644 --- a/packages/features/booking-audit/lib/service/BookingAuditTaskerProducerService.ts +++ b/packages/features/booking-audit/lib/service/BookingAuditTaskerProducerService.ts @@ -1,7 +1,6 @@ import { z } from "zod"; import { v4 as uuidv4 } from "uuid"; import type { Tasker } from "@calcom/features/tasker/tasker"; -import { IS_PRODUCTION } from "@calcom/lib/constants"; import { safeStringify } from "@calcom/lib/safeStringify"; import type { ISimpleLogger } from "@calcom/features/di/shared/services/logger.service"; @@ -89,6 +88,8 @@ export class BookingAuditTaskerProducerService implements BookingAuditProducerSe * Internal helper to queue audit task to Tasker * @param params.action - Must be a valid BookingAuditAction value (TYPE from action services are string-typed) * @param params.operationId - Optional operation ID for correlating bulk operations. If null, will be auto-generated. + * @param params.isBookingAuditEnabled - Flag indicating if booking audit is enabled for the organization. + * When false, skips queueing. */ private async queueTask(params: { bookingUid: string; @@ -99,12 +100,18 @@ export class BookingAuditTaskerProducerService implements BookingAuditProducerSe operationId?: string | null; data: unknown; context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise { // Skip queueing for non-organization bookings if (params.organizationId === null) { return; } - if (IS_PRODUCTION) { + // Skip queueing if booking audit is disabled for this organization + if (!params.isBookingAuditEnabled) { + this.log.debug( + `Skipping ${params.action} audit: booking-audit feature is disabled for organization`, + { organizationId: params.organizationId, bookingUid: params.bookingUid, action: params.action } + ); return; } try { @@ -138,6 +145,8 @@ export class BookingAuditTaskerProducerService implements BookingAuditProducerSe source: ActionSource; operationId?: string | null; data: z.infer; + context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise { await this.queueTask({ ...params, @@ -152,6 +161,8 @@ export class BookingAuditTaskerProducerService implements BookingAuditProducerSe source: ActionSource; operationId?: string | null; data: z.infer; + context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise { await this.queueTask({ ...params, @@ -166,6 +177,8 @@ export class BookingAuditTaskerProducerService implements BookingAuditProducerSe source: ActionSource; operationId?: string | null; data: z.infer; + context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise { await this.queueTask({ ...params, @@ -180,6 +193,8 @@ export class BookingAuditTaskerProducerService implements BookingAuditProducerSe source: ActionSource; operationId?: string | null; data: z.infer; + context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise { await this.queueTask({ ...params, @@ -194,6 +209,8 @@ export class BookingAuditTaskerProducerService implements BookingAuditProducerSe source: ActionSource; operationId?: string | null; data: z.infer; + context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise { await this.queueTask({ ...params, @@ -208,6 +225,8 @@ export class BookingAuditTaskerProducerService implements BookingAuditProducerSe source: ActionSource; operationId?: string | null; data: z.infer; + context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise { await this.queueTask({ ...params, @@ -223,6 +242,7 @@ export class BookingAuditTaskerProducerService implements BookingAuditProducerSe operationId?: string | null; data: z.infer; context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise { await this.queueTask({ ...params, @@ -237,6 +257,8 @@ export class BookingAuditTaskerProducerService implements BookingAuditProducerSe source: ActionSource; operationId?: string | null; data: z.infer; + context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise { await this.queueTask({ ...params, @@ -251,6 +273,8 @@ export class BookingAuditTaskerProducerService implements BookingAuditProducerSe source: ActionSource; operationId?: string | null; data: z.infer; + context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise { await this.queueTask({ ...params, @@ -265,6 +289,8 @@ export class BookingAuditTaskerProducerService implements BookingAuditProducerSe source: ActionSource; operationId?: string | null; data: z.infer; + context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise { await this.queueTask({ ...params, @@ -279,6 +305,8 @@ export class BookingAuditTaskerProducerService implements BookingAuditProducerSe source: ActionSource; operationId?: string | null; data: z.infer; + context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise { await this.queueTask({ ...params, @@ -293,6 +321,8 @@ export class BookingAuditTaskerProducerService implements BookingAuditProducerSe source: ActionSource; operationId?: string | null; data: z.infer; + context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise { await this.queueTask({ ...params, @@ -307,6 +337,8 @@ export class BookingAuditTaskerProducerService implements BookingAuditProducerSe source: ActionSource; operationId?: string | null; data: z.infer; + context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise { await this.queueTask({ ...params, @@ -325,12 +357,18 @@ export class BookingAuditTaskerProducerService implements BookingAuditProducerSe source: ActionSource; operationId?: string | null; context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise { // Skip queueing for non-organization bookings if (params.organizationId === null) { return; } - if (IS_PRODUCTION) { + // Skip queueing if booking audit is disabled for this organization + if (!params.isBookingAuditEnabled) { + this.log.debug( + `Skipping bulk ${params.action} audit: booking-audit feature is disabled for organization`, + { organizationId: params.organizationId, bookingCount: params.bookings.length, action: params.action } + ); return; } try { @@ -365,6 +403,8 @@ export class BookingAuditTaskerProducerService implements BookingAuditProducerSe organizationId: number | null; source: ActionSource; operationId?: string | null; + context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise { await this.queueBulkTask({ ...params, @@ -381,6 +421,8 @@ export class BookingAuditTaskerProducerService implements BookingAuditProducerSe organizationId: number | null; source: ActionSource; operationId?: string | null; + context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise { await this.queueBulkTask({ ...params, @@ -397,6 +439,7 @@ export class BookingAuditTaskerProducerService implements BookingAuditProducerSe organizationId: number | null; source: ActionSource; operationId?: string | null; + isBookingAuditEnabled: boolean; }): Promise { await this.queueBulkTask({ ...params, @@ -413,6 +456,7 @@ export class BookingAuditTaskerProducerService implements BookingAuditProducerSe organizationId: number | null; source: ActionSource; operationId?: string | null; + isBookingAuditEnabled: boolean; }): Promise { await this.queueBulkTask({ ...params, @@ -429,6 +473,8 @@ export class BookingAuditTaskerProducerService implements BookingAuditProducerSe organizationId: number | null; source: ActionSource; operationId?: string | null; + context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }): Promise { await this.queueBulkTask({ ...params, diff --git a/packages/features/bookings/di/RecurringBookingService.module.ts b/packages/features/bookings/di/RecurringBookingService.module.ts index 06f1f69a8a..bbcb1977ae 100644 --- a/packages/features/bookings/di/RecurringBookingService.module.ts +++ b/packages/features/bookings/di/RecurringBookingService.module.ts @@ -1,5 +1,6 @@ import { RecurringBookingService } from "@calcom/features/bookings/lib/service/RecurringBookingService"; import { createModule, bindModuleToClassOnToken } from "@calcom/features/di/di"; +import { moduleLoader as featuresRepositoryModuleLoader } from "@calcom/features/di/modules/FeaturesRepository"; import { DI_TOKENS } from "@calcom/features/di/tokens"; import { moduleLoader as regularBookingServiceModuleLoader } from "./RegularBookingService.module"; @@ -17,6 +18,7 @@ const loadModule = bindModuleToClassOnToken({ depsMap: { regularBookingService: regularBookingServiceModuleLoader, bookingEventHandler: bookingEventHandlerModuleLoader, + featuresRepository: featuresRepositoryModuleLoader, }, }); diff --git a/packages/features/bookings/lib/handleCancelBooking.ts b/packages/features/bookings/lib/handleCancelBooking.ts index 8f4bcfbd7a..92f278c543 100644 --- a/packages/features/bookings/lib/handleCancelBooking.ts +++ b/packages/features/bookings/lib/handleCancelBooking.ts @@ -13,6 +13,7 @@ import { import type { ActionSource } from "@calcom/features/booking-audit/lib/types/actionSource"; import { BookingReferenceRepository } from "@calcom/features/bookingReference/repositories/BookingReferenceRepository"; import { getBookingEventHandlerService } from "@calcom/features/bookings/di/BookingEventHandlerService.container"; +import { getFeaturesRepository } from "@calcom/features/di/containers/FeaturesRepository"; import EventManager from "@calcom/features/bookings/lib/EventManager"; import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses"; import { processNoShowFeeOnCancellation } from "@calcom/features/bookings/lib/payment/processNoShowFeeOnCancellation"; @@ -273,6 +274,11 @@ async function handler(input: CancelBookingInput, dependencies?: Dependencies) { const orgId = await getOrgIdFromMemberOrTeamId({ memberId: organizerUserId, teamId }); + const featuresRepository = getFeaturesRepository(); + const isBookingAuditEnabled = orgId + ? await featuresRepository.checkIfTeamHasFeature(orgId, "booking-audit") + : false; + const subscriberOptions: GetSubscriberOptions = { userId: organizerUserId, eventTypeId: bookingToDelete.eventTypeId as number, @@ -536,6 +542,7 @@ async function handler(input: CancelBookingInput, dependencies?: Dependencies) { organizationId: orgId ?? null, operationId, source: actionSource, + isBookingAuditEnabled, }); } else { if (bookingToDelete?.eventType?.seatsPerTimeSlot) { @@ -570,6 +577,7 @@ async function handler(input: CancelBookingInput, dependencies?: Dependencies) { new: BookingStatus.CANCELLED, }, }, + isBookingAuditEnabled, }); if (bookingToDelete.payment.some((payment) => payment.paymentOption === "ON_BOOKING")) { diff --git a/packages/features/bookings/lib/handleConfirmation.ts b/packages/features/bookings/lib/handleConfirmation.ts index f12a7dacf5..8f7bf89ffd 100644 --- a/packages/features/bookings/lib/handleConfirmation.ts +++ b/packages/features/bookings/lib/handleConfirmation.ts @@ -4,6 +4,7 @@ import { sendScheduledEmailsAndSMS } from "@calcom/emails/email-manager"; import type { Actor } from "@calcom/features/booking-audit/lib/dto/types"; import type { ActionSource } from "@calcom/features/booking-audit/lib/types/actionSource"; import { getBookingEventHandlerService } from "@calcom/features/bookings/di/BookingEventHandlerService.container"; +import { getFeaturesRepository } from "@calcom/features/di/containers/FeaturesRepository"; import type { EventManagerUser } from "@calcom/features/bookings/lib/EventManager"; import EventManager, { placeholderCreatedEvent } from "@calcom/features/bookings/lib/EventManager"; import { CreditService } from "@calcom/features/ee/billing/credit-service"; @@ -43,6 +44,7 @@ async function fireBookingAcceptedEvent({ organizationId, actionSource, acceptedBookings, + isBookingAuditEnabled, tracingLogger, }: { actor: Actor; @@ -52,6 +54,7 @@ async function fireBookingAcceptedEvent({ uid: string; oldStatus: BookingStatus; }[]; + isBookingAuditEnabled: boolean; tracingLogger: ISimpleLogger; }) { try { @@ -69,6 +72,7 @@ async function fireBookingAcceptedEvent({ organizationId, operationId, source: actionSource, + isBookingAuditEnabled, }); } else if (acceptedBookings.length === 1) { const acceptedBooking = acceptedBookings[0]; @@ -80,6 +84,7 @@ async function fireBookingAcceptedEvent({ status: { old: acceptedBooking.oldStatus, new: BookingStatus.ACCEPTED }, }, source: actionSource, + isBookingAuditEnabled, }); } } catch (error) { @@ -410,6 +415,11 @@ export async function handleConfirmation(args: { const orgId = await getOrgIdFromMemberOrTeamId({ memberId: userId, teamId }); + const featuresRepository = getFeaturesRepository(); + const isBookingAuditEnabled = orgId + ? await featuresRepository.checkIfTeamHasFeature(orgId, "booking-audit") + : false; + const bookerUrl = await getBookerBaseUrl(orgId ?? null); await fireBookingAcceptedEvent({ @@ -417,6 +427,7 @@ export async function handleConfirmation(args: { acceptedBookings, organizationId: orgId ?? null, actionSource, + isBookingAuditEnabled, tracingLogger, }); diff --git a/packages/features/bookings/lib/handleSeats/handleSeats.ts b/packages/features/bookings/lib/handleSeats/handleSeats.ts index 62b696d63a..7c1153a449 100644 --- a/packages/features/bookings/lib/handleSeats/handleSeats.ts +++ b/packages/features/bookings/lib/handleSeats/handleSeats.ts @@ -3,6 +3,7 @@ import { handleWebhookTrigger } from "@calcom/features/bookings/lib/handleWebhoo import { CreditService } from "@calcom/features/ee/billing/credit-service"; import { WorkflowService } from "@calcom/features/ee/workflows/lib/service/WorkflowService"; import type { EventPayloadType } from "@calcom/features/webhooks/lib/sendPayload"; +import type { FeaturesRepository } from "@calcom/features/flags/features.repository"; import { ErrorCode } from "@calcom/lib/errorCodes"; import { HttpError } from "@calcom/lib/http-error"; import prisma from "@calcom/prisma"; @@ -28,6 +29,7 @@ const fireBookingEvents = async ({ rescheduledBy, actionSource, actorUserUuid, + isBookingAuditEnabled, deps, }: { previousSeatedBooking: SeatedBooking; @@ -40,6 +42,7 @@ const fireBookingEvents = async ({ rescheduledBy: string | undefined; actionSource: ActionSource; actorUserUuid: string | null; + isBookingAuditEnabled: boolean; deps: { bookingEventHandler: BookingEventHandlerService; logger: ISimpleLogger; @@ -104,6 +107,7 @@ const fireBookingEvents = async ({ }, }, source: actionSource, + isBookingAuditEnabled, }); } else { await deps.bookingEventHandler.onSeatBooked({ @@ -118,6 +122,7 @@ const fireBookingEvents = async ({ endTime: previousSeatedBooking.endTime.getTime(), }, source: actionSource, + isBookingAuditEnabled, }); } } catch (error) { @@ -125,7 +130,10 @@ const fireBookingEvents = async ({ } }; -const handleSeats = async (newSeatedBookingObject: NewSeatedBookingObject) => { +const handleSeats = async ( + newSeatedBookingObject: NewSeatedBookingObject, + featuresRepository: FeaturesRepository +) => { const { eventType, reqBodyUser, @@ -221,6 +229,10 @@ const handleSeats = async (newSeatedBookingObject: NewSeatedBookingObject) => { // If the resultBooking is defined we should trigger workflows else, trigger in handleNewBooking if (resultBooking) { + const isBookingAuditEnabled = organizationId + ? await featuresRepository.checkIfTeamHasFeature(organizationId, "booking-audit") + : false; + await fireBookingEvents({ previousSeatedBooking: seatedBooking, newBooking: resultBooking, @@ -232,6 +244,7 @@ const handleSeats = async (newSeatedBookingObject: NewSeatedBookingObject) => { rescheduledBy, actionSource, actorUserUuid: reqUserUuid ?? null, + isBookingAuditEnabled, deps: { bookingEventHandler, logger: loggerWithEventDetails }, }); const metadata = { diff --git a/packages/features/bookings/lib/onBookingEvents/BookingEventHandlerService.ts b/packages/features/bookings/lib/onBookingEvents/BookingEventHandlerService.ts index 2999afb04c..24d27fa883 100644 --- a/packages/features/bookings/lib/onBookingEvents/BookingEventHandlerService.ts +++ b/packages/features/bookings/lib/onBookingEvents/BookingEventHandlerService.ts @@ -32,6 +32,7 @@ interface OnBookingCreatedParams { source: ActionSource; operationId?: string | null; context?: BookingAuditContext; + isBookingAuditEnabled: boolean; } interface OnBookingRescheduledParams { @@ -41,6 +42,7 @@ interface OnBookingRescheduledParams { source: ActionSource; operationId?: string | null; context?: BookingAuditContext; + isBookingAuditEnabled: boolean; } interface BaseBookingEventParams { @@ -51,6 +53,7 @@ interface BaseBookingEventParams { source: ActionSource; operationId?: string | null; context?: BookingAuditContext; + isBookingAuditEnabled: boolean; } type OnBookingAcceptedParams = BaseBookingEventParams; @@ -75,7 +78,7 @@ export class BookingEventHandlerService { } async onBookingCreated(params: OnBookingCreatedParams) { - const { payload, actor, auditData, source, operationId, context } = params; + const { payload, actor, auditData, source, operationId, context, isBookingAuditEnabled } = params; this.log.debug("onBookingCreated", safeStringify(payload)); if (payload.config.isDryRun) { return; @@ -89,11 +92,12 @@ export class BookingEventHandlerService { operationId, data: auditData, context, + isBookingAuditEnabled, }); } async onBookingRescheduled(params: OnBookingRescheduledParams) { - const { payload, actor, auditData, source, operationId, context } = params; + const { payload, actor, auditData, source, operationId, context, isBookingAuditEnabled } = params; this.log.debug("onBookingRescheduled", safeStringify(payload)); if (payload.config.isDryRun) { return; @@ -109,6 +113,7 @@ export class BookingEventHandlerService { operationId, data: auditData, context, + isBookingAuditEnabled, }); } @@ -142,7 +147,8 @@ export class BookingEventHandlerService { } async onBookingAccepted(params: OnBookingAcceptedParams) { - const { bookingUid, actor, organizationId, auditData, source, operationId, context } = params; + const { bookingUid, actor, organizationId, auditData, source, operationId, context, isBookingAuditEnabled } = + params; await this.bookingAuditProducerService.queueAcceptedAudit({ bookingUid, actor, @@ -151,11 +157,13 @@ export class BookingEventHandlerService { operationId, data: auditData, context, + isBookingAuditEnabled, }); } async onBookingCancelled(params: OnBookingCancelledParams) { - const { bookingUid, actor, organizationId, auditData, source, operationId, context } = params; + const { bookingUid, actor, organizationId, auditData, source, operationId, context, isBookingAuditEnabled } = + params; await this.bookingAuditProducerService.queueCancelledAudit({ bookingUid, actor, @@ -164,11 +172,13 @@ export class BookingEventHandlerService { operationId, data: auditData, context, + isBookingAuditEnabled, }); } async onRescheduleRequested(params: OnRescheduleRequestedParams) { - const { bookingUid, actor, organizationId, auditData, source, operationId, context } = params; + const { bookingUid, actor, organizationId, auditData, source, operationId, context, isBookingAuditEnabled } = + params; await this.bookingAuditProducerService.queueRescheduleRequestedAudit({ bookingUid, actor, @@ -177,11 +187,13 @@ export class BookingEventHandlerService { operationId, data: auditData, context, + isBookingAuditEnabled, }); } async onAttendeeAdded(params: OnAttendeeAddedParams) { - const { bookingUid, actor, organizationId, auditData, source, operationId, context } = params; + const { bookingUid, actor, organizationId, auditData, source, operationId, context, isBookingAuditEnabled } = + params; await this.bookingAuditProducerService.queueAttendeeAddedAudit({ bookingUid, actor, @@ -190,11 +202,13 @@ export class BookingEventHandlerService { operationId, data: auditData, context, + isBookingAuditEnabled, }); } async onNoShowUpdated(params: OnNoShowUpdatedParams) { - const { bookingUid, actor, organizationId, auditData, source, operationId, context } = params; + const { bookingUid, actor, organizationId, auditData, source, operationId, context, isBookingAuditEnabled } = + params; await this.bookingAuditProducerService.queueNoShowUpdatedAudit({ bookingUid, actor, @@ -203,11 +217,13 @@ export class BookingEventHandlerService { operationId, data: auditData, context, + isBookingAuditEnabled, }); } async onBookingRejected(params: OnBookingRejectedParams) { - const { bookingUid, actor, organizationId, auditData, source, operationId, context } = params; + const { bookingUid, actor, organizationId, auditData, source, operationId, context, isBookingAuditEnabled } = + params; await this.bookingAuditProducerService.queueRejectedAudit({ bookingUid, actor, @@ -216,11 +232,13 @@ export class BookingEventHandlerService { operationId, data: auditData, context, + isBookingAuditEnabled, }); } async onAttendeeRemoved(params: OnAttendeeRemovedParams) { - const { bookingUid, actor, organizationId, auditData, source, operationId, context } = params; + const { bookingUid, actor, organizationId, auditData, source, operationId, context, isBookingAuditEnabled } = + params; await this.bookingAuditProducerService.queueAttendeeRemovedAudit({ bookingUid, actor, @@ -229,11 +247,13 @@ export class BookingEventHandlerService { operationId, data: auditData, context, + isBookingAuditEnabled, }); } async onReassignment(params: OnReassignmentParams) { - const { bookingUid, actor, organizationId, auditData, source, operationId, context } = params; + const { bookingUid, actor, organizationId, auditData, source, operationId, context, isBookingAuditEnabled } = + params; await this.bookingAuditProducerService.queueReassignmentAudit({ bookingUid, actor, @@ -242,12 +262,14 @@ export class BookingEventHandlerService { operationId, data: auditData, context, + isBookingAuditEnabled, }); } async onLocationChanged(params: OnLocationChangedParams) { try { - const { bookingUid, actor, organizationId, auditData, source, operationId, context } = params; + const { bookingUid, actor, organizationId, auditData, source, operationId, context, isBookingAuditEnabled } = + params; await this.bookingAuditProducerService.queueLocationChangedAudit({ bookingUid, actor, @@ -256,6 +278,7 @@ export class BookingEventHandlerService { operationId, data: auditData, context, + isBookingAuditEnabled, }); } catch (error) { this.log.error("Error while onLocationChanged", safeStringify(error)); @@ -263,7 +286,8 @@ export class BookingEventHandlerService { } async onSeatBooked(params: OnSeatBookedParams) { - const { bookingUid, actor, organizationId, auditData, source, operationId, context } = params; + const { bookingUid, actor, organizationId, auditData, source, operationId, context, isBookingAuditEnabled } = + params; await this.bookingAuditProducerService.queueSeatBookedAudit({ bookingUid, actor, @@ -272,11 +296,13 @@ export class BookingEventHandlerService { operationId, data: auditData, context, + isBookingAuditEnabled, }); } async onSeatRescheduled(params: OnSeatRescheduledParams) { - const { bookingUid, actor, organizationId, auditData, source, operationId, context } = params; + const { bookingUid, actor, organizationId, auditData, source, operationId, context, isBookingAuditEnabled } = + params; await this.bookingAuditProducerService.queueSeatRescheduledAudit({ bookingUid, actor, @@ -285,13 +311,10 @@ export class BookingEventHandlerService { operationId, data: auditData, context, + isBookingAuditEnabled, }); } - /** - * Handles bulk booking acceptance for recurring bookings - * Creates a single task that will be processed to create multiple audit logs atomically - */ async onBulkBookingsAccepted(params: { bookings: Array<{ bookingUid: string; @@ -302,8 +325,9 @@ export class BookingEventHandlerService { operationId?: string | null; source: ActionSource; context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }) { - const { bookings, actor, organizationId, operationId, source, context } = params; + const { bookings, actor, organizationId, operationId, source, context, isBookingAuditEnabled } = params; await this.bookingAuditProducerService.queueBulkAcceptedAudit({ bookings: bookings.map((booking) => ({ bookingUid: booking.bookingUid, @@ -314,13 +338,10 @@ export class BookingEventHandlerService { source, operationId, context, + isBookingAuditEnabled, }); } - /** - * Handles bulk booking cancellation for recurring bookings - * Creates a single task that will be processed to create multiple audit logs atomically - */ async onBulkBookingsCancelled(params: { bookings: Array<{ bookingUid: string; @@ -331,8 +352,9 @@ export class BookingEventHandlerService { operationId?: string | null; source: ActionSource; context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }) { - const { bookings, actor, organizationId, operationId, source, context } = params; + const { bookings, actor, organizationId, operationId, source, context, isBookingAuditEnabled } = params; await this.bookingAuditProducerService.queueBulkCancelledAudit({ bookings: bookings.map((booking) => ({ bookingUid: booking.bookingUid, @@ -343,6 +365,7 @@ export class BookingEventHandlerService { source, operationId, context, + isBookingAuditEnabled, }); } @@ -355,8 +378,9 @@ export class BookingEventHandlerService { organizationId: number | null; operationId?: string | null; source: ActionSource; + isBookingAuditEnabled: boolean; }) { - const { bookings, actor, organizationId, operationId, source } = params; + const { bookings, actor, organizationId, operationId, source, isBookingAuditEnabled } = params; await this.bookingAuditProducerService.queueBulkCreatedAudit({ bookings: bookings.map((booking) => ({ bookingUid: booking.bookingUid, @@ -366,6 +390,7 @@ export class BookingEventHandlerService { organizationId, source, operationId, + isBookingAuditEnabled, }); } @@ -378,8 +403,9 @@ export class BookingEventHandlerService { organizationId: number | null; operationId?: string | null; source: ActionSource; + isBookingAuditEnabled: boolean; }) { - const { bookings, actor, organizationId, operationId, source } = params; + const { bookings, actor, organizationId, operationId, source, isBookingAuditEnabled } = params; await this.bookingAuditProducerService.queueBulkRescheduledAudit({ bookings: bookings.map((booking) => ({ bookingUid: booking.bookingUid, @@ -389,6 +415,7 @@ export class BookingEventHandlerService { organizationId, source, operationId, + isBookingAuditEnabled, }); } @@ -402,8 +429,9 @@ export class BookingEventHandlerService { operationId?: string | null; source: ActionSource; context?: BookingAuditContext; + isBookingAuditEnabled: boolean; }) { - const { bookings, actor, organizationId, operationId, source, context } = params; + const { bookings, actor, organizationId, operationId, source, context, isBookingAuditEnabled } = params; await this.bookingAuditProducerService.queueBulkRejectedAudit({ bookings: bookings.map((booking) => ({ bookingUid: booking.bookingUid, @@ -414,6 +442,7 @@ export class BookingEventHandlerService { source, operationId, context, + isBookingAuditEnabled, }); } } diff --git a/packages/features/bookings/lib/service/RecurringBookingService.ts b/packages/features/bookings/lib/service/RecurringBookingService.ts index 0ae1e0b36b..3fcf92aef8 100644 --- a/packages/features/bookings/lib/service/RecurringBookingService.ts +++ b/packages/features/bookings/lib/service/RecurringBookingService.ts @@ -1,5 +1,6 @@ import type { CreateBookingMeta, CreateRecurringBookingData } from "@calcom/features/bookings/lib/dto/types"; import type { BookingResponse } from "@calcom/features/bookings/types"; +import type { FeaturesRepository } from "@calcom/features/flags/features.repository"; import { CreationSource, SchedulingType } from "@calcom/prisma/enums"; import type { AppsStatus } from "@calcom/types/Calendar"; import { v4 as uuidv4 } from "uuid"; @@ -156,6 +157,7 @@ export const handleNewRecurringBooking = async function ( export interface IRecurringBookingServiceDependencies { regularBookingService: RegularBookingService; bookingEventHandler: BookingEventHandlerService; + featuresRepository: FeaturesRepository; } /** @@ -199,6 +201,10 @@ export class RecurringBookingService implements IBookingService { const bookerName = bookerAttendee?.name || ""; const bookerEmail = bookerAttendee?.email || ""; + const isBookingAuditEnabled = eventOrganizationId + ? await this.deps.featuresRepository.checkIfTeamHasFeature(eventOrganizationId, "booking-audit") + : false; + const rescheduledByAttendeeId = firstCreatedBooking.attendees?.find( (attendee) => attendee.email === rescheduledBy )?.id; @@ -255,6 +261,7 @@ export class RecurringBookingService implements IBookingService { organizationId: eventOrganizationId, operationId, source: actionSource, + isBookingAuditEnabled, }); } } else { @@ -271,6 +278,7 @@ export class RecurringBookingService implements IBookingService { organizationId: eventOrganizationId, operationId, source: actionSource, + isBookingAuditEnabled, }); } } diff --git a/packages/features/bookings/lib/service/RegularBookingService.ts b/packages/features/bookings/lib/service/RegularBookingService.ts index f65c43873a..156f19ad48 100644 --- a/packages/features/bookings/lib/service/RegularBookingService.ts +++ b/packages/features/bookings/lib/service/RegularBookingService.ts @@ -1769,46 +1769,49 @@ async function handler( // For seats, if the booking already exists then we want to add the new attendee to the existing booking if (eventType.seatsPerTimeSlot) { - const newBooking = await handleSeats({ - rescheduleUid, - reqBookingUid: reqBody.bookingUid, - eventType, - evt: { ...evt, seatsPerTimeSlot: eventType.seatsPerTimeSlot, bookerUrl }, - invitee, - allCredentials, - organizerUser, - originalRescheduledBooking, - bookerEmail, - bookerPhoneNumber, - tAttendees, - bookingSeat, - reqUserId: input.userId, - reqUserUuid: userUuid, - rescheduleReason, - reqBodyUser: reqBody.user, - noEmail, - isConfirmedByDefault, - additionalNotes, - reqAppsStatus, - attendeeLanguage, - paymentAppData, - fullName, - smsReminderNumber, - eventTypeInfo, - uid, - eventTypeId, - reqBodyMetadata: reqBody.metadata, - subscriberOptions, - eventTrigger, - responses, - workflows, - rescheduledBy: reqBody.rescheduledBy, - isDryRun, - organizationId: eventOrganizationId, - actionSource, - traceContext, - deps, - }); + const newBooking = await handleSeats( + { + rescheduleUid, + reqBookingUid: reqBody.bookingUid, + eventType, + evt: { ...evt, seatsPerTimeSlot: eventType.seatsPerTimeSlot, bookerUrl }, + invitee, + allCredentials, + organizerUser, + originalRescheduledBooking, + bookerEmail, + bookerPhoneNumber, + tAttendees, + bookingSeat, + reqUserId: input.userId, + reqUserUuid: userUuid, + rescheduleReason, + reqBodyUser: reqBody.user, + noEmail, + isConfirmedByDefault, + additionalNotes, + reqAppsStatus, + attendeeLanguage, + paymentAppData, + fullName, + smsReminderNumber, + eventTypeInfo, + uid, + eventTypeId, + reqBodyMetadata: reqBody.metadata, + subscriberOptions, + eventTrigger, + responses, + workflows, + rescheduledBy: reqBody.rescheduledBy, + isDryRun, + organizationId: eventOrganizationId, + actionSource, + traceContext, + deps, + }, + deps.featuresRepository + ); if (newBooking) { const bookingResponse = { @@ -2463,6 +2466,15 @@ async function handler( } : undefined; + // Query feature flags in parallel before firing booking events + // TODO: We should support checkIfOrgHasFeatures - Bulk plus orgId check, that would be more efficient. + const [isBookingEmailSmsTaskerEnabled, isBookingAuditEnabled] = orgId + ? await Promise.all([ + deps.featuresRepository.checkIfTeamHasFeature(orgId, "booking-email-sms-tasker"), + deps.featuresRepository.checkIfTeamHasFeature(orgId, "booking-audit"), + ]) + : [false, false]; + await this.fireBookingEvents({ booking: { ...booking, @@ -2482,6 +2494,7 @@ async function handler( isRecurringBooking: !!input.bookingData.allRecurringDates, attendeeSeatId: evt.attendeeSeatId ?? null, tracingLogger, + isBookingAuditEnabled, }); const webhookLocation = metadata?.videoCallUrl || evt.location; @@ -2840,29 +2853,21 @@ async function handler( // Unused until we deploy to trigger.dev production // for now we only enable for cal.com org and we keep our current email system // cal.com org members will see emails in double while we test - if (ENABLE_ASYNC_TASKER && !noEmail) { + if (ENABLE_ASYNC_TASKER && !noEmail && isBookingEmailSmsTaskerEnabled) { try { - if (orgId) { - const hasTeamFeature = await deps.featuresRepository.checkIfTeamHasFeature( - orgId, - "booking-email-sms-tasker" - ); - if (hasTeamFeature) { - await deps.bookingEmailAndSmsTasker.send({ - action: bookingEmailsAndSmsTaskerAction, - schedulingType: evtWithMetadata.eventType.schedulingType, - payload: { - bookingId: booking.id, - conferenceCredentialId, - platformClientId, - platformRescheduleUrl, - platformCancelUrl, - platformBookingUrl, - isRescheduledByBooker: reqBody.rescheduledBy === bookerEmail, - }, - }); - } - } + await deps.bookingEmailAndSmsTasker.send({ + action: bookingEmailsAndSmsTaskerAction, + schedulingType: evtWithMetadata.eventType.schedulingType, + payload: { + bookingId: booking.id, + conferenceCredentialId, + platformClientId, + platformRescheduleUrl, + platformCancelUrl, + platformBookingUrl, + isRescheduledByBooker: reqBody.rescheduledBy === bookerEmail, + }, + }); } catch (err) { tracingLogger.error("bookingEmailAndSmsTasker error:", err); } @@ -2922,6 +2927,7 @@ export class RegularBookingService implements IBookingService { isRecurringBooking, attendeeSeatId, tracingLogger, + isBookingAuditEnabled, }: { booking: { id: number; @@ -2947,6 +2953,7 @@ export class RegularBookingService implements IBookingService { isRecurringBooking: boolean; tracingLogger: ReturnType; attendeeSeatId: string | null; + isBookingAuditEnabled: boolean; }) { try { const bookingCreatedPayload = buildBookingCreatedPayload({ @@ -2999,6 +3006,7 @@ export class RegularBookingService implements IBookingService { }), source: actionSource, operationId: null, + isBookingAuditEnabled, }); } else { await bookingEventHandler.onBookingCreated({ @@ -3007,6 +3015,7 @@ export class RegularBookingService implements IBookingService { auditData: buildBookingCreatedAuditData({ booking, attendeeSeatId }), source: actionSource, operationId: null, + isBookingAuditEnabled, }); } } diff --git a/packages/features/ee/round-robin/roundRobinManualReassignment.test.ts b/packages/features/ee/round-robin/roundRobinManualReassignment.test.ts index a0bd9454fd..ce2061a712 100644 --- a/packages/features/ee/round-robin/roundRobinManualReassignment.test.ts +++ b/packages/features/ee/round-robin/roundRobinManualReassignment.test.ts @@ -1148,6 +1148,7 @@ describe("roundRobinManualReassignment - Audit Data Verification", () => { reassignmentReason: "Test reassignment reason", reassignmentType: "manual", }, + isBookingAuditEnabled: false, }); }); @@ -1227,6 +1228,7 @@ describe("roundRobinManualReassignment - Audit Data Verification", () => { expect(callArgs.actor).toEqual({ identifiedBy: "user", userUuid: reassigningUser.uuid }); expect(callArgs.organizationId).toBe(null); expect(callArgs.source).toBe("WEBAPP"); + expect(callArgs.isBookingAuditEnabled).toBe(false); // organizerUuid should NOT be included when organizer hasn't changed (fixed host scenario) expect(callArgs.auditData.organizerUuid).toBeUndefined(); expect(callArgs.auditData.reassignmentType).toBe("manual"); diff --git a/packages/features/ee/round-robin/roundRobinManualReassignment.ts b/packages/features/ee/round-robin/roundRobinManualReassignment.ts index 61c2abcafd..20dfe81586 100644 --- a/packages/features/ee/round-robin/roundRobinManualReassignment.ts +++ b/packages/features/ee/round-robin/roundRobinManualReassignment.ts @@ -9,6 +9,7 @@ import { import { makeUserActor } from "@calcom/features/booking-audit/lib/makeActor"; import type { ValidActionSource } from "@calcom/features/booking-audit/lib/types/actionSource"; import { getBookingEventHandlerService } from "@calcom/features/bookings/di/BookingEventHandlerService.container"; +import { getFeaturesRepository } from "@calcom/features/di/containers/FeaturesRepository"; import EventManager from "@calcom/features/bookings/lib/EventManager"; import { getAllCredentialsIncludeServiceAccountKey } from "@calcom/features/bookings/lib/getAllCredentialsForUsersOnEvent/getAllCredentials"; import { getBookingResponsesPartialSchema } from "@calcom/features/bookings/lib/getBookingResponsesSchema"; @@ -256,6 +257,10 @@ export const roundRobinManualReassignment = async ({ } const bookingEventHandlerService = getBookingEventHandlerService(); + const featuresRepository = getFeaturesRepository(); + const isBookingAuditEnabled = orgId + ? await featuresRepository.checkIfTeamHasFeature(orgId, "booking-audit") + : false; await bookingEventHandlerService.onReassignment({ bookingUid: booking.uid, @@ -280,6 +285,7 @@ export const roundRobinManualReassignment = async ({ reassignmentReason: reassignReason ?? null, reassignmentType: "manual", }, + isBookingAuditEnabled, }); // When organizer hasn't changed, still extract conferenceCredentialId from event type locations diff --git a/packages/features/ee/round-robin/roundRobinReassignment.test.ts b/packages/features/ee/round-robin/roundRobinReassignment.test.ts index 8be59a6f71..1bfa51f4ee 100644 --- a/packages/features/ee/round-robin/roundRobinReassignment.test.ts +++ b/packages/features/ee/round-robin/roundRobinReassignment.test.ts @@ -603,6 +603,7 @@ describe("roundRobinReassignment - Audit Data Verification", () => { reassignmentReason: null, reassignmentType: "roundRobin", }, + isBookingAuditEnabled: false, }); }); @@ -690,6 +691,7 @@ describe("roundRobinReassignment - Audit Data Verification", () => { expect(callArgs.actor).toEqual({ identifiedBy: "user", userUuid: reassigningUser.uuid }); expect(callArgs.organizationId).toBe(null); expect(callArgs.source).toBe("WEBAPP"); + expect(callArgs.isBookingAuditEnabled).toBe(false); // organizerUuid should NOT be included when organizer hasn't changed (fixed host scenario) expect(callArgs.auditData.organizerUuid).toBeUndefined(); expect(callArgs.auditData.reassignmentType).toBe("roundRobin"); diff --git a/packages/features/ee/round-robin/roundRobinReassignment.ts b/packages/features/ee/round-robin/roundRobinReassignment.ts index 85d9b4905d..d32c4e05ba 100644 --- a/packages/features/ee/round-robin/roundRobinReassignment.ts +++ b/packages/features/ee/round-robin/roundRobinReassignment.ts @@ -13,6 +13,7 @@ import { import { makeUserActor } from "@calcom/features/booking-audit/lib/makeActor"; import type { ValidActionSource } from "@calcom/features/booking-audit/lib/types/actionSource"; import { getBookingEventHandlerService } from "@calcom/features/bookings/di/BookingEventHandlerService.container"; +import { getFeaturesRepository } from "@calcom/features/di/containers/FeaturesRepository"; import EventManager from "@calcom/features/bookings/lib/EventManager"; import { getAllCredentialsIncludeServiceAccountKey } from "@calcom/features/bookings/lib/getAllCredentialsForUsersOnEvent/getAllCredentials"; import { getBookingResponsesPartialSchema } from "@calcom/features/bookings/lib/getBookingResponsesSchema"; @@ -294,6 +295,11 @@ export const roundRobinReassignment = async ({ } const bookingEventHandlerService = getBookingEventHandlerService(); + const featuresRepository = getFeaturesRepository(); + const isBookingAuditEnabled = orgId + ? await featuresRepository.checkIfTeamHasFeature(orgId, "booking-audit") + : false; + await bookingEventHandlerService.onReassignment({ bookingUid: booking.uid, actor: makeUserActor(reassignedByUuid), @@ -317,6 +323,7 @@ export const roundRobinReassignment = async ({ reassignmentReason: null, reassignmentType: "roundRobin", }, + isBookingAuditEnabled, }); roundRobinReassignLogger.info(`Successfully reassigned to user ${reassignedRRHost.id}`); diff --git a/packages/features/handleMarkNoShow.ts b/packages/features/handleMarkNoShow.ts index 00ff932a57..fc33276b0c 100644 --- a/packages/features/handleMarkNoShow.ts +++ b/packages/features/handleMarkNoShow.ts @@ -8,6 +8,7 @@ import { } from "@calcom/features/booking-audit/lib/makeActor"; import type { ValidActionSource } from "@calcom/features/booking-audit/lib/types/actionSource"; import { getBookingEventHandlerService } from "@calcom/features/bookings/di/BookingEventHandlerService.container"; +import { getFeaturesRepository } from "@calcom/features/di/containers/FeaturesRepository"; import { AttendeeRepository } from "@calcom/features/bookings/repositories/AttendeeRepository"; import { BookingRepository } from "@calcom/features/bookings/repositories/BookingRepository"; import { BookingAccessService } from "@calcom/features/bookings/services/BookingAccessService"; @@ -209,6 +210,10 @@ async function fireNoShowUpdated({ } const bookingEventHandlerService = getBookingEventHandlerService(); + const featuresRepository = getFeaturesRepository(); + const isBookingAuditEnabled = orgId + ? await featuresRepository.checkIfTeamHasFeature(orgId, "booking-audit") + : false; const isSomethingChanged = auditData.host || (auditData.attendeesNoShow && auditData.attendeesNoShow.length > 0); if (isSomethingChanged) { @@ -218,6 +223,7 @@ async function fireNoShowUpdated({ organizationId: orgId ?? null, source: actionSource, auditData, + isBookingAuditEnabled, }); } } diff --git a/packages/features/tasker/tasks/triggerNoShow/common.ts b/packages/features/tasker/tasks/triggerNoShow/common.ts index 8f8ee30ea8..0a1dce68fe 100644 --- a/packages/features/tasker/tasks/triggerNoShow/common.ts +++ b/packages/features/tasker/tasks/triggerNoShow/common.ts @@ -2,6 +2,7 @@ import dayjs from "@calcom/dayjs"; import type { NoShowUpdatedAuditData } from "@calcom/features/booking-audit/lib/actions/NoShowUpdatedAuditActionService"; import { makeSystemActor } from "@calcom/features/booking-audit/lib/makeActor"; import { getBookingEventHandlerService } from "@calcom/features/bookings/di/BookingEventHandlerService.container"; +import { getFeaturesRepository } from "@calcom/features/di/containers/FeaturesRepository"; import type { Host } from "@calcom/features/bookings/lib/getHostsAndGuests"; import { getHostsAndGuests } from "@calcom/features/bookings/lib/getHostsAndGuests"; import { sendGenericWebhookPayload } from "@calcom/features/webhooks/lib/sendPayload"; @@ -176,6 +177,11 @@ export const fireNoShowUpdatedEvent = async ({ }); const bookingEventHandlerService = getBookingEventHandlerService(); + const featuresRepository = getFeaturesRepository(); + const isBookingAuditEnabled = orgId + ? await featuresRepository.checkIfTeamHasFeature(orgId, "booking-audit") + : false; + await bookingEventHandlerService.onNoShowUpdated({ bookingUid: booking.uid, // This action is taken by the scheduled tasker job, so we use the system actor @@ -193,6 +199,7 @@ export const fireNoShowUpdatedEvent = async ({ : {}), ...(hasAttendeesNoShow ? { attendeesNoShow: attendeesNoShowAudit } : {}), }, + isBookingAuditEnabled, }); } catch (error) { log.error("Error logging audit for automatic no-show", error); diff --git a/packages/features/tasker/tasks/triggerNoShow/triggerGuestNoShow.test.ts b/packages/features/tasker/tasks/triggerNoShow/triggerGuestNoShow.test.ts index 265f03581b..a3e801c1a5 100644 --- a/packages/features/tasker/tasks/triggerNoShow/triggerGuestNoShow.test.ts +++ b/packages/features/tasker/tasks/triggerNoShow/triggerGuestNoShow.test.ts @@ -33,6 +33,12 @@ vi.mock("@calcom/features/bookings/di/BookingEventHandlerService.container", () }), })); +vi.mock("@calcom/features/di/containers/FeaturesRepository", () => ({ + getFeaturesRepository: vi.fn().mockReturnValue({ + checkIfTeamHasFeature: vi.fn().mockResolvedValue(false), + }), +})); + const timeout = process.env.CI ? 5000 : 20000; const EMPTY_MEETING_SESSIONS = { @@ -57,6 +63,7 @@ type ExpectNoShowAuditParams = { }; }>; }; + isBookingAuditEnabled: boolean; }; function expectNoShowAuditToBeDone(expected: ExpectNoShowAuditParams): void { @@ -240,6 +247,7 @@ describe("Trigger Guest No Show:", () => { { attendeeEmail: "guest@example.com", noShow: { new: true, old: false } }, ], }, + isBookingAuditEnabled: false, }); }, timeout @@ -442,6 +450,7 @@ describe("Trigger Guest No Show:", () => { { attendeeEmail: "guest@example.com", noShow: { new: true, old: false } }, ], }, + isBookingAuditEnabled: false, }); }, timeout @@ -683,6 +692,7 @@ describe("Trigger Guest No Show:", () => { { attendeeEmail: "guest@example.com", noShow: { new: true, old: false } }, ], }, + isBookingAuditEnabled: false, }); }, timeout diff --git a/packages/features/tasker/tasks/triggerNoShow/triggerHostNoShow.test.ts b/packages/features/tasker/tasks/triggerNoShow/triggerHostNoShow.test.ts index c738843949..480b951d2c 100644 --- a/packages/features/tasker/tasks/triggerNoShow/triggerHostNoShow.test.ts +++ b/packages/features/tasker/tasks/triggerNoShow/triggerHostNoShow.test.ts @@ -33,6 +33,12 @@ vi.mock("@calcom/features/bookings/di/BookingEventHandlerService.container", () }), })); +vi.mock("@calcom/features/di/containers/FeaturesRepository", () => ({ + getFeaturesRepository: vi.fn().mockReturnValue({ + checkIfTeamHasFeature: vi.fn().mockResolvedValue(false), + }), +})); + const timeout = process.env.CI ? 5000 : 20000; const EMPTY_MEETING_SESSIONS = { @@ -64,6 +70,7 @@ type ExpectNoShowAuditParams = { }; }>; }; + isBookingAuditEnabled: boolean; }; function expectNoShowAuditToBeDone(expected: ExpectNoShowAuditParams): void { @@ -224,6 +231,7 @@ describe("Trigger Host No Show:", () => { auditData: { host: { userUuid: expect.any(String), noShow: { old: false, new: true } }, }, + isBookingAuditEnabled: false, }); }, timeout @@ -401,6 +409,7 @@ describe("Trigger Host No Show:", () => { auditData: { host: { userUuid: expect.any(String), noShow: { old: false, new: true } }, }, + isBookingAuditEnabled: false, }); }, timeout @@ -618,6 +627,7 @@ describe("Trigger Host No Show:", () => { auditData: { host: { userUuid: expect.any(String), noShow: { old: false, new: true } }, }, + isBookingAuditEnabled: false, }); }, timeout @@ -782,6 +792,7 @@ describe("Trigger Host No Show:", () => { auditData: { host: { userUuid: expect.any(String), noShow: { old: false, new: true } }, }, + isBookingAuditEnabled: false, }); }, timeout diff --git a/packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts b/packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts index 6b8b560ddc..abda8a55ad 100644 --- a/packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts +++ b/packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts @@ -4,6 +4,7 @@ import dayjs from "@calcom/dayjs"; import { makeUserActor } from "@calcom/features/booking-audit/lib/makeActor"; import type { ActionSource } from "@calcom/features/booking-audit/lib/types/actionSource"; import { getBookingEventHandlerService } from "@calcom/features/bookings/di/BookingEventHandlerService.container"; +import { getFeaturesRepository } from "@calcom/features/di/containers/FeaturesRepository"; import { BookingEmailSmsHandler } from "@calcom/features/bookings/lib/BookingEmailSmsHandler"; import EventManager from "@calcom/features/bookings/lib/EventManager"; import { BookingRepository } from "@calcom/features/bookings/repositories/BookingRepository"; @@ -88,14 +89,21 @@ export const addGuestsHandler = async ({ } const bookingEventHandlerService = getBookingEventHandlerService(); + const featuresRepository = getFeaturesRepository(); + const organizationId = user.organizationId ?? null; + const isBookingAuditEnabled = organizationId + ? await featuresRepository.checkIfTeamHasFeature(organizationId, "booking-audit") + : false; + await bookingEventHandlerService.onAttendeeAdded({ bookingUid: booking.uid, actor: makeUserActor(user.uuid), - organizationId: user.organizationId ?? null, + organizationId, source: actionSource, auditData: { added: uniqueGuestEmails, }, + isBookingAuditEnabled, }); return { message: "Guests added" }; diff --git a/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts b/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts index 70f544e96f..511805852a 100644 --- a/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts +++ b/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts @@ -3,6 +3,7 @@ import type { LocationObject } from "@calcom/app-store/locations"; import { getLocationValueForDB } from "@calcom/app-store/locations"; import { sendDeclinedEmailsAndSMS } from "@calcom/emails/email-manager"; import { getBookingEventHandlerService } from "@calcom/features/bookings/di/BookingEventHandlerService.container"; +import { getFeaturesRepository } from "@calcom/features/di/containers/FeaturesRepository"; import { getAllCredentialsIncludeServiceAccountKey } from "@calcom/features/bookings/lib/getAllCredentialsForUsersOnEvent/getAllCredentials"; import { getAssignmentReasonCategory } from "@calcom/features/bookings/lib/getAssignmentReasonCategory"; import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses"; @@ -55,6 +56,7 @@ async function fireRejectionEvent({ actionSource, rejectedBookings, rejectionReason, + isBookingAuditEnabled, tracingLogger, }: { actor: Actor; @@ -65,6 +67,7 @@ async function fireRejectionEvent({ uid: string; oldStatus: BookingStatus; }[]; + isBookingAuditEnabled: boolean; tracingLogger: ISimpleLogger; }): Promise { try { @@ -83,6 +86,7 @@ async function fireRejectionEvent({ organizationId, operationId, source: actionSource, + isBookingAuditEnabled, }); } else if (rejectedBookings.length === 1) { const booking = rejectedBookings[0]; @@ -95,6 +99,7 @@ async function fireRejectionEvent({ status: { old: booking.oldStatus, new: BookingStatus.REJECTED }, }, source: actionSource, + isBookingAuditEnabled, }); } } catch (error) { @@ -495,12 +500,18 @@ export const confirmHandler = async ({ ctx, input }: ConfirmOptions) => { const orgId = await getOrgIdFromMemberOrTeamId({ memberId: booking.userId, teamId }); + const featuresRepository = getFeaturesRepository(); + const isBookingAuditEnabled = orgId + ? await featuresRepository.checkIfTeamHasFeature(orgId, "booking-audit") + : false; + await fireRejectionEvent({ actor, actionSource, organizationId: orgId ?? null, rejectionReason: rejectionReason ?? null, rejectedBookings, + isBookingAuditEnabled, tracingLogger: log, }); diff --git a/packages/trpc/server/routers/viewer/bookings/editLocation.handler.ts b/packages/trpc/server/routers/viewer/bookings/editLocation.handler.ts index bf2345654b..94d8bad248 100644 --- a/packages/trpc/server/routers/viewer/bookings/editLocation.handler.ts +++ b/packages/trpc/server/routers/viewer/bookings/editLocation.handler.ts @@ -5,6 +5,7 @@ import { sendLocationChangeEmailsAndSMS } from "@calcom/emails/email-manager"; import { makeUserActor } from "@calcom/features/booking-audit/lib/makeActor"; import type { ValidActionSource } from "@calcom/features/booking-audit/lib/types/actionSource"; import { getBookingEventHandlerService } from "@calcom/features/bookings/di/BookingEventHandlerService.container"; +import { getFeaturesRepository } from "@calcom/features/di/containers/FeaturesRepository"; import EventManager from "@calcom/features/bookings/lib/EventManager"; import { BookingRepository } from "@calcom/features/bookings/repositories/BookingRepository"; import { CredentialRepository } from "@calcom/features/credentials/repositories/CredentialRepository"; @@ -315,6 +316,11 @@ export async function editLocationHandler({ ctx, input, actionSource }: EditLoca } const bookingEventHandlerService = getBookingEventHandlerService(); + const featuresRepository = getFeaturesRepository(); + const isBookingAuditEnabled = organizationId + ? await featuresRepository.checkIfTeamHasFeature(organizationId, "booking-audit") + : false; + await bookingEventHandlerService.onLocationChanged({ bookingUid: booking.uid, actor: makeUserActor(loggedInUser.uuid), @@ -326,6 +332,7 @@ export async function editLocationHandler({ ctx, input, actionSource }: EditLoca new: updatedLocation, }, }, + isBookingAuditEnabled, }); return { message: "Location updated" }; diff --git a/packages/trpc/server/routers/viewer/bookings/requestReschedule.handler.ts b/packages/trpc/server/routers/viewer/bookings/requestReschedule.handler.ts index 96437685e6..6d46e8dc87 100644 --- a/packages/trpc/server/routers/viewer/bookings/requestReschedule.handler.ts +++ b/packages/trpc/server/routers/viewer/bookings/requestReschedule.handler.ts @@ -8,6 +8,7 @@ import { sendRequestRescheduleEmailAndSMS } from "@calcom/emails/email-manager"; import { makeUserActor } from "@calcom/features/booking-audit/lib/makeActor"; import type { ActionSource } from "@calcom/features/booking-audit/lib/types/actionSource"; import { getBookingEventHandlerService } from "@calcom/features/bookings/di/BookingEventHandlerService.container"; +import { getFeaturesRepository } from "@calcom/features/di/containers/FeaturesRepository"; import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses"; import { BookingRepository } from "@calcom/features/bookings/repositories/BookingRepository"; import { deleteMeeting } from "@calcom/features/conferencing/lib/videoClient"; @@ -315,6 +316,11 @@ export const requestRescheduleHandler = async ({ ctx, input, source }: RequestRe await Promise.all(promises); const bookingEventHandlerService = getBookingEventHandlerService(); + const featuresRepository = getFeaturesRepository(); + const isBookingAuditEnabled = orgId + ? await featuresRepository.checkIfTeamHasFeature(orgId, "booking-audit") + : false; + await bookingEventHandlerService.onRescheduleRequested({ bookingUid: bookingToReschedule.uid, actor: makeUserActor(user.uuid), @@ -324,5 +330,6 @@ export const requestRescheduleHandler = async ({ ctx, input, source }: RequestRe rescheduleReason: cancellationReason ?? null, rescheduledRequestedBy: user.email, }, + isBookingAuditEnabled, }); };