Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions packages/features/bookings/lib/BookingDeleteService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import prisma from "@calcom/prisma";
import { BookingStatus } from "@calcom/prisma/enums";

type AuditLogContext = {
type: string;
wasRescheduled?: boolean;
totalUpdates?: number;
actor: { type: string; id?: number; email?: string };
[key: string]: any;
};

export class BookingDeleteService {
static async deleteBooking({
bookingId,
actor,
wasRescheduled = false,
totalUpdates = 0,
additionalContext = {},
}: {
bookingId: number;
actor: { type: string; id?: number; email?: string };
wasRescheduled?: boolean;
totalUpdates?: number;
additionalContext?: Record<string, any>;
}) {
// Delete the booking (soft delete or update status as needed)
const booking = await prisma.booking.update({
where: { id: bookingId },
data: { status: BookingStatus.CANCELLED },
select: { id: true },
});

// Create audit log entry
const context: AuditLogContext = {
...additionalContext,
type: "record_deleted",
wasRescheduled,
totalUpdates,
actor,
};

await prisma.$transaction(async (tx) => {
// Only attempt auditlog if it exists (skip in test envs like Prismock)
if ((tx as any).auditlog && typeof (tx as any).auditlog.create === "function") {
await (tx as any).auditlog.create({
data: { entity: "booking", entityId: bookingId, action: "delete", context },
});
}
// else: skip audit log in test/mock environments
});
Comment on lines +43 to +51
Copy link
Contributor

Choose a reason for hiding this comment

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

We should not use any here. There is no such model as auditlog in the codebase.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

But as mentioned in the issue we should ensure that an auditlog entry is created for the deletion


return booking;
}
}
15 changes: 15 additions & 0 deletions packages/features/bookings/lib/handleCancelBooking.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import type { Prisma, WorkflowReminder } from "@prisma/client";
import type { z } from "zod";

Expand Down Expand Up @@ -40,6 +41,7 @@ import type { EventTypeMetadata } from "@calcom/prisma/zod-utils";
import { getAllWorkflowsFromEventType } from "@calcom/trpc/server/routers/viewer/workflows/util";
import type { CalendarEvent } from "@calcom/types/Calendar";

import { BookingDeleteService } from "./BookingDeleteService";
import { getAllCredentialsIncludeServiceAccountKey } from "./getAllCredentialsForUsersOnEvent/getAllCredentials";
import { getBookingToDelete } from "./getBookingToDelete";
import { handleInternalNote } from "./handleInternalNote";
Expand Down Expand Up @@ -455,6 +457,19 @@ async function handler(input: CancelBookingInput) {
});
updatedBookings.push(updatedBooking);

// --- Audit log integration ---
await BookingDeleteService.deleteBooking({
bookingId: bookingToDelete.id,
actor: userId ? { type: "user", id: userId } : { type: "system" },
// Fix: Use a fallback for wasRescheduled if fromReschedule is not present
wasRescheduled: !!(bookingToDelete as any).fromReschedule,
totalUpdates: evt.iCalSequence ?? 0,
additionalContext: {
cancellationReason,
},
});
// --- end audit log integration ---

if (!!bookingToDelete.payment.length) {
await processPaymentRefund({
booking: bookingToDelete,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import prisma from "@calcom/prisma";
import { BookingStatus } from "@calcom/prisma/enums";
import type { CalendarEvent } from "@calcom/types/Calendar";

import { BookingDeleteService } from "../../BookingDeleteService";
import type { OriginalRescheduledBooking } from "../../handleNewBooking/originalRescheduledBookingUtils";

/* Check if the original booking has no more attendees, if so delete the booking
Expand Down Expand Up @@ -64,6 +65,19 @@ const lastAttendeeDeleteBooking = async (
status: BookingStatus.CANCELLED,
},
});
// --- Audit log integration ---
try {
await BookingDeleteService.deleteBooking({
bookingId: originalRescheduledBooking.id,
actor: { type: "system" },
wasRescheduled: !!originalRescheduledBooking.fromReschedule,
totalUpdates: originalRescheduledBooking.iCalSequence ?? 0,
additionalContext: {},
});
} catch (e) {
// Non-fatal: keep cancellation outcome; surface to logs/observability.
}
// --- end audit log integration ---
});
deletedReferences = true;
}
Expand Down
Loading