diff --git a/apps/api/v1/pages/api/availability/_get.ts b/apps/api/v1/pages/api/availability/_get.ts index 4dada07b66..1a7059bd83 100644 --- a/apps/api/v1/pages/api/availability/_get.ts +++ b/apps/api/v1/pages/api/availability/_get.ts @@ -1,7 +1,7 @@ import type { NextApiRequest } from "next"; import { z } from "zod"; -import { getUserAvailabilityService } from "@calcom/lib/di/containers/get-user-availability"; +import { getUserAvailabilityService } from "@calcom/lib/di/containers/GetUserAvailability"; import { HttpError } from "@calcom/lib/http-error"; import { defaultResponder } from "@calcom/lib/server/defaultResponder"; import prisma from "@calcom/prisma"; diff --git a/apps/api/v1/pages/api/slots/_get.ts b/apps/api/v1/pages/api/slots/_get.ts index f47731f2e7..7589a41737 100644 --- a/apps/api/v1/pages/api/slots/_get.ts +++ b/apps/api/v1/pages/api/slots/_get.ts @@ -4,7 +4,7 @@ import type { NextApiRequest, NextApiResponse } from "next"; import dayjs from "@calcom/dayjs"; import { isSupportedTimeZone } from "@calcom/lib/dayjs"; -import { getAvailableSlotsService } from "@calcom/lib/di/containers/available-slots"; +import { getAvailableSlotsService } from "@calcom/lib/di/containers/AvailableSlots"; import { HttpError } from "@calcom/lib/http-error"; import { defaultResponder } from "@calcom/lib/server/defaultResponder"; import { createContext } from "@calcom/trpc/server/createContext"; diff --git a/apps/api/v1/test/lib/bookings/_post.test.ts b/apps/api/v1/test/lib/bookings/_post.test.ts index 2b9f5680ae..87d2a12b9b 100644 --- a/apps/api/v1/test/lib/bookings/_post.test.ts +++ b/apps/api/v1/test/lib/bookings/_post.test.ts @@ -76,11 +76,13 @@ vi.mock("@calcom/features/watchlist/operations/check-if-users-are-blocked.contro checkIfUsersAreBlocked: vi.fn().mockResolvedValue(false), })); -vi.mock("@calcom/lib/bookings/findQualifiedHostsWithDelegationCredentials", () => ({ - findQualifiedHostsWithDelegationCredentials: vi.fn().mockResolvedValue({ - qualifiedRRHosts: [], - allFallbackRRHosts: [], - fixedHosts: [], +vi.mock("@calcom/lib/di/containers/QualifiedHosts", () => ({ + getQualifiedHostsService: vi.fn().mockReturnValue({ + findQualifiedHostsWithDelegationCredentials: vi.fn().mockResolvedValue({ + qualifiedRRHosts: [], + allFallbackRRHosts: [], + fixedHosts: [], + }), }), })); diff --git a/apps/api/v2/package.json b/apps/api/v2/package.json index cb06e17687..023091970c 100644 --- a/apps/api/v2/package.json +++ b/apps/api/v2/package.json @@ -38,7 +38,7 @@ "@axiomhq/winston": "^1.2.0", "@calcom/platform-constants": "*", "@calcom/platform-enums": "*", - "@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.294", + "@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.298", "@calcom/platform-types": "*", "@calcom/platform-utils": "*", "@calcom/prisma": "*", diff --git a/apps/web/pages/api/get-inbound-dynamic-variables.ts b/apps/web/pages/api/get-inbound-dynamic-variables.ts index 088a13a099..595259d2eb 100644 --- a/apps/web/pages/api/get-inbound-dynamic-variables.ts +++ b/apps/web/pages/api/get-inbound-dynamic-variables.ts @@ -5,7 +5,7 @@ import { z } from "zod"; import dayjs from "@calcom/dayjs"; import { ZGetRetellLLMSchema } from "@calcom/features/calAIPhone/zod-utils"; import type { TGetRetellLLMSchema } from "@calcom/features/calAIPhone/zod-utils"; -import { getAvailableSlotsService } from "@calcom/lib/di/containers/available-slots"; +import { getAvailableSlotsService } from "@calcom/lib/di/containers/AvailableSlots"; import { fetcher } from "@calcom/lib/retellAIFetcher"; import { defaultHandler } from "@calcom/lib/server/defaultHandler"; import prisma from "@calcom/prisma"; diff --git a/apps/web/test/lib/checkBookingLimits.test.ts b/apps/web/test/lib/checkBookingLimits.test.ts index 24b2bfa5bc..bd937f8b8b 100644 --- a/apps/web/test/lib/checkBookingLimits.test.ts +++ b/apps/web/test/lib/checkBookingLimits.test.ts @@ -3,7 +3,7 @@ import prismaMock from "../../../../tests/libs/__mocks__/prismaMock"; import { describe, expect, it } from "vitest"; import dayjs from "@calcom/dayjs"; -import { getCheckBookingLimitsService } from "@calcom/lib/di/containers/booking-limits"; +import { getCheckBookingLimitsService } from "@calcom/lib/di/containers/BookingLimits"; import type { IntervalLimit } from "@calcom/lib/intervalLimits/intervalLimitSchema"; import { validateIntervalLimitOrder } from "@calcom/lib/intervalLimits/validateIntervalLimitOrder"; diff --git a/apps/web/test/lib/getSchedule.test.ts b/apps/web/test/lib/getSchedule.test.ts index d0bb7c218d..49ce568582 100644 --- a/apps/web/test/lib/getSchedule.test.ts +++ b/apps/web/test/lib/getSchedule.test.ts @@ -17,7 +17,7 @@ import { import { describe, vi, test } from "vitest"; import dayjs from "@calcom/dayjs"; -import { getAvailableSlotsService } from "@calcom/lib/di/containers/available-slots"; +import { getAvailableSlotsService } from "@calcom/lib/di/containers/AvailableSlots"; import { SchedulingType, type BookingStatus } from "@calcom/prisma/enums"; import { expect, expectedSlotsForSchedule } from "./getSchedule/expects"; diff --git a/apps/web/test/lib/getSchedule/calendarEvents.test.ts b/apps/web/test/lib/getSchedule/calendarEvents.test.ts index 7eb99a49f2..8d5e4d730f 100644 --- a/apps/web/test/lib/getSchedule/calendarEvents.test.ts +++ b/apps/web/test/lib/getSchedule/calendarEvents.test.ts @@ -9,7 +9,7 @@ import { import { describe, test } from "vitest"; -import { getAvailableSlotsService } from "@calcom/lib/di/containers/available-slots"; +import { getAvailableSlotsService } from "@calcom/lib/di/containers/AvailableSlots"; import { expect, expectedSlotsForSchedule } from "./expects"; import { setupAndTeardown } from "./setupAndTeardown"; diff --git a/apps/web/test/lib/getSchedule/delegation-credential.test.ts b/apps/web/test/lib/getSchedule/delegation-credential.test.ts index f2f1e8f5f2..24b7b60a80 100644 --- a/apps/web/test/lib/getSchedule/delegation-credential.test.ts +++ b/apps/web/test/lib/getSchedule/delegation-credential.test.ts @@ -12,7 +12,7 @@ import { expectNoAttemptToGetAvailability } from "../../utils/bookingScenario/ex import { describe, test } from "vitest"; -import { getAvailableSlotsService } from "@calcom/lib/di/containers/available-slots"; +import { getAvailableSlotsService } from "@calcom/lib/di/containers/AvailableSlots"; import { MembershipRole } from "@calcom/prisma/enums"; import { expect, expectedSlotsForSchedule } from "./expects"; diff --git a/apps/web/test/lib/getSchedule/futureLimit.timezone.test.ts b/apps/web/test/lib/getSchedule/futureLimit.timezone.test.ts index b424590700..78d3eb8ad8 100644 --- a/apps/web/test/lib/getSchedule/futureLimit.timezone.test.ts +++ b/apps/web/test/lib/getSchedule/futureLimit.timezone.test.ts @@ -8,7 +8,7 @@ import type { ScenarioData } from "../../utils/bookingScenario/bookingScenario"; import { describe, expect, vi, test } from "vitest"; -import { getAvailableSlotsService } from "@calcom/lib/di/containers/available-slots"; +import { getAvailableSlotsService } from "@calcom/lib/di/containers/AvailableSlots"; import { PeriodType } from "@calcom/prisma/enums"; import { expectedSlotsForSchedule } from "./expects"; diff --git a/apps/web/test/lib/getSchedule/restrictionSchedule.test.ts b/apps/web/test/lib/getSchedule/restrictionSchedule.test.ts index cccb2655c7..e00beaabe1 100644 --- a/apps/web/test/lib/getSchedule/restrictionSchedule.test.ts +++ b/apps/web/test/lib/getSchedule/restrictionSchedule.test.ts @@ -10,7 +10,7 @@ import { import { describe, test, vi } from "vitest"; import type { z } from "zod"; -import { getAvailableSlotsService } from "@calcom/lib/di/containers/available-slots"; +import { getAvailableSlotsService } from "@calcom/lib/di/containers/AvailableSlots"; import type { getScheduleSchema } from "@calcom/trpc/server/routers/viewer/slots/types"; import { expect } from "./expects"; diff --git a/apps/web/test/lib/getSchedule/selectedSlots.test.ts b/apps/web/test/lib/getSchedule/selectedSlots.test.ts index edeb3c7108..0cdaff3543 100644 --- a/apps/web/test/lib/getSchedule/selectedSlots.test.ts +++ b/apps/web/test/lib/getSchedule/selectedSlots.test.ts @@ -10,7 +10,7 @@ import type { IncomingMessage } from "http"; import { describe, test, beforeEach, vi } from "vitest"; import type { z } from "zod"; -import { getAvailableSlotsService } from "@calcom/lib/di/containers/available-slots"; +import { getAvailableSlotsService } from "@calcom/lib/di/containers/AvailableSlots"; import { prisma } from "@calcom/prisma"; import type { getScheduleSchema, GetScheduleOptions } from "@calcom/trpc/server/routers/viewer/slots/types"; diff --git a/packages/features/bookings/lib/handleNewBooking.ts b/packages/features/bookings/lib/handleNewBooking.ts index d70efa30c5..f0453c20ee 100644 --- a/packages/features/bookings/lib/handleNewBooking.ts +++ b/packages/features/bookings/lib/handleNewBooking.ts @@ -52,8 +52,8 @@ import { enrichHostsWithDelegationCredentials, getFirstDelegationConferencingCredentialAppLocation, } from "@calcom/lib/delegationCredential/server"; -import { getCheckBookingAndDurationLimitsService } from "@calcom/lib/di/containers/booking-limits"; -import { getCacheService } from "@calcom/lib/di/containers/cache"; +import { getCheckBookingAndDurationLimitsService } from "@calcom/lib/di/containers/BookingLimits"; +import { getCacheService } from "@calcom/lib/di/containers/Cache"; import { ErrorCode } from "@calcom/lib/errorCodes"; import { getErrorFromUnknown } from "@calcom/lib/errors"; import { getEventName, updateHostInEventName } from "@calcom/lib/event"; diff --git a/packages/features/bookings/lib/handleNewBooking/ensureAvailableUsers.ts b/packages/features/bookings/lib/handleNewBooking/ensureAvailableUsers.ts index f0c571a8dc..15d849cc86 100644 --- a/packages/features/bookings/lib/handleNewBooking/ensureAvailableUsers.ts +++ b/packages/features/bookings/lib/handleNewBooking/ensureAvailableUsers.ts @@ -4,8 +4,8 @@ import dayjs from "@calcom/dayjs"; import type { Dayjs } from "@calcom/dayjs"; import { checkForConflicts } from "@calcom/features/bookings/lib/conflictChecker/checkForConflicts"; import { buildDateRanges } from "@calcom/lib/date-ranges"; -import { getBusyTimesService } from "@calcom/lib/di/containers/busy-times"; -import { getUserAvailabilityService } from "@calcom/lib/di/containers/get-user-availability"; +import { getBusyTimesService } from "@calcom/lib/di/containers/BusyTimes"; +import { getUserAvailabilityService } from "@calcom/lib/di/containers/GetUserAvailability"; import { ErrorCode } from "@calcom/lib/errorCodes"; import { parseBookingLimit } from "@calcom/lib/intervalLimits/isBookingLimits"; import { parseDurationLimit } from "@calcom/lib/intervalLimits/isDurationLimits"; diff --git a/packages/features/bookings/lib/handleNewBooking/loadAndValidateUsers.ts b/packages/features/bookings/lib/handleNewBooking/loadAndValidateUsers.ts index 7bc5fe9c87..9a99e82a79 100644 --- a/packages/features/bookings/lib/handleNewBooking/loadAndValidateUsers.ts +++ b/packages/features/bookings/lib/handleNewBooking/loadAndValidateUsers.ts @@ -2,8 +2,8 @@ import type { Prisma } from "@prisma/client"; import type { Logger } from "tslog"; import { checkIfUsersAreBlocked } from "@calcom/features/watchlist/operations/check-if-users-are-blocked.controller"; -import { findQualifiedHostsWithDelegationCredentials } from "@calcom/lib/bookings/findQualifiedHostsWithDelegationCredentials"; import { enrichUsersWithDelegationCredentials } from "@calcom/lib/delegationCredential/server"; +import { getQualifiedHostsService } from "@calcom/lib/di/containers/QualifiedHosts"; import getOrgIdFromMemberOrTeamId from "@calcom/lib/getOrgIdFromMemberOrTeamId"; import { HttpError } from "@calcom/lib/http-error"; import { getPiiFreeUser } from "@calcom/lib/piiFreeData"; @@ -142,8 +142,9 @@ const _loadAndValidateUsers = async ({ ? false : user.isFixed || eventType.schedulingType !== SchedulingType.ROUND_ROBIN, })); + const qualifiedHostsService = getQualifiedHostsService(); const { qualifiedRRHosts, allFallbackRRHosts, fixedHosts } = - await findQualifiedHostsWithDelegationCredentials({ + await qualifiedHostsService.findQualifiedHostsWithDelegationCredentials({ eventType, routedTeamMemberIds: routedTeamMemberIds || [], rescheduleUid, @@ -159,7 +160,7 @@ const _loadAndValidateUsers = async ({ }, {} as { [key: number]: Awaited< - ReturnType + ReturnType["findQualifiedHostsWithDelegationCredentials"]> >["qualifiedRRHosts"][number]; } ); diff --git a/packages/features/insights/server/trpc-router.ts b/packages/features/insights/server/trpc-router.ts index cf858336f8..11d64db251 100644 --- a/packages/features/insights/server/trpc-router.ts +++ b/packages/features/insights/server/trpc-router.ts @@ -9,8 +9,8 @@ import { routingRepositoryBaseInputSchema, bookingRepositoryBaseInputSchema, } from "@calcom/features/insights/server/raw-data.schema"; -import { getInsightsBookingService } from "@calcom/lib/di/containers/insights-booking"; -import { getInsightsRoutingService } from "@calcom/lib/di/containers/insights-routing"; +import { getInsightsBookingService } from "@calcom/lib/di/containers/InsightsBooking"; +import { getInsightsRoutingService } from "@calcom/lib/di/containers/InsightsRouting"; import type { readonlyPrisma } from "@calcom/prisma"; import { BookingStatus } from "@calcom/prisma/enums"; import authedProcedure from "@calcom/trpc/server/procedures/authedProcedure"; diff --git a/packages/lib/bookings/filterHostsBySameRoundRobinHost.test.ts b/packages/lib/bookings/filterHostsBySameRoundRobinHost.test.ts index 86be937c1b..db914bea67 100644 --- a/packages/lib/bookings/filterHostsBySameRoundRobinHost.test.ts +++ b/packages/lib/bookings/filterHostsBySameRoundRobinHost.test.ts @@ -1,31 +1,29 @@ +import type { Mock } from "vitest"; import { describe, expect, it, vi, afterEach } from "vitest"; -import prisma from "@calcom/prisma"; +import type { BookingRepository } from "@calcom/lib/server/repository/booking"; -import { filterHostsBySameRoundRobinHost } from "./filterHostsBySameRoundRobinHost"; +import { FilterHostsService } from "./filterHostsBySameRoundRobinHost"; -// Mocking setup -const prismaMock = { - booking: { - findFirst: vi.fn(), // Mock the findFirst method - }, -}; +const mockBookingRepo = { + findOriginalRescheduledBookingUserId: vi.fn(), +} as unknown as BookingRepository; -// Use `vi.spyOn` to make `prisma.booking.groupBy` call the mock instead -vi.spyOn(prisma.booking, "findFirst").mockImplementation(prismaMock.booking.findFirst); - -afterEach(() => { - // Clear call history before each test to avoid cross-test interference - prismaMock.booking.findFirst.mockClear(); +const filterHostsService = new FilterHostsService({ + bookingRepo: mockBookingRepo, }); -describe("filterHostsBySameRoundRobinHost", () => { +afterEach(() => { + (mockBookingRepo.findOriginalRescheduledBookingUserId as Mock).mockClear(); +}); + +describe("FilterHostsService", () => { it("skips filter if rescheduleWithSameRoundRobinHost set to false", async () => { const hosts = [ { isFixed: false as const, createdAt: new Date(), user: { id: 1, email: "example1@acme.com" } }, ]; expect( - filterHostsBySameRoundRobinHost({ + filterHostsService.filterHostsBySameRoundRobinHost({ hosts, rescheduleUid: "some-uid", rescheduleWithSameRoundRobinHost: false, @@ -35,10 +33,10 @@ describe("filterHostsBySameRoundRobinHost", () => { }); it("skips filter if rerouting", async () => { const hosts = [ - { isFixed: true as const, createdAt: new Date(), user: { id: 1, email: "example1@acme.com" } }, + { isFixed: false as const, createdAt: new Date(), user: { id: 1, email: "example1@acme.com" } }, ]; expect( - filterHostsBySameRoundRobinHost({ + filterHostsService.filterHostsBySameRoundRobinHost({ hosts, rescheduleUid: "some-uid", rescheduleWithSameRoundRobinHost: true, @@ -48,15 +46,14 @@ describe("filterHostsBySameRoundRobinHost", () => { }); it("correctly selects the same host if the filter applies and the host is in the RR users", async () => { - prismaMock.booking.findFirst.mockResolvedValue({ userId: 1 }); + (mockBookingRepo.findOriginalRescheduledBookingUserId as Mock).mockResolvedValue({ userId: 1 }); const hosts = [ { isFixed: false as const, createdAt: new Date(), user: { id: 1, email: "example1@acme.com" } }, { isFixed: false as const, createdAt: new Date(), user: { id: 2, email: "example2@acme.com" } }, ]; - // Same host should be selected expect( - filterHostsBySameRoundRobinHost({ + filterHostsService.filterHostsBySameRoundRobinHost({ hosts, rescheduleUid: "some-uid", rescheduleWithSameRoundRobinHost: true, @@ -68,7 +65,7 @@ describe("filterHostsBySameRoundRobinHost", () => { // Tests for bookings that have more than one host describe("Fixed hosts and round robin groups support", () => { it("should return organizer and attendee hosts", async () => { - prismaMock.booking.findFirst.mockResolvedValue({ + (mockBookingRepo.findOriginalRescheduledBookingUserId as Mock).mockResolvedValue({ userId: 1, attendees: [ { email: "host2@acme.com" }, @@ -84,7 +81,7 @@ describe("filterHostsBySameRoundRobinHost", () => { { isFixed: false as const, createdAt: new Date(), user: { id: 4, email: "host4@acme.com" } }, ]; - const result = await filterHostsBySameRoundRobinHost({ + const result = await filterHostsService.filterHostsBySameRoundRobinHost({ hosts, rescheduleUid: "some-uid", rescheduleWithSameRoundRobinHost: true, @@ -101,7 +98,7 @@ describe("filterHostsBySameRoundRobinHost", () => { }); it("should return only organizer host when no attendees match current hosts", async () => { - prismaMock.booking.findFirst.mockResolvedValue({ + (mockBookingRepo.findOriginalRescheduledBookingUserId as Mock).mockResolvedValue({ userId: 1, attendees: [ { email: "attendee1@example.com" }, // Non-host attendee @@ -114,7 +111,7 @@ describe("filterHostsBySameRoundRobinHost", () => { { isFixed: false as const, createdAt: new Date(), user: { id: 2, email: "host2@acme.com" } }, ]; - const result = await filterHostsBySameRoundRobinHost({ + const result = await filterHostsService.filterHostsBySameRoundRobinHost({ hosts, rescheduleUid: "some-uid", rescheduleWithSameRoundRobinHost: true, diff --git a/packages/lib/bookings/filterHostsBySameRoundRobinHost.ts b/packages/lib/bookings/filterHostsBySameRoundRobinHost.ts index 001c44ca80..1c54960272 100644 --- a/packages/lib/bookings/filterHostsBySameRoundRobinHost.ts +++ b/packages/lib/bookings/filterHostsBySameRoundRobinHost.ts @@ -1,57 +1,53 @@ -import { prisma } from "@calcom/prisma"; -import { BookingStatus } from "@calcom/prisma/enums"; +import type { BookingRepository } from "@calcom/lib/server/repository/booking"; import { isRerouting } from "./routing/utils"; -export const filterHostsBySameRoundRobinHost = async < - T extends { - isFixed: false; // ensure no fixed hosts are passed. - user: { id: number; email: string }; - } ->({ - hosts, - rescheduleUid, - rescheduleWithSameRoundRobinHost, - routedTeamMemberIds, -}: { - hosts: T[]; - rescheduleUid: string | null; - rescheduleWithSameRoundRobinHost: boolean; - routedTeamMemberIds: number[] | null; -}) => { - if ( - !rescheduleUid || - !rescheduleWithSameRoundRobinHost || - isRerouting({ rescheduleUid, routedTeamMemberIds }) - ) { - return hosts; - } - const originalRescheduledBooking = await prisma.booking.findFirst({ - where: { - uid: rescheduleUid, - status: { - in: [BookingStatus.ACCEPTED, BookingStatus.CANCELLED, BookingStatus.PENDING], - }, - }, - select: { - userId: true, - attendees: { - select: { - email: true, - }, - }, - }, - }); +export interface IFilterHostsService { + bookingRepo: BookingRepository; +} - if (!originalRescheduledBooking) { - return hosts; +export class FilterHostsService { + constructor(public readonly dependencies: IFilterHostsService) {} + + async filterHostsBySameRoundRobinHost< + T extends { + isFixed: false; // ensure no fixed hosts are passed. + user: { id: number; email: string }; + } + >({ + hosts, + rescheduleUid, + rescheduleWithSameRoundRobinHost, + routedTeamMemberIds, + }: { + hosts: T[]; + rescheduleUid: string | null; + rescheduleWithSameRoundRobinHost: boolean; + routedTeamMemberIds: number[] | null; + }) { + if ( + !rescheduleUid || + !rescheduleWithSameRoundRobinHost || + isRerouting({ rescheduleUid, routedTeamMemberIds }) + ) { + return hosts; + } + + const originalRescheduledBooking = + await this.dependencies.bookingRepo.findOriginalRescheduledBookingUserId({ + rescheduleUid, + }); + + if (!originalRescheduledBooking) { + return hosts; + } + + const attendeeEmails = originalRescheduledBooking.attendees?.map((attendee) => attendee.email) || []; + + return hosts.filter((host) => { + const isOrganizer = host.user.id === originalRescheduledBooking.userId; + const isAttendee = attendeeEmails.includes(host.user.email); + return isOrganizer || isAttendee; + }); } - - const attendeeEmails = originalRescheduledBooking.attendees?.map((attendee) => attendee.email) || []; - - return hosts.filter((host) => { - const isOrganizer = host.user.id === originalRescheduledBooking.userId; - const isAttendee = attendeeEmails.includes(host.user.email); - return isOrganizer || isAttendee; - }); -}; +} diff --git a/packages/lib/bookings/findQualifiedHostsWithDelegationCredentials.ts b/packages/lib/bookings/findQualifiedHostsWithDelegationCredentials.ts index b2c5bab219..8341d71549 100644 --- a/packages/lib/bookings/findQualifiedHostsWithDelegationCredentials.ts +++ b/packages/lib/bookings/findQualifiedHostsWithDelegationCredentials.ts @@ -1,16 +1,24 @@ +import type { FilterHostsService } from "@calcom/lib/bookings/filterHostsBySameRoundRobinHost"; import { findMatchingHostsWithEventSegment, getNormalizedHostsWithDelegationCredentials, } from "@calcom/lib/bookings/getRoutedUsers"; import type { EventType } from "@calcom/lib/bookings/getRoutedUsers"; import { withReporting } from "@calcom/lib/sentryWrapper"; +import type { BookingRepository } from "@calcom/lib/server/repository/booking"; +import type { PrismaClient } from "@calcom/prisma"; import type { SelectedCalendar } from "@calcom/prisma/client"; import type { SchedulingType } from "@calcom/prisma/enums"; import type { CredentialForCalendarService, CredentialPayload } from "@calcom/types/Credential"; import type { RoutingFormResponse } from "../server/getLuckyUser"; import { filterHostsByLeadThreshold } from "./filterHostsByLeadThreshold"; -import { filterHostsBySameRoundRobinHost } from "./filterHostsBySameRoundRobinHost"; + +export interface IQualifiedHostsService { + prisma: PrismaClient; + bookingRepo: BookingRepository; + filterHostsService: FilterHostsService; +} type Host = { isFixed: boolean; @@ -48,6 +56,178 @@ const isFixedHost = (host: T): host is T & { isF return host.isFixed; }; +export class QualifiedHostsService { + constructor(public readonly dependencies: IQualifiedHostsService) {} + + async _findQualifiedHostsWithDelegationCredentials< + T extends { + email: string; + id: number; + credentials: CredentialPayload[]; + userLevelSelectedCalendars: SelectedCalendar[]; + } & Record + >({ + eventType, + rescheduleUid, + routedTeamMemberIds, + contactOwnerEmail, + routingFormResponse, + }: { + eventType: { + id: number; + maxLeadThreshold?: number | null; + hosts?: Host[]; + users: T[]; + schedulingType: SchedulingType | null; + isRRWeightsEnabled: boolean; + rescheduleWithSameRoundRobinHost: boolean; + includeNoShowInRRCalculation: boolean; + } & EventType; + rescheduleUid: string | null; + routedTeamMemberIds: number[]; + contactOwnerEmail: string | null; + routingFormResponse: RoutingFormResponse | null; + }): Promise<{ + qualifiedRRHosts: { + isFixed: boolean; + createdAt: Date | null; + priority?: number | null; + weight?: number | null; + user: Omit & { credentials: CredentialForCalendarService[] }; + }[]; + fixedHosts: { + isFixed: boolean; + createdAt: Date | null; + priority?: number | null; + weight?: number | null; + user: Omit & { credentials: CredentialForCalendarService[] }; + }[]; + // all hosts we want to fallback to including the qualifiedRRHosts (fairness + crm contact owner) + allFallbackRRHosts?: { + isFixed: boolean; + createdAt: Date | null; + priority?: number | null; + weight?: number | null; + user: Omit & { credentials: CredentialForCalendarService[] }; + }[]; + }> { + const { hosts: normalizedHosts, fallbackHosts: fallbackUsers } = + await getNormalizedHostsWithDelegationCredentials({ + eventType, + }); + // not a team event type, or some other reason - segment matching isn't necessary. + if (!normalizedHosts) { + const fixedHosts = fallbackUsers.filter(isFixedHost); + const roundRobinHosts = fallbackUsers.filter(isRoundRobinHost); + return { qualifiedRRHosts: roundRobinHosts, fixedHosts }; + } + + const fixedHosts = normalizedHosts.filter(isFixedHost); + const roundRobinHosts = normalizedHosts.filter(isRoundRobinHost); + + // If it is rerouting, we should not force reschedule with same host. + const hostsAfterRescheduleWithSameRoundRobinHost = applyFilterWithFallback( + roundRobinHosts, + await this.dependencies.filterHostsService.filterHostsBySameRoundRobinHost({ + hosts: roundRobinHosts, + rescheduleUid, + rescheduleWithSameRoundRobinHost: eventType.rescheduleWithSameRoundRobinHost, + routedTeamMemberIds, + }) + ); + + if (hostsAfterRescheduleWithSameRoundRobinHost.length === 1) { + return { + qualifiedRRHosts: hostsAfterRescheduleWithSameRoundRobinHost, + fixedHosts, + }; + } + + const hostsAfterSegmentMatching = applyFilterWithFallback( + hostsAfterRescheduleWithSameRoundRobinHost, + (await findMatchingHostsWithEventSegment({ + eventType, + hosts: hostsAfterRescheduleWithSameRoundRobinHost, + })) as typeof hostsAfterRescheduleWithSameRoundRobinHost + ); + + if (hostsAfterSegmentMatching.length === 1) { + return { + qualifiedRRHosts: hostsAfterSegmentMatching, + fixedHosts, + }; + } + + //if segment matching doesn't return any hosts we fall back to all round robin hosts + const officalRRHosts = hostsAfterSegmentMatching.length + ? hostsAfterSegmentMatching + : hostsAfterRescheduleWithSameRoundRobinHost; + + const hostsAfterContactOwnerMatching = applyFilterWithFallback( + officalRRHosts, + officalRRHosts.filter((host) => host.user.email === contactOwnerEmail) + ); + + const hostsAfterRoutedTeamMemberIdsMatching = applyFilterWithFallback( + officalRRHosts, + officalRRHosts.filter((host) => routedTeamMemberIds.includes(host.user.id)) + ); + + if (hostsAfterRoutedTeamMemberIdsMatching.length === 1) { + if (hostsAfterContactOwnerMatching.length === 1) { + return { + qualifiedRRHosts: hostsAfterContactOwnerMatching, + allFallbackRRHosts: getFallBackWithContactOwner( + hostsAfterRoutedTeamMemberIdsMatching, + hostsAfterContactOwnerMatching[0] + ), + fixedHosts, + }; + } + return { + qualifiedRRHosts: hostsAfterRoutedTeamMemberIdsMatching, + fixedHosts, + }; + } + + const hostsAfterFairnessMatching = applyFilterWithFallback( + hostsAfterRoutedTeamMemberIdsMatching, + await filterHostsByLeadThreshold({ + eventType, + hosts: hostsAfterRoutedTeamMemberIdsMatching, + maxLeadThreshold: eventType.maxLeadThreshold ?? null, + routingFormResponse, + }) + ); + + if (hostsAfterContactOwnerMatching.length === 1) { + return { + qualifiedRRHosts: hostsAfterContactOwnerMatching, + allFallbackRRHosts: getFallBackWithContactOwner( + hostsAfterFairnessMatching, + hostsAfterContactOwnerMatching[0] + ), + fixedHosts, + }; + } + + return { + qualifiedRRHosts: hostsAfterFairnessMatching, + // only if fairness filtering is active + allFallbackRRHosts: + hostsAfterFairnessMatching.length !== hostsAfterRoutedTeamMemberIdsMatching.length + ? hostsAfterRoutedTeamMemberIdsMatching + : undefined, + fixedHosts, + }; + } + + findQualifiedHostsWithDelegationCredentials = withReporting( + this._findQualifiedHostsWithDelegationCredentials.bind(this), + "findQualifiedHostsWithDelegationCredentials" + ); +} + const _findQualifiedHostsWithDelegationCredentials = async < T extends { email: string; @@ -64,154 +244,45 @@ const _findQualifiedHostsWithDelegationCredentials = async < }: { eventType: { id: number; - maxLeadThreshold: number | null; + schedulingType: SchedulingType | null; hosts?: Host[]; users: T[]; - schedulingType: SchedulingType | null; + maxLeadThreshold?: number | null; + team?: { + parentId?: number | null; + rrResetInterval: string | null; + rrTimestampBasis: string | null; + } | null; isRRWeightsEnabled: boolean; rescheduleWithSameRoundRobinHost: boolean; + teamId?: number; includeNoShowInRRCalculation: boolean; } & EventType; rescheduleUid: string | null; routedTeamMemberIds: number[]; contactOwnerEmail: string | null; routingFormResponse: RoutingFormResponse | null; -}): Promise<{ - qualifiedRRHosts: { - isFixed: boolean; - createdAt: Date | null; - priority?: number | null; - weight?: number | null; - groupId?: string | null; - user: Omit & { credentials: CredentialForCalendarService[] }; - }[]; - fixedHosts: { - isFixed: boolean; - createdAt: Date | null; - priority?: number | null; - weight?: number | null; - groupId?: string | null; - user: Omit & { credentials: CredentialForCalendarService[] }; - }[]; - // all hosts we want to fallback to including the qualifiedRRHosts (fairness + crm contact owner) - allFallbackRRHosts?: { - isFixed: boolean; - createdAt: Date | null; - priority?: number | null; - weight?: number | null; - groupId?: string | null; - user: Omit & { credentials: CredentialForCalendarService[] }; - }[]; -}> => { - const { hosts: normalizedHosts, fallbackHosts: fallbackUsers } = - await getNormalizedHostsWithDelegationCredentials({ - eventType, - }); - // not a team event type, or some other reason - segment matching isn't necessary. - if (!normalizedHosts) { - const fixedHosts = fallbackUsers.filter(isFixedHost); - const roundRobinHosts = fallbackUsers.filter(isRoundRobinHost); - return { qualifiedRRHosts: roundRobinHosts, fixedHosts }; - } +}) => { + const { prisma } = await import("@calcom/prisma"); + const { BookingRepository } = await import("@calcom/lib/server/repository/booking"); + const { FilterHostsService } = await import("@calcom/lib/bookings/filterHostsBySameRoundRobinHost"); - const fixedHosts = normalizedHosts.filter(isFixedHost); - const roundRobinHosts = normalizedHosts.filter(isRoundRobinHost); + const bookingRepo = new BookingRepository(prisma); + const filterHostsService = new FilterHostsService({ bookingRepo }); - // If it is rerouting, we should not force reschedule with same host. - const hostsAfterRescheduleWithSameRoundRobinHost = applyFilterWithFallback( - roundRobinHosts, - await filterHostsBySameRoundRobinHost({ - hosts: roundRobinHosts, - rescheduleUid, - rescheduleWithSameRoundRobinHost: eventType.rescheduleWithSameRoundRobinHost, - routedTeamMemberIds, - }) - ); + const service = new QualifiedHostsService({ + prisma, + bookingRepo, + filterHostsService, + }); - if (hostsAfterRescheduleWithSameRoundRobinHost.length === 1) { - return { - qualifiedRRHosts: hostsAfterRescheduleWithSameRoundRobinHost, - fixedHosts, - }; - } - - const hostsAfterSegmentMatching = applyFilterWithFallback( - hostsAfterRescheduleWithSameRoundRobinHost, - (await findMatchingHostsWithEventSegment({ - eventType, - hosts: hostsAfterRescheduleWithSameRoundRobinHost, - })) as typeof hostsAfterRescheduleWithSameRoundRobinHost - ); - - if (hostsAfterSegmentMatching.length === 1) { - return { - qualifiedRRHosts: hostsAfterSegmentMatching, - fixedHosts, - }; - } - - //if segment matching doesn't return any hosts we fall back to all round robin hosts - const officalRRHosts = hostsAfterSegmentMatching.length - ? hostsAfterSegmentMatching - : hostsAfterRescheduleWithSameRoundRobinHost; - - const hostsAfterContactOwnerMatching = applyFilterWithFallback( - officalRRHosts, - officalRRHosts.filter((host) => host.user.email === contactOwnerEmail) - ); - - const hostsAfterRoutedTeamMemberIdsMatching = applyFilterWithFallback( - officalRRHosts, - officalRRHosts.filter((host) => routedTeamMemberIds.includes(host.user.id)) - ); - - if (hostsAfterRoutedTeamMemberIdsMatching.length === 1) { - if (hostsAfterContactOwnerMatching.length === 1) { - return { - qualifiedRRHosts: hostsAfterContactOwnerMatching, - allFallbackRRHosts: getFallBackWithContactOwner( - hostsAfterRoutedTeamMemberIdsMatching, - hostsAfterContactOwnerMatching[0] - ), - fixedHosts, - }; - } - return { - qualifiedRRHosts: hostsAfterRoutedTeamMemberIdsMatching, - fixedHosts, - }; - } - - const hostsAfterFairnessMatching = applyFilterWithFallback( - hostsAfterRoutedTeamMemberIdsMatching, - await filterHostsByLeadThreshold({ - eventType, - hosts: hostsAfterRoutedTeamMemberIdsMatching, - maxLeadThreshold: eventType.maxLeadThreshold, - routingFormResponse, - }) - ); - - if (hostsAfterContactOwnerMatching.length === 1) { - return { - qualifiedRRHosts: hostsAfterContactOwnerMatching, - allFallbackRRHosts: getFallBackWithContactOwner( - hostsAfterFairnessMatching, - hostsAfterContactOwnerMatching[0] - ), - fixedHosts, - }; - } - - return { - qualifiedRRHosts: hostsAfterFairnessMatching, - // only if fairness filtering is active - allFallbackRRHosts: - hostsAfterFairnessMatching.length !== hostsAfterRoutedTeamMemberIdsMatching.length - ? hostsAfterRoutedTeamMemberIdsMatching - : undefined, - fixedHosts, - }; + return service.findQualifiedHostsWithDelegationCredentials({ + eventType, + rescheduleUid, + routedTeamMemberIds, + contactOwnerEmail, + routingFormResponse, + }); }; export const findQualifiedHostsWithDelegationCredentials = withReporting( diff --git a/packages/lib/di/containers/available-slots.ts b/packages/lib/di/containers/AvailableSlots.ts similarity index 68% rename from packages/lib/di/containers/available-slots.ts rename to packages/lib/di/containers/AvailableSlots.ts index 8ebdba18e6..e414cded5a 100644 --- a/packages/lib/di/containers/available-slots.ts +++ b/packages/lib/di/containers/AvailableSlots.ts @@ -5,20 +5,20 @@ import { DI_TOKENS } from "@calcom/lib/di/tokens"; import { prismaModule } from "@calcom/prisma/prisma.module"; import type { AvailableSlotsService } from "@calcom/trpc/server/routers/viewer/slots/util"; -import { availableSlotsModule } from "../modules/available-slots"; -import { bookingRepositoryModule } from "../modules/booking"; -import { busyTimesModule } from "../modules/busy-times"; -import { cacheModule } from "../modules/cache"; -import { checkBookingLimitsModule } from "../modules/check-booking-limits"; -import { eventTypeRepositoryModule } from "../modules/eventType"; -import { featuresRepositoryModule } from "../modules/features"; -import { getUserAvailabilityModule } from "../modules/get-user-availability"; -import { oooRepositoryModule } from "../modules/ooo"; -import { routingFormResponseRepositoryModule } from "../modules/routingFormResponse"; -import { scheduleRepositoryModule } from "../modules/schedule"; -import { selectedSlotsRepositoryModule } from "../modules/selectedSlots"; -import { teamRepositoryModule } from "../modules/team"; -import { userRepositoryModule } from "../modules/user"; +import { availableSlotsModule } from "../modules/AvailableSlots"; +import { bookingRepositoryModule } from "../modules/Booking"; +import { busyTimesModule } from "../modules/BusyTimes"; +import { cacheModule } from "../modules/Cache"; +import { checkBookingLimitsModule } from "../modules/CheckBookingLimits"; +import { eventTypeRepositoryModule } from "../modules/EventType"; +import { featuresRepositoryModule } from "../modules/Features"; +import { getUserAvailabilityModule } from "../modules/GetUserAvailability"; +import { oooRepositoryModule } from "../modules/Ooo"; +import { routingFormResponseRepositoryModule } from "../modules/RoutingFormResponse"; +import { scheduleRepositoryModule } from "../modules/Schedule"; +import { selectedSlotsRepositoryModule } from "../modules/SelectedSlots"; +import { teamRepositoryModule } from "../modules/Team"; +import { userRepositoryModule } from "../modules/User"; const container = createContainer(); container.load(DI_TOKENS.REDIS_CLIENT, redisModule); diff --git a/packages/lib/di/containers/booking-limits.ts b/packages/lib/di/containers/BookingLimits.ts similarity index 87% rename from packages/lib/di/containers/booking-limits.ts rename to packages/lib/di/containers/BookingLimits.ts index 3f22003c59..aabedde8b5 100644 --- a/packages/lib/di/containers/booking-limits.ts +++ b/packages/lib/di/containers/BookingLimits.ts @@ -5,9 +5,9 @@ import { prismaModule } from "@calcom/prisma/prisma.module"; import type { CheckBookingAndDurationLimitsService } from "../../../features/bookings/lib/handleNewBooking/checkBookingAndDurationLimits"; import type { CheckBookingLimitsService } from "../../intervalLimits/server/checkBookingLimits"; -import { bookingRepositoryModule } from "../modules/booking"; -import { checkBookingAndDurationLimitsModule } from "../modules/check-booking-and-duration-limits"; -import { checkBookingLimitsModule } from "../modules/check-booking-limits"; +import { bookingRepositoryModule } from "../modules/Booking"; +import { checkBookingAndDurationLimitsModule } from "../modules/CheckBookingAndDurationLimits"; +import { checkBookingLimitsModule } from "../modules/CheckBookingLimits"; const container = createContainer(); container.load(DI_TOKENS.PRISMA_MODULE, prismaModule); diff --git a/packages/lib/di/containers/busy-times.ts b/packages/lib/di/containers/BusyTimes.ts similarity index 83% rename from packages/lib/di/containers/busy-times.ts rename to packages/lib/di/containers/BusyTimes.ts index 11b659d5a9..c8a83300c1 100644 --- a/packages/lib/di/containers/busy-times.ts +++ b/packages/lib/di/containers/BusyTimes.ts @@ -4,8 +4,8 @@ import { DI_TOKENS } from "@calcom/lib/di/tokens"; import { prismaModule } from "@calcom/prisma/prisma.module"; import type { BusyTimesService } from "../../getBusyTimes"; -import { bookingRepositoryModule } from "../modules/booking"; -import { busyTimesModule } from "../modules/busy-times"; +import { bookingRepositoryModule } from "../modules/Booking"; +import { busyTimesModule } from "../modules/BusyTimes"; const container = createContainer(); container.load(DI_TOKENS.PRISMA_MODULE, prismaModule); diff --git a/packages/lib/di/containers/cache.ts b/packages/lib/di/containers/Cache.ts similarity index 84% rename from packages/lib/di/containers/cache.ts rename to packages/lib/di/containers/Cache.ts index 958102de4a..002255a900 100644 --- a/packages/lib/di/containers/cache.ts +++ b/packages/lib/di/containers/Cache.ts @@ -4,8 +4,8 @@ import { DI_TOKENS } from "@calcom/lib/di/tokens"; import { prismaModule } from "@calcom/prisma/prisma.module"; import type { CacheService } from "../../../features/calendar-cache/lib/getShouldServeCache"; -import { cacheModule } from "../modules/cache"; -import { featuresRepositoryModule } from "../modules/features"; +import { cacheModule } from "../modules/Cache"; +import { featuresRepositoryModule } from "../modules/Features"; const container = createContainer(); container.load(DI_TOKENS.PRISMA_MODULE, prismaModule); diff --git a/packages/lib/di/containers/FilterHosts.ts b/packages/lib/di/containers/FilterHosts.ts new file mode 100644 index 0000000000..c83de8841d --- /dev/null +++ b/packages/lib/di/containers/FilterHosts.ts @@ -0,0 +1,17 @@ +import { createContainer } from "@evyweb/ioctopus"; + +import { DI_TOKENS } from "@calcom/lib/di/tokens"; +import { prismaModule } from "@calcom/prisma/prisma.module"; + +import type { FilterHostsService } from "../../bookings/filterHostsBySameRoundRobinHost"; +import { bookingRepositoryModule } from "../modules/Booking"; +import { filterHostsModule } from "../modules/FilterHosts"; + +const container = createContainer(); +container.load(DI_TOKENS.PRISMA_MODULE, prismaModule); +container.load(DI_TOKENS.BOOKING_REPOSITORY_MODULE, bookingRepositoryModule); +container.load(DI_TOKENS.FILTER_HOSTS_SERVICE_MODULE, filterHostsModule); + +export function getFilterHostsService() { + return container.get(DI_TOKENS.FILTER_HOSTS_SERVICE); +} diff --git a/packages/lib/di/containers/get-user-availability.ts b/packages/lib/di/containers/GetUserAvailability.ts similarity index 75% rename from packages/lib/di/containers/get-user-availability.ts rename to packages/lib/di/containers/GetUserAvailability.ts index 29d1de1852..e30143e4be 100644 --- a/packages/lib/di/containers/get-user-availability.ts +++ b/packages/lib/di/containers/GetUserAvailability.ts @@ -5,11 +5,11 @@ import { DI_TOKENS } from "@calcom/lib/di/tokens"; import { prismaModule } from "@calcom/prisma/prisma.module"; import type { UserAvailabilityService } from "../../getUserAvailability"; -import { bookingRepositoryModule } from "../modules/booking"; -import { busyTimesModule } from "../modules/busy-times"; -import { eventTypeRepositoryModule } from "../modules/eventType"; -import { getUserAvailabilityModule } from "../modules/get-user-availability"; -import { oooRepositoryModule } from "../modules/ooo"; +import { bookingRepositoryModule } from "../modules/Booking"; +import { busyTimesModule } from "../modules/BusyTimes"; +import { eventTypeRepositoryModule } from "../modules/EventType"; +import { getUserAvailabilityModule } from "../modules/GetUserAvailability"; +import { oooRepositoryModule } from "../modules/Ooo"; const container = createContainer(); container.load(DI_TOKENS.PRISMA_MODULE, prismaModule); diff --git a/packages/lib/di/containers/insights-booking.ts b/packages/lib/di/containers/InsightsBooking.ts similarity index 93% rename from packages/lib/di/containers/insights-booking.ts rename to packages/lib/di/containers/InsightsBooking.ts index 20b9e640ff..a5fe712d51 100644 --- a/packages/lib/di/containers/insights-booking.ts +++ b/packages/lib/di/containers/InsightsBooking.ts @@ -9,7 +9,7 @@ import type { import type { InsightsBookingService } from "@calcom/lib/server/service/InsightsBookingDIService"; import { prismaModule } from "@calcom/prisma/prisma.module"; -import { insightsBookingModule } from "../modules/insights-booking"; +import { insightsBookingModule } from "../modules/InsightsBooking"; export function getInsightsBookingService({ options, diff --git a/packages/lib/di/containers/insights-routing.ts b/packages/lib/di/containers/InsightsRouting.ts similarity index 93% rename from packages/lib/di/containers/insights-routing.ts rename to packages/lib/di/containers/InsightsRouting.ts index a2e6c18ea8..f8c0b59c79 100644 --- a/packages/lib/di/containers/insights-routing.ts +++ b/packages/lib/di/containers/InsightsRouting.ts @@ -9,7 +9,7 @@ import type { import type { InsightsRoutingService } from "@calcom/lib/server/service/insightsRoutingDI"; import { prismaModule } from "@calcom/prisma/prisma.module"; -import { insightsRoutingModule } from "../modules/insights-routing"; +import { insightsRoutingModule } from "../modules/InsightsRouting"; export function getInsightsRoutingService({ options, diff --git a/packages/lib/di/containers/QualifiedHosts.ts b/packages/lib/di/containers/QualifiedHosts.ts new file mode 100644 index 0000000000..0ac3aabc98 --- /dev/null +++ b/packages/lib/di/containers/QualifiedHosts.ts @@ -0,0 +1,19 @@ +import { createContainer } from "@evyweb/ioctopus"; + +import { DI_TOKENS } from "@calcom/lib/di/tokens"; +import { prismaModule } from "@calcom/prisma/prisma.module"; + +import type { QualifiedHostsService } from "../../bookings/findQualifiedHostsWithDelegationCredentials"; +import { bookingRepositoryModule } from "../modules/Booking"; +import { filterHostsModule } from "../modules/FilterHosts"; +import { qualifiedHostsModule } from "../modules/QualifiedHosts"; + +const container = createContainer(); +container.load(DI_TOKENS.PRISMA_MODULE, prismaModule); +container.load(DI_TOKENS.BOOKING_REPOSITORY_MODULE, bookingRepositoryModule); +container.load(DI_TOKENS.FILTER_HOSTS_SERVICE_MODULE, filterHostsModule); +container.load(DI_TOKENS.QUALIFIED_HOSTS_SERVICE_MODULE, qualifiedHostsModule); + +export function getQualifiedHostsService() { + return container.get(DI_TOKENS.QUALIFIED_HOSTS_SERVICE); +} diff --git a/packages/lib/di/modules/available-slots.ts b/packages/lib/di/modules/AvailableSlots.ts similarity index 100% rename from packages/lib/di/modules/available-slots.ts rename to packages/lib/di/modules/AvailableSlots.ts diff --git a/packages/lib/di/modules/booking.ts b/packages/lib/di/modules/Booking.ts similarity index 100% rename from packages/lib/di/modules/booking.ts rename to packages/lib/di/modules/Booking.ts diff --git a/packages/lib/di/modules/busy-times.ts b/packages/lib/di/modules/BusyTimes.ts similarity index 100% rename from packages/lib/di/modules/busy-times.ts rename to packages/lib/di/modules/BusyTimes.ts diff --git a/packages/lib/di/modules/cache.ts b/packages/lib/di/modules/Cache.ts similarity index 100% rename from packages/lib/di/modules/cache.ts rename to packages/lib/di/modules/Cache.ts diff --git a/packages/lib/di/modules/check-booking-and-duration-limits.ts b/packages/lib/di/modules/CheckBookingAndDurationLimits.ts similarity index 100% rename from packages/lib/di/modules/check-booking-and-duration-limits.ts rename to packages/lib/di/modules/CheckBookingAndDurationLimits.ts diff --git a/packages/lib/di/modules/check-booking-limits.ts b/packages/lib/di/modules/CheckBookingLimits.ts similarity index 100% rename from packages/lib/di/modules/check-booking-limits.ts rename to packages/lib/di/modules/CheckBookingLimits.ts diff --git a/packages/lib/di/modules/eventType.ts b/packages/lib/di/modules/EventType.ts similarity index 100% rename from packages/lib/di/modules/eventType.ts rename to packages/lib/di/modules/EventType.ts diff --git a/packages/lib/di/modules/features.ts b/packages/lib/di/modules/Features.ts similarity index 100% rename from packages/lib/di/modules/features.ts rename to packages/lib/di/modules/Features.ts diff --git a/packages/lib/di/modules/FilterHosts.ts b/packages/lib/di/modules/FilterHosts.ts new file mode 100644 index 0000000000..3a17e33e7b --- /dev/null +++ b/packages/lib/di/modules/FilterHosts.ts @@ -0,0 +1,10 @@ +import { createModule } from "@evyweb/ioctopus"; + +import type { IFilterHostsService } from "../../bookings/filterHostsBySameRoundRobinHost"; +import { FilterHostsService } from "../../bookings/filterHostsBySameRoundRobinHost"; +import { DI_TOKENS } from "../tokens"; + +export const filterHostsModule = createModule(); +filterHostsModule.bind(DI_TOKENS.FILTER_HOSTS_SERVICE).toClass(FilterHostsService, { + bookingRepo: DI_TOKENS.BOOKING_REPOSITORY, +} satisfies Record); diff --git a/packages/lib/di/modules/get-user-availability.ts b/packages/lib/di/modules/GetUserAvailability.ts similarity index 100% rename from packages/lib/di/modules/get-user-availability.ts rename to packages/lib/di/modules/GetUserAvailability.ts diff --git a/packages/lib/di/modules/insights-booking.ts b/packages/lib/di/modules/InsightsBooking.ts similarity index 100% rename from packages/lib/di/modules/insights-booking.ts rename to packages/lib/di/modules/InsightsBooking.ts diff --git a/packages/lib/di/modules/insights-routing.ts b/packages/lib/di/modules/InsightsRouting.ts similarity index 100% rename from packages/lib/di/modules/insights-routing.ts rename to packages/lib/di/modules/InsightsRouting.ts diff --git a/packages/lib/di/modules/ooo.ts b/packages/lib/di/modules/Ooo.ts similarity index 100% rename from packages/lib/di/modules/ooo.ts rename to packages/lib/di/modules/Ooo.ts diff --git a/packages/lib/di/modules/QualifiedHosts.ts b/packages/lib/di/modules/QualifiedHosts.ts new file mode 100644 index 0000000000..fcd703c60f --- /dev/null +++ b/packages/lib/di/modules/QualifiedHosts.ts @@ -0,0 +1,12 @@ +import { createModule } from "@evyweb/ioctopus"; + +import type { IQualifiedHostsService } from "../../bookings/findQualifiedHostsWithDelegationCredentials"; +import { QualifiedHostsService } from "../../bookings/findQualifiedHostsWithDelegationCredentials"; +import { DI_TOKENS } from "../tokens"; + +export const qualifiedHostsModule = createModule(); +qualifiedHostsModule.bind(DI_TOKENS.QUALIFIED_HOSTS_SERVICE).toClass(QualifiedHostsService, { + prisma: DI_TOKENS.PRISMA_CLIENT, + bookingRepo: DI_TOKENS.BOOKING_REPOSITORY, + filterHostsService: DI_TOKENS.FILTER_HOSTS_SERVICE, +} satisfies Record); diff --git a/packages/lib/di/modules/routingFormResponse.ts b/packages/lib/di/modules/RoutingFormResponse.ts similarity index 100% rename from packages/lib/di/modules/routingFormResponse.ts rename to packages/lib/di/modules/RoutingFormResponse.ts diff --git a/packages/lib/di/modules/schedule.ts b/packages/lib/di/modules/Schedule.ts similarity index 100% rename from packages/lib/di/modules/schedule.ts rename to packages/lib/di/modules/Schedule.ts diff --git a/packages/lib/di/modules/selectedSlots.ts b/packages/lib/di/modules/SelectedSlots.ts similarity index 100% rename from packages/lib/di/modules/selectedSlots.ts rename to packages/lib/di/modules/SelectedSlots.ts diff --git a/packages/lib/di/modules/team.ts b/packages/lib/di/modules/Team.ts similarity index 100% rename from packages/lib/di/modules/team.ts rename to packages/lib/di/modules/Team.ts diff --git a/packages/lib/di/modules/user.ts b/packages/lib/di/modules/User.ts similarity index 100% rename from packages/lib/di/modules/user.ts rename to packages/lib/di/modules/User.ts diff --git a/packages/lib/di/tokens.ts b/packages/lib/di/tokens.ts index 2dc40117cc..944ca851ef 100644 --- a/packages/lib/di/tokens.ts +++ b/packages/lib/di/tokens.ts @@ -37,4 +37,8 @@ export const DI_TOKENS = { GET_USER_AVAILABILITY_SERVICE_MODULE: Symbol("GetUserAvailabilityModule"), BUSY_TIMES_SERVICE: Symbol("BusyTimesService"), BUSY_TIMES_SERVICE_MODULE: Symbol("BusyTimesServiceModule"), + QUALIFIED_HOSTS_SERVICE: Symbol("QualifiedHostsService"), + QUALIFIED_HOSTS_SERVICE_MODULE: Symbol("QualifiedHostsServiceModule"), + FILTER_HOSTS_SERVICE: Symbol("FilterHostsService"), + FILTER_HOSTS_SERVICE_MODULE: Symbol("FilterHostsServiceModule"), }; diff --git a/packages/lib/getBusyTimes.test.ts b/packages/lib/getBusyTimes.test.ts index bdb56290dd..9ff355e4d9 100644 --- a/packages/lib/getBusyTimes.test.ts +++ b/packages/lib/getBusyTimes.test.ts @@ -2,7 +2,7 @@ import { describe, expect, it } from "vitest"; import dayjs from "@calcom/dayjs"; -import { getBusyTimesService } from "./di/containers/busy-times"; +import { getBusyTimesService } from "./di/containers/BusyTimes"; const startOfTomorrow = dayjs().add(1, "day").startOf("day"); const tomorrowDate = startOfTomorrow.format("YYYY-MM-DD"); diff --git a/packages/lib/getUserAvailability.ts b/packages/lib/getUserAvailability.ts index 25335eeb4b..b850bef376 100644 --- a/packages/lib/getUserAvailability.ts +++ b/packages/lib/getUserAvailability.ts @@ -36,7 +36,7 @@ import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils"; import type { EventBusyDetails, IntervalLimitUnit } from "@calcom/types/Calendar"; import type { TimeRange } from "@calcom/types/schedule"; -import { getBusyTimesService } from "./di/containers/busy-times"; +import { getBusyTimesService } from "./di/containers/BusyTimes"; import { getPeriodStartDatesBetween as getPeriodStartDatesBetweenUtil } from "./intervalLimits/utils/getPeriodStartDatesBetween"; import { withReporting } from "./sentryWrapper"; diff --git a/packages/lib/intervalLimits/server/getBusyTimesFromLimits.ts b/packages/lib/intervalLimits/server/getBusyTimesFromLimits.ts index 6f385e71db..e45cd6918c 100644 --- a/packages/lib/intervalLimits/server/getBusyTimesFromLimits.ts +++ b/packages/lib/intervalLimits/server/getBusyTimesFromLimits.ts @@ -1,7 +1,7 @@ import type { Dayjs } from "@calcom/dayjs"; import dayjs from "@calcom/dayjs"; -import { getCheckBookingLimitsService } from "@calcom/lib/di/containers/booking-limits"; -import { getBusyTimesService } from "@calcom/lib/di/containers/busy-times"; +import { getCheckBookingLimitsService } from "@calcom/lib/di/containers/BookingLimits"; +import { getBusyTimesService } from "@calcom/lib/di/containers/BusyTimes"; import type { EventType } from "@calcom/lib/getUserAvailability"; import { getPeriodStartDatesBetween } from "@calcom/lib/intervalLimits/utils/getPeriodStartDatesBetween"; import { withReporting } from "@calcom/lib/sentryWrapper"; diff --git a/packages/lib/server/repository/booking.ts b/packages/lib/server/repository/booking.ts index 9be11147ad..07ce1f53d7 100644 --- a/packages/lib/server/repository/booking.ts +++ b/packages/lib/server/repository/booking.ts @@ -935,4 +935,23 @@ export class BookingRepository { } return totalBookingTime.totalMinutes ?? 0; } + + async findOriginalRescheduledBookingUserId({ rescheduleUid }: { rescheduleUid: string }) { + return await this.prismaClient.booking.findFirst({ + where: { + uid: rescheduleUid, + status: { + in: [BookingStatus.ACCEPTED, BookingStatus.CANCELLED, BookingStatus.PENDING], + }, + }, + select: { + userId: true, + attendees: { + select: { + email: true, + }, + }, + }, + }); + } } diff --git a/packages/trpc/server/routers/viewer/availability/user.handler.ts b/packages/trpc/server/routers/viewer/availability/user.handler.ts index 16358c0d9d..f89e80b075 100644 --- a/packages/trpc/server/routers/viewer/availability/user.handler.ts +++ b/packages/trpc/server/routers/viewer/availability/user.handler.ts @@ -1,4 +1,4 @@ -import { getUserAvailabilityService } from "@calcom/lib/di/containers/get-user-availability"; +import { getUserAvailabilityService } from "@calcom/lib/di/containers/GetUserAvailability"; import type { TrpcSessionUser } from "../../../types"; import type { TUserInputSchema } from "./user.schema"; @@ -11,7 +11,7 @@ type UserOptions = { }; export const userHandler = async ({ input }: UserOptions) => { - const userAvailabilityService = getUserAvailabilityService() + const userAvailabilityService = getUserAvailabilityService(); return userAvailabilityService.getUserAvailability( { returnDateOverrides: true, bypassBusyCalendarTimes: false, ...input }, undefined diff --git a/packages/trpc/server/routers/viewer/highPerf/getTeamSchedule.handler.ts b/packages/trpc/server/routers/viewer/highPerf/getTeamSchedule.handler.ts index 755523ff5d..37013eb4cb 100644 --- a/packages/trpc/server/routers/viewer/highPerf/getTeamSchedule.handler.ts +++ b/packages/trpc/server/routers/viewer/highPerf/getTeamSchedule.handler.ts @@ -1,6 +1,6 @@ import type { IncomingMessage } from "http"; -import { getAvailableSlotsService } from "@calcom/lib/di/containers/available-slots"; +import { getAvailableSlotsService } from "@calcom/lib/di/containers/AvailableSlots"; import type { TGetTeamScheduleInputSchema } from "./getTeamSchedule.schema"; diff --git a/packages/trpc/server/routers/viewer/slots/getSchedule.handler.ts b/packages/trpc/server/routers/viewer/slots/getSchedule.handler.ts index e2285bd7e7..b05728c9d0 100644 --- a/packages/trpc/server/routers/viewer/slots/getSchedule.handler.ts +++ b/packages/trpc/server/routers/viewer/slots/getSchedule.handler.ts @@ -1,4 +1,4 @@ -import { getAvailableSlotsService } from "@calcom/lib/di/containers/available-slots"; +import { getAvailableSlotsService } from "@calcom/lib/di/containers/AvailableSlots"; import type { GetScheduleOptions } from "./types"; diff --git a/packages/trpc/server/routers/viewer/slots/util.ts b/packages/trpc/server/routers/viewer/slots/util.ts index 5fb155c03b..8fa2cc0fa0 100644 --- a/packages/trpc/server/routers/viewer/slots/util.ts +++ b/packages/trpc/server/routers/viewer/slots/util.ts @@ -9,13 +9,13 @@ import { checkForConflicts } from "@calcom/features/bookings/lib/conflictChecker import { isEventTypeLoggingEnabled } from "@calcom/features/bookings/lib/isEventTypeLoggingEnabled"; import type { CacheService } from "@calcom/features/calendar-cache/lib/getShouldServeCache"; import type { IRedisService } from "@calcom/features/redis/IRedisService"; -import { findQualifiedHostsWithDelegationCredentials } from "@calcom/lib/bookings/findQualifiedHostsWithDelegationCredentials"; import { shouldIgnoreContactOwner } from "@calcom/lib/bookings/routing/utils"; import { RESERVED_SUBDOMAINS } from "@calcom/lib/constants"; import { buildDateRanges } from "@calcom/lib/date-ranges"; import { getUTCOffsetByTimezone } from "@calcom/lib/dayjs"; import { getDefaultEvent } from "@calcom/lib/defaultEvents"; -import type { getBusyTimesService } from "@calcom/lib/di/containers/busy-times"; +import type { getBusyTimesService } from "@calcom/lib/di/containers/BusyTimes"; +import { getQualifiedHostsService } from "@calcom/lib/di/containers/QualifiedHosts"; import { getAggregatedAvailability } from "@calcom/lib/getAggregatedAvailability"; import type { BusyTimesService } from "@calcom/lib/getBusyTimes"; import type { @@ -1027,9 +1027,9 @@ export class AvailableSlotsService { }); } - // TODO: DI findQualifiedHostsWithDelegationCredentials + const qualifiedHostsService = getQualifiedHostsService(); const { qualifiedRRHosts, allFallbackRRHosts, fixedHosts } = - await findQualifiedHostsWithDelegationCredentials({ + await qualifiedHostsService.findQualifiedHostsWithDelegationCredentials({ eventType, rescheduleUid: input.rescheduleUid ?? null, routedTeamMemberIds, diff --git a/packages/trpc/server/routers/viewer/teams/getMemberAvailability.handler.ts b/packages/trpc/server/routers/viewer/teams/getMemberAvailability.handler.ts index db6994b81f..45472dbe2e 100644 --- a/packages/trpc/server/routers/viewer/teams/getMemberAvailability.handler.ts +++ b/packages/trpc/server/routers/viewer/teams/getMemberAvailability.handler.ts @@ -1,5 +1,5 @@ import { enrichUserWithDelegationCredentialsIncludeServiceAccountKey } from "@calcom/lib/delegationCredential/server"; -import { getUserAvailabilityService } from "@calcom/lib/di/containers/get-user-availability"; +import { getUserAvailabilityService } from "@calcom/lib/di/containers/GetUserAvailability"; import { isTeamMember } from "@calcom/lib/server/queries/teams"; import { MembershipRepository } from "@calcom/lib/server/repository/membership"; import type { TrpcSessionUser } from "@calcom/trpc/server/types"; @@ -16,7 +16,7 @@ type GetMemberAvailabilityOptions = { }; export const getMemberAvailabilityHandler = async ({ ctx, input }: GetMemberAvailabilityOptions) => { - const userAvailabilityService = getUserAvailabilityService() + const userAvailabilityService = getUserAvailabilityService(); const team = await isTeamMember(ctx.user?.id, input.teamId); if (!team) throw new TRPCError({ code: "UNAUTHORIZED" }); diff --git a/yarn.lock b/yarn.lock index 700323729b..e1a942af6a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2500,7 +2500,7 @@ __metadata: "@axiomhq/winston": ^1.2.0 "@calcom/platform-constants": "*" "@calcom/platform-enums": "*" - "@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.294" + "@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.298" "@calcom/platform-types": "*" "@calcom/platform-utils": "*" "@calcom/prisma": "*" @@ -3558,13 +3558,13 @@ __metadata: languageName: unknown linkType: soft -"@calcom/platform-libraries@npm:@calcom/platform-libraries@0.0.294": - version: 0.0.294 - resolution: "@calcom/platform-libraries@npm:0.0.294" +"@calcom/platform-libraries@npm:@calcom/platform-libraries@0.0.298": + version: 0.0.298 + resolution: "@calcom/platform-libraries@npm:0.0.298" dependencies: "@calcom/features": "*" "@calcom/lib": "*" - checksum: ce7beda30e629e87d560386bf1f117280ec3466ba7dfa2089de79c2d03c46d24ee3a5388a63974ba084b6624e863c2feb5e02f42ef08c72daefc7c51e771d5f8 + checksum: 63401c90d15ce05a17905f68eb36e655c6368b430ee8c28df371c2b91d5033cab846e96406e28d2c716e8e361eefaf96caf2c3a26fa63bc239ec97632b0b7613 languageName: node linkType: hard