feat: v2 api allow switching event type between collective and round robin (#25045)

* refactor: create team event type hosts

* refactor: update team event type hosts

* feat: allow switching between collective and round robin

* fix: make schedulingType optional when updating

* fix: e2e tests

* fix: e2e and add more tests

* test: only hosts update

* fix: remove test that makes no sense
This commit is contained in:
Lauris Skraucis
2025-11-11 09:40:21 +01:00
committed by GitHub
parent 53a931da4e
commit 8b4f675cee
4 changed files with 686 additions and 20 deletions
@@ -15,6 +15,10 @@ import {
HostPriority,
EmailSettings_2024_06_14,
} from "@calcom/platform-types";
import type { EventType } from "@calcom/prisma/client";
export const HOSTS_REQUIRED_WHEN_SWITCHING_SCHEDULING_TYPE_ERROR =
"Hosts required when switching schedulingType. Please provide 'hosts' or set 'assignAllTeamMembers: true' to specify how hosts should be configured for the new scheduling type.";
export type TransformedCreateTeamEventTypeInput = Awaited<
ReturnType<InstanceType<typeof InputOrganizationsEventTypesService>["transformInputCreateTeamEventType"]>
@@ -116,10 +120,12 @@ export class InputOrganizationsEventTypesService {
teamId: number,
inputEventType: CreateTeamEventTypeInput_2024_06_14
) {
const { hosts, assignAllTeamMembers, locations, emailSettings, ...rest } = inputEventType;
const { assignAllTeamMembers, locations, emailSettings, ...rest } = inputEventType;
const eventType = this.inputEventTypesService.transformInputCreateEventType(rest);
const isManagedEventType = rest.schedulingType === "MANAGED";
const defaultLocations: CreateTeamEventTypeInput_2024_06_14["locations"] = [
{
type: "integration",
@@ -127,12 +133,13 @@ export class InputOrganizationsEventTypesService {
},
];
const children = await this.getChildEventTypesForManagedEventTypeCreate(inputEventType, teamId);
const children = isManagedEventType
? await this.getChildEventTypesForManagedEventTypeCreate(inputEventType, teamId)
: undefined;
let metadata =
rest.schedulingType === "MANAGED"
? { managedEventConfig: {}, ...eventType.metadata }
: eventType.metadata;
let metadata = isManagedEventType
? { managedEventConfig: {}, ...eventType.metadata }
: eventType.metadata;
if (emailSettings) {
metadata = this.addEmailSettingsToMetadata(emailSettings, metadata);
@@ -140,12 +147,7 @@ export class InputOrganizationsEventTypesService {
const teamEventType = {
...eventType,
// note(Lauris): we don't populate hosts for managed event-types because they are handled by the children
hosts: !(rest.schedulingType === "MANAGED")
? assignAllTeamMembers
? await this.getAllTeamMembers(teamId, inputEventType.schedulingType)
: this.transformInputHosts(hosts, inputEventType.schedulingType)
: undefined,
hosts: await this.transformInputCreateTeamEventTypeHosts(teamId, inputEventType),
assignAllTeamMembers,
locations: this.transformInputTeamLocations(locations || defaultLocations),
metadata,
@@ -155,6 +157,24 @@ export class InputOrganizationsEventTypesService {
return teamEventType;
}
private async transformInputCreateTeamEventTypeHosts(
teamId: number,
inputEventType: CreateTeamEventTypeInput_2024_06_14
) {
const { hosts, assignAllTeamMembers, schedulingType } = inputEventType;
// note(Lauris): we don't populate hosts for managed event-types because they are handled by the children - each child managed event type is associated with
// a specific user and hosts property is only for team event types e.g round robin and collective.
if (schedulingType === "MANAGED") {
return undefined;
}
if (assignAllTeamMembers) {
return await this.getAllTeamMembers(teamId, inputEventType.schedulingType);
}
return this.transformInputHosts(hosts, inputEventType.schedulingType);
}
private addEmailSettingsToMetadata(
emailSettings: EmailSettings_2024_06_14,
metadata: NonNullable<EventTypeMetadata>
@@ -190,7 +210,7 @@ export class InputOrganizationsEventTypesService {
teamId: number,
inputEventType: UpdateTeamEventTypeInput_2024_06_14
) {
const { hosts, assignAllTeamMembers, locations, emailSettings, ...rest } = inputEventType;
const { assignAllTeamMembers, locations, emailSettings, ...rest } = inputEventType;
const eventType = await this.inputEventTypesService.transformInputUpdateEventType(rest, eventTypeId);
const dbEventType = await this.teamsEventTypesRepository.getTeamEventType(teamId, eventTypeId);
@@ -213,11 +233,11 @@ export class InputOrganizationsEventTypesService {
const teamEventType = {
...eventType,
// note(Lauris): we don't populate hosts for managed event-types because they are handled by the children
hosts: !children
? assignAllTeamMembers
? await this.getAllTeamMembers(teamId, dbEventType.schedulingType)
: this.transformInputHosts(hosts, dbEventType.schedulingType)
: undefined,
hosts: await this.transformInputUpdateTeamEventTypeHosts(
teamId,
dbEventType.schedulingType,
inputEventType
),
assignAllTeamMembers,
children,
locations: locations ? this.transformInputTeamLocations(locations) : undefined,
@@ -227,6 +247,33 @@ export class InputOrganizationsEventTypesService {
return teamEventType;
}
private async transformInputUpdateTeamEventTypeHosts(
teamId: number,
dbEventTypeSchedulingType: EventType["schedulingType"],
inputEventType: UpdateTeamEventTypeInput_2024_06_14
) {
const { hosts, assignAllTeamMembers } = inputEventType;
if (dbEventTypeSchedulingType === "MANAGED") {
// note(Lauris): we don't populate hosts for managed event-types because they are handled by the event type children
return undefined;
}
const isSchedulingTypeChanging =
inputEventType.schedulingType && inputEventType.schedulingType !== dbEventTypeSchedulingType;
if (isSchedulingTypeChanging && !assignAllTeamMembers && !hosts) {
throw new BadRequestException(HOSTS_REQUIRED_WHEN_SWITCHING_SCHEDULING_TYPE_ERROR);
}
const nextSchedulingType = inputEventType.schedulingType || dbEventTypeSchedulingType;
if (assignAllTeamMembers) {
return await this.getAllTeamMembers(teamId, nextSchedulingType);
}
return this.transformInputHosts(hosts, nextSchedulingType);
}
async getChildEventTypesForManagedEventTypeUpdate(
eventTypeId: number,
inputEventType: UpdateTeamEventTypeInput_2024_06_14,
@@ -273,7 +320,7 @@ export class InputOrganizationsEventTypesService {
}
async getChildEventTypesForManagedEventTypeCreate(
inputEventType: UpdateTeamEventTypeInput_2024_06_14,
inputEventType: Pick<CreateTeamEventTypeInput_2024_06_14, "assignAllTeamMembers" | "hosts">,
teamId: number
) {
const ownersIds = await this.getOwnersIdsForManagedEventTypeCreate(teamId, inputEventType);
@@ -289,7 +336,7 @@ export class InputOrganizationsEventTypesService {
async getOwnersIdsForManagedEventTypeCreate(
teamId: number,
inputEventType: UpdateTeamEventTypeInput_2024_06_14
inputEventType: Pick<CreateTeamEventTypeInput_2024_06_14, "assignAllTeamMembers" | "hosts">
) {
if (inputEventType.assignAllTeamMembers) {
return await this.getTeamUsersIds(teamId);
@@ -1,5 +1,6 @@
import { bootstrap } from "@/app";
import { AppModule } from "@/app.module";
import { HOSTS_REQUIRED_WHEN_SWITCHING_SCHEDULING_TYPE_ERROR } from "@/modules/organizations/event-types/services/input.service";
import { PrismaModule } from "@/modules/prisma/prisma.module";
import { TokensModule } from "@/modules/tokens/tokens.module";
import { UsersModule } from "@/modules/users/users.module";
@@ -758,6 +759,586 @@ describe("Organizations Event Types Endpoints", () => {
expect(expected.priority).toEqual(received?.priority);
}
describe("updating scheduling type", () => {
it("should return 400 error if schedulingType: managed is passed", async () => {
const createBody: CreateTeamEventTypeInput_2024_06_14 = {
title: `teams-event-types-scheduling-collective-${randomString()}`,
slug: `teams-event-types-scheduling-collective-${randomString()}`,
description: "Test collective event type",
lengthInMinutes: 60,
locations: [
{
type: "integration",
integration: "cal-video",
},
],
schedulingType: "COLLECTIVE",
hosts: [
{
userId: teamMember1.id,
},
],
};
const createResponse = await request(app.getHttpServer())
.post(`/v2/teams/${team.id}/event-types`)
.send(createBody)
.expect(201);
const createdEventType: ApiSuccessResponse<TeamEventTypeOutput_2024_06_14> = createResponse.body;
const updateBody = {
schedulingType: "managed",
};
await request(app.getHttpServer())
.patch(`/v2/teams/${team.id}/event-types/${createdEventType.data.id}`)
.send(updateBody)
.expect(400);
await eventTypesRepositoryFixture.delete(createdEventType.data.id);
});
it("should require hosts when changing round robin event type to collective without providing hosts", async () => {
const createBody: CreateTeamEventTypeInput_2024_06_14 = {
title: `teams-event-types-scheduling-roundrobin-${randomString()}`,
slug: `teams-event-types-scheduling-roundrobin-${randomString()}`,
description: "Test round robin event type",
lengthInMinutes: 60,
locations: [
{
type: "integration",
integration: "cal-video",
},
],
schedulingType: "ROUND_ROBIN",
hosts: [
{
userId: teamMember1.id,
priority: "high",
},
{
userId: teamMember2.id,
priority: "low",
},
],
};
const createResponse = await request(app.getHttpServer())
.post(`/v2/teams/${team.id}/event-types`)
.send(createBody)
.expect(201);
const createdEventType: ApiSuccessResponse<TeamEventTypeOutput_2024_06_14> = createResponse.body;
const updateBody = {
schedulingType: "collective",
};
const response = await request(app.getHttpServer())
.patch(`/v2/teams/${team.id}/event-types/${createdEventType.data.id}`)
.send(updateBody);
expect(response.status).toBe(400);
expect(response.body.error.message).toBe(HOSTS_REQUIRED_WHEN_SWITCHING_SCHEDULING_TYPE_ERROR);
await eventTypesRepositoryFixture.delete(createdEventType.data.id);
});
it("should require hosts when changing collective event type to roundRobin without providing hosts", async () => {
const createBody: CreateTeamEventTypeInput_2024_06_14 = {
title: `teams-event-types-scheduling-collective-${randomString()}`,
slug: `teams-event-types-scheduling-collective-${randomString()}`,
description: "Test collective event type",
lengthInMinutes: 60,
locations: [
{
type: "integration",
integration: "cal-video",
},
],
schedulingType: "COLLECTIVE",
hosts: [
{
userId: teamMember1.id,
},
{
userId: teamMember2.id,
},
],
};
const createResponse = await request(app.getHttpServer())
.post(`/v2/teams/${team.id}/event-types`)
.send(createBody)
.expect(201);
const createdEventType: ApiSuccessResponse<TeamEventTypeOutput_2024_06_14> = createResponse.body;
const updateBody = {
schedulingType: "roundRobin",
};
const response = await request(app.getHttpServer())
.patch(`/v2/teams/${team.id}/event-types/${createdEventType.data.id}`)
.send(updateBody);
expect(response.status).toBe(400);
expect(response.body.error.message).toBe(HOSTS_REQUIRED_WHEN_SWITCHING_SCHEDULING_TYPE_ERROR);
await eventTypesRepositoryFixture.delete(createdEventType.data.id);
});
it("should change round robin event type to collective and pass new hosts", async () => {
const createBody: CreateTeamEventTypeInput_2024_06_14 = {
title: `teams-event-types-scheduling-roundrobin-${randomString()}`,
slug: `teams-event-types-scheduling-roundrobin-${randomString()}`,
description: "Test round robin event type",
lengthInMinutes: 60,
locations: [
{
type: "integration",
integration: "cal-video",
},
],
schedulingType: "ROUND_ROBIN",
hosts: [
{
userId: teamMember1.id,
priority: "high",
},
],
};
const createResponse = await request(app.getHttpServer())
.post(`/v2/teams/${team.id}/event-types`)
.send(createBody)
.expect(201);
const createdEventType: ApiSuccessResponse<TeamEventTypeOutput_2024_06_14> = createResponse.body;
const updateBody = {
schedulingType: "collective",
hosts: [
{
userId: teamMember2.id,
},
],
};
const updateResponse = await request(app.getHttpServer())
.patch(`/v2/teams/${team.id}/event-types/${createdEventType.data.id}`)
.send(updateBody)
.expect(200);
const responseBody: ApiSuccessResponse<TeamEventTypeOutput_2024_06_14> = updateResponse.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data.schedulingType).toEqual("collective");
expect(responseBody.data.hosts).toHaveLength(1);
expect(responseBody.data.hosts[0].userId).toEqual(teamMember2.id);
await eventTypesRepositoryFixture.delete(createdEventType.data.id);
});
it("should change collective event type to roundRobin and pass new hosts", async () => {
const createBody = {
title: `teams-event-types-scheduling-collective-${randomString()}`,
slug: `teams-event-types-scheduling-collective-${randomString()}`,
description: "Test collective event type",
lengthInMinutes: 60,
locations: [
{
type: "integration",
integration: "cal-video",
},
],
schedulingType: "collective",
hosts: [
{
userId: teamMember1.id,
},
],
};
const createResponse = await request(app.getHttpServer())
.post(`/v2/teams/${team.id}/event-types`)
.send(createBody)
.expect(201);
const createdEventType: ApiSuccessResponse<TeamEventTypeOutput_2024_06_14> = createResponse.body;
const updateBody = {
schedulingType: "roundRobin",
hosts: [
{
userId: teamMember2.id,
priority: "medium",
},
],
};
const updateResponse = await request(app.getHttpServer())
.patch(`/v2/teams/${team.id}/event-types/${createdEventType.data.id}`)
.send(updateBody)
.expect(200);
const responseBody: ApiSuccessResponse<TeamEventTypeOutput_2024_06_14> = updateResponse.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data.schedulingType).toEqual("roundRobin");
expect(responseBody.data.hosts).toHaveLength(1);
expect(responseBody.data.hosts[0].userId).toEqual(teamMember2.id);
await eventTypesRepositoryFixture.delete(createdEventType.data.id);
});
it("should change collective event type to roundRobin with assignAllTeamMembers: true", async () => {
const createBody: CreateTeamEventTypeInput_2024_06_14 = {
title: `teams-event-types-scheduling-collective-${randomString()}`,
slug: `teams-event-types-scheduling-collective-${randomString()}`,
description: "Test collective event type",
lengthInMinutes: 60,
locations: [
{
type: "integration",
integration: "cal-video",
},
],
schedulingType: "COLLECTIVE",
hosts: [
{
userId: teamMember1.id,
},
],
};
const createResponse = await request(app.getHttpServer())
.post(`/v2/teams/${team.id}/event-types`)
.send(createBody)
.expect(201);
const createdEventType: ApiSuccessResponse<TeamEventTypeOutput_2024_06_14> = createResponse.body;
const updateBody = {
schedulingType: "roundRobin",
assignAllTeamMembers: true,
};
const updateResponse = await request(app.getHttpServer())
.patch(`/v2/teams/${team.id}/event-types/${createdEventType.data.id}`)
.send(updateBody)
.expect(200);
const responseBody: ApiSuccessResponse<TeamEventTypeOutput_2024_06_14> = updateResponse.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data.schedulingType).toEqual("roundRobin");
expect(responseBody.data.hosts).toHaveLength(3);
const hostUserIds = responseBody.data.hosts.map((host) => host.userId);
expect(hostUserIds).toContain(teamMember1.id);
expect(hostUserIds).toContain(teamMember2.id);
expect(hostUserIds).toContain(userAdmin.id);
await eventTypesRepositoryFixture.delete(createdEventType.data.id);
});
it("should change round robin event type to collective with assignAllTeamMembers: true", async () => {
const createBody: CreateTeamEventTypeInput_2024_06_14 = {
title: `teams-event-types-scheduling-roundrobin-${randomString()}`,
slug: `teams-event-types-scheduling-roundrobin-${randomString()}`,
description: "Test round robin event type",
lengthInMinutes: 60,
locations: [
{
type: "integration",
integration: "cal-video",
},
],
schedulingType: "ROUND_ROBIN",
hosts: [
{
userId: teamMember1.id,
priority: "high",
},
],
};
const createResponse = await request(app.getHttpServer())
.post(`/v2/teams/${team.id}/event-types`)
.send(createBody)
.expect(201);
const createdEventType: ApiSuccessResponse<TeamEventTypeOutput_2024_06_14> = createResponse.body;
const updateBody = {
schedulingType: "collective",
assignAllTeamMembers: true,
};
const updateResponse = await request(app.getHttpServer())
.patch(`/v2/teams/${team.id}/event-types/${createdEventType.data.id}`)
.send(updateBody)
.expect(200);
const responseBody: ApiSuccessResponse<TeamEventTypeOutput_2024_06_14> = updateResponse.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data.schedulingType).toEqual("collective");
expect(responseBody.data.hosts).toHaveLength(3);
const hostUserIds = responseBody.data.hosts.map((host) => host.userId);
expect(hostUserIds).toContain(teamMember1.id);
expect(hostUserIds).toContain(teamMember2.id);
expect(hostUserIds).toContain(userAdmin.id);
await eventTypesRepositoryFixture.delete(createdEventType.data.id);
});
it("should preserve existing hosts when updating without changing scheduling type", async () => {
const createBody = {
title: `teams-event-types-scheduling-preserve-${randomString()}`,
slug: `teams-event-types-scheduling-preserve-${randomString()}`,
description: "Test preserve hosts",
lengthInMinutes: 60,
locations: [
{
type: "integration",
integration: "cal-video",
},
],
schedulingType: "collective",
hosts: [
{
userId: teamMember1.id,
},
{
userId: teamMember2.id,
},
],
};
const createResponse = await request(app.getHttpServer())
.post(`/v2/teams/${team.id}/event-types`)
.send(createBody)
.expect(201);
const createdEventType: ApiSuccessResponse<TeamEventTypeOutput_2024_06_14> = createResponse.body;
const updateBody = {
title: "Updated title",
};
const updateResponse = await request(app.getHttpServer())
.patch(`/v2/teams/${team.id}/event-types/${createdEventType.data.id}`)
.send(updateBody)
.expect(200);
const responseBody: ApiSuccessResponse<TeamEventTypeOutput_2024_06_14> = updateResponse.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data.title).toEqual("Updated title");
expect(responseBody.data.schedulingType).toEqual("collective");
expect(responseBody.data.hosts).toHaveLength(2);
expect(responseBody.data.hosts.map((h) => h.userId)).toContain(teamMember1.id);
expect(responseBody.data.hosts.map((h) => h.userId)).toContain(teamMember2.id);
await eventTypesRepositoryFixture.delete(createdEventType.data.id);
});
});
describe("should update event type title", () => {
it("should preserve hosts when updating collective event type title", async () => {
const collectiveEventType = await eventTypesRepositoryFixture.create(
{
title: `collective-preserve-hosts-${randomString()}`,
slug: `collective-preserve-hosts-${randomString()}`,
length: 60,
team: {
connect: {
id: team.id,
},
},
schedulingType: "COLLECTIVE",
hosts: {
create: [
{
userId: teamMember1.id,
isFixed: true,
},
{
userId: teamMember2.id,
isFixed: true,
},
],
},
},
userAdmin.id
);
const newTitle = `updated-collective-title-${randomString()}`;
const updateBody = {
title: newTitle,
};
const response = await request(app.getHttpServer())
.patch(`/v2/teams/${team.id}/event-types/${collectiveEventType.id}`)
.send(updateBody)
.expect(200);
const responseBody: ApiSuccessResponse<TeamEventTypeOutput_2024_06_14> = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data.title).toEqual(newTitle);
expect(responseBody.data.schedulingType).toEqual("collective");
expect(responseBody.data.hosts).toHaveLength(2);
expect(responseBody.data.hosts.map((h) => h.userId)).toContain(teamMember1.id);
expect(responseBody.data.hosts.map((h) => h.userId)).toContain(teamMember2.id);
await eventTypesRepositoryFixture.delete(collectiveEventType.id);
});
it("should preserve hosts when updating round robin event type title", async () => {
const roundRobinEventType = await eventTypesRepositoryFixture.create(
{
title: `roundrobin-preserve-hosts-${randomString()}`,
slug: `roundrobin-preserve-hosts-${randomString()}`,
length: 60,
team: {
connect: {
id: team.id,
},
},
schedulingType: "ROUND_ROBIN",
hosts: {
create: [
{
userId: teamMember1.id,
isFixed: false,
priority: 2,
},
{
userId: teamMember2.id,
isFixed: true,
priority: 1,
},
],
},
},
userAdmin.id
);
const newTitle = `updated-roundrobin-title-${randomString()}`;
const updateBody = {
title: newTitle,
};
const response = await request(app.getHttpServer())
.patch(`/v2/teams/${team.id}/event-types/${roundRobinEventType.id}`)
.send(updateBody)
.expect(200);
const responseBody: ApiSuccessResponse<TeamEventTypeOutput_2024_06_14> = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data.title).toEqual(newTitle);
expect(responseBody.data.schedulingType).toEqual("roundRobin");
expect(responseBody.data.hosts).toHaveLength(2);
expect(responseBody.data.hosts.map((h) => h.userId)).toContain(teamMember1.id);
expect(responseBody.data.hosts.map((h) => h.userId)).toContain(teamMember2.id);
await eventTypesRepositoryFixture.delete(roundRobinEventType.id);
});
});
describe("should update event type hosts", () => {
it("should update collective event type hosts", async () => {
const collectiveEventType = await eventTypesRepositoryFixture.create(
{
title: `collective-update-hosts-${randomString()}`,
slug: `collective-update-hosts-${randomString()}`,
length: 60,
team: {
connect: {
id: team.id,
},
},
schedulingType: "COLLECTIVE",
hosts: {
create: [
{
userId: teamMember1.id,
isFixed: true,
},
],
},
},
userAdmin.id
);
const updateBody = {
hosts: [
{
userId: teamMember2.id,
},
],
};
const response = await request(app.getHttpServer())
.patch(`/v2/teams/${team.id}/event-types/${collectiveEventType.id}`)
.send(updateBody)
.expect(200);
const responseBody: ApiSuccessResponse<TeamEventTypeOutput_2024_06_14> = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data.schedulingType).toEqual("collective");
expect(responseBody.data.hosts).toHaveLength(1);
expect(responseBody.data.hosts[0].userId).toEqual(teamMember2.id);
await eventTypesRepositoryFixture.delete(collectiveEventType.id);
});
it("should update round robin event type hosts", async () => {
const roundRobinEventType = await eventTypesRepositoryFixture.create(
{
title: `roundrobin-update-hosts-${randomString()}`,
slug: `roundrobin-update-hosts-${randomString()}`,
length: 60,
team: {
connect: {
id: team.id,
},
},
schedulingType: "ROUND_ROBIN",
hosts: {
create: [
{
userId: teamMember1.id,
isFixed: true,
priority: 1,
},
],
},
},
userAdmin.id
);
const updateBody = {
hosts: [
{
userId: teamMember2.id,
priority: "high",
},
],
};
const response = await request(app.getHttpServer())
.patch(`/v2/teams/${team.id}/event-types/${roundRobinEventType.id}`)
.send(updateBody)
.expect(200);
const responseBody: ApiSuccessResponse<TeamEventTypeOutput_2024_06_14> = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data.schedulingType).toEqual("roundRobin");
expect(responseBody.data.hosts).toHaveLength(1);
expect(responseBody.data.hosts[0].userId).toEqual(teamMember2.id);
await eventTypesRepositoryFixture.delete(roundRobinEventType.id);
});
});
afterAll(async () => {
await userRepositoryFixture.deleteByEmail(userAdmin.email);
await userRepositoryFixture.deleteByEmail(teamMember1.email);
+16
View File
@@ -3864,6 +3864,16 @@
"type": "string"
}
},
{
"name": "bookingUid",
"required": false,
"in": "query",
"description": "Filter bookings by the booking Uid.",
"example": "2NtaeaVcKfpmSZ4CthFdfk",
"schema": {
"type": "string"
}
},
{
"name": "eventTypeIds",
"required": false,
@@ -21928,6 +21938,12 @@
"default": false,
"description": "Boolean to require authentication for booking this event type via api. If true, only authenticated users who are the event-type owner or org/team admin/owner can book this event type."
},
"schedulingType": {
"type": "string",
"enum": ["collective", "roundRobin"],
"example": "collective",
"description": "The scheduling type for the team event - collective or roundRobin. ❗If you change scheduling type you must also provide `hosts` or `assignAllTeamMembers` in the request body, otherwise the event type will have no hosts - this is required because\n in case of collective event type all hosts are mandatory but in case of round robin some or non can be mandatory so we can't predict how you want the hosts to be setup which is why you must provide that information. If you want to convert round robin or collective into managed or managed into round robin or collective then you will have to create a new team event type and delete old one."
},
"hosts": {
"description": "Hosts contain specific team members you want to assign to this event type, but if you want to assign all team members, use `assignAllTeamMembers: true` instead and omit this field. For platform customers the hosts can include userIds only of managed users. Provide either hosts or assignAllTeamMembers but not both",
"type": "array",
@@ -11,8 +11,11 @@ import {
ArrayNotEmpty,
ArrayUnique,
IsUrl,
IsIn,
} from "class-validator";
import { SchedulingType } from "@calcom/platform-enums";
import { RequiresAtLeastOnePropertyWhenNotDisabled } from "../../../utils/RequiresOneOfPropertiesWhenNotDisabled";
import { BookerActiveBookingsLimit_2024_06_14 } from "./booker-active-booking-limit.input";
import { BookerLayouts_2024_06_14 } from "./booker-layouts.input";
@@ -476,6 +479,25 @@ export class UpdateEventTypeInput_2024_06_14 extends BaseUpdateEventTypeInput {
}
export class UpdateTeamEventTypeInput_2024_06_14 extends BaseUpdateEventTypeInput {
@Transform(({ value }) => {
if (value === "collective") {
return SchedulingType.COLLECTIVE;
}
if (value === "roundRobin") {
return SchedulingType.ROUND_ROBIN;
}
return value;
})
@IsIn([SchedulingType.COLLECTIVE, SchedulingType.ROUND_ROBIN])
@IsOptional()
@DocsPropertyOptional({
enum: ["collective", "roundRobin"],
example: "collective",
description: `The scheduling type for the team event - collective or roundRobin. ❗If you change scheduling type you must also provide \`hosts\` or \`assignAllTeamMembers\` in the request body, otherwise the event type will have no hosts - this is required because
in case of collective event type all hosts are mandatory but in case of round robin some or non can be mandatory so we can't predict how you want the hosts to be setup which is why you must provide that information. If you want to convert round robin or collective into managed or managed into round robin or collective then you will have to create a new team event type and delete old one.`,
})
schedulingType?: "COLLECTIVE" | "ROUND_ROBIN";
@ValidateNested({ each: true })
@Type(() => Host)
@IsArray()