Skip to content

Commit 8b4f675

Browse files
authored
feat: v2 api allow switching event type between collective and round robin (calcom#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
1 parent 53a931d commit 8b4f675

4 files changed

Lines changed: 686 additions & 20 deletions

File tree

apps/api/v2/src/modules/organizations/event-types/services/input.service.ts

Lines changed: 67 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ import {
1515
HostPriority,
1616
EmailSettings_2024_06_14,
1717
} from "@calcom/platform-types";
18+
import type { EventType } from "@calcom/prisma/client";
19+
20+
export const HOSTS_REQUIRED_WHEN_SWITCHING_SCHEDULING_TYPE_ERROR =
21+
"Hosts required when switching schedulingType. Please provide 'hosts' or set 'assignAllTeamMembers: true' to specify how hosts should be configured for the new scheduling type.";
1822

1923
export type TransformedCreateTeamEventTypeInput = Awaited<
2024
ReturnType<InstanceType<typeof InputOrganizationsEventTypesService>["transformInputCreateTeamEventType"]>
@@ -116,36 +120,34 @@ export class InputOrganizationsEventTypesService {
116120
teamId: number,
117121
inputEventType: CreateTeamEventTypeInput_2024_06_14
118122
) {
119-
const { hosts, assignAllTeamMembers, locations, emailSettings, ...rest } = inputEventType;
123+
const { assignAllTeamMembers, locations, emailSettings, ...rest } = inputEventType;
120124

121125
const eventType = this.inputEventTypesService.transformInputCreateEventType(rest);
122126

127+
const isManagedEventType = rest.schedulingType === "MANAGED";
128+
123129
const defaultLocations: CreateTeamEventTypeInput_2024_06_14["locations"] = [
124130
{
125131
type: "integration",
126132
integration: "cal-video",
127133
},
128134
];
129135

130-
const children = await this.getChildEventTypesForManagedEventTypeCreate(inputEventType, teamId);
136+
const children = isManagedEventType
137+
? await this.getChildEventTypesForManagedEventTypeCreate(inputEventType, teamId)
138+
: undefined;
131139

132-
let metadata =
133-
rest.schedulingType === "MANAGED"
134-
? { managedEventConfig: {}, ...eventType.metadata }
135-
: eventType.metadata;
140+
let metadata = isManagedEventType
141+
? { managedEventConfig: {}, ...eventType.metadata }
142+
: eventType.metadata;
136143

137144
if (emailSettings) {
138145
metadata = this.addEmailSettingsToMetadata(emailSettings, metadata);
139146
}
140147

141148
const teamEventType = {
142149
...eventType,
143-
// note(Lauris): we don't populate hosts for managed event-types because they are handled by the children
144-
hosts: !(rest.schedulingType === "MANAGED")
145-
? assignAllTeamMembers
146-
? await this.getAllTeamMembers(teamId, inputEventType.schedulingType)
147-
: this.transformInputHosts(hosts, inputEventType.schedulingType)
148-
: undefined,
150+
hosts: await this.transformInputCreateTeamEventTypeHosts(teamId, inputEventType),
149151
assignAllTeamMembers,
150152
locations: this.transformInputTeamLocations(locations || defaultLocations),
151153
metadata,
@@ -155,6 +157,24 @@ export class InputOrganizationsEventTypesService {
155157
return teamEventType;
156158
}
157159

160+
private async transformInputCreateTeamEventTypeHosts(
161+
teamId: number,
162+
inputEventType: CreateTeamEventTypeInput_2024_06_14
163+
) {
164+
const { hosts, assignAllTeamMembers, schedulingType } = inputEventType;
165+
166+
// 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
167+
// a specific user and hosts property is only for team event types e.g round robin and collective.
168+
if (schedulingType === "MANAGED") {
169+
return undefined;
170+
}
171+
172+
if (assignAllTeamMembers) {
173+
return await this.getAllTeamMembers(teamId, inputEventType.schedulingType);
174+
}
175+
return this.transformInputHosts(hosts, inputEventType.schedulingType);
176+
}
177+
158178
private addEmailSettingsToMetadata(
159179
emailSettings: EmailSettings_2024_06_14,
160180
metadata: NonNullable<EventTypeMetadata>
@@ -190,7 +210,7 @@ export class InputOrganizationsEventTypesService {
190210
teamId: number,
191211
inputEventType: UpdateTeamEventTypeInput_2024_06_14
192212
) {
193-
const { hosts, assignAllTeamMembers, locations, emailSettings, ...rest } = inputEventType;
213+
const { assignAllTeamMembers, locations, emailSettings, ...rest } = inputEventType;
194214

195215
const eventType = await this.inputEventTypesService.transformInputUpdateEventType(rest, eventTypeId);
196216
const dbEventType = await this.teamsEventTypesRepository.getTeamEventType(teamId, eventTypeId);
@@ -213,11 +233,11 @@ export class InputOrganizationsEventTypesService {
213233
const teamEventType = {
214234
...eventType,
215235
// note(Lauris): we don't populate hosts for managed event-types because they are handled by the children
216-
hosts: !children
217-
? assignAllTeamMembers
218-
? await this.getAllTeamMembers(teamId, dbEventType.schedulingType)
219-
: this.transformInputHosts(hosts, dbEventType.schedulingType)
220-
: undefined,
236+
hosts: await this.transformInputUpdateTeamEventTypeHosts(
237+
teamId,
238+
dbEventType.schedulingType,
239+
inputEventType
240+
),
221241
assignAllTeamMembers,
222242
children,
223243
locations: locations ? this.transformInputTeamLocations(locations) : undefined,
@@ -227,6 +247,33 @@ export class InputOrganizationsEventTypesService {
227247
return teamEventType;
228248
}
229249

250+
private async transformInputUpdateTeamEventTypeHosts(
251+
teamId: number,
252+
dbEventTypeSchedulingType: EventType["schedulingType"],
253+
inputEventType: UpdateTeamEventTypeInput_2024_06_14
254+
) {
255+
const { hosts, assignAllTeamMembers } = inputEventType;
256+
257+
if (dbEventTypeSchedulingType === "MANAGED") {
258+
// note(Lauris): we don't populate hosts for managed event-types because they are handled by the event type children
259+
return undefined;
260+
}
261+
262+
const isSchedulingTypeChanging =
263+
inputEventType.schedulingType && inputEventType.schedulingType !== dbEventTypeSchedulingType;
264+
265+
if (isSchedulingTypeChanging && !assignAllTeamMembers && !hosts) {
266+
throw new BadRequestException(HOSTS_REQUIRED_WHEN_SWITCHING_SCHEDULING_TYPE_ERROR);
267+
}
268+
269+
const nextSchedulingType = inputEventType.schedulingType || dbEventTypeSchedulingType;
270+
if (assignAllTeamMembers) {
271+
return await this.getAllTeamMembers(teamId, nextSchedulingType);
272+
}
273+
274+
return this.transformInputHosts(hosts, nextSchedulingType);
275+
}
276+
230277
async getChildEventTypesForManagedEventTypeUpdate(
231278
eventTypeId: number,
232279
inputEventType: UpdateTeamEventTypeInput_2024_06_14,
@@ -273,7 +320,7 @@ export class InputOrganizationsEventTypesService {
273320
}
274321

275322
async getChildEventTypesForManagedEventTypeCreate(
276-
inputEventType: UpdateTeamEventTypeInput_2024_06_14,
323+
inputEventType: Pick<CreateTeamEventTypeInput_2024_06_14, "assignAllTeamMembers" | "hosts">,
277324
teamId: number
278325
) {
279326
const ownersIds = await this.getOwnersIdsForManagedEventTypeCreate(teamId, inputEventType);
@@ -289,7 +336,7 @@ export class InputOrganizationsEventTypesService {
289336

290337
async getOwnersIdsForManagedEventTypeCreate(
291338
teamId: number,
292-
inputEventType: UpdateTeamEventTypeInput_2024_06_14
339+
inputEventType: Pick<CreateTeamEventTypeInput_2024_06_14, "assignAllTeamMembers" | "hosts">
293340
) {
294341
if (inputEventType.assignAllTeamMembers) {
295342
return await this.getTeamUsersIds(teamId);

0 commit comments

Comments
 (0)