feat: round robin reset interval for teams (#19441)
* add component to set interval * fix variable namings * fetch data for given interval * migration * fix type errors in test * test correct filtering for interval * fix start end end times to interval * fix type error * pass rrResetInterval to getOrderedListOfLuckyUsers * fix imports * add translations * add missing translation * code clean up * fix type errors * fix type error * fix import * add translation --------- Co-authored-by: CarinaWolli <wollencarina@gmail.com> Co-authored-by: Alex van Andel <me@alexvanandel.com>
This commit is contained in:
17 files changed
+357
-98
No files matched your search
@@ -2988,6 +2988,10 @@
|
||||
"desc": "Desc",
|
||||
"verify_email": "Verify email",
|
||||
"verify_email_change": "Verify email change",
|
||||
"rr_reset_interval": "Round Robin Reset Interval",
|
||||
"interval": "Interval",
|
||||
"rr_reset_interval_description": "Determines how often the round robin booking count resets to ensure balanced distribution.",
|
||||
"daily": "Daily",
|
||||
"end_date_not_selected": "You didn't set an end date for your OOO",
|
||||
"my_ooo": "My OOO",
|
||||
"team_ooo": "Team OOO",
|
||||
@@ -3001,6 +3005,7 @@
|
||||
"ooo_team_redirect_infinite_not_allowed": "There is already a reverse forwarding between these users.",
|
||||
"previous_ooo_empty_title": "No previous OOO found",
|
||||
"previous": "Previous",
|
||||
"rr_interval_successfully_updated":"Round Robin interval successfully updated",
|
||||
"trial_mode": "Trial mode",
|
||||
"inactive_team_plan": "Inactive team plan",
|
||||
"inactive_team_plan_description": "Your team plan is inactive. Check your subcription or reach out to customer support",
|
||||
|
||||
@@ -42,6 +42,7 @@ export const getEventTypesFromDB = async (eventTypeId: number) => {
|
||||
parentId: true,
|
||||
bookingLimits: true,
|
||||
includeManagedEventsInLimits: true,
|
||||
rrResetInterval: true,
|
||||
},
|
||||
},
|
||||
bookingFields: true,
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
"use client";
|
||||
|
||||
import { useForm, Controller } from "react-hook-form";
|
||||
|
||||
import SectionBottomActions from "@calcom/features/settings/SectionBottomActions";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { MembershipRole, RRResetInterval } from "@calcom/prisma/enums";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import type { RouterOutputs } from "@calcom/trpc/react";
|
||||
import { Form, showToast, Label, Select, Button } from "@calcom/ui";
|
||||
|
||||
interface ProfileViewProps {
|
||||
team: RouterOutputs["viewer"]["teams"]["get"];
|
||||
}
|
||||
|
||||
const RoundRobinResetInterval = ({ team }: ProfileViewProps) => {
|
||||
const { t } = useLocale();
|
||||
const utils = trpc.useUtils();
|
||||
|
||||
const intervalOptions: {
|
||||
value: RRResetInterval;
|
||||
label: string;
|
||||
}[] = [
|
||||
{
|
||||
value: RRResetInterval.MONTH,
|
||||
label: t("monthly"),
|
||||
},
|
||||
{
|
||||
value: RRResetInterval.DAY,
|
||||
label: t("daily"),
|
||||
},
|
||||
];
|
||||
|
||||
const form = useForm<{ rrResetInterval: RRResetInterval }>({
|
||||
defaultValues: {
|
||||
rrResetInterval: team?.rrResetInterval ?? RRResetInterval.MONTH,
|
||||
},
|
||||
});
|
||||
|
||||
const mutation = trpc.viewer.teams.update.useMutation({
|
||||
onError: (err) => {
|
||||
showToast(err.message, "error");
|
||||
},
|
||||
async onSuccess(res) {
|
||||
await utils.viewer.teams.get.invalidate();
|
||||
showToast(t("rr_interval_successfully_updated"), "success");
|
||||
},
|
||||
});
|
||||
|
||||
const isAdmin =
|
||||
team && (team.membership.role === MembershipRole.OWNER || team.membership.role === MembershipRole.ADMIN);
|
||||
|
||||
if (!isAdmin) {
|
||||
return (
|
||||
<div className="border-subtle rounded-md border p-5">
|
||||
<span className="text-default text-sm">{t("only_owner_change")}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Form
|
||||
form={form}
|
||||
handleSubmit={(values) => {
|
||||
mutation.mutate({ ...values, id: team.id });
|
||||
}}>
|
||||
<Controller
|
||||
name="rrResetInterval"
|
||||
render={({ field: { value, onChange } }) => {
|
||||
return (
|
||||
<div className="border-subtle mt-6 space-x-3 rounded-t-lg border border-b-0 px-4 py-6 sm:px-6">
|
||||
<div>
|
||||
<h4 className="text-emphasis text-sm font-semibold leading-5">{t("rr_reset_interval")}</h4>
|
||||
<p className="text-default text-sm leading-tight">{t("rr_reset_interval_description")}</p>
|
||||
<div className="-mx-4 mt-4 border-t sm:-mx-6">
|
||||
<div className="px-4 pt-6 sm:px-6">
|
||||
<Label>{t("interval")}</Label>
|
||||
<Select
|
||||
options={intervalOptions}
|
||||
value={intervalOptions.find((opt) => opt.value === value)}
|
||||
onChange={(val) => {
|
||||
onChange(val?.value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<SectionBottomActions align="end">
|
||||
<Button type="submit" color="primary" loading={mutation.isPending}>
|
||||
{t("update")}
|
||||
</Button>
|
||||
</SectionBottomActions>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
export default RoundRobinResetInterval;
|
||||
@@ -18,6 +18,7 @@ import { Button, CheckboxField, Form, SettingsToggle, showToast } from "@calcom/
|
||||
import classNames from "@calcom/ui/classNames";
|
||||
|
||||
import { default as InternalNotePresetsView } from "../components/InternalNotePresetsView";
|
||||
import RoundRobinResetInterval from "../components/RoundRobinResetInterval";
|
||||
|
||||
type ProfileViewProps = { team: RouterOutputs["viewer"]["teams"]["get"] };
|
||||
|
||||
@@ -171,6 +172,7 @@ const TeamSettingsViewWrapper = () => {
|
||||
<>
|
||||
<BookingLimitsView team={team} />
|
||||
<InternalNotePresetsView team={team} />
|
||||
<RoundRobinResetInterval team={team} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { describe, expect, it, vi, afterEach } from "vitest";
|
||||
|
||||
import prisma from "@calcom/prisma";
|
||||
import { RRResetInterval } from "@calcom/prisma/client";
|
||||
|
||||
import { getOrderedListOfLuckyUsers } from "../server/getLuckyUser";
|
||||
import { filterHostsByLeadThreshold, errorCodes } from "./filterHostsByLeadThreshold";
|
||||
@@ -49,6 +50,7 @@ describe("filterHostByLeadThreshold", () => {
|
||||
isRRWeightsEnabled: true,
|
||||
team: {
|
||||
parentId: null,
|
||||
rrResetInterval: RRResetInterval.MONTH,
|
||||
},
|
||||
},
|
||||
routingFormResponse: null,
|
||||
@@ -66,6 +68,7 @@ describe("filterHostByLeadThreshold", () => {
|
||||
isRRWeightsEnabled: true,
|
||||
team: {
|
||||
parentId: null,
|
||||
rrResetInterval: RRResetInterval.MONTH,
|
||||
},
|
||||
},
|
||||
routingFormResponse: null,
|
||||
@@ -112,6 +115,7 @@ describe("filterHostByLeadThreshold", () => {
|
||||
isRRWeightsEnabled: false,
|
||||
team: {
|
||||
parentId: null,
|
||||
rrResetInterval: RRResetInterval.MONTH,
|
||||
},
|
||||
},
|
||||
routingFormResponse: null,
|
||||
@@ -170,6 +174,7 @@ describe("filterHostByLeadThreshold", () => {
|
||||
isRRWeightsEnabled: true,
|
||||
team: {
|
||||
parentId: null,
|
||||
rrResetInterval: RRResetInterval.MONTH,
|
||||
},
|
||||
},
|
||||
routingFormResponse: null,
|
||||
@@ -184,6 +189,7 @@ describe("filterHostByLeadThreshold", () => {
|
||||
isRRWeightsEnabled: true,
|
||||
team: {
|
||||
parentId: null,
|
||||
rrResetInterval: RRResetInterval.MONTH,
|
||||
},
|
||||
},
|
||||
routingFormResponse: null,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import logger from "@calcom/lib/logger";
|
||||
import type { SelectedCalendar } from "@calcom/prisma/client";
|
||||
import type { RRResetInterval, SelectedCalendar } from "@calcom/prisma/client";
|
||||
import type { CredentialForCalendarService } from "@calcom/types/Credential";
|
||||
|
||||
import type { RoutingFormResponse } from "../server/getLuckyUser";
|
||||
@@ -96,6 +96,7 @@ export const filterHostsByLeadThreshold = async <T extends BaseHost<BaseUser>>({
|
||||
isRRWeightsEnabled: boolean;
|
||||
team: {
|
||||
parentId?: number | null;
|
||||
rrResetInterval: RRResetInterval | null;
|
||||
} | null;
|
||||
};
|
||||
routingFormResponse: RoutingFormResponse | null;
|
||||
|
||||
@@ -3,7 +3,7 @@ import prismaMock from "../../../tests/libs/__mocks__/prismaMock";
|
||||
import { vi, it, describe, expect, afterEach } from "vitest";
|
||||
import type { Mock } from "vitest";
|
||||
|
||||
import { SchedulingType } from "@calcom/prisma/enums";
|
||||
import { RRResetInterval, SchedulingType } from "@calcom/prisma/enums";
|
||||
|
||||
import { filterHostsByLeadThreshold } from "./filterHostsByLeadThreshold";
|
||||
import { findQualifiedHostsWithDelegationCredentials } from "./findQualifiedHostsWithDelegationCredentials";
|
||||
@@ -84,6 +84,7 @@ describe("findQualifiedHostsWithDelegationCredentials", async () => {
|
||||
team: {
|
||||
id: 1,
|
||||
parentId: null,
|
||||
rrResetInterval: RRResetInterval.MONTH,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -135,6 +136,7 @@ describe("findQualifiedHostsWithDelegationCredentials", async () => {
|
||||
team: {
|
||||
id: 1,
|
||||
parentId: null,
|
||||
rrResetInterval: RRResetInterval.MONTH,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -221,6 +223,7 @@ describe("findQualifiedHostsWithDelegationCredentials", async () => {
|
||||
team: {
|
||||
id: 1,
|
||||
parentId: null,
|
||||
rrResetInterval: RRResetInterval.MONTH,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -309,6 +312,7 @@ describe("findQualifiedHostsWithDelegationCredentials", async () => {
|
||||
team: {
|
||||
id: 1,
|
||||
parentId: null,
|
||||
rrResetInterval: RRResetInterval.MONTH,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -386,6 +390,7 @@ describe("findQualifiedHostsWithDelegationCredentials", async () => {
|
||||
team: {
|
||||
id: 1,
|
||||
parentId: null,
|
||||
rrResetInterval: RRResetInterval.MONTH,
|
||||
},
|
||||
};
|
||||
prismaMock.booking.findFirst.mockResolvedValue({ userId: 2 });
|
||||
@@ -461,6 +466,7 @@ describe("findQualifiedHostsWithDelegationCredentials", async () => {
|
||||
team: {
|
||||
id: 1,
|
||||
parentId: null,
|
||||
rrResetInterval: RRResetInterval.MONTH,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -553,6 +559,7 @@ describe("findQualifiedHostsWithDelegationCredentials", async () => {
|
||||
team: {
|
||||
id: 1,
|
||||
parentId: null,
|
||||
rrResetInterval: RRResetInterval.MONTH,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -646,6 +653,7 @@ describe("findQualifiedHostsWithDelegationCredentials", async () => {
|
||||
team: {
|
||||
id: 1,
|
||||
parentId: null,
|
||||
rrResetInterval: RRResetInterval.MONTH,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import logger from "@calcom/lib/logger";
|
||||
import { findTeamMembersMatchingAttributeLogic } from "@calcom/lib/raqb/findTeamMembersMatchingAttributeLogic";
|
||||
import type { AttributesQueryValue } from "@calcom/lib/raqb/types";
|
||||
import { safeStringify } from "@calcom/lib/safeStringify";
|
||||
import type { RRResetInterval } from "@calcom/prisma/client";
|
||||
import { SchedulingType } from "@calcom/prisma/enums";
|
||||
import type { CredentialPayload } from "@calcom/types/Credential";
|
||||
|
||||
@@ -80,7 +81,7 @@ export type EventType = {
|
||||
assignAllTeamMembers: boolean;
|
||||
assignRRMembersUsingSegment: boolean;
|
||||
rrSegmentQueryValue: AttributesQueryValue | null | undefined;
|
||||
team: { id: number; parentId: number | null } | null;
|
||||
team: { id: number; parentId: number | null; rrResetInterval: RRResetInterval | null } | null;
|
||||
};
|
||||
|
||||
export function getNormalizedHosts<User extends BaseUser, Host extends BaseHost<User>>({
|
||||
|
||||
@@ -124,6 +124,7 @@ const commons = {
|
||||
fieldTranslations: [],
|
||||
maxLeadThreshold: null,
|
||||
useEventLevelSelectedCalendars: false,
|
||||
rrResetInterval: null,
|
||||
};
|
||||
|
||||
export const dynamicEvent = {
|
||||
|
||||
@@ -6,7 +6,7 @@ import { expect, it, describe, vi, beforeAll } from "vitest";
|
||||
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { buildUser, buildBooking } from "@calcom/lib/test/builder";
|
||||
import { AttributeType } from "@calcom/prisma/enums";
|
||||
import { AttributeType, RRResetInterval } from "@calcom/prisma/enums";
|
||||
|
||||
import { getLuckyUser, prepareQueuesAndAttributesData } from "./getLuckyUser";
|
||||
|
||||
@@ -65,7 +65,7 @@ it("can find lucky user with maximize availability", async () => {
|
||||
eventType: {
|
||||
id: 1,
|
||||
isRRWeightsEnabled: false,
|
||||
team: {},
|
||||
team: { rrResetInterval: RRResetInterval.MONTH },
|
||||
},
|
||||
allRRHosts: [],
|
||||
routingFormResponse: null,
|
||||
@@ -118,7 +118,7 @@ it("can find lucky user with maximize availability and priority ranking", async
|
||||
eventType: {
|
||||
id: 1,
|
||||
isRRWeightsEnabled: false,
|
||||
team: {},
|
||||
team: { rrResetInterval: RRResetInterval.MONTH },
|
||||
},
|
||||
allRRHosts: [],
|
||||
routingFormResponse: null,
|
||||
@@ -175,7 +175,7 @@ it("can find lucky user with maximize availability and priority ranking", async
|
||||
eventType: {
|
||||
id: 1,
|
||||
isRRWeightsEnabled: false,
|
||||
team: {},
|
||||
team: { rrResetInterval: RRResetInterval.MONTH },
|
||||
},
|
||||
allRRHosts: [],
|
||||
routingFormResponse: null,
|
||||
@@ -237,7 +237,7 @@ it("can find lucky user with maximize availability and priority ranking", async
|
||||
eventType: {
|
||||
id: 1,
|
||||
isRRWeightsEnabled: false,
|
||||
team: {},
|
||||
team: { rrResetInterval: RRResetInterval.MONTH },
|
||||
},
|
||||
allRRHosts: [],
|
||||
routingFormResponse: null,
|
||||
@@ -328,12 +328,22 @@ describe("maximize availability and weights", () => {
|
||||
eventType: {
|
||||
id: 1,
|
||||
isRRWeightsEnabled: true,
|
||||
team: {},
|
||||
team: { rrResetInterval: RRResetInterval.MONTH },
|
||||
},
|
||||
allRRHosts,
|
||||
routingFormResponse: null,
|
||||
})
|
||||
).resolves.toStrictEqual(users[1]);
|
||||
|
||||
const queryArgs = prismaMock.booking.findMany.mock.calls[0][0];
|
||||
|
||||
// Today: 2021-06-20T11:59:59Z, monthly interval
|
||||
expect(queryArgs.where?.createdAt).toEqual(
|
||||
expect.objectContaining({
|
||||
gte: new Date("2021-06-01T00:00:00Z"),
|
||||
lte: new Date("2021-06-20T11:59:59.000Z"),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("can find lucky user if hosts have different weights", async () => {
|
||||
@@ -426,12 +436,22 @@ describe("maximize availability and weights", () => {
|
||||
eventType: {
|
||||
id: 1,
|
||||
isRRWeightsEnabled: true,
|
||||
team: {},
|
||||
team: { rrResetInterval: RRResetInterval.DAY },
|
||||
},
|
||||
allRRHosts,
|
||||
routingFormResponse: null,
|
||||
})
|
||||
).resolves.toStrictEqual(users[0]);
|
||||
|
||||
const queryArgs = prismaMock.booking.findMany.mock.calls[0][0];
|
||||
|
||||
// Today: 2021-06-20T11:59:59Z, daily interval
|
||||
expect(queryArgs.where?.createdAt).toEqual(
|
||||
expect.objectContaining({
|
||||
gte: new Date("2021-06-20T00:00:00Z"),
|
||||
lte: new Date("2021-06-20T11:59:59.000Z"),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("can find lucky user with weights and adjusted weights", async () => {
|
||||
@@ -524,12 +544,22 @@ describe("maximize availability and weights", () => {
|
||||
eventType: {
|
||||
id: 1,
|
||||
isRRWeightsEnabled: true,
|
||||
team: {},
|
||||
team: { rrResetInterval: RRResetInterval.DAY },
|
||||
},
|
||||
allRRHosts,
|
||||
routingFormResponse: null,
|
||||
})
|
||||
).resolves.toStrictEqual(users[0]);
|
||||
|
||||
const queryArgs = prismaMock.booking.findMany.mock.calls[0][0];
|
||||
|
||||
// Today: 2021-06-20T11:59:59Z, daily interval
|
||||
expect(queryArgs.where?.createdAt).toEqual(
|
||||
expect.objectContaining({
|
||||
gte: new Date("2021-06-20T00:00:00Z"),
|
||||
lte: new Date("2021-06-20T11:59:59.000Z"),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("applies calibration when user had OOO entries this month", async () => {
|
||||
@@ -609,12 +639,22 @@ describe("maximize availability and weights", () => {
|
||||
eventType: {
|
||||
id: 1,
|
||||
isRRWeightsEnabled: true,
|
||||
team: {},
|
||||
team: { rrResetInterval: RRResetInterval.MONTH },
|
||||
},
|
||||
allRRHosts,
|
||||
routingFormResponse: null,
|
||||
})
|
||||
).resolves.toStrictEqual(users[1]); // user[1] has one more bookings, but user[0] has calibration 2
|
||||
|
||||
const queryArgs = prismaMock.booking.findMany.mock.calls[0][0];
|
||||
|
||||
// Today: 2021-06-20T11:59:59Z, monthly interval
|
||||
expect(queryArgs.where?.createdAt).toEqual(
|
||||
expect.objectContaining({
|
||||
gte: new Date("2021-06-01T00:00:00Z"),
|
||||
lte: new Date("2021-06-20T11:59:59.000Z"),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("applies calibration when user had full day calendar events this month", async () => {
|
||||
@@ -706,12 +746,22 @@ describe("maximize availability and weights", () => {
|
||||
eventType: {
|
||||
id: 1,
|
||||
isRRWeightsEnabled: true,
|
||||
team: {},
|
||||
team: { rrResetInterval: RRResetInterval.MONTH },
|
||||
},
|
||||
allRRHosts,
|
||||
routingFormResponse: null,
|
||||
})
|
||||
).resolves.toStrictEqual(users[1]); // user[1] has one more booking, but user[0] has calibration 2
|
||||
|
||||
const queryArgs = prismaMock.booking.findMany.mock.calls[0][0];
|
||||
|
||||
// Today: 2021-06-20T11:59:59Z, monthly interval
|
||||
expect(queryArgs.where?.createdAt).toEqual(
|
||||
expect.objectContaining({
|
||||
gte: new Date("2021-06-01T00:00:00Z"),
|
||||
lte: new Date("2021-06-20T11:59:59.000Z"),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("applies calibration to newly added hosts so they are not penalized unfairly compared to their peers", async () => {
|
||||
@@ -791,7 +841,7 @@ describe("maximize availability and weights", () => {
|
||||
eventType: {
|
||||
id: 1,
|
||||
isRRWeightsEnabled: true,
|
||||
team: {},
|
||||
team: { rrResetInterval: RRResetInterval.MONTH },
|
||||
},
|
||||
allRRHosts,
|
||||
routingFormResponse: null,
|
||||
@@ -816,12 +866,22 @@ describe("maximize availability and weights", () => {
|
||||
eventType: {
|
||||
id: 1,
|
||||
isRRWeightsEnabled: true,
|
||||
team: {},
|
||||
team: { rrResetInterval: RRResetInterval.MONTH },
|
||||
},
|
||||
allRRHosts,
|
||||
routingFormResponse: null,
|
||||
})
|
||||
).resolves.toStrictEqual(users[0]);
|
||||
|
||||
const queryArgs = prismaMock.booking.findMany.mock.calls[0][0];
|
||||
|
||||
// Today: 2021-06-20T11:59:59Z, monthly interval
|
||||
expect(queryArgs.where?.createdAt).toEqual(
|
||||
expect.objectContaining({
|
||||
gte: new Date("2021-06-01T00:00:00Z"),
|
||||
lte: new Date("2021-06-20T11:59:59.000Z"),
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -950,7 +1010,11 @@ describe("attribute weights and virtual queues", () => {
|
||||
});
|
||||
|
||||
const queuesAndAttributesData = await prepareQueuesAndAttributesData({
|
||||
eventType: { id: 1, isRRWeightsEnabled: true, team: { parentId: 1 } },
|
||||
eventType: {
|
||||
id: 1,
|
||||
isRRWeightsEnabled: true,
|
||||
team: { parentId: 1, rrResetInterval: RRResetInterval.DAY },
|
||||
},
|
||||
routingFormResponse,
|
||||
allRRHosts: [
|
||||
{
|
||||
@@ -1190,11 +1254,21 @@ describe("attribute weights and virtual queues", () => {
|
||||
eventType: {
|
||||
id: 1,
|
||||
isRRWeightsEnabled: true,
|
||||
team: { parentId: 1 },
|
||||
team: { parentId: 1, rrResetInterval: RRResetInterval.DAY },
|
||||
},
|
||||
allRRHosts,
|
||||
routingFormResponse,
|
||||
})
|
||||
).resolves.toStrictEqual(users[1]);
|
||||
|
||||
const queryArgs = prismaMock.booking.findMany.mock.calls[0][0];
|
||||
|
||||
// Today: 2021-06-20T11:59:59Z, daily interval
|
||||
expect(queryArgs.where?.createdAt).toEqual(
|
||||
expect.objectContaining({
|
||||
gte: new Date("2021-06-20T00:00:00Z"),
|
||||
lte: new Date("2021-06-20T11:59:59.000Z"),
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -13,7 +13,7 @@ import prisma from "@calcom/prisma";
|
||||
import type { Booking } from "@calcom/prisma/client";
|
||||
import type { SelectedCalendar } from "@calcom/prisma/client";
|
||||
import type { AttributeType } from "@calcom/prisma/enums";
|
||||
import { BookingStatus } from "@calcom/prisma/enums";
|
||||
import { BookingStatus, RRResetInterval } from "@calcom/prisma/enums";
|
||||
import type { EventBusyDate } from "@calcom/types/Calendar";
|
||||
import type { CredentialForCalendarService } from "@calcom/types/Credential";
|
||||
|
||||
@@ -63,7 +63,11 @@ type VirtualQueuesDataType = {
|
||||
|
||||
interface GetLuckyUserParams<T extends PartialUser> {
|
||||
availableUsers: [T, ...T[]]; // ensure contains at least 1
|
||||
eventType: { id: number; isRRWeightsEnabled: boolean; team: { parentId?: number | null } | null };
|
||||
eventType: {
|
||||
id: number;
|
||||
isRRWeightsEnabled: boolean;
|
||||
team: { parentId?: number | null; rrResetInterval: RRResetInterval | null } | null;
|
||||
};
|
||||
// all routedTeamMemberIds or all hosts of event types
|
||||
allRRHosts: {
|
||||
user: {
|
||||
@@ -77,9 +81,20 @@ interface GetLuckyUserParams<T extends PartialUser> {
|
||||
}[];
|
||||
routingFormResponse: RoutingFormResponse | null;
|
||||
}
|
||||
|
||||
// === dayjs.utc().startOf("month").toDate();
|
||||
const startOfMonth = () => new Date(Date.UTC(new Date().getUTCFullYear(), new Date().getUTCMonth(), 1));
|
||||
|
||||
const startOfToday = () =>
|
||||
new Date(Date.UTC(new Date().getUTCFullYear(), new Date().getUTCMonth(), new Date().getUTCDate()));
|
||||
|
||||
const getIntervalStartDate = (interval: RRResetInterval) => {
|
||||
if (interval === RRResetInterval.DAY) {
|
||||
return startOfToday();
|
||||
}
|
||||
return startOfMonth();
|
||||
};
|
||||
|
||||
// TS helper function.
|
||||
const isNonEmptyArray = <T>(arr: T[]): arr is [T, ...T[]] => arr.length > 0;
|
||||
|
||||
@@ -143,13 +158,13 @@ function leastRecentlyBookedUser<T extends PartialUser>({
|
||||
|
||||
function getHostsWithCalibration({
|
||||
hosts,
|
||||
allRRHostsBookingsOfThisMonth,
|
||||
allRRHostsCreatedThisMonth,
|
||||
allRRHostsBookingsOfInterval,
|
||||
allRRHostsCreatedInInterval,
|
||||
oooData,
|
||||
}: {
|
||||
hosts: { userId: number; email: string; createdAt: Date }[];
|
||||
allRRHostsBookingsOfThisMonth: PartialBooking[];
|
||||
allRRHostsCreatedThisMonth: { userId: number; createdAt: Date }[];
|
||||
allRRHostsBookingsOfInterval: PartialBooking[];
|
||||
allRRHostsCreatedInInterval: { userId: number; createdAt: Date }[];
|
||||
oooData: OOODataType;
|
||||
}) {
|
||||
// Helper function to calculate calibration for a new host
|
||||
@@ -177,7 +192,7 @@ function getHostsWithCalibration({
|
||||
return calibration;
|
||||
}
|
||||
|
||||
const existingBookings = allRRHostsBookingsOfThisMonth;
|
||||
const existingBookings = allRRHostsBookingsOfInterval;
|
||||
|
||||
const oooCalibration = new Map<number, number>();
|
||||
|
||||
@@ -208,10 +223,10 @@ function getHostsWithCalibration({
|
||||
}
|
||||
> = new Map();
|
||||
|
||||
if (allRRHostsCreatedThisMonth.length && existingBookings.length) {
|
||||
if (allRRHostsCreatedInInterval.length && existingBookings.length) {
|
||||
// Calculate calibration for each new host and store in a Map
|
||||
newHostsWithCalibration = new Map(
|
||||
allRRHostsCreatedThisMonth.map((newHost) => [
|
||||
allRRHostsCreatedInInterval.map((newHost) => [
|
||||
newHost.userId,
|
||||
{ ...newHost, calibration: calculateNewHostCalibration(newHost) },
|
||||
])
|
||||
@@ -256,24 +271,24 @@ function filterUsersBasedOnWeights<
|
||||
}
|
||||
>({
|
||||
availableUsers,
|
||||
currentMonthBookingsOfAvailableUsers,
|
||||
bookingsOfNotAvailableUsersOfThisMonth,
|
||||
bookingsOfAvailableUsersOfInterval,
|
||||
bookingsOfNotAvailableUsersOfInterval,
|
||||
allRRHosts,
|
||||
allRRHostsBookingsOfThisMonth,
|
||||
allRRHostsCreatedThisMonth,
|
||||
allRRHostsBookingsOfInterval,
|
||||
allRRHostsCreatedInInterval,
|
||||
attributeWeights,
|
||||
oooData,
|
||||
}: GetLuckyUserParams<T> & FetchedData) {
|
||||
//get all bookings of all other RR hosts that are not available
|
||||
|
||||
const allBookings = currentMonthBookingsOfAvailableUsers.concat(bookingsOfNotAvailableUsersOfThisMonth);
|
||||
const allBookings = bookingsOfAvailableUsersOfInterval.concat(bookingsOfNotAvailableUsersOfInterval);
|
||||
|
||||
const allHostsWithCalibration = getHostsWithCalibration({
|
||||
hosts: allRRHosts.map((host) => {
|
||||
return { email: host.user.email, userId: host.user.id, createdAt: host.createdAt };
|
||||
}),
|
||||
allRRHostsBookingsOfThisMonth,
|
||||
allRRHostsCreatedThisMonth,
|
||||
allRRHostsBookingsOfInterval,
|
||||
allRRHostsCreatedInInterval,
|
||||
oooData,
|
||||
});
|
||||
|
||||
@@ -304,7 +319,7 @@ function filterUsersBasedOnWeights<
|
||||
userWeight = attributeWeights.find((userWeight) => userWeight.userId === user.id)?.weight ?? 100;
|
||||
}
|
||||
const targetPercentage = userWeight / totalWeight;
|
||||
const userBookings = currentMonthBookingsOfAvailableUsers.filter(
|
||||
const userBookings = bookingsOfAvailableUsersOfInterval.filter(
|
||||
(booking) =>
|
||||
booking.userId === user.id || booking.attendees.some((attendee) => attendee.email === user.email)
|
||||
);
|
||||
@@ -377,19 +392,20 @@ function filterUsersBasedOnWeights<
|
||||
};
|
||||
}
|
||||
|
||||
async function getCurrentMonthCalendarBusyTimes(
|
||||
async function getCalendarBusyTimesOfInterval(
|
||||
usersWithCredentials: {
|
||||
id: number;
|
||||
email: string;
|
||||
credentials: CredentialForCalendarService[];
|
||||
userLevelSelectedCalendars: SelectedCalendar[];
|
||||
}[]
|
||||
}[],
|
||||
interval: RRResetInterval
|
||||
): Promise<{ userId: number; busyTimes: (EventBusyDate & { timeZone?: string })[] }[]> {
|
||||
return Promise.all(
|
||||
usersWithCredentials.map((user) =>
|
||||
getBusyCalendarTimes(
|
||||
user.credentials,
|
||||
startOfMonth().toISOString(),
|
||||
getIntervalStartDate(interval).toISOString(),
|
||||
new Date().toISOString(),
|
||||
user.userLevelSelectedCalendars,
|
||||
true
|
||||
@@ -401,19 +417,21 @@ async function getCurrentMonthCalendarBusyTimes(
|
||||
);
|
||||
}
|
||||
|
||||
async function getCurrentMonthsBookings({
|
||||
async function getBookingsOfInterval({
|
||||
eventTypeId,
|
||||
users,
|
||||
virtualQueuesData,
|
||||
interval,
|
||||
}: {
|
||||
eventTypeId: number;
|
||||
users: { id: number; email: string }[];
|
||||
virtualQueuesData: VirtualQueuesDataType | null;
|
||||
interval: RRResetInterval;
|
||||
}) {
|
||||
return await BookingRepository.getAllBookingsForRoundRobin({
|
||||
eventTypeId: eventTypeId,
|
||||
users,
|
||||
startDate: startOfMonth(),
|
||||
startDate: getIntervalStartDate(interval),
|
||||
endDate: new Date(),
|
||||
virtualQueuesData,
|
||||
});
|
||||
@@ -426,10 +444,10 @@ export async function getLuckyUser<
|
||||
}
|
||||
>(getLuckyUserParams: GetLuckyUserParams<T>) {
|
||||
const {
|
||||
currentMonthBookingsOfAvailableUsers,
|
||||
bookingsOfNotAvailableUsersOfThisMonth,
|
||||
allRRHostsBookingsOfThisMonth,
|
||||
allRRHostsCreatedThisMonth,
|
||||
bookingsOfAvailableUsersOfInterval,
|
||||
bookingsOfNotAvailableUsersOfInterval,
|
||||
allRRHostsBookingsOfInterval,
|
||||
allRRHostsCreatedInInterval,
|
||||
organizersWithLastCreated,
|
||||
attributeWeights,
|
||||
virtualQueuesData,
|
||||
@@ -438,10 +456,10 @@ export async function getLuckyUser<
|
||||
|
||||
const { luckyUser } = getLuckyUser_requiresDataToBePreFetched({
|
||||
...getLuckyUserParams,
|
||||
currentMonthBookingsOfAvailableUsers,
|
||||
bookingsOfNotAvailableUsersOfThisMonth,
|
||||
allRRHostsBookingsOfThisMonth,
|
||||
allRRHostsCreatedThisMonth,
|
||||
bookingsOfAvailableUsersOfInterval,
|
||||
bookingsOfNotAvailableUsersOfInterval,
|
||||
allRRHostsBookingsOfInterval,
|
||||
allRRHostsCreatedInInterval,
|
||||
organizersWithLastCreated,
|
||||
attributeWeights,
|
||||
virtualQueuesData,
|
||||
@@ -452,10 +470,10 @@ export async function getLuckyUser<
|
||||
}
|
||||
|
||||
type FetchedData = {
|
||||
bookingsOfNotAvailableUsersOfThisMonth: PartialBooking[];
|
||||
currentMonthBookingsOfAvailableUsers: PartialBooking[];
|
||||
allRRHostsBookingsOfThisMonth: PartialBooking[];
|
||||
allRRHostsCreatedThisMonth: { userId: number; createdAt: Date }[];
|
||||
bookingsOfNotAvailableUsersOfInterval: PartialBooking[];
|
||||
bookingsOfAvailableUsersOfInterval: PartialBooking[];
|
||||
allRRHostsBookingsOfInterval: PartialBooking[];
|
||||
allRRHostsCreatedInInterval: { userId: number; createdAt: Date }[];
|
||||
organizersWithLastCreated: { id: number; bookings: { createdAt: Date }[] }[];
|
||||
attributeWeights?:
|
||||
| {
|
||||
@@ -475,10 +493,10 @@ export function getLuckyUser_requiresDataToBePreFetched<
|
||||
>({ availableUsers, ...getLuckyUserParams }: GetLuckyUserParams<T> & FetchedData) {
|
||||
const {
|
||||
eventType,
|
||||
currentMonthBookingsOfAvailableUsers,
|
||||
bookingsOfNotAvailableUsersOfThisMonth,
|
||||
allRRHostsBookingsOfThisMonth,
|
||||
allRRHostsCreatedThisMonth,
|
||||
bookingsOfAvailableUsersOfInterval,
|
||||
bookingsOfNotAvailableUsersOfInterval,
|
||||
allRRHostsBookingsOfInterval,
|
||||
allRRHostsCreatedInInterval,
|
||||
organizersWithLastCreated,
|
||||
oooData,
|
||||
} = getLuckyUserParams;
|
||||
@@ -501,10 +519,10 @@ export function getLuckyUser_requiresDataToBePreFetched<
|
||||
} = filterUsersBasedOnWeights({
|
||||
...getLuckyUserParams,
|
||||
availableUsers,
|
||||
currentMonthBookingsOfAvailableUsers,
|
||||
bookingsOfNotAvailableUsersOfThisMonth,
|
||||
allRRHostsBookingsOfThisMonth,
|
||||
allRRHostsCreatedThisMonth,
|
||||
bookingsOfAvailableUsersOfInterval,
|
||||
bookingsOfNotAvailableUsersOfInterval,
|
||||
allRRHostsBookingsOfInterval,
|
||||
allRRHostsCreatedInInterval,
|
||||
oooData,
|
||||
});
|
||||
availableUsers = remainingUsersAfterWeightFilter;
|
||||
@@ -524,7 +542,7 @@ export function getLuckyUser_requiresDataToBePreFetched<
|
||||
luckyUser: leastRecentlyBookedUser({
|
||||
...getLuckyUserParams,
|
||||
availableUsers: highestPriorityUsers,
|
||||
bookingsOfAvailableUsers: currentMonthBookingsOfAvailableUsers,
|
||||
bookingsOfAvailableUsers: bookingsOfAvailableUsersOfInterval,
|
||||
organizersWithLastCreated,
|
||||
}),
|
||||
usersAndTheirBookingShortfalls,
|
||||
@@ -571,35 +589,43 @@ async function fetchAllDataNeededForCalculations<
|
||||
|
||||
const { attributeWeights, virtualQueuesData } = await prepareQueuesAndAttributesData(getLuckyUserParams);
|
||||
|
||||
const interval = getLuckyUserParams.eventType.team?.rrResetInterval ?? RRResetInterval.MONTH;
|
||||
|
||||
const [
|
||||
currentMonthUserBusyTimes,
|
||||
currentMonthBookingsOfAvailableUsers,
|
||||
bookingsOfNotAvailableUsersOfThisMonth,
|
||||
allRRHostsBookingsOfThisMonth,
|
||||
allRRHostsCreatedThisMonth,
|
||||
userBusyTimesOfInterval,
|
||||
bookingsOfAvailableUsersOfInterval,
|
||||
bookingsOfNotAvailableUsersOfInterval,
|
||||
allRRHostsBookingsOfInterval,
|
||||
allRRHostsCreatedInInterval,
|
||||
organizersWithLastCreated,
|
||||
] = await Promise.all([
|
||||
getCurrentMonthCalendarBusyTimes(allRRHosts.map((host) => host.user)),
|
||||
getCurrentMonthsBookings({
|
||||
getCalendarBusyTimesOfInterval(
|
||||
allRRHosts.map((host) => host.user),
|
||||
interval
|
||||
),
|
||||
getBookingsOfInterval({
|
||||
eventTypeId: eventType.id,
|
||||
users: availableUsers.map((user) => {
|
||||
return { id: user.id, email: user.email };
|
||||
}),
|
||||
virtualQueuesData: virtualQueuesData ?? null,
|
||||
interval,
|
||||
}),
|
||||
|
||||
getCurrentMonthsBookings({
|
||||
getBookingsOfInterval({
|
||||
eventTypeId: eventType.id,
|
||||
users: notAvailableHosts,
|
||||
virtualQueuesData: virtualQueuesData ?? null,
|
||||
interval,
|
||||
}),
|
||||
|
||||
getCurrentMonthsBookings({
|
||||
getBookingsOfInterval({
|
||||
eventTypeId: eventType.id,
|
||||
users: allRRHosts.map((host) => {
|
||||
return { id: host.user.id, email: host.user.email };
|
||||
}),
|
||||
virtualQueuesData: virtualQueuesData ?? null,
|
||||
interval,
|
||||
}),
|
||||
|
||||
prisma.host.findMany({
|
||||
@@ -610,7 +636,7 @@ async function fetchAllDataNeededForCalculations<
|
||||
eventTypeId: eventType.id,
|
||||
isFixed: false,
|
||||
createdAt: {
|
||||
gte: startOfMonth(),
|
||||
gte: getIntervalStartDate(interval),
|
||||
},
|
||||
},
|
||||
}),
|
||||
@@ -657,7 +683,7 @@ async function fetchAllDataNeededForCalculations<
|
||||
|
||||
const userFullDayBusyTimes = new Map<number, { start: Date; end: Date }[]>();
|
||||
|
||||
currentMonthUserBusyTimes.forEach((userBusyTime) => {
|
||||
userBusyTimesOfInterval.forEach((userBusyTime) => {
|
||||
const fullDayBusyTimes = userBusyTime.busyTimes
|
||||
.filter((busyTime) => {
|
||||
//timeZone is always defined when calling getBusyCalendarTimes with includeTimeZone true
|
||||
@@ -687,7 +713,7 @@ async function fetchAllDataNeededForCalculations<
|
||||
},
|
||||
end: {
|
||||
lte: new Date(),
|
||||
gte: startOfMonth(),
|
||||
gte: getIntervalStartDate(interval),
|
||||
},
|
||||
},
|
||||
select: {
|
||||
@@ -725,10 +751,10 @@ async function fetchAllDataNeededForCalculations<
|
||||
log.debug(
|
||||
"fetchAllDataNeededForCalculations",
|
||||
safeStringify({
|
||||
currentMonthBookingsOfAvailableUsers: currentMonthBookingsOfAvailableUsers.length,
|
||||
bookingsOfNotAvailableUsersOfThisMonth: bookingsOfNotAvailableUsersOfThisMonth.length,
|
||||
allRRHostsBookingsOfThisMonth: allRRHostsBookingsOfThisMonth.length,
|
||||
allRRHostsCreatedThisMonth: allRRHostsCreatedThisMonth.length,
|
||||
bookingsOfAvailableUsersOfInterval: bookingsOfAvailableUsersOfInterval.length,
|
||||
bookingsOfNotAvailableUsersOfInterval: bookingsOfNotAvailableUsersOfInterval.length,
|
||||
allRRHostsBookingsOfInterval: allRRHostsBookingsOfInterval.length,
|
||||
allRRHostsCreatedInInterval: allRRHostsCreatedInInterval.length,
|
||||
virtualQueuesData,
|
||||
attributeWeights,
|
||||
oooData,
|
||||
@@ -736,10 +762,10 @@ async function fetchAllDataNeededForCalculations<
|
||||
);
|
||||
|
||||
return {
|
||||
currentMonthBookingsOfAvailableUsers,
|
||||
bookingsOfNotAvailableUsersOfThisMonth,
|
||||
allRRHostsBookingsOfThisMonth,
|
||||
allRRHostsCreatedThisMonth,
|
||||
bookingsOfAvailableUsersOfInterval,
|
||||
bookingsOfNotAvailableUsersOfInterval,
|
||||
allRRHostsBookingsOfInterval,
|
||||
allRRHostsCreatedInInterval,
|
||||
organizersWithLastCreated,
|
||||
attributeWeights,
|
||||
virtualQueuesData,
|
||||
@@ -758,10 +784,10 @@ export async function getOrderedListOfLuckyUsers<AvailableUser extends Available
|
||||
const { availableUsers, eventType } = getLuckyUserParams;
|
||||
|
||||
const {
|
||||
currentMonthBookingsOfAvailableUsers,
|
||||
bookingsOfNotAvailableUsersOfThisMonth,
|
||||
allRRHostsBookingsOfThisMonth,
|
||||
allRRHostsCreatedThisMonth,
|
||||
bookingsOfAvailableUsersOfInterval,
|
||||
bookingsOfNotAvailableUsersOfInterval,
|
||||
allRRHostsBookingsOfInterval,
|
||||
allRRHostsCreatedInInterval,
|
||||
organizersWithLastCreated,
|
||||
attributeWeights,
|
||||
virtualQueuesData,
|
||||
@@ -774,16 +800,16 @@ export async function getOrderedListOfLuckyUsers<AvailableUser extends Available
|
||||
availableUsers: availableUsers.map((user) => {
|
||||
return { id: user.id, email: user.email, priority: user.priority, weight: user.weight };
|
||||
}),
|
||||
currentMonthBookingsOfAvailableUsers,
|
||||
bookingsOfNotAvailableUsersOfThisMonth,
|
||||
allRRHostsBookingsOfThisMonth,
|
||||
allRRHostsCreatedThisMonth,
|
||||
bookingsOfAvailableUsersOfInterval,
|
||||
bookingsOfNotAvailableUsersOfInterval,
|
||||
allRRHostsBookingsOfInterval,
|
||||
allRRHostsCreatedInInterval,
|
||||
organizersWithLastCreated,
|
||||
})
|
||||
);
|
||||
|
||||
let remainingAvailableUsers = [...availableUsers];
|
||||
let currentMonthBookingsOfRemainingAvailableUsers = [...currentMonthBookingsOfAvailableUsers];
|
||||
let bookingsOfRemainingAvailableUsersOfInterval = [...bookingsOfAvailableUsersOfInterval];
|
||||
const orderedUsersSet = new Set<AvailableUser>();
|
||||
const perUserBookingsCount: Record<number, number> = {};
|
||||
|
||||
@@ -801,10 +827,10 @@ export async function getOrderedListOfLuckyUsers<AvailableUser extends Available
|
||||
...getLuckyUserParams,
|
||||
eventType,
|
||||
availableUsers: remainingAvailableUsers as [AvailableUser, ...AvailableUser[]],
|
||||
currentMonthBookingsOfAvailableUsers: currentMonthBookingsOfRemainingAvailableUsers,
|
||||
bookingsOfNotAvailableUsersOfThisMonth,
|
||||
allRRHostsBookingsOfThisMonth,
|
||||
allRRHostsCreatedThisMonth,
|
||||
bookingsOfAvailableUsersOfInterval: bookingsOfRemainingAvailableUsersOfInterval,
|
||||
bookingsOfNotAvailableUsersOfInterval,
|
||||
allRRHostsBookingsOfInterval,
|
||||
allRRHostsCreatedInInterval,
|
||||
organizersWithLastCreated,
|
||||
attributeWeights,
|
||||
virtualQueuesData,
|
||||
@@ -824,11 +850,11 @@ export async function getOrderedListOfLuckyUsers<AvailableUser extends Available
|
||||
}
|
||||
|
||||
orderedUsersSet.add(luckyUser);
|
||||
perUserBookingsCount[luckyUser.id] = currentMonthBookingsOfAvailableUsers.filter(
|
||||
perUserBookingsCount[luckyUser.id] = bookingsOfAvailableUsersOfInterval.filter(
|
||||
(booking) => booking.userId === luckyUser.id
|
||||
).length;
|
||||
remainingAvailableUsers = remainingAvailableUsers.filter((user) => user.id !== luckyUser.id);
|
||||
currentMonthBookingsOfRemainingAvailableUsers = currentMonthBookingsOfRemainingAvailableUsers.filter(
|
||||
bookingsOfRemainingAvailableUsersOfInterval = bookingsOfRemainingAvailableUsersOfInterval.filter(
|
||||
(booking) => remainingAvailableUsers.map((user) => user.id).includes(booking.userId ?? 0)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -313,6 +313,7 @@ export async function getTeamWithoutMembers(args: {
|
||||
isPrivate: true,
|
||||
metadata: true,
|
||||
bookingLimits: true,
|
||||
rrResetInterval: true,
|
||||
includeManagedEventsInLimits: true,
|
||||
parent: {
|
||||
select: {
|
||||
|
||||
@@ -741,6 +741,7 @@ export class EventTypeRepository {
|
||||
team: {
|
||||
select: {
|
||||
parentId: true,
|
||||
rrResetInterval: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -813,6 +814,7 @@ export class EventTypeRepository {
|
||||
bookingLimits: true,
|
||||
includeManagedEventsInLimits: true,
|
||||
parentId: true,
|
||||
rrResetInterval: true,
|
||||
},
|
||||
},
|
||||
parent: {
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "RRResetInterval" AS ENUM ('MONTH', 'DAY');
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Team" ADD COLUMN "rrResetInterval" "RRResetInterval" DEFAULT 'MONTH';
|
||||
@@ -446,6 +446,7 @@ model Team {
|
||||
/// @zod.custom(imports.teamMetadataSchema)
|
||||
metadata Json?
|
||||
theme String?
|
||||
rrResetInterval RRResetInterval? @default(MONTH)
|
||||
brandColor String?
|
||||
darkBrandColor String?
|
||||
verifiedNumbers VerifiedNumber[]
|
||||
@@ -1355,6 +1356,11 @@ enum FeatureType {
|
||||
PERMISSION
|
||||
}
|
||||
|
||||
enum RRResetInterval {
|
||||
MONTH
|
||||
DAY
|
||||
}
|
||||
|
||||
model SelectedSlots {
|
||||
id Int @id @default(autoincrement())
|
||||
eventTypeId Int
|
||||
|
||||
@@ -65,6 +65,7 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => {
|
||||
theme: input.theme,
|
||||
bookingLimits: input.bookingLimits ?? undefined,
|
||||
includeManagedEventsInLimits: input.includeManagedEventsInLimits ?? undefined,
|
||||
rrResetInterval: input.rrResetInterval,
|
||||
};
|
||||
|
||||
if (input.logo && input.logo.startsWith("data:image/png;base64,")) {
|
||||
@@ -145,6 +146,7 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => {
|
||||
darkBrandColor: updatedTeam.darkBrandColor,
|
||||
bookingLimits: updatedTeam.bookingLimits as IntervalLimit,
|
||||
includeManagedEventsInLimits: updatedTeam.includeManagedEventsInLimits,
|
||||
rrResetInterval: updatedTeam.rrResetInterval,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -1,10 +1,28 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { intervalLimitsType } from "@calcom/lib/intervalLimits/intervalLimitSchema";
|
||||
import type { IntervalLimit } from "@calcom/lib/intervalLimits/intervalLimitSchema";
|
||||
import { resizeBase64Image } from "@calcom/lib/server/resizeBase64Image";
|
||||
import slugify from "@calcom/lib/slugify";
|
||||
|
||||
export const ZUpdateInputSchema = z.object({
|
||||
export type TUpdateInputSchema = {
|
||||
id: number;
|
||||
bio?: string;
|
||||
name?: string;
|
||||
logo?: string | null;
|
||||
slug?: string;
|
||||
hideBranding?: boolean;
|
||||
hideBookATeamMember?: boolean;
|
||||
isPrivate?: boolean;
|
||||
brandColor?: string;
|
||||
darkBrandColor?: string;
|
||||
theme?: string | null;
|
||||
bookingLimits?: IntervalLimit | null;
|
||||
includeManagedEventsInLimits?: boolean;
|
||||
rrResetInterval?: "DAY" | "MONTH";
|
||||
};
|
||||
|
||||
export const ZUpdateInputSchema: z.Schema<TUpdateInputSchema> = z.object({
|
||||
id: z.number(),
|
||||
bio: z.string().optional(),
|
||||
name: z.string().optional(),
|
||||
@@ -25,6 +43,5 @@ export const ZUpdateInputSchema = z.object({
|
||||
theme: z.string().optional().nullable(),
|
||||
bookingLimits: intervalLimitsType.optional(),
|
||||
includeManagedEventsInLimits: z.boolean().optional(),
|
||||
rrResetInterval: z.enum(["DAY", "MONTH"]).optional(),
|
||||
});
|
||||
|
||||
export type TUpdateInputSchema = z.infer<typeof ZUpdateInputSchema>;
|
||||
Reference in new issue
Block a user