Adds eventTypeId as a parameter (#1217)
This commit is contained in:
		
							parent
							
								
									8c1b69cc0f
								
							
						
					
					
						commit
						22aa083883
					
				
					 6 changed files with 116 additions and 59 deletions
				
			
		|  | @ -19,6 +19,7 @@ import { createPaymentLink } from "@ee/lib/stripe/client"; | |||
| 
 | ||||
| import { asStringOrNull } from "@lib/asStringOrNull"; | ||||
| import { timeZone } from "@lib/clock"; | ||||
| import { ensureArray } from "@lib/ensureArray"; | ||||
| import { useLocale } from "@lib/hooks/useLocale"; | ||||
| import useTheme from "@lib/hooks/useTheme"; | ||||
| import { LocationType } from "@lib/location"; | ||||
|  | @ -137,19 +138,12 @@ const BookingPage = (props: BookingPageProps) => { | |||
|     }; | ||||
|   }; | ||||
| 
 | ||||
|   // can be shortened using .filter(), except TypeScript doesn't know what to make of the types.
 | ||||
|   const guests = router.query.guest | ||||
|     ? Array.isArray(router.query.guest) | ||||
|       ? router.query.guest | ||||
|       : [router.query.guest] | ||||
|     : []; | ||||
| 
 | ||||
|   const bookingForm = useForm<BookingFormValues>({ | ||||
|     defaultValues: { | ||||
|       name: (router.query.name as string) || "", | ||||
|       email: (router.query.email as string) || "", | ||||
|       notes: (router.query.notes as string) || "", | ||||
|       guests, | ||||
|       guests: ensureArray(router.query.guest), | ||||
|       customInputs: props.eventType.customInputs.reduce( | ||||
|         (customInputs, input) => ({ | ||||
|           ...customInputs, | ||||
|  | @ -213,7 +207,7 @@ const BookingPage = (props: BookingPageProps) => { | |||
|       timeZone: timeZone(), | ||||
|       language: i18n.language, | ||||
|       rescheduleUid, | ||||
|       user: router.query.user as string, | ||||
|       user: router.query.user, | ||||
|       location: getLocationValue(booking), | ||||
|       metadata, | ||||
|       customInputs: Object.keys(booking.customInputs || {}).map((inputId) => ({ | ||||
|  |  | |||
|  | @ -14,6 +14,7 @@ const config: Config.InitialOptions = { | |||
|     "^@components(.*)$": "<rootDir>/components$1", | ||||
|     "^@lib(.*)$": "<rootDir>/lib$1", | ||||
|     "^@server(.*)$": "<rootDir>/server$1", | ||||
|     "^@ee(.*)$": "<rootDir>/ee$1", | ||||
|   }, | ||||
| }; | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										9
									
								
								lib/ensureArray.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								lib/ensureArray.ts
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,9 @@ | |||
| export function ensureArray<T>(val: unknown): T[] { | ||||
|   if (Array.isArray(val)) { | ||||
|     return val; | ||||
|   } | ||||
|   if (typeof val === "undefined") { | ||||
|     return []; | ||||
|   } | ||||
|   return [val] as T[]; | ||||
| } | ||||
|  | @ -16,8 +16,7 @@ export type BookingCreateBody = { | |||
|   rescheduleUid?: string; | ||||
|   start: string; | ||||
|   timeZone: string; | ||||
|   users?: string[]; | ||||
|   user?: string; | ||||
|   user?: string | string[]; | ||||
|   language: string; | ||||
|   customInputs: { label: string; value: string }[]; | ||||
|   metadata: { | ||||
|  |  | |||
|  | @ -17,6 +17,7 @@ import { | |||
|   sendRescheduledEmails, | ||||
|   sendOrganizerRequestEmail, | ||||
| } from "@lib/emails/email-manager"; | ||||
| import { ensureArray } from "@lib/ensureArray"; | ||||
| import { getErrorFromUnknown } from "@lib/errors"; | ||||
| import { getEventName } from "@lib/event"; | ||||
| import EventManager, { EventResult, PartialReference } from "@lib/events/EventManager"; | ||||
|  | @ -123,6 +124,59 @@ function isOutOfBounds( | |||
|   } | ||||
| } | ||||
| 
 | ||||
| const userSelect = Prisma.validator<Prisma.UserArgs>()({ | ||||
|   select: { | ||||
|     id: true, | ||||
|     email: true, | ||||
|     name: true, | ||||
|     username: true, | ||||
|     timeZone: true, | ||||
|     credentials: true, | ||||
|     bufferTime: true, | ||||
|   }, | ||||
| }); | ||||
| 
 | ||||
| const getUserNameWithBookingCounts = async (eventTypeId: number, selectedUserNames: string[]) => { | ||||
|   const users = await prisma.user.findMany({ | ||||
|     where: { | ||||
|       username: { in: selectedUserNames }, | ||||
|       bookings: { | ||||
|         some: { | ||||
|           startTime: { | ||||
|             gt: new Date(), | ||||
|           }, | ||||
|           eventTypeId, | ||||
|         }, | ||||
|       }, | ||||
|     }, | ||||
|     select: { | ||||
|       id: true, | ||||
|       username: true, | ||||
|     }, | ||||
|   }); | ||||
| 
 | ||||
|   const userNamesWithBookingCounts = await Promise.all( | ||||
|     users.map(async (user) => ({ | ||||
|       username: user.username, | ||||
|       bookingCount: await prisma.booking.count({ | ||||
|         where: { | ||||
|           user: { | ||||
|             id: user.id, | ||||
|           }, | ||||
|           startTime: { | ||||
|             gt: new Date(), | ||||
|           }, | ||||
|           eventTypeId, | ||||
|         }, | ||||
|       }), | ||||
|     })) | ||||
|   ); | ||||
| 
 | ||||
|   return userNamesWithBookingCounts; | ||||
| }; | ||||
| 
 | ||||
| type User = Prisma.UserGetPayload<typeof userSelect>; | ||||
| 
 | ||||
| export default async function handler(req: NextApiRequest, res: NextApiResponse) { | ||||
|   const reqBody = req.body as BookingCreateBody; | ||||
|   const eventTypeId = reqBody.eventTypeId; | ||||
|  | @ -144,28 +198,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) | |||
|     return res.status(400).json(error); | ||||
|   } | ||||
| 
 | ||||
|   const userSelect = Prisma.validator<Prisma.UserSelect>()({ | ||||
|     id: true, | ||||
|     email: true, | ||||
|     name: true, | ||||
|     username: true, | ||||
|     timeZone: true, | ||||
|     credentials: true, | ||||
|     bufferTime: true, | ||||
|   }); | ||||
| 
 | ||||
|   const userData = Prisma.validator<Prisma.UserArgs>()({ | ||||
|     select: userSelect, | ||||
|   }); | ||||
| 
 | ||||
|   const eventType = await prisma.eventType.findUnique({ | ||||
|     where: { | ||||
|       id: eventTypeId, | ||||
|     }, | ||||
|     select: { | ||||
|       users: { | ||||
|         select: userSelect, | ||||
|       }, | ||||
|       users: userSelect, | ||||
|       team: { | ||||
|         select: { | ||||
|           id: true, | ||||
|  | @ -198,43 +236,19 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) | |||
|       where: { | ||||
|         id: eventType.userId, | ||||
|       }, | ||||
|       select: userSelect, | ||||
|       ...userSelect, | ||||
|     }); | ||||
|     if (!eventTypeUser) return res.status(404).json({ message: "eventTypeUser.notFound" }); | ||||
|     users.push(eventTypeUser); | ||||
|   } | ||||
| 
 | ||||
|   if (eventType.schedulingType === SchedulingType.ROUND_ROBIN) { | ||||
|     const selectedUsers = reqBody.users || []; | ||||
|     const selectedUsersDataWithBookingsCount = await prisma.user.findMany({ | ||||
|       where: { | ||||
|         username: { in: selectedUsers }, | ||||
|         bookings: { | ||||
|           every: { | ||||
|             startTime: { | ||||
|               gt: new Date(), | ||||
|             }, | ||||
|           }, | ||||
|         }, | ||||
|       }, | ||||
|       select: { | ||||
|         username: true, | ||||
|         _count: { | ||||
|           select: { bookings: true }, | ||||
|         }, | ||||
|       }, | ||||
|     }); | ||||
|     const bookingCounts = await getUserNameWithBookingCounts( | ||||
|       eventTypeId, | ||||
|       ensureArray(reqBody.user) || users.map((user) => user.username) | ||||
|     ); | ||||
| 
 | ||||
|     const bookingCounts = selectedUsersDataWithBookingsCount.map((userData) => ({ | ||||
|       username: userData.username, | ||||
|       bookingCount: userData._count?.bookings || 0, | ||||
|     })); | ||||
| 
 | ||||
|     if (!bookingCounts.length) users.slice(0, 1); | ||||
| 
 | ||||
|     const [firstMostAvailableUser] = bookingCounts.sort((a, b) => (a.bookingCount > b.bookingCount ? 1 : -1)); | ||||
|     const luckyUser = users.find((user) => user.username === firstMostAvailableUser?.username); | ||||
|     users = luckyUser ? [luckyUser] : users; | ||||
|     users = getLuckyUsers(users, bookingCounts); | ||||
|   } | ||||
| 
 | ||||
|   const invitee = [{ email: reqBody.email, name: reqBody.name, timeZone: reqBody.timeZone }]; | ||||
|  | @ -354,7 +368,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) | |||
| 
 | ||||
|   let results: EventResult[] = []; | ||||
|   let referencesToCreate: PartialReference[] = []; | ||||
|   type User = Prisma.UserGetPayload<typeof userData>; | ||||
|   let user: User | null = null; | ||||
| 
 | ||||
|   for (const currentUser of users) { | ||||
|  | @ -555,3 +568,14 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) | |||
|   // booking successful
 | ||||
|   return res.status(201).json(booking); | ||||
| } | ||||
| 
 | ||||
| export function getLuckyUsers( | ||||
|   users: User[], | ||||
|   bookingCounts: Prisma.PromiseReturnType<typeof getUserNameWithBookingCounts> | ||||
| ) { | ||||
|   if (!bookingCounts.length) users.slice(0, 1); | ||||
| 
 | ||||
|   const [firstMostAvailableUser] = bookingCounts.sort((a, b) => (a.bookingCount > b.bookingCount ? 1 : -1)); | ||||
|   const luckyUser = users.find((user) => user.username === firstMostAvailableUser?.username); | ||||
|   return luckyUser ? [luckyUser] : users; | ||||
| } | ||||
|  |  | |||
							
								
								
									
										30
									
								
								test/lib/team-event-types.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								test/lib/team-event-types.test.ts
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,30 @@ | |||
| import { getLuckyUsers } from "../../pages/api/book/event"; | ||||
| 
 | ||||
| it("can find lucky users", async () => { | ||||
|   const users = [ | ||||
|     { | ||||
|       id: 1, | ||||
|       username: "test", | ||||
|       name: "Test User", | ||||
|       credentials: [], | ||||
|       timeZone: "GMT", | ||||
|       bufferTime: 0, | ||||
|       email: "test@example.com", | ||||
|     }, | ||||
|     { | ||||
|       id: 2, | ||||
|       username: "test2", | ||||
|       name: "Test 2 User", | ||||
|       credentials: [], | ||||
|       timeZone: "GMT", | ||||
|       bufferTime: 0, | ||||
|       email: "test2@example.com", | ||||
|     }, | ||||
|   ]; | ||||
|   expect( | ||||
|     getLuckyUsers(users, [ | ||||
|       { username: "test", bookingCount: 2 }, | ||||
|       { username: "test2", bookingCount: 0 }, | ||||
|     ]) | ||||
|   ).toStrictEqual([users[1]]); | ||||
| }); | ||||
		Loading…
	
		Reference in a new issue
	
	 Alex van Andel
						Alex van Andel