fix: Set teamMemberEmail server side for CRM RR Skip (#16367)
Co-authored-by: Keith Williams <keithwillcode@gmail.com> Co-authored-by: zomars <zomars@me.com>
This commit is contained in:
co-authored by
Keith Williams
zomars
parent
ae55e5e264
commit
3aa6878c27
@@ -1,14 +1,16 @@
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
import { z } from "zod";
|
||||
|
||||
import { getCRMContactOwnerForRRLeadSkip } from "@calcom/app-store/_utils/CRMRoundRobinSkip";
|
||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
import { getBookingForReschedule } from "@calcom/features/bookings/lib/get-booking";
|
||||
import type { GetBookingType } from "@calcom/features/bookings/lib/get-booking";
|
||||
import { getSlugOrRequestedSlug } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||
import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||
import { getBookingForReschedule } from "@calcom/features/bookings/lib/get-booking";
|
||||
import { getSlugOrRequestedSlug, orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||
import slugify from "@calcom/lib/slugify";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { RedirectType } from "@calcom/prisma/client";
|
||||
import { SchedulingType } from "@calcom/prisma/enums";
|
||||
import type { RouterOutputs } from "@calcom/trpc";
|
||||
|
||||
import { getTemporaryOrgRedirect } from "@lib/getTemporaryOrgRedirect";
|
||||
|
||||
@@ -23,7 +25,12 @@ const paramsSchema = z.object({
|
||||
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
|
||||
const session = await getServerSession(context);
|
||||
const { slug: teamSlug, type: meetingSlug } = paramsSchema.parse(context.params);
|
||||
const { rescheduleUid, duration: queryDuration, isInstantMeeting: queryIsInstantMeeting } = context.query;
|
||||
const {
|
||||
rescheduleUid,
|
||||
duration: queryDuration,
|
||||
isInstantMeeting: queryIsInstantMeeting,
|
||||
email,
|
||||
} = context.query;
|
||||
const { ssrInit } = await import("@server/lib/ssr");
|
||||
const ssr = await ssrInit(context);
|
||||
const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(context.req, context.params?.orgSlug);
|
||||
@@ -97,6 +104,29 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
||||
isInstantMeeting: eventData.isInstantEvent && queryIsInstantMeeting ? true : false,
|
||||
themeBasis: null,
|
||||
orgBannerUrl: eventData?.team?.parent?.bannerUrl ?? "",
|
||||
teamMemberEmail: await getTeamMemberEmail(eventData, email as string),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
type EventData = RouterOutputs["viewer"]["public"]["event"];
|
||||
|
||||
async function getTeamMemberEmail(eventData: EventData, email?: string): Promise<string | null> {
|
||||
// Pre-requisites
|
||||
if (!eventData || !email || eventData.schedulingType !== SchedulingType.ROUND_ROBIN) return null;
|
||||
const crmContactOwnerEmail = await getCRMContactOwnerForRRLeadSkip(email, eventData.id);
|
||||
if (!crmContactOwnerEmail) return null;
|
||||
// Determine if the contactOwner is a part of the event type
|
||||
const contactOwnerQuery = await prisma.user.findFirst({
|
||||
where: {
|
||||
email: crmContactOwnerEmail,
|
||||
hosts: {
|
||||
some: {
|
||||
eventTypeId: eventData.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
if (!contactOwnerQuery) return null;
|
||||
return crmContactOwnerEmail;
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ export default function Type({
|
||||
eventData,
|
||||
isInstantMeeting,
|
||||
orgBannerUrl,
|
||||
teamMemberEmail,
|
||||
}: PageProps) {
|
||||
const searchParams = useSearchParams();
|
||||
|
||||
@@ -67,6 +68,7 @@ export default function Type({
|
||||
eventData.length
|
||||
)}
|
||||
orgBannerUrl={orgBannerUrl}
|
||||
teamMemberEmail={teamMemberEmail}
|
||||
/>
|
||||
</main>
|
||||
);
|
||||
|
||||
@@ -175,12 +175,10 @@ describe("getSchedule", () => {
|
||||
endTime: `${plus2DateString}T18:29:59.999Z`,
|
||||
timeZone: Timezones["+5:30"],
|
||||
isTeamEvent: true,
|
||||
bookerEmail: "test@test.com",
|
||||
teamMemberEmail: "example@example.com",
|
||||
},
|
||||
});
|
||||
|
||||
expect(scheduleWithLeadSkip.teamMember).toBe("example@example.com");
|
||||
|
||||
// only slots where example@example.com is available
|
||||
expect(scheduleWithLeadSkip).toHaveTimeSlots(
|
||||
[`11:30:00.000Z`, `12:30:00.000Z`, `13:30:00.000Z`, `14:30:00.000Z`, `15:30:00.000Z`],
|
||||
@@ -197,12 +195,9 @@ describe("getSchedule", () => {
|
||||
endTime: `${plus2DateString}T18:29:59.999Z`,
|
||||
timeZone: Timezones["+5:30"],
|
||||
isTeamEvent: true,
|
||||
bookerEmail: "testtest@test.com",
|
||||
},
|
||||
});
|
||||
|
||||
expect(scheduleWithoutLeadSkip.teamMember).toBe(undefined);
|
||||
|
||||
// slots where either one of the rr hosts is available
|
||||
expect(scheduleWithoutLeadSkip).toHaveTimeSlots(
|
||||
[
|
||||
@@ -325,12 +320,10 @@ describe("getSchedule", () => {
|
||||
endTime: `${plus2DateString}T18:29:59.999Z`,
|
||||
timeZone: Timezones["+5:30"],
|
||||
isTeamEvent: true,
|
||||
bookerEmail: "test@test.com",
|
||||
teamMemberEmail: "example@example.com",
|
||||
},
|
||||
});
|
||||
|
||||
expect(scheduleFixedHostLead.teamMember).toBe("example@example.com");
|
||||
|
||||
// show normal slots, example@example + one RR host needs to be available
|
||||
expect(scheduleFixedHostLead).toHaveTimeSlots(
|
||||
[
|
||||
@@ -355,12 +348,10 @@ describe("getSchedule", () => {
|
||||
endTime: `${plus2DateString}T18:29:59.999Z`,
|
||||
timeZone: Timezones["+5:30"],
|
||||
isTeamEvent: true,
|
||||
bookerEmail: "test1@test.com",
|
||||
teamMemberEmail: "example1@example.com",
|
||||
},
|
||||
});
|
||||
|
||||
expect(scheduleRRHostLead.teamMember).toBe("example1@example.com");
|
||||
|
||||
// slots where example@example (fixed host) + example1@example.com are available together
|
||||
expect(scheduleRRHostLead).toHaveTimeSlots(
|
||||
[`07:30:00.000Z`, `08:30:00.000Z`, `09:30:00.000Z`, `10:30:00.000Z`, `11:30:00.000Z`],
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import type { z } from "zod";
|
||||
|
||||
import CrmManager from "@calcom/core/crmManager/crmManager";
|
||||
import { prisma } from "@calcom/prisma";
|
||||
import type { EventTypeAppMetadataSchema } from "@calcom/prisma/zod-utils";
|
||||
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
||||
|
||||
export async function getCRMContactOwnerForRRLeadSkip(
|
||||
bookerEmail: string,
|
||||
eventTypeId: number
|
||||
): Promise<string | undefined> {
|
||||
const eventTypeMetadataQuery = await prisma.eventType.findUnique({
|
||||
where: {
|
||||
id: eventTypeId,
|
||||
},
|
||||
select: { metadata: true },
|
||||
});
|
||||
|
||||
const eventTypeMetadata = EventTypeMetaDataSchema.safeParse(eventTypeMetadataQuery?.metadata);
|
||||
|
||||
if (!eventTypeMetadata.success || !eventTypeMetadata.data?.apps) return;
|
||||
|
||||
const crm = await getCRMManagerWithRRLeadSkip(eventTypeMetadata.data.apps);
|
||||
|
||||
if (!crm) return;
|
||||
|
||||
const contact = await crm.getContacts(bookerEmail, true);
|
||||
if (!contact?.length) return;
|
||||
return contact[0].ownerEmail;
|
||||
}
|
||||
|
||||
async function getCRMManagerWithRRLeadSkip(apps: z.infer<typeof EventTypeAppMetadataSchema>) {
|
||||
let crmRoundRobinLeadSkip;
|
||||
for (const appKey in apps) {
|
||||
const app = apps[appKey as keyof typeof apps];
|
||||
if (
|
||||
app.enabled &&
|
||||
typeof app.appCategories === "object" &&
|
||||
app.appCategories.some((category: string) => category === "crm") &&
|
||||
app.roundRobinLeadSkip
|
||||
) {
|
||||
crmRoundRobinLeadSkip = app;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!crmRoundRobinLeadSkip) return;
|
||||
const crmCredential = await prisma.credential.findUnique({
|
||||
where: {
|
||||
id: crmRoundRobinLeadSkip.credentialId,
|
||||
},
|
||||
include: {
|
||||
user: {
|
||||
select: {
|
||||
email: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
if (!crmCredential) return;
|
||||
return new CrmManager(crmCredential);
|
||||
}
|
||||
@@ -39,7 +39,7 @@ export default class CrmManager {
|
||||
const contactsToCreate = event.attendees.filter(
|
||||
(attendee) => !contacts.some((contact) => contact.email === attendee.email)
|
||||
);
|
||||
const createdContacts = await this.createContacts(contactsToCreate);
|
||||
const createdContacts = await this.createContacts(contactsToCreate, event.organizer?.email);
|
||||
contacts = contacts.concat(createdContacts);
|
||||
return await crmService?.createEvent(event, contacts);
|
||||
}
|
||||
@@ -60,9 +60,9 @@ export default class CrmManager {
|
||||
return contacts;
|
||||
}
|
||||
|
||||
public async createContacts(contactsToCreate: ContactCreateInput[]) {
|
||||
public async createContacts(contactsToCreate: ContactCreateInput[], organizerEmail?: string) {
|
||||
const crmService = await this.getCrmService(this.credential);
|
||||
const createdContacts = (await crmService?.createContacts(contactsToCreate)) || [];
|
||||
const createdContacts = (await crmService?.createContacts(contactsToCreate, organizerEmail)) || [];
|
||||
return createdContacts;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -839,13 +839,14 @@ const _getOutOfOfficeDays = async ({
|
||||
|
||||
type GetUserAvailabilityQuery = Parameters<typeof getUserAvailability>[0];
|
||||
type GetUserAvailabilityInitialData = NonNullable<Parameters<typeof getUserAvailability>[1]>;
|
||||
export type GetAvailabilityUser = NonNullable<GetUserAvailabilityInitialData["user"]>;
|
||||
|
||||
const _getUsersAvailability = async ({
|
||||
users,
|
||||
query,
|
||||
initialData,
|
||||
}: {
|
||||
users: (NonNullable<GetUserAvailabilityInitialData["user"]> & {
|
||||
users: (GetAvailabilityUser & {
|
||||
currentBookings?: GetUserAvailabilityInitialData["currentBookings"];
|
||||
})[];
|
||||
query: Omit<GetUserAvailabilityQuery, "userId" | "username">;
|
||||
|
||||
@@ -47,7 +47,7 @@ export interface IUseBookings {
|
||||
hashedLink?: string | null;
|
||||
bookingForm: UseBookingFormReturnType["bookingForm"];
|
||||
metadata: Record<string, string>;
|
||||
teamMemberEmail?: string;
|
||||
teamMemberEmail?: string | null;
|
||||
}
|
||||
|
||||
const getBookingSuccessfulEventPayload = (booking: {
|
||||
@@ -310,7 +310,6 @@ export const useBookings = ({ event, hashedLink, bookingForm, metadata, teamMemb
|
||||
bookingForm,
|
||||
hashedLink,
|
||||
metadata,
|
||||
teamMemberEmail,
|
||||
handleInstantBooking: createInstantBookingMutation.mutate,
|
||||
handleRecBooking: createRecurringBookingMutation.mutate,
|
||||
handleBooking: createBookingMutation.mutate,
|
||||
|
||||
@@ -32,6 +32,7 @@ type StoreInitializeType = {
|
||||
durationConfig?: number[] | null;
|
||||
org?: string | null;
|
||||
isInstantMeeting?: boolean;
|
||||
teamMemberEmail?: string | null;
|
||||
};
|
||||
|
||||
type SeatedEventData = {
|
||||
@@ -148,6 +149,8 @@ export type BookerStore = {
|
||||
|
||||
org?: string | null;
|
||||
setOrg: (org: string | null | undefined) => void;
|
||||
|
||||
teamMemberEmail?: string | null;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -253,6 +256,7 @@ export const useBookerStore = create<BookerStore>((set, get) => ({
|
||||
durationConfig,
|
||||
org,
|
||||
isInstantMeeting,
|
||||
teamMemberEmail,
|
||||
}: StoreInitializeType) => {
|
||||
const selectedDateInStore = get().selectedDate;
|
||||
|
||||
@@ -265,7 +269,8 @@ export const useBookerStore = create<BookerStore>((set, get) => ({
|
||||
get().bookingUid === bookingUid &&
|
||||
get().bookingData?.responses.email === bookingData?.responses.email &&
|
||||
get().layout === layout &&
|
||||
get().rescheduledBy === rescheduledBy
|
||||
get().rescheduledBy === rescheduledBy &&
|
||||
get().teamMemberEmail
|
||||
)
|
||||
return;
|
||||
set({
|
||||
@@ -284,6 +289,7 @@ export const useBookerStore = create<BookerStore>((set, get) => ({
|
||||
selectedDate:
|
||||
selectedDateInStore ||
|
||||
(["week_view", "column_view"].includes(layout) ? dayjs().format("YYYY-MM-DD") : null),
|
||||
teamMemberEmail,
|
||||
});
|
||||
|
||||
if (durationConfig?.includes(Number(getQueryParam("duration")))) {
|
||||
@@ -372,6 +378,7 @@ export const useInitializeBookerStore = ({
|
||||
durationConfig,
|
||||
org,
|
||||
isInstantMeeting,
|
||||
teamMemberEmail,
|
||||
}: StoreInitializeType) => {
|
||||
const initializeStore = useBookerStore((state) => state.initialize);
|
||||
useEffect(() => {
|
||||
@@ -389,6 +396,7 @@ export const useInitializeBookerStore = ({
|
||||
verifiedEmail,
|
||||
durationConfig,
|
||||
isInstantMeeting,
|
||||
teamMemberEmail,
|
||||
});
|
||||
}, [
|
||||
initializeStore,
|
||||
@@ -405,5 +413,6 @@ export const useInitializeBookerStore = ({
|
||||
verifiedEmail,
|
||||
durationConfig,
|
||||
isInstantMeeting,
|
||||
teamMemberEmail,
|
||||
]);
|
||||
};
|
||||
|
||||
@@ -85,6 +85,7 @@ export interface BookerProps {
|
||||
*/
|
||||
hashedLink?: string | null;
|
||||
isInstantMeeting?: boolean;
|
||||
teamMemberEmail?: string | null;
|
||||
}
|
||||
|
||||
export type WrappedBookerPropsMain = {
|
||||
|
||||
@@ -65,7 +65,7 @@ export const useScheduleForEvent = ({
|
||||
dayCount,
|
||||
selectedDate,
|
||||
orgSlug,
|
||||
bookerEmail,
|
||||
teamMemberEmail,
|
||||
}: {
|
||||
prefetchNextMonth?: boolean;
|
||||
username?: string | null;
|
||||
@@ -77,7 +77,7 @@ export const useScheduleForEvent = ({
|
||||
dayCount?: number | null;
|
||||
selectedDate?: string | null;
|
||||
orgSlug?: string;
|
||||
bookerEmail?: string;
|
||||
teamMemberEmail?: string | null;
|
||||
} = {}) => {
|
||||
const { timezone } = useTimePreferences();
|
||||
const event = useEvent();
|
||||
@@ -107,7 +107,7 @@ export const useScheduleForEvent = ({
|
||||
duration: durationFromStore ?? duration,
|
||||
isTeamEvent: pathname?.indexOf("/team/") !== -1 || isTeam,
|
||||
orgSlug,
|
||||
bookerEmail,
|
||||
teamMemberEmail,
|
||||
});
|
||||
|
||||
return {
|
||||
|
||||
@@ -20,7 +20,7 @@ export type BookingOptions = {
|
||||
bookingUid?: string;
|
||||
seatReferenceUid?: string;
|
||||
hashedLink?: string | null;
|
||||
teamMemberEmail?: string;
|
||||
teamMemberEmail?: string | null;
|
||||
orgSlug?: string;
|
||||
};
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ export type UseScheduleWithCacheArgs = {
|
||||
rescheduleUid?: string | null;
|
||||
isTeamEvent?: boolean;
|
||||
orgSlug?: string;
|
||||
bookerEmail?: string;
|
||||
teamMemberEmail?: string | null;
|
||||
};
|
||||
|
||||
export const useSchedule = ({
|
||||
@@ -33,7 +33,7 @@ export const useSchedule = ({
|
||||
rescheduleUid,
|
||||
isTeamEvent,
|
||||
orgSlug,
|
||||
bookerEmail,
|
||||
teamMemberEmail,
|
||||
}: UseScheduleWithCacheArgs) => {
|
||||
const [startTime, endTime] = useTimesForSchedule({
|
||||
month,
|
||||
@@ -60,7 +60,7 @@ export const useSchedule = ({
|
||||
duration: duration ? `${duration}` : undefined,
|
||||
rescheduleUid,
|
||||
orgSlug,
|
||||
bookerEmail,
|
||||
teamMemberEmail,
|
||||
},
|
||||
{
|
||||
trpc: {
|
||||
|
||||
@@ -21,7 +21,6 @@ import { useBookerStore, useInitializeBookerStore } from "@calcom/features/booki
|
||||
import { useEvent, useScheduleForEvent } from "@calcom/features/bookings/Booker/utils/event";
|
||||
import { useBrandColors } from "@calcom/features/bookings/Booker/utils/use-brand-colors";
|
||||
import { DEFAULT_LIGHT_BRAND_COLOR, DEFAULT_DARK_BRAND_COLOR } from "@calcom/lib/constants";
|
||||
import { useDebounce } from "@calcom/lib/hooks/useDebounce";
|
||||
import { useRouterQuery } from "@calcom/lib/hooks/useRouterQuery";
|
||||
import { BookerLayouts } from "@calcom/prisma/zod-utils";
|
||||
|
||||
@@ -122,7 +121,6 @@ export const BookerWebWrapper = (props: BookerWebWrapperAtomProps) => {
|
||||
* Prioritize dateSchedule load
|
||||
* Component will render but use data already fetched from here, and no duplicate requests will be made
|
||||
* */
|
||||
const debouncedFormEmail = useDebounce(bookerForm.formEmail, 600);
|
||||
const schedule = useScheduleForEvent({
|
||||
prefetchNextMonth,
|
||||
username: props.username,
|
||||
@@ -132,14 +130,14 @@ export const BookerWebWrapper = (props: BookerWebWrapperAtomProps) => {
|
||||
month: props.month,
|
||||
duration: props.duration,
|
||||
selectedDate,
|
||||
bookerEmail: debouncedFormEmail,
|
||||
teamMemberEmail: props.teamMemberEmail,
|
||||
});
|
||||
const bookings = useBookings({
|
||||
event,
|
||||
hashedLink: props.hashedLink,
|
||||
bookingForm: bookerForm.bookingForm,
|
||||
metadata: metadata ?? {},
|
||||
teamMemberEmail: schedule.data?.teamMember,
|
||||
teamMemberEmail: props.teamMemberEmail,
|
||||
});
|
||||
|
||||
const verifyCode = useVerifyCode({
|
||||
|
||||
@@ -21,7 +21,6 @@ type UseHandleBookingProps = {
|
||||
};
|
||||
metadata: Record<string, string>;
|
||||
hashedLink?: string | null;
|
||||
teamMemberEmail?: string;
|
||||
handleBooking: (input: UseCreateBookingInput) => void;
|
||||
handleInstantBooking: (input: BookingCreateBody) => void;
|
||||
handleRecBooking: (input: BookingCreateBody[]) => void;
|
||||
@@ -33,7 +32,6 @@ export const useHandleBookEvent = ({
|
||||
event,
|
||||
metadata,
|
||||
hashedLink,
|
||||
teamMemberEmail,
|
||||
handleBooking,
|
||||
handleInstantBooking,
|
||||
handleRecBooking,
|
||||
@@ -52,6 +50,7 @@ export const useHandleBookEvent = ({
|
||||
const seatedEventData = useBookerStore((state) => state.seatedEventData);
|
||||
const isInstantMeeting = useBookerStore((state) => state.isInstantMeeting);
|
||||
const orgSlug = useBookerStore((state) => state.org);
|
||||
const teamMemberEmail = useBookerStore((state) => state.teamMemberEmail);
|
||||
|
||||
const handleBookEvent = () => {
|
||||
const values = bookingForm.getValues();
|
||||
|
||||
@@ -246,7 +246,7 @@ export const bookingCreateBodySchema = z.object({
|
||||
hashedLink: z.string().nullish(),
|
||||
seatReferenceUid: z.string().optional(),
|
||||
orgSlug: z.string().optional(),
|
||||
teamMemberEmail: z.string().optional(),
|
||||
teamMemberEmail: z.string().nullish(),
|
||||
});
|
||||
|
||||
export const requiredCustomInputSchema = z.union([
|
||||
@@ -293,7 +293,6 @@ export const extendedBookingCreateBody = bookingCreateBodySchema.merge(
|
||||
.optional(),
|
||||
luckyUsers: z.array(z.number()).optional(),
|
||||
customInputs: z.undefined().optional(),
|
||||
teamMemberEmail: z.string().optional(),
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ export const getScheduleSchema = z
|
||||
// whether to do team event or user event
|
||||
isTeamEvent: z.boolean().optional().default(false),
|
||||
orgSlug: z.string().optional(),
|
||||
bookerEmail: z.string().optional(),
|
||||
teamMemberEmail: z.string().nullable().optional(),
|
||||
})
|
||||
.transform((val) => {
|
||||
// Need this so we can pass a single username in the query string form public API
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { countBy } from "lodash";
|
||||
import { v4 as uuid } from "uuid";
|
||||
import type z from "zod";
|
||||
|
||||
import CrmManager from "@calcom/core/crmManager/crmManager";
|
||||
import { getAggregatedAvailability } from "@calcom/core/getAggregatedAvailability";
|
||||
import { getBusyTimesForLimitChecks } from "@calcom/core/getBusyTimes";
|
||||
import type { CurrentSeats, IFromUser, IToUser } from "@calcom/core/getUserAvailability";
|
||||
import type { CurrentSeats, IFromUser, IToUser, GetAvailabilityUser } from "@calcom/core/getUserAvailability";
|
||||
import { getUsersAvailability } from "@calcom/core/getUserAvailability";
|
||||
import type { Dayjs } from "@calcom/dayjs";
|
||||
import dayjs from "@calcom/dayjs";
|
||||
@@ -32,7 +30,6 @@ import { SchedulingType } from "@calcom/prisma/enums";
|
||||
import { BookingStatus } from "@calcom/prisma/enums";
|
||||
import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential";
|
||||
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
||||
import type { EventTypeAppMetadataSchema } from "@calcom/prisma/zod-utils";
|
||||
import type { EventBusyDate } from "@calcom/types/Calendar";
|
||||
|
||||
import { TRPCError } from "@trpc/server";
|
||||
@@ -322,57 +319,6 @@ export interface IGetAvailableSlots {
|
||||
emoji?: string | undefined;
|
||||
}[]
|
||||
>;
|
||||
teamMember?: string | undefined;
|
||||
}
|
||||
|
||||
async function getCRMContactOwnerForRRLeadSkip(
|
||||
bookerEmail: string,
|
||||
apps?: z.infer<typeof EventTypeAppMetadataSchema>
|
||||
) {
|
||||
if (!apps) return;
|
||||
const crm = await getCRMManagerWithRRLeadSkip(apps);
|
||||
|
||||
if (!crm) return;
|
||||
|
||||
const contact = await crm.getContacts(bookerEmail, true);
|
||||
if (contact?.length) {
|
||||
return contact[0].ownerEmail;
|
||||
}
|
||||
}
|
||||
|
||||
async function getCRMManagerWithRRLeadSkip(apps: z.infer<typeof EventTypeAppMetadataSchema>) {
|
||||
let crmRoundRobinLeadSkip;
|
||||
for (const appKey in apps) {
|
||||
const app = apps[appKey as keyof typeof apps];
|
||||
if (
|
||||
app.enabled &&
|
||||
typeof app.appCategories === "object" &&
|
||||
app.appCategories.some((category: string) => category === "crm") &&
|
||||
app.roundRobinLeadSkip
|
||||
) {
|
||||
crmRoundRobinLeadSkip = app;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (crmRoundRobinLeadSkip) {
|
||||
const crmCredential = await prisma.credential.findUnique({
|
||||
where: {
|
||||
id: crmRoundRobinLeadSkip.credentialId,
|
||||
},
|
||||
include: {
|
||||
user: {
|
||||
select: {
|
||||
email: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
if (crmCredential) {
|
||||
return new CrmManager(crmCredential);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
export async function getAvailableSlots({ input, ctx }: GetScheduleOptions): Promise<IGetAvailableSlots> {
|
||||
@@ -435,8 +381,6 @@ export async function getAvailableSlots({ input, ctx }: GetScheduleOptions): Pro
|
||||
}
|
||||
let currentSeats: CurrentSeats | undefined;
|
||||
|
||||
let teamMember: string | undefined;
|
||||
|
||||
let hosts =
|
||||
eventType.hosts?.length && eventType.schedulingType
|
||||
? eventType.hosts
|
||||
@@ -466,25 +410,18 @@ export async function getAvailableSlots({ input, ctx }: GetScheduleOptions): Pro
|
||||
hosts = hosts.filter((host) => host.user.id === originalRescheduledBooking?.userId || 0);
|
||||
}
|
||||
|
||||
let usersWithCredentials = hosts.map(({ isFixed, user }) => ({ isFixed, ...user }));
|
||||
const teamMemberHost = hosts.find((host) => host.user.email === input?.teamMemberEmail);
|
||||
|
||||
if (eventType.schedulingType === SchedulingType.ROUND_ROBIN && input.bookerEmail) {
|
||||
const crmContactOwner = await getCRMContactOwnerForRRLeadSkip(
|
||||
input.bookerEmail,
|
||||
eventType?.metadata?.apps
|
||||
);
|
||||
const contactOwnerHost = hosts.find((host) => host.user.email === crmContactOwner);
|
||||
// If the requested team member is a fixed host proceed as normal else get availability like the requested member is a fixed host
|
||||
const usersWithCredentials =
|
||||
!input.teamMemberEmail || !teamMemberHost || teamMemberHost.isFixed
|
||||
? hosts.map(({ isFixed, user }) => ({ isFixed, ...user }))
|
||||
: hosts.reduce((usersArray, host) => {
|
||||
if (host.isFixed || host.user.email === input.teamMemberEmail)
|
||||
usersArray.push({ ...host.user, isFixed: host.isFixed });
|
||||
|
||||
if (contactOwnerHost) {
|
||||
teamMember = contactOwnerHost.user.email;
|
||||
const contactOwnerIsRRHost = !contactOwnerHost.isFixed;
|
||||
|
||||
usersWithCredentials = usersWithCredentials.filter(
|
||||
(user) => user.email !== contactOwnerHost.user.email && (!contactOwnerIsRRHost || user.isFixed)
|
||||
);
|
||||
usersWithCredentials.push({ ...contactOwnerHost.user, isFixed: true });
|
||||
}
|
||||
}
|
||||
return usersArray;
|
||||
}, [] as (GetAvailabilityUser & { isFixed: boolean })[]);
|
||||
|
||||
const durationToUse = input.duration || 0;
|
||||
|
||||
@@ -894,7 +831,6 @@ export async function getAvailableSlots({ input, ctx }: GetScheduleOptions): Pro
|
||||
|
||||
return {
|
||||
slots: withinBoundsSlotsMappedToDate,
|
||||
teamMember,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user