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);