2
0
Files
2024-08-09 00:39:27 +02:00

268 lines
8.0 KiB
TypeScript

import { Prisma } from "@prisma/client";
import type short from "short-uuid";
import dayjs from "@calcom/dayjs";
import { isPrismaObjOrUndefined } from "@calcom/lib";
import prisma from "@calcom/prisma";
import { BookingStatus } from "@calcom/prisma/enums";
import type { CalendarEvent } from "@calcom/types/Calendar";
import type { TgetBookingDataSchema } from "../getBookingDataSchema";
import type {
EventTypeId,
AwaitedBookingData,
NewBookingEventType,
IsConfirmedByDefault,
PaymentAppData,
OriginalRescheduledBooking,
AwaitedLoadUsers,
} from "./types";
type ReqBodyWithEnd = TgetBookingDataSchema & { end: string };
type CreateBookingParams = {
originalRescheduledBooking: OriginalRescheduledBooking;
evt: CalendarEvent;
eventType: NewBookingEventType;
eventTypeId: EventTypeId;
eventTypeSlug: AwaitedBookingData["eventTypeSlug"];
reqBodyUser: ReqBodyWithEnd["user"];
reqBodyMetadata: ReqBodyWithEnd["metadata"];
reqBodyRecurringEventId: ReqBodyWithEnd["recurringEventId"];
uid: short.SUUID;
responses: ReqBodyWithEnd["responses"] | null;
isConfirmedByDefault: IsConfirmedByDefault;
smsReminderNumber: AwaitedBookingData["smsReminderNumber"];
organizerUser: AwaitedLoadUsers[number] & {
isFixed?: boolean;
metadata?: Prisma.JsonValue;
};
rescheduleReason: AwaitedBookingData["rescheduleReason"];
bookerEmail: AwaitedBookingData["email"];
paymentAppData: PaymentAppData;
changedOrganizer: boolean;
};
function updateEventDetails(
evt: CalendarEvent,
originalRescheduledBooking: OriginalRescheduledBooking | null,
changedOrganizer: boolean
) {
if (originalRescheduledBooking) {
evt.title = originalRescheduledBooking?.title || evt.title;
evt.description = originalRescheduledBooking?.description || evt.description;
evt.location = originalRescheduledBooking?.location || evt.location;
evt.location = changedOrganizer ? evt.location : originalRescheduledBooking?.location || evt.location;
}
}
export async function createBooking({
originalRescheduledBooking,
evt,
eventTypeId,
eventTypeSlug,
reqBodyUser,
reqBodyMetadata,
reqBodyRecurringEventId,
uid,
responses,
isConfirmedByDefault,
smsReminderNumber,
organizerUser,
rescheduleReason,
eventType,
bookerEmail,
paymentAppData,
changedOrganizer,
}: CreateBookingParams) {
updateEventDetails(evt, originalRescheduledBooking, changedOrganizer);
const newBookingData = buildNewBookingData({
uid,
evt,
responses,
isConfirmedByDefault,
reqBodyMetadata,
smsReminderNumber,
eventTypeSlug,
organizerUser,
reqBodyRecurringEventId,
originalRescheduledBooking,
bookerEmail,
rescheduleReason,
eventType,
eventTypeId,
reqBodyUser,
});
return await saveBooking(newBookingData, originalRescheduledBooking, paymentAppData, organizerUser);
}
async function saveBooking(
newBookingData: Prisma.BookingCreateInput,
originalRescheduledBooking: OriginalRescheduledBooking,
paymentAppData: PaymentAppData,
organizerUser: CreateBookingParams["organizerUser"]
) {
const createBookingObj = {
include: {
user: {
select: { email: true, name: true, timeZone: true, username: true },
},
attendees: true,
payment: true,
references: true,
},
data: newBookingData,
};
if (originalRescheduledBooking?.paid && originalRescheduledBooking?.payment) {
const bookingPayment = originalRescheduledBooking.payment.find((payment) => payment.success);
if (bookingPayment) {
createBookingObj.data.payment = { connect: { id: bookingPayment.id } };
}
}
if (typeof paymentAppData.price === "number" && paymentAppData.price > 0) {
await prisma.credential.findFirstOrThrow({
where: {
appId: paymentAppData.appId,
...(paymentAppData.credentialId ? { id: paymentAppData.credentialId } : { userId: organizerUser.id }),
},
select: { id: true },
});
}
return prisma.booking.create(createBookingObj);
}
function getEventTypeRel(eventTypeId: EventTypeId) {
return eventTypeId ? { connect: { id: eventTypeId } } : {};
}
function getAttendeesData(evt: Pick<CalendarEvent, "attendees" | "team">) {
//if attendee is team member, it should fetch their locale not booker's locale
//perhaps make email fetch request to see if his locale is stored, else
const attendees = evt.attendees.map((attendee) => ({
name: attendee.name,
email: attendee.email,
timeZone: attendee.timeZone,
locale: attendee.language.locale,
}));
if (evt.team?.members) {
attendees.push(
...evt.team.members.map((member) => ({
email: member.email,
name: member.name,
timeZone: member.timeZone,
locale: member.language.locale,
}))
);
}
return attendees;
}
function buildNewBookingData(params: {
uid: short.SUUID;
evt: CalendarEvent;
responses: ReqBodyWithEnd["responses"] | null;
isConfirmedByDefault: IsConfirmedByDefault;
reqBodyMetadata: ReqBodyWithEnd["metadata"];
smsReminderNumber: AwaitedBookingData["smsReminderNumber"];
eventTypeSlug: AwaitedBookingData["eventTypeSlug"];
organizerUser: CreateBookingParams["organizerUser"];
reqBodyRecurringEventId: ReqBodyWithEnd["recurringEventId"];
originalRescheduledBooking: OriginalRescheduledBooking | null;
bookerEmail: AwaitedBookingData["email"];
rescheduleReason: AwaitedBookingData["rescheduleReason"];
eventType: NewBookingEventType;
eventTypeId: EventTypeId;
reqBodyUser: ReqBodyWithEnd["user"];
}): Prisma.BookingCreateInput {
const {
uid,
evt,
responses,
isConfirmedByDefault,
reqBodyMetadata,
smsReminderNumber,
eventTypeSlug,
organizerUser,
reqBodyRecurringEventId,
originalRescheduledBooking,
bookerEmail,
rescheduleReason,
eventType,
eventTypeId,
reqBodyUser,
} = params;
const attendeesData = getAttendeesData(evt);
const eventTypeRel = getEventTypeRel(eventTypeId);
const newBookingData: Prisma.BookingCreateInput = {
uid,
userPrimaryEmail: evt.organizer.email,
responses: responses === null || evt.seatsPerTimeSlot ? Prisma.JsonNull : responses,
title: evt.title,
startTime: dayjs.utc(evt.startTime).toDate(),
endTime: dayjs.utc(evt.endTime).toDate(),
description: evt.seatsPerTimeSlot ? null : evt.additionalNotes,
customInputs: isPrismaObjOrUndefined(evt.customInputs),
status: isConfirmedByDefault ? BookingStatus.ACCEPTED : BookingStatus.PENDING,
location: evt.location,
eventType: eventTypeRel,
smsReminderNumber,
metadata: reqBodyMetadata,
attendees: {
createMany: {
data: attendeesData,
},
},
dynamicEventSlugRef: !eventTypeId ? eventTypeSlug : null,
dynamicGroupSlugRef: !eventTypeId ? (reqBodyUser as string).toLowerCase() : null,
iCalUID: evt.iCalUID ?? "",
user: {
connect: {
id: organizerUser.id,
},
},
destinationCalendar:
evt.destinationCalendar && evt.destinationCalendar.length > 0
? {
connect: { id: evt.destinationCalendar[0].id },
}
: undefined,
};
if (reqBodyRecurringEventId) {
newBookingData.recurringEventId = reqBodyRecurringEventId;
}
if (originalRescheduledBooking) {
newBookingData.metadata = {
...(typeof originalRescheduledBooking.metadata === "object" && originalRescheduledBooking.metadata),
};
newBookingData.paid = originalRescheduledBooking.paid;
newBookingData.fromReschedule = originalRescheduledBooking.uid;
if (originalRescheduledBooking.uid) {
newBookingData.cancellationReason = rescheduleReason;
}
// Reschedule logic with booking with seats
if (newBookingData.attendees?.createMany?.data && eventType?.seatsPerTimeSlot && bookerEmail) {
newBookingData.attendees.createMany.data = attendeesData.filter(
(attendee) => attendee.email === bookerEmail
);
}
if (originalRescheduledBooking.recurringEventId) {
newBookingData.recurringEventId = originalRescheduledBooking.recurringEventId;
}
}
return newBookingData;
}
export type Booking = Prisma.PromiseReturnType<typeof createBooking>;