Files
calendar/packages/features/bookings/lib/handleNewBooking/loadAndValidateUsers.ts
T
Alex van AndelandGitHub f76696032e feat: Implements maxLeadThreshold (#17643)
* feat: Implements maxLeadThreshold which puts a maximum to the amount of lead a given host is allowed to be ahead

* Refactor getLuckyUser + implement in handleNewBooking + move logic to repo

* Decoupled @prisma from findQualifiedHosts

* Fix deep type error

* Add maxLeadThreshold to builder.ts

* Removed a console.log :O + fix handleChildrenEventTypes input

* Add distribution UI to enable balanced distribution/maximize availability

* 0-1-2 instead of 0-1-2-3 (3 bookings instead of 4)
2024-11-15 15:54:02 +00:00

121 lines
3.5 KiB
TypeScript

import type { Prisma } from "@prisma/client";
import type { IncomingMessage } from "http";
import type { Logger } from "tslog";
import { filterHostsByLeadThreshold } from "@calcom/lib/bookings/filterHostsByLeadThreshold";
import { HttpError } from "@calcom/lib/http-error";
import { getPiiFreeUser } from "@calcom/lib/piiFreeData";
import { safeStringify } from "@calcom/lib/safeStringify";
import { userSelect } from "@calcom/prisma";
import prisma from "@calcom/prisma";
import { SchedulingType } from "@calcom/prisma/enums";
import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential";
import { loadUsers } from "./loadUsers";
import type { NewBookingEventType } from "./types";
type Users = (Awaited<ReturnType<typeof loadUsers>>[number] & {
isFixed?: boolean;
metadata?: Prisma.JsonValue;
createdAt?: Date;
})[];
type EventType = Pick<
NewBookingEventType,
"hosts" | "users" | "id" | "userId" | "schedulingType" | "maxLeadThreshold"
>;
type InputProps = {
req: IncomingMessage;
eventType: EventType;
eventTypeId: number;
dynamicUserList: string[];
logger: Logger<unknown>;
routedTeamMemberIds: number[] | null;
contactOwnerEmail: string | null;
};
export async function loadAndValidateUsers({
req,
eventType,
eventTypeId,
dynamicUserList,
logger,
routedTeamMemberIds,
contactOwnerEmail,
}: InputProps): Promise<Users> {
let users: Users = await loadUsers({
eventType,
dynamicUserList,
req,
routedTeamMemberIds,
contactOwnerEmail,
});
const isDynamicAllowed = !users.some((user) => !user.allowDynamicBooking);
if (!isDynamicAllowed && !eventTypeId) {
logger.warn({
message: "NewBooking: Some of the users in this group do not allow dynamic booking",
});
throw new HttpError({
message: "Some of the users in this group do not allow dynamic booking",
statusCode: 400,
});
}
// If this event was pre-relationship migration
// TODO: Establish whether this is dead code.
if (!users.length && eventType.userId) {
const eventTypeUser = await prisma.user.findUnique({
where: {
id: eventType.userId,
},
select: {
credentials: {
select: credentialForCalendarServiceSelect,
}, // Don't leak to client
...userSelect.select,
},
});
if (!eventTypeUser) {
logger.warn({ message: "NewBooking: eventTypeUser.notFound" });
throw new HttpError({ statusCode: 404, message: "eventTypeUser.notFound" });
}
users.push(eventTypeUser);
}
if (!users) throw new HttpError({ statusCode: 404, message: "eventTypeUser.notFound" });
// map fixed users
users = users.map((user) => ({
...user,
isFixed:
user.isFixed === false
? false
: user.isFixed || eventType.schedulingType !== SchedulingType.ROUND_ROBIN,
}));
const qualifiedHosts = await filterHostsByLeadThreshold({
eventTypeId: eventType.id,
hosts: eventType.hosts.map((host) => ({
isFixed: host.isFixed,
createdAt: host.createdAt,
email: host.user.email,
user: host.user,
})),
maxLeadThreshold: eventType.maxLeadThreshold,
});
if (qualifiedHosts.length) {
// remove users that are not in the qualified hosts array
const qualifiedHostIds = new Set(qualifiedHosts.map((qualifiedHost) => qualifiedHost.user.id));
users = users.filter((user) => qualifiedHostIds.has(user.id));
}
logger.debug(
"Concerned users",
safeStringify({
users: users.map(getPiiFreeUser),
})
);
return users;
}