feat: checkbox to count managed events for team-wide limits (#16923)

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
This commit is contained in:
Carina Wollendorfer
2024-10-07 11:36:50 -04:00
committed by GitHub
co-authored by CarinaWolli
parent 0d757039f7
commit b5fa43f086
17 changed files with 266 additions and 61 deletions
+1
View File
@@ -11,6 +11,7 @@ export const schemaTeamBaseBodyParams = Team.omit({ id: true, createdAt: true })
smsLockState: true,
smsLockReviewedByAdmin: true,
bookingLimits: true,
includeManagedEventsInLimits: true,
});
const schemaTeamRequiredParams = z.object({
@@ -76,4 +76,8 @@ export class UpdateOrgTeamDto {
@IsOptional()
@IsString()
readonly bookingLimits?: string;
@IsOptional()
@IsBoolean()
readonly includeManagedEventsInLimits?: boolean;
}
@@ -1525,6 +1525,7 @@
"workflow_example_4": "Send email reminder 1 hour before events starts to attendee",
"workflow_example_5": "Send custom email when event is rescheduled to host",
"workflow_example_6": "Send custom SMS when new event is booked to host",
"count_managed_to_limit": "Include booking counts from managed event types",
"welcome_to_cal_header": "Welcome to {{appName}}!",
"edit_form_later_subtitle": "Youll be able to edit this later.",
"connect_calendar_later": "I'll connect my calendar later",
@@ -2625,7 +2626,7 @@
"disable_input_if_prefilled": "Disable input if the URL identifier is prefilled",
"booking_limits": "Booking Limits",
"booking_limits_team_description": "Booking limits for team members across all team event types",
"limit_team_booking_frequency_description": "Limit how many times members can be booked across all team event types (collective and round-robin)",
"limit_team_booking_frequency_description": "Limit how many times members can be booked across all team event types",
"booking_limits_updated_successfully": "Booking limits updated successfully",
"you_are_unauthorized_to_make_this_change_to_the_booking": "You are unauthorized to make this change to the booking",
"hide_calendar_event_details": "Hide calendar event details on shared calendars",
@@ -153,6 +153,7 @@ export type InputEventType = {
users?: { id: number }[];
hosts?: InputHost[];
schedulingType?: SchedulingType;
parent?: { id: number };
beforeEventBuffer?: number;
afterEventBuffer?: number;
teamId?: number | null;
@@ -160,6 +161,7 @@ export type InputEventType = {
id?: number | null;
parentId?: number | null;
bookingLimits?: IntervalLimit;
includeManagedEventsInLimits?: boolean;
};
requiresConfirmation?: boolean;
destinationCalendar?: Prisma.DestinationCalendarCreateInput;
@@ -246,7 +248,7 @@ export async function addEventTypesToDb(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
schedule?: any;
metadata?: any;
team?: { id?: number | null; bookingLimits?: IntervalLimit };
team?: { id?: number | null; bookingLimits?: IntervalLimit; includeManagedEventsInLimits?: boolean };
})[]
) {
log.silly("TestData: Add EventTypes to DB", JSON.stringify(eventTypes));
@@ -292,6 +294,7 @@ export async function addEventTypesToDb(
data: {
id: eventType.team?.id,
bookingLimits: eventType.team?.bookingLimits,
includeManagedEventsInLimits: eventType.team?.includeManagedEventsInLimits,
name: "",
},
});
@@ -323,6 +326,7 @@ export async function addEventTypes(eventTypes: InputEventType[], usersStore: In
beforeEventBuffer: 0,
afterEventBuffer: 0,
bookingLimits: {},
includeManagedEventsInLimits: false,
schedulingType: null,
length: 15,
//TODO: What is the purpose of periodStartDate and periodEndDate? Test these?
@@ -378,6 +382,7 @@ export async function addEventTypes(eventTypes: InputEventType[], usersStore: In
: eventType.schedule,
owner: eventType.owner ? { connect: { id: eventType.owner } } : undefined,
schedulingType: eventType.schedulingType,
parent: eventType.parent ? { connect: { id: eventType.parent.id } } : undefined,
rescheduleWithSameRoundRobinHost: eventType.rescheduleWithSameRoundRobinHost,
};
});
@@ -1285,6 +1290,7 @@ export function getScenarioData(
id: eventType.teamId ?? eventType.team?.id,
parentId: org ? org.id : null,
bookingLimits: eventType?.team?.bookingLimits,
includeManagedEventsInLimits: eventType?.team?.includeManagedEventsInLimits,
},
title: `Test Event Type - ${index + 1}`,
description: `It's a test event type - ${index + 1}`,
@@ -27,6 +27,7 @@ const _getBusyTimesFromLimits = async (
duration: number | undefined,
eventType: NonNullable<EventType>,
bookings: EventBusyDetails[],
timeZone: string,
rescheduleUid?: string
) => {
performance.mark("limitsStart");
@@ -45,6 +46,7 @@ const _getBusyTimesFromLimits = async (
eventTypeId: eventType.id,
limitManager,
rescheduleUid,
timeZone,
});
performance.mark("bookingLimitsEnd");
performance.measure(`checking booking limits took $1'`, "bookingLimitsStart", "bookingLimitsEnd");
@@ -89,6 +91,8 @@ const _getBusyTimesFromBookingLimits = async (params: {
eventTypeId?: number;
teamId?: number;
user?: { id: number; email: string };
includeManagedEvents?: boolean;
timeZone?: string | null;
}) => {
const {
bookings,
@@ -100,6 +104,8 @@ const _getBusyTimesFromBookingLimits = async (params: {
teamId,
user,
rescheduleUid,
includeManagedEvents = false,
timeZone,
} = params;
for (const key of descendingLimitKeys) {
@@ -123,6 +129,8 @@ const _getBusyTimesFromBookingLimits = async (params: {
teamId,
user,
rescheduleUid,
includeManagedEvents,
timeZone,
});
} catch (_) {
limitManager.addBusyTime(periodStart, unit);
@@ -231,6 +239,8 @@ const _getBusyTimesFromTeamLimits = async (
dateFrom: Dayjs,
dateTo: Dayjs,
teamId: number,
includeManagedEvents: boolean,
timeZone: string,
rescheduleUid?: string
) => {
const { limitDateFrom, limitDateTo } = getStartEndDateforLimitCheck(
@@ -245,6 +255,7 @@ const _getBusyTimesFromTeamLimits = async (
startDate: limitDateFrom.toDate(),
endDate: limitDateTo.toDate(),
excludedUid: rescheduleUid,
includeManagedEvents,
});
const busyTimes = bookings.map(({ id, startTime, endTime, eventTypeId, title, userId }) => ({
@@ -266,6 +277,8 @@ const _getBusyTimesFromTeamLimits = async (
rescheduleUid,
teamId,
user,
includeManagedEvents,
timeZone,
});
return limitManager.getBusyTimes();
+23 -3
View File
@@ -53,10 +53,22 @@ const _getEventType = async (id: number) => {
id: true,
seatsPerTimeSlot: true,
bookingLimits: true,
parent: {
select: {
team: {
select: {
id: true,
bookingLimits: true,
includeManagedEventsInLimits: true,
},
},
},
},
team: {
select: {
id: true,
bookingLimits: true,
includeManagedEventsInLimits: true,
},
},
hosts: {
@@ -286,20 +298,27 @@ const _getUserAvailability = async function getUsersWorkingHoursLifeTheUniverseA
duration,
eventType,
initialData?.busyTimesFromLimitsBookings ?? [],
timeZone,
initialData?.rescheduleUid ?? undefined
)
: [];
const teamBookingLimits = parseBookingLimit(eventType?.team?.bookingLimits);
const teamForBookingLimits =
eventType?.team ??
(eventType?.parent?.team?.includeManagedEventsInLimits ? eventType?.parent?.team : null);
const teamBookingLimits = parseBookingLimit(teamForBookingLimits?.bookingLimits);
const busyTimesFromTeamLimits =
eventType?.team && teamBookingLimits
teamForBookingLimits && teamBookingLimits
? await getBusyTimesFromTeamLimits(
user,
teamBookingLimits,
dateFrom.tz(timeZone),
dateTo.tz(timeZone),
eventType?.team.id,
teamForBookingLimits.id,
teamForBookingLimits.includeManagedEventsInLimits,
timeZone,
initialData?.rescheduleUid ?? undefined
)
: [];
@@ -458,6 +477,7 @@ const _getPeriodStartDatesBetween = (dateFrom: Dayjs, dateTo: Dayjs, period: Int
const dates = [];
let startDate = dayjs(dateFrom).startOf(period);
const endDate = dayjs(dateTo).endOf(period);
while (startDate.isBefore(endDate)) {
dates.push(startDate);
startDate = startDate.add(1, period);
@@ -36,6 +36,7 @@ export const getEventTypesFromDB = async (eventTypeId: number) => {
name: true,
parentId: true,
bookingLimits: true,
includeManagedEventsInLimits: true,
},
},
bookingFields: true,
@@ -73,6 +74,13 @@ export const getEventTypesFromDB = async (eventTypeId: number) => {
parent: {
select: {
teamId: true,
team: {
select: {
id: true,
bookingLimits: true,
includeManagedEventsInLimits: true,
},
},
},
},
useEventTypeDestinationCalendarEmail: true,
@@ -85,6 +85,7 @@ describe(
team: {
id: 1,
bookingLimits: { PER_WEEK: 2 },
includeManagedEventsInLimits: false,
},
schedulingType: SchedulingType.COLLECTIVE,
},
@@ -101,6 +102,27 @@ describe(
teamId: 1,
schedulingType: SchedulingType.COLLECTIVE,
},
{
id: 3,
slotInterval: eventLength,
length: eventLength,
hosts: [
{
userId: 101,
},
],
teamId: 1,
schedulingType: SchedulingType.MANAGED,
},
{
id: 4,
slotInterval: eventLength,
length: eventLength,
userId: 101,
parent: {
id: 3,
},
},
],
bookings: [
{
@@ -124,13 +146,21 @@ describe(
startTime: `2024-08-06T04:30:00.000Z`,
endTime: `2024-08-06T05:00:00.000Z`,
},
{
// managed event type doesn't count, includeManagedEventsInLimits is false
eventTypeId: 4,
userId: 101,
status: BookingStatus.ACCEPTED,
startTime: `2024-08-07T04:30:00.000Z`,
endTime: `2024-08-07T05:00:00.000Z`,
},
],
organizer,
usersApartFromOrganizer: otherTeamMembers,
})
);
const mockBookingData1 = getMockRequestDataForBooking({
const mockBookingWithinLimit = getMockRequestDataForBooking({
data: {
start: `2024-08-08T04:00:00.000Z`,
end: `2024-08-08T04:30:00.000Z`,
@@ -145,7 +175,7 @@ describe(
const { req: req1 } = createMockNextJsRequest({
method: "POST",
body: mockBookingData1,
body: mockBookingWithinLimit,
});
const createdBooking = await handleNewBooking(req1);
@@ -157,7 +187,7 @@ describe(
})
);
const mockBookingData2 = getMockRequestDataForBooking({
const mockBookingAboveLimit = getMockRequestDataForBooking({
data: {
start: `2024-08-08T04:00:00.000Z`,
end: `2024-08-08T04:30:00.000Z`,
@@ -172,7 +202,7 @@ describe(
const { req: req2 } = createMockNextJsRequest({
method: "POST",
body: mockBookingData2,
body: mockBookingAboveLimit,
});
// this is the third team booking of this week for user 101, limit reached
@@ -221,7 +251,7 @@ describe(
})
);
const mockBookingData1 = getMockRequestDataForBooking({
const mockBookingWithinLimit = getMockRequestDataForBooking({
data: {
start: `2024-08-07T04:30:00.000Z`,
end: `2024-08-07T05:00:00.000Z`,
@@ -236,7 +266,7 @@ describe(
const { req: req1 } = createMockNextJsRequest({
method: "POST",
body: mockBookingData1,
body: mockBookingWithinLimit,
});
const createdBooking = await handleNewBooking(req1);
@@ -248,7 +278,7 @@ describe(
})
);
const mockBookingData2 = getMockRequestDataForBooking({
const mockBookingAboveLimit = getMockRequestDataForBooking({
data: {
start: `2024-08-07T04:00:00.000Z`,
end: `2024-08-07T04:30:00.000Z`,
@@ -263,10 +293,10 @@ describe(
const { req: req2 } = createMockNextJsRequest({
method: "POST",
body: mockBookingData1,
body: mockBookingAboveLimit,
});
// this is the second team booking of this days for user 101, limit reached
// this is the second team booking of this day for user 101, limit reached
await expect(async () => await handleNewBooking(req2)).rejects.toThrowError(
"no_available_users_found_error"
);
@@ -288,7 +318,8 @@ describe(
],
team: {
id: 1,
bookingLimits: { PER_MONTH: 3 },
bookingLimits: { PER_MONTH: 4 },
includeManagedEventsInLimits: true,
},
schedulingType: SchedulingType.COLLECTIVE,
},
@@ -305,6 +336,27 @@ describe(
teamId: 1,
schedulingType: SchedulingType.COLLECTIVE,
},
{
id: 3,
slotInterval: eventLength,
length: eventLength,
hosts: [
{
userId: 101,
},
],
teamId: 1,
schedulingType: SchedulingType.MANAGED,
},
{
id: 4,
slotInterval: eventLength,
length: eventLength,
userId: 101,
parent: {
id: 3,
},
},
],
bookings: [
{
@@ -321,13 +373,21 @@ describe(
startTime: `2024-08-22T03:30:00.000Z`,
endTime: `2024-08-22T04:00:00.000Z`,
},
{
//managed event type also counts towards limits
eventTypeId: 4,
userId: 101,
status: BookingStatus.ACCEPTED,
startTime: `2024-08-15T03:30:00.000Z`,
endTime: `2024-08-15T04:00:00.000Z`,
},
],
organizer,
usersApartFromOrganizer: otherTeamMembers,
})
);
const mockBookingData1 = getMockRequestDataForBooking({
const mockBookingWithinLimit = getMockRequestDataForBooking({
data: {
start: `2024-08-29T04:30:00.000Z`,
end: `2024-08-29T05:00:00.000Z`,
@@ -342,7 +402,7 @@ describe(
const { req: req1 } = createMockNextJsRequest({
method: "POST",
body: mockBookingData1,
body: mockBookingWithinLimit,
});
const createdBooking = await handleNewBooking(req1);
@@ -354,7 +414,7 @@ describe(
})
);
const mockBookingData2 = getMockRequestDataForBooking({
const mockBookingAboveLimit = getMockRequestDataForBooking({
data: {
start: `2024-08-25T04:00:00.000Z`,
end: `2024-08-25T04:30:00.000Z`,
@@ -369,10 +429,10 @@ describe(
const { req: req2 } = createMockNextJsRequest({
method: "POST",
body: mockBookingData1,
body: mockBookingAboveLimit,
});
// this is the second team booking of this days for user 101, limit reached
// this is the firth team booking (incl. managed) of this month for user 101, limit reached
await expect(async () => await handleNewBooking(req2)).rejects.toThrowError(
"no_available_users_found_error"
);
@@ -394,7 +454,8 @@ describe(
],
team: {
id: 1,
bookingLimits: { PER_YEAR: 2 },
bookingLimits: { PER_YEAR: 3 },
includeManagedEventsInLimits: true,
},
schedulingType: SchedulingType.COLLECTIVE,
},
@@ -411,6 +472,27 @@ describe(
teamId: 1,
schedulingType: SchedulingType.COLLECTIVE,
},
{
id: 3,
slotInterval: eventLength,
length: eventLength,
hosts: [
{
userId: 101,
},
],
teamId: 1,
schedulingType: SchedulingType.MANAGED,
},
{
id: 4,
slotInterval: eventLength,
length: eventLength,
userId: 101,
parent: {
id: 3,
},
},
],
bookings: [
{
@@ -418,6 +500,13 @@ describe(
userId: 101,
status: BookingStatus.ACCEPTED,
startTime: `2024-02-03T03:30:00.000Z`,
endTime: `2024-02-03T04:00:00.000Z`,
},
{
eventTypeId: 4,
userId: 101,
status: BookingStatus.ACCEPTED,
startTime: `2024-08-03T03:30:00.000Z`,
endTime: `2024-08-03T04:00:00.000Z`,
},
],
@@ -426,7 +515,7 @@ describe(
})
);
const mockBookingData1 = getMockRequestDataForBooking({
const mockBookingWithinLimit = getMockRequestDataForBooking({
data: {
start: `2024-08-29T04:30:00.000Z`,
end: `2024-08-29T05:00:00.000Z`,
@@ -441,7 +530,7 @@ describe(
const { req: req1 } = createMockNextJsRequest({
method: "POST",
body: mockBookingData1,
body: mockBookingWithinLimit,
});
const createdBooking = await handleNewBooking(req1);
@@ -453,7 +542,7 @@ describe(
})
);
const mockBookingData2 = getMockRequestDataForBooking({
const mockBookingAboveLimit = getMockRequestDataForBooking({
data: {
start: `2024-11-25T04:00:00.000Z`,
end: `2024-11-25T04:30:00.000Z`,
@@ -468,10 +557,9 @@ describe(
const { req: req2 } = createMockNextJsRequest({
method: "POST",
body: mockBookingData1,
body: mockBookingAboveLimit,
});
// this is the second team booking of this days for user 101, limit reached
await expect(async () => await handleNewBooking(req2)).rejects.toThrowError(
"no_available_users_found_error"
);
@@ -14,7 +14,7 @@ import { MembershipRole } from "@calcom/prisma/enums";
import { trpc } from "@calcom/trpc/react";
import type { RouterOutputs } from "@calcom/trpc/react";
import type { IntervalLimit } from "@calcom/types/Calendar";
import { Button, Form, SettingsToggle, showToast } from "@calcom/ui";
import { Button, CheckboxField, Form, SettingsToggle, showToast } from "@calcom/ui";
type ProfileViewProps = { team: RouterOutputs["viewer"]["teams"]["getMinimal"] };
@@ -22,9 +22,10 @@ const BookingLimitsView = ({ team }: ProfileViewProps) => {
const { t } = useLocale();
const utils = trpc.useUtils();
const form = useForm<{ bookingLimits?: IntervalLimit }>({
const form = useForm<{ bookingLimits?: IntervalLimit; includeManagedEventsInLimits: boolean }>({
defaultValues: {
bookingLimits: team?.bookingLimits || undefined,
includeManagedEventsInLimits: team?.includeManagedEventsInLimits ?? false,
},
});
@@ -40,7 +41,10 @@ const BookingLimitsView = ({ team }: ProfileViewProps) => {
async onSuccess(res) {
await utils.viewer.teams.get.invalidate();
if (res) {
reset({ bookingLimits: res.bookingLimits });
reset({
bookingLimits: res.bookingLimits,
includeManagedEventsInLimits: res.includeManagedEventsInLimits,
});
}
showToast(t("booking_limits_updated_successfully"), "success");
},
@@ -83,9 +87,12 @@ const BookingLimitsView = ({ team }: ProfileViewProps) => {
});
} else {
form.setValue("bookingLimits", {});
form.setValue("includeManagedEventsInLimits", false);
}
const bookingLimits = form.getValues("bookingLimits");
mutation.mutate({ bookingLimits, id: team.id });
const includeManagedEventsInLimits = form.getValues("includeManagedEventsInLimits");
mutation.mutate({ bookingLimits, includeManagedEventsInLimits, id: team.id });
}}
switchContainerClassName={classNames(
"border-subtle mt-6 rounded-lg border py-6 px-4 sm:px-6",
@@ -93,7 +100,21 @@ const BookingLimitsView = ({ team }: ProfileViewProps) => {
)}
childrenClassName="lg:ml-0">
<div className="border-subtle border border-y-0 p-6">
<IntervalLimitsManager propertyName="bookingLimits" defaultLimit={1} step={1} />
<Controller
name="includeManagedEventsInLimits"
render={({ field: { value, onChange } }) => (
<CheckboxField
description={t("count_managed_to_limit")}
descriptionAsLabel
onChange={(e) => onChange(e)}
checked={value}
/>
)}
/>
<div className="pt-6">
<IntervalLimitsManager propertyName="bookingLimits" defaultLimit={1} step={1} />
</div>
</div>
<SectionBottomActions className="mb-6" align="end">
<Button disabled={isSubmitting || !isDirty} type="submit" color="primary">
+7 -2
View File
@@ -14,7 +14,8 @@ export async function checkBookingLimits(
eventStartDate: Date,
eventId: number,
rescheduleUid?: string | undefined,
timeZone?: string | null
timeZone?: string | null,
includeManagedEvents?: boolean
) {
const parsedBookingLimits = parseBookingLimit(bookingLimits);
if (!parsedBookingLimits) return false;
@@ -28,6 +29,7 @@ export async function checkBookingLimits(
eventId,
timeZone,
rescheduleUid,
includeManagedEvents,
})
);
@@ -47,6 +49,7 @@ export async function checkBookingLimit({
timeZone,
teamId,
user,
includeManagedEvents = false,
}: {
eventStartDate: Date;
eventId?: number;
@@ -56,6 +59,7 @@ export async function checkBookingLimit({
timeZone?: string | null;
teamId?: number;
user?: { id: number; email: string };
includeManagedEvents?: boolean;
}) {
{
const eventDateInOrganizerTz = timeZone ? dayjs(eventStartDate).tz(timeZone) : dayjs(eventStartDate);
@@ -75,8 +79,9 @@ export async function checkBookingLimit({
teamId,
startDate: startDate,
endDate: endDate,
returnCount: true,
shouldReturnCount: true,
excludedUid: rescheduleUid,
includeManagedEvents,
});
} else {
bookingsInPeriod = await prisma.booking.count({
@@ -299,6 +299,7 @@ export async function getMinimalTeam(args: {
isPrivate: true,
metadata: true,
bookingLimits: true,
includeManagedEventsInLimits: true,
parent: {
select: {
id: true,
+56 -27
View File
@@ -6,6 +6,22 @@ import { BookingStatus } from "@calcom/prisma/enums";
import { UserRepository } from "./user";
type TeamBookingsParamsBase = {
user: { id: number; email: string };
teamId: number;
startDate: Date;
endDate: Date;
excludedUid?: string | null;
includeManagedEvents: boolean;
shouldReturnCount?: boolean;
};
type TeamBookingsParamsWithCount = TeamBookingsParamsBase & {
shouldReturnCount: true;
};
type TeamBookingsParamsWithoutCount = TeamBookingsParamsBase;
export class BookingRepository {
static async getBookingAttendees(bookingId: number) {
return await prisma.attendee.findMany({
@@ -251,32 +267,14 @@ export class BookingRepository {
});
}
static async getAllAcceptedTeamBookingsOfUser(params: {
user: { id: number; email: string };
teamId: number;
startDate: Date;
endDate: Date;
excludedUid?: string | null;
returnCount: true;
}): Promise<number>;
static async getAllAcceptedTeamBookingsOfUser(params: TeamBookingsParamsWithCount): Promise<number>;
static async getAllAcceptedTeamBookingsOfUser(params: {
user: { id: number; email: string };
teamId: number;
startDate: Date;
endDate: Date;
excludedUid?: string | null;
}): Promise<Array<Booking>>;
static async getAllAcceptedTeamBookingsOfUser(
params: TeamBookingsParamsWithoutCount
): Promise<Array<Booking>>;
static async getAllAcceptedTeamBookingsOfUser(params: {
user: { id: number; email: string };
teamId: number;
startDate: Date;
endDate: Date;
excludedUid?: string | null;
returnCount?: boolean;
}) {
const { user, teamId, startDate, endDate, returnCount, excludedUid } = params;
static async getAllAcceptedTeamBookingsOfUser(params: TeamBookingsParamsBase) {
const { user, teamId, startDate, endDate, excludedUid, shouldReturnCount, includeManagedEvents } = params;
const baseWhere: Prisma.BookingWhereInput = {
status: BookingStatus.ACCEPTED,
@@ -313,7 +311,17 @@ export class BookingRepository {
},
};
if (returnCount) {
const whereManagedBookings: Prisma.BookingWhereInput = {
...baseWhere,
userId: user.id,
eventType: {
parent: {
teamId,
},
},
};
if (shouldReturnCount) {
const collectiveRoundRobinBookingsOwner = await prisma.booking.count({
where: whereCollectiveRoundRobinOwner,
});
@@ -322,7 +330,16 @@ export class BookingRepository {
where: whereCollectiveRoundRobinBookingsAttendee,
});
const totalNrOfBooking = collectiveRoundRobinBookingsOwner + collectiveRoundRobinBookingsAttendee;
let managedBookings = 0;
if (includeManagedEvents) {
managedBookings = await prisma.booking.count({
where: whereManagedBookings,
});
}
const totalNrOfBooking =
collectiveRoundRobinBookingsOwner + collectiveRoundRobinBookingsAttendee + managedBookings;
return totalNrOfBooking;
}
@@ -334,7 +351,19 @@ export class BookingRepository {
where: whereCollectiveRoundRobinBookingsAttendee,
});
return [...collectiveRoundRobinBookingsOwner, ...collectiveRoundRobinBookingsAttendee];
let managedBookings: typeof collectiveRoundRobinBookingsAttendee = [];
if (includeManagedEvents) {
managedBookings = await prisma.booking.findMany({
where: whereManagedBookings,
});
}
return [
...collectiveRoundRobinBookingsOwner,
...collectiveRoundRobinBookingsAttendee,
...managedBookings,
];
}
static async findOriginalRescheduledBooking(uid: string, seatsEventType?: boolean) {
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Team" ADD COLUMN "includeManagedEventsInLimits" BOOLEAN NOT NULL DEFAULT false;
+2 -1
View File
@@ -442,7 +442,8 @@ model Team {
features TeamFeatures[]
/// @zod.custom(imports.intervalLimitsType)
bookingLimits Json?
bookingLimits Json?
includeManagedEventsInLimits Boolean @default(false)
@@unique([slug, parentId])
@@index([parentId])
@@ -182,6 +182,7 @@ export async function getEventType(
select: {
id: true,
bookingLimits: true,
includeManagedEventsInLimits: true,
},
},
parent: {
@@ -190,6 +191,7 @@ export async function getEventType(
select: {
id: true,
bookingLimits: true,
includeManagedEventsInLimits: true,
},
},
},
@@ -65,6 +65,7 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => {
darkBrandColor: input.darkBrandColor,
theme: input.theme,
bookingLimits: input.bookingLimits ?? undefined,
includeManagedEventsInLimits: input.includeManagedEventsInLimits ?? undefined,
};
if (input.logo && input.logo.startsWith("data:image/png;base64,")) {
@@ -147,6 +148,7 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => {
brandColor: updatedTeam.brandColor,
darkBrandColor: updatedTeam.darkBrandColor,
bookingLimits: updatedTeam.bookingLimits as IntervalLimit,
includeManagedEventsInLimits: updatedTeam.includeManagedEventsInLimits,
};
};
@@ -24,6 +24,7 @@ export const ZUpdateInputSchema = z.object({
darkBrandColor: z.string().optional(),
theme: z.string().optional().nullable(),
bookingLimits: intervalLimitsType.optional(),
includeManagedEventsInLimits: z.boolean().optional(),
});
export type TUpdateInputSchema = z.infer<typeof ZUpdateInputSchema>;