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

206 lines
6.5 KiB
TypeScript

// eslint-disable-next-line no-restricted-imports
import { cloneDeep } from "lodash";
import { uuid } from "short-uuid";
import EventManager from "@calcom/core/EventManager";
import { sendScheduledSeatsEmails } from "@calcom/emails";
import { refreshCredentials } from "@calcom/features/bookings/lib/getAllCredentialsForUsersOnEvent/refreshCredentials";
import {
allowDisablingAttendeeConfirmationEmails,
allowDisablingHostConfirmationEmails,
} from "@calcom/features/ee/workflows/lib/allowDisablingStandardEmails";
import { ErrorCode } from "@calcom/lib/errorCodes";
import { HttpError } from "@calcom/lib/http-error";
import { handlePayment } from "@calcom/lib/payment/handlePayment";
import prisma from "@calcom/prisma";
import { BookingStatus } from "@calcom/prisma/enums";
import { findBookingQuery } from "../../handleNewBooking/findBookingQuery";
import type { IEventTypePaymentCredentialType } from "../../handleNewBooking/types";
import type { SeatedBooking, NewSeatedBookingObject, HandleSeatsResultBooking } from "../types";
const createNewSeat = async (
rescheduleSeatedBookingObject: NewSeatedBookingObject,
seatedBooking: SeatedBooking
) => {
const {
tAttendees,
attendeeLanguage,
invitee,
eventType,
additionalNotes,
noEmail,
paymentAppData,
allCredentials,
organizerUser,
fullName,
bookerEmail,
responses,
workflows,
} = rescheduleSeatedBookingObject;
let { evt } = rescheduleSeatedBookingObject;
let resultBooking: HandleSeatsResultBooking;
// Need to add translation for attendees to pass type checks. Since these values are never written to the db we can just use the new attendee language
const bookingAttendees = seatedBooking.attendees.map((attendee) => {
return { ...attendee, language: { translate: tAttendees, locale: attendeeLanguage ?? "en" } };
});
evt = { ...evt, attendees: [...bookingAttendees, invitee[0]] };
if (
eventType.seatsPerTimeSlot &&
eventType.seatsPerTimeSlot <= seatedBooking.attendees.filter((attendee) => !!attendee.bookingSeat).length
) {
throw new HttpError({ statusCode: 409, message: ErrorCode.BookingSeatsFull });
}
const videoCallReference = seatedBooking.references.find((reference) => reference.type.includes("_video"));
if (videoCallReference) {
evt.videoCallData = {
type: videoCallReference.type,
id: videoCallReference.meetingId,
password: videoCallReference?.meetingPassword,
url: videoCallReference.meetingUrl,
};
}
const attendeeUniqueId = uuid();
const inviteeToAdd = invitee[0];
await prisma.booking.update({
where: {
uid: seatedBooking.uid,
},
include: {
attendees: true,
},
data: {
attendees: {
create: {
email: inviteeToAdd.email,
name: inviteeToAdd.name,
timeZone: inviteeToAdd.timeZone,
locale: inviteeToAdd.language.locale,
bookingSeat: {
create: {
referenceUid: attendeeUniqueId,
data: {
description: additionalNotes,
responses,
},
booking: {
connect: {
id: seatedBooking.id,
},
},
},
},
},
},
...(seatedBooking.status === BookingStatus.CANCELLED && { status: BookingStatus.ACCEPTED }),
},
});
evt.attendeeSeatId = attendeeUniqueId;
const newSeat = seatedBooking.attendees.length !== 0;
/**
* Remember objects are passed into functions as references
* so if you modify it in a inner function it will be modified in the outer function
* deep cloning evt to avoid this
*/
if (!evt?.uid) {
evt.uid = seatedBooking?.uid ?? null;
}
const copyEvent = cloneDeep(evt);
copyEvent.uid = seatedBooking.uid;
if (noEmail !== true) {
let isHostConfirmationEmailsDisabled = false;
let isAttendeeConfirmationEmailDisabled = false;
isHostConfirmationEmailsDisabled = eventType.metadata?.disableStandardEmails?.confirmation?.host || false;
isAttendeeConfirmationEmailDisabled =
eventType.metadata?.disableStandardEmails?.confirmation?.attendee || false;
if (isHostConfirmationEmailsDisabled) {
isHostConfirmationEmailsDisabled = allowDisablingHostConfirmationEmails(workflows);
}
if (isAttendeeConfirmationEmailDisabled) {
isAttendeeConfirmationEmailDisabled = allowDisablingAttendeeConfirmationEmails(workflows);
}
await sendScheduledSeatsEmails(
copyEvent,
inviteeToAdd,
newSeat,
!!eventType.seatsShowAttendees,
isHostConfirmationEmailsDisabled,
isAttendeeConfirmationEmailDisabled
);
}
const credentials = await refreshCredentials(allCredentials);
const eventManager = new EventManager({ ...organizerUser, credentials }, eventType?.metadata?.apps);
await eventManager.updateCalendarAttendees(evt, seatedBooking);
const foundBooking = await findBookingQuery(seatedBooking.id);
if (!Number.isNaN(paymentAppData.price) && paymentAppData.price > 0 && !!seatedBooking) {
const credentialPaymentAppCategories = await prisma.credential.findMany({
where: {
...(paymentAppData.credentialId ? { id: paymentAppData.credentialId } : { userId: organizerUser.id }),
app: {
categories: {
hasSome: ["payment"],
},
},
},
select: {
key: true,
appId: true,
app: {
select: {
categories: true,
dirName: true,
},
},
},
});
const eventTypePaymentAppCredential = credentialPaymentAppCategories.find((credential) => {
return credential.appId === paymentAppData.appId;
});
if (!eventTypePaymentAppCredential) {
throw new HttpError({ statusCode: 400, message: ErrorCode.MissingPaymentCredential });
}
if (!eventTypePaymentAppCredential?.appId) {
throw new HttpError({ statusCode: 400, message: ErrorCode.MissingPaymentAppId });
}
const payment = await handlePayment(
evt,
eventType,
eventTypePaymentAppCredential as IEventTypePaymentCredentialType,
seatedBooking,
fullName,
bookerEmail
);
resultBooking = { ...foundBooking };
resultBooking["message"] = "Payment required";
resultBooking["paymentUid"] = payment?.uid;
resultBooking["id"] = payment?.id;
} else {
resultBooking = { ...foundBooking };
}
resultBooking["seatReferenceUid"] = evt.attendeeSeatId;
return resultBooking;
};
export default createNewSeat;