From 8b581ae8b62463228934ec9eabce5f64c44d89c3 Mon Sep 17 00:00:00 2001 From: Jagjeevan Kashid Date: Tue, 17 Jun 2025 13:42:52 +0530 Subject: [PATCH 01/11] feat: Refactor RR logic to exclude the Salesforce logic Signed-off-by: Jagjeevan Kashid --- .../assignment/EventTeamAssignmentTab.tsx | 13 +++ packages/features/eventtypes/lib/types.ts | 1 + packages/lib/server/getLuckyUser.test.ts | 100 ++++++++++++++++++ packages/lib/server/getLuckyUser.ts | 7 ++ packages/lib/server/repository/booking.ts | 18 +++- .../event-types/hooks/useEventTypeForm.ts | 1 + packages/prisma/schema.prisma | 1 + 7 files changed, 140 insertions(+), 1 deletion(-) diff --git a/packages/features/eventtypes/components/tabs/assignment/EventTeamAssignmentTab.tsx b/packages/features/eventtypes/components/tabs/assignment/EventTeamAssignmentTab.tsx index 88b595443998d1..9da35a0f8c5f92 100644 --- a/packages/features/eventtypes/components/tabs/assignment/EventTeamAssignmentTab.tsx +++ b/packages/features/eventtypes/components/tabs/assignment/EventTeamAssignmentTab.tsx @@ -367,6 +367,19 @@ const RoundRobinHosts = ({ )} /> + + name="excludeSalesforceBookingsFromRR" + render={({ field: { value: excludeSalesforceBookingsFromRR, onChange } }) => ( + { + onChange(active); + }} + /> + )} + /> { }) ); }); + + it("should exclude Salesforce bookings from round robin when excludeSalesforceBookingsFromRR is true", async () => { + const users: GetLuckyUserAvailableUsersType = [ + buildUser({ + id: 1, + username: "test1", + name: "Test User 1", + email: "test1@example.com", + bookings: [ + { + createdAt: new Date("2022-01-25T05:30:00.000Z"), + }, + ], + }), + buildUser({ + id: 2, + username: "test2", + name: "Test User 2", + email: "test2@example.com", + bookings: [ + { + createdAt: new Date("2022-01-25T04:30:00.000Z"), + }, + ], + }), + ]; + + CalendarManagerMock.getBusyCalendarTimes.mockResolvedValue([]); + prismaMock.outOfOfficeEntry.findMany.mockResolvedValue([]); + prismaMock.user.findMany.mockResolvedValue(users); + prismaMock.host.findMany.mockResolvedValue([]); + prismaMock.booking.findMany.mockResolvedValue([]); + + await getLuckyUser({ + availableUsers: users, + eventType: { + id: 1, + isRRWeightsEnabled: false, + excludeSalesforceBookingsFromRR: true, + team: { rrResetInterval: RRResetInterval.MONTH }, + }, + allRRHosts: [], + routingFormResponse: null, + }); + + const queryArgs = prismaMock.booking.findMany.mock.calls[0][0]; + + // Verify that the query excludes Salesforce assignments + expect(queryArgs.where?.NOT?.assignmentReason?.some?.reasonEnum).toEqual("SALESFORCE_ASSIGNMENT"); + }); + + it("should include Salesforce bookings in round robin when excludeSalesforceBookingsFromRR is false", async () => { + const users: GetLuckyUserAvailableUsersType = [ + buildUser({ + id: 1, + username: "test1", + name: "Test User 1", + email: "test1@example.com", + bookings: [ + { + createdAt: new Date("2022-01-25T05:30:00.000Z"), + }, + ], + }), + buildUser({ + id: 2, + username: "test2", + name: "Test User 2", + email: "test2@example.com", + bookings: [ + { + createdAt: new Date("2022-01-25T04:30:00.000Z"), + }, + ], + }), + ]; + + CalendarManagerMock.getBusyCalendarTimes.mockResolvedValue([]); + prismaMock.outOfOfficeEntry.findMany.mockResolvedValue([]); + prismaMock.user.findMany.mockResolvedValue(users); + prismaMock.host.findMany.mockResolvedValue([]); + prismaMock.booking.findMany.mockResolvedValue([]); + + await getLuckyUser({ + availableUsers: users, + eventType: { + id: 1, + isRRWeightsEnabled: false, + excludeSalesforceBookingsFromRR: false, + team: { rrResetInterval: RRResetInterval.MONTH }, + }, + allRRHosts: [], + routingFormResponse: null, + }); + + const queryArgs = prismaMock.booking.findMany.mock.calls[0][0]; + + // Verify that the query does NOT exclude Salesforce assignments + expect(queryArgs.where?.NOT?.assignmentReason?.some?.reasonEnum).toBeUndefined(); + }); }); diff --git a/packages/lib/server/getLuckyUser.ts b/packages/lib/server/getLuckyUser.ts index 2cd350e30ab046..d554ccdeb251ff 100644 --- a/packages/lib/server/getLuckyUser.ts +++ b/packages/lib/server/getLuckyUser.ts @@ -68,6 +68,7 @@ interface GetLuckyUserParams { isRRWeightsEnabled: boolean; team: { parentId?: number | null; rrResetInterval: RRResetInterval | null } | null; includeNoShowInRRCalculation: boolean; + excludeSalesforceBookingsFromRR?: boolean; }; // all routedTeamMemberIds or all hosts of event types allRRHosts: { @@ -425,12 +426,14 @@ async function getBookingsOfInterval({ virtualQueuesData, interval, includeNoShowInRRCalculation, + excludeSalesforceBookingsFromRR = false, }: { eventTypeId: number; users: { id: number; email: string }[]; virtualQueuesData: VirtualQueuesDataType | null; interval: RRResetInterval; includeNoShowInRRCalculation: boolean; + excludeSalesforceBookingsFromRR?: boolean; }) { return await BookingRepository.getAllBookingsForRoundRobin({ eventTypeId: eventTypeId, @@ -439,6 +442,7 @@ async function getBookingsOfInterval({ endDate: new Date(), virtualQueuesData, includeNoShowInRRCalculation, + excludeSalesforceBookingsFromRR, }); } @@ -616,6 +620,7 @@ async function fetchAllDataNeededForCalculations< virtualQueuesData: virtualQueuesData ?? null, interval, includeNoShowInRRCalculation: eventType.includeNoShowInRRCalculation, + excludeSalesforceBookingsFromRR: eventType.excludeSalesforceBookingsFromRR, }), getBookingsOfInterval({ @@ -624,6 +629,7 @@ async function fetchAllDataNeededForCalculations< virtualQueuesData: virtualQueuesData ?? null, interval, includeNoShowInRRCalculation: eventType.includeNoShowInRRCalculation, + excludeSalesforceBookingsFromRR: eventType.excludeSalesforceBookingsFromRR, }), getBookingsOfInterval({ @@ -634,6 +640,7 @@ async function fetchAllDataNeededForCalculations< virtualQueuesData: virtualQueuesData ?? null, interval, includeNoShowInRRCalculation: eventType.includeNoShowInRRCalculation, + excludeSalesforceBookingsFromRR: eventType.excludeSalesforceBookingsFromRR, }), prisma.host.findMany({ diff --git a/packages/lib/server/repository/booking.ts b/packages/lib/server/repository/booking.ts index c90f1fcf6f9325..47c478f9412e1b 100644 --- a/packages/lib/server/repository/booking.ts +++ b/packages/lib/server/repository/booking.ts @@ -4,7 +4,7 @@ import type { FormResponse } from "@calcom/app-store/routing-forms/types/types"; import { withReporting } from "@calcom/lib/sentryWrapper"; import prisma, { bookingMinimalSelect } from "@calcom/prisma"; import type { Booking } from "@calcom/prisma/client"; -import { BookingStatus } from "@calcom/prisma/enums"; +import { BookingStatus, AssignmentReasonEnum } from "@calcom/prisma/enums"; import { UserRepository } from "./user"; @@ -47,6 +47,7 @@ const buildWhereClauseForActiveBookings = ({ users, virtualQueuesData, includeNoShowInRRCalculation = false, + excludeSalesforceBookingsFromRR = false, }: { eventTypeId: number; startDate?: Date; @@ -60,6 +61,7 @@ const buildWhereClauseForActiveBookings = ({ }; } | null; includeNoShowInRRCalculation: boolean; + excludeSalesforceBookingsFromRR?: boolean; }): Prisma.BookingWhereInput => ({ OR: [ { @@ -100,6 +102,17 @@ const buildWhereClauseForActiveBookings = ({ }, } : {}), + ...(excludeSalesforceBookingsFromRR + ? { + NOT: { + assignmentReason: { + some: { + reasonEnum: AssignmentReasonEnum.SALESFORCE_ASSIGNMENT, + }, + }, + }, + } + : {}), }); export class BookingRepository { @@ -290,6 +303,7 @@ export class BookingRepository { endDate, virtualQueuesData, includeNoShowInRRCalculation, + excludeSalesforceBookingsFromRR = false, }: { users: { id: number; email: string }[]; eventTypeId: number; @@ -303,6 +317,7 @@ export class BookingRepository { }; } | null; includeNoShowInRRCalculation: boolean; + excludeSalesforceBookingsFromRR: boolean; }) { const allBookings = await prisma.booking.findMany({ where: buildWhereClauseForActiveBookings({ @@ -312,6 +327,7 @@ export class BookingRepository { users, virtualQueuesData, includeNoShowInRRCalculation, + excludeSalesforceBookingsFromRR, }), select: { id: true, diff --git a/packages/platform/atoms/event-types/hooks/useEventTypeForm.ts b/packages/platform/atoms/event-types/hooks/useEventTypeForm.ts index 42fb485d5c8e16..b946a59ec16c14 100644 --- a/packages/platform/atoms/event-types/hooks/useEventTypeForm.ts +++ b/packages/platform/atoms/event-types/hooks/useEventTypeForm.ts @@ -128,6 +128,7 @@ export const useEventTypeForm = ({ isRRWeightsEnabled: eventType.isRRWeightsEnabled, maxLeadThreshold: eventType.maxLeadThreshold, includeNoShowInRRCalculation: eventType.includeNoShowInRRCalculation, + excludeSalesforceBookingsFromRR: eventType.excludeSalesforceBookingsFromRR, useEventLevelSelectedCalendars: eventType.useEventLevelSelectedCalendars, customReplyToEmail: eventType.customReplyToEmail || null, calVideoSettings: eventType.calVideoSettings, diff --git a/packages/prisma/schema.prisma b/packages/prisma/schema.prisma index 56aa60c42f9eac..770bd81976a604 100644 --- a/packages/prisma/schema.prisma +++ b/packages/prisma/schema.prisma @@ -193,6 +193,7 @@ model EventType { /// @zod.custom(imports.eventTypeColor) eventTypeColor Json? rescheduleWithSameRoundRobinHost Boolean @default(false) + excludeSalesforceBookingsFromRR Boolean @default(false) secondaryEmailId Int? secondaryEmail SecondaryEmail? @relation(fields: [secondaryEmailId], references: [id], onDelete: Cascade) From 605e998dc868d81a2402bdc2ffbd95e2dd16b61c Mon Sep 17 00:00:00 2001 From: Jagjeevan Kashid Date: Tue, 17 Jun 2025 13:43:41 +0530 Subject: [PATCH 02/11] feat: Refactor RR logic to exclude the Salesforce logic Signed-off-by: Jagjeevan Kashid --- apps/web/public/static/locales/en/common.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json index d9c7563da6f220..f705fc5e708702 100644 --- a/apps/web/public/static/locales/en/common.json +++ b/apps/web/public/static/locales/en/common.json @@ -2775,6 +2775,8 @@ "number_of_options": "{{count}} options", "reschedule_with_same_round_robin_host_title": "Reschedule with same Round-Robin host", "reschedule_with_same_round_robin_host_description": "Rescheduled events will be assigned to the same host as initially scheduled", + "exclude_salesforce_bookings_from_round_robin": "Only count Round-Robin assigned bookings", + "exclude_salesforce_bookings_from_round_robin_description": "When enabled, only bookings assigned via Round-Robin logic count towards Round-Robin calculations. Bookings assigned due to Salesforce ownership will not count.", "disable_input_if_prefilled": "Disable input if the URL identifier is prefilled", "booking_limits": "Booking Limits", "booking_limits_team_description": "Booking limits for team members across all team event types", From bf7484ab446e6b6ba6b7fa8ee692e20720e20287 Mon Sep 17 00:00:00 2001 From: Jagjeevan Kashid Date: Tue, 17 Jun 2025 14:22:52 +0530 Subject: [PATCH 03/11] Update getLuckyUser.ts --- packages/lib/server/getLuckyUser.ts | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/packages/lib/server/getLuckyUser.ts b/packages/lib/server/getLuckyUser.ts index 7b7f224ede31d7..4b79338ebdd7b6 100644 --- a/packages/lib/server/getLuckyUser.ts +++ b/packages/lib/server/getLuckyUser.ts @@ -480,24 +480,18 @@ async function getBookingsOfInterval({ virtualQueuesData, interval, includeNoShowInRRCalculation, -<<<<<<< JagjeevanAK/RR-refactor -- Incoming Change excludeSalesforceBookingsFromRR = false, -======= rrTimestampBasis, meetingStartTime, ->>>>>>> main -- Current Change }: { eventTypeId: number; users: { id: number; email: string }[]; virtualQueuesData: VirtualQueuesDataType | null; interval: RRResetInterval; includeNoShowInRRCalculation: boolean; -<<<<<<< JagjeevanAK/RR-refactor -- Incoming Change excludeSalesforceBookingsFromRR?: boolean; -======= rrTimestampBasis: RRTimestampBasis; meetingStartTime?: Date; ->>>>>>> main -- Current Change }) { return await BookingRepository.getAllBookingsForRoundRobin({ eventTypeId: eventTypeId, @@ -506,11 +500,8 @@ async function getBookingsOfInterval({ endDate: getIntervalEndDate({ interval, rrTimestampBasis, meetingStartTime }), virtualQueuesData, includeNoShowInRRCalculation, -<<<<<<< JagjeevanAK/RR-refactor -- Incoming Change excludeSalesforceBookingsFromRR, -======= rrTimestampBasis, ->>>>>>> main -- Current Change }); } @@ -698,12 +689,9 @@ async function fetchAllDataNeededForCalculations< virtualQueuesData: virtualQueuesData ?? null, interval, includeNoShowInRRCalculation: eventType.includeNoShowInRRCalculation, -<<<<<<< JagjeevanAK/RR-refactor -- Incoming Change excludeSalesforceBookingsFromRR: eventType.excludeSalesforceBookingsFromRR, -======= rrTimestampBasis, meetingStartTime, ->>>>>>> main -- Current Change }), getBookingsOfInterval({ @@ -712,12 +700,9 @@ async function fetchAllDataNeededForCalculations< virtualQueuesData: virtualQueuesData ?? null, interval, includeNoShowInRRCalculation: eventType.includeNoShowInRRCalculation, -<<<<<<< JagjeevanAK/RR-refactor -- Incoming Change excludeSalesforceBookingsFromRR: eventType.excludeSalesforceBookingsFromRR, -======= rrTimestampBasis, meetingStartTime, ->>>>>>> main -- Current Change }), getBookingsOfInterval({ @@ -728,12 +713,9 @@ async function fetchAllDataNeededForCalculations< virtualQueuesData: virtualQueuesData ?? null, interval, includeNoShowInRRCalculation: eventType.includeNoShowInRRCalculation, -<<<<<<< JagjeevanAK/RR-refactor -- Incoming Change excludeSalesforceBookingsFromRR: eventType.excludeSalesforceBookingsFromRR, -======= rrTimestampBasis, meetingStartTime, ->>>>>>> main -- Current Change }), prisma.host.findMany({ From 7ae7f99740f92f1cd2fb778abda60603767a082c Mon Sep 17 00:00:00 2001 From: Jagjeevan Kashid Date: Tue, 17 Jun 2025 14:59:36 +0530 Subject: [PATCH 04/11] feat: Refactor RR logic to exclude the Salesforce logic Signed-off-by: Jagjeevan Kashid --- .../migration.sql | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 packages/prisma/migrations/20250617092831_add_exclude_salesforce_bookings_from_rr/migration.sql diff --git a/packages/prisma/migrations/20250617092831_add_exclude_salesforce_bookings_from_rr/migration.sql b/packages/prisma/migrations/20250617092831_add_exclude_salesforce_bookings_from_rr/migration.sql new file mode 100644 index 00000000000000..4bdca609b3b860 --- /dev/null +++ b/packages/prisma/migrations/20250617092831_add_exclude_salesforce_bookings_from_rr/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "EventType" ADD COLUMN "excludeSalesforceBookingsFromRR" BOOLEAN NOT NULL DEFAULT false; From 9e442254823854678c79568d0bc5bcae74adbe3c Mon Sep 17 00:00:00 2001 From: Jagjeevan Kashid Date: Tue, 17 Jun 2025 15:22:26 +0530 Subject: [PATCH 05/11] fixed some merge conflict errors Signed-off-by: Jagjeevan Kashid --- packages/lib/server/repository/booking.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/lib/server/repository/booking.ts b/packages/lib/server/repository/booking.ts index cb95cf5dcf33b7..5fb09b6af14e87 100644 --- a/packages/lib/server/repository/booking.ts +++ b/packages/lib/server/repository/booking.ts @@ -4,9 +4,7 @@ import type { FormResponse } from "@calcom/app-store/routing-forms/types/types"; import { withReporting } from "@calcom/lib/sentryWrapper"; import prisma, { bookingMinimalSelect } from "@calcom/prisma"; import type { Booking } from "@calcom/prisma/client"; -import { BookingStatus, AssignmentReasonEnum } from "@calcom/prisma/enums"; -import { RRTimestampBasis } from "@calcom/prisma/enums"; -import { BookingStatus } from "@calcom/prisma/enums"; +import { BookingStatus, AssignmentReasonEnum, RRTimestampBasis } from "@calcom/prisma/enums"; import { UserRepository } from "./user"; From c60310ad2a0c4d5af9c2338db8253bedc00660e3 Mon Sep 17 00:00:00 2001 From: Jagjeevan Kashid Date: Tue, 17 Jun 2025 15:49:14 +0530 Subject: [PATCH 06/11] fixed some type errors Signed-off-by: Jagjeevan Kashid --- packages/lib/test/builder.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/lib/test/builder.ts b/packages/lib/test/builder.ts index 2905a2f78166b6..1db6a2282c3722 100644 --- a/packages/lib/test/builder.ts +++ b/packages/lib/test/builder.ts @@ -138,6 +138,7 @@ export const buildEventType = (eventType?: Partial): EventType => { durationLimits: null, assignAllTeamMembers: false, rescheduleWithSameRoundRobinHost: false, + excludeSalesforceBookingsFromRR: false, price: 0, currency: "usd", slotInterval: null, From 449118036a4ed6ca36aa2a9e0c242beda2f9c36d Mon Sep 17 00:00:00 2001 From: Jagjeevan Kashid Date: Tue, 17 Jun 2025 16:21:49 +0530 Subject: [PATCH 07/11] fixed some test failure errors Signed-off-by: Jagjeevan Kashid --- apps/web/test/lib/handleChildrenEventTypes.test.ts | 5 +++++ packages/lib/server/repository/eventType.ts | 1 + 2 files changed, 6 insertions(+) diff --git a/apps/web/test/lib/handleChildrenEventTypes.test.ts b/apps/web/test/lib/handleChildrenEventTypes.test.ts index 9c9b4a4c6bfe63..77136ce2b82ac9 100644 --- a/apps/web/test/lib/handleChildrenEventTypes.test.ts +++ b/apps/web/test/lib/handleChildrenEventTypes.test.ts @@ -148,6 +148,7 @@ describe("handleChildrenEventTypes", () => { rrSegmentQueryValue: undefined, assignRRMembersUsingSegment: false, allowReschedulingCancelledBookings: false, + excludeSalesforceBookingsFromRR: false, }, }); expect(result.newUserIds).toEqual([4]); @@ -208,6 +209,7 @@ describe("handleChildrenEventTypes", () => { }, instantMeetingScheduleId: undefined, allowReschedulingCancelledBookings: false, + excludeSalesforceBookingsFromRR: false, }, where: { userId_parentId: { @@ -318,6 +320,7 @@ describe("handleChildrenEventTypes", () => { rrSegmentQueryValue: undefined, assignRRMembersUsingSegment: false, allowReschedulingCancelledBookings: false, + excludeSalesforceBookingsFromRR: false, }, }); expect(result.newUserIds).toEqual([4]); @@ -375,6 +378,7 @@ describe("handleChildrenEventTypes", () => { lockTimeZoneToggleOnBookingPage: false, requiresBookerEmailVerification: false, allowReschedulingCancelledBookings: false, + excludeSalesforceBookingsFromRR: false, }, where: { userId_parentId: { @@ -481,6 +485,7 @@ describe("handleChildrenEventTypes", () => { assignRRMembersUsingSegment: false, useEventLevelSelectedCalendars: false, allowReschedulingCancelledBookings: false, + excludeSalesforceBookingsFromRR: false, }, }); const { profileId, rrSegmentQueryValue, ...rest } = evType; diff --git a/packages/lib/server/repository/eventType.ts b/packages/lib/server/repository/eventType.ts index 8691ac7660a397..246fc36a1b7665 100644 --- a/packages/lib/server/repository/eventType.ts +++ b/packages/lib/server/repository/eventType.ts @@ -539,6 +539,7 @@ export class EventTypeRepository { rrSegmentQueryValue: true, isRRWeightsEnabled: true, rescheduleWithSameRoundRobinHost: true, + excludeSalesforceBookingsFromRR: true, successRedirectUrl: true, forwardParamsSuccessRedirect: true, currency: true, From 0755f80e0986f9b7947670d2577aa08ca03d4f1c Mon Sep 17 00:00:00 2001 From: Jagjeevan Kashid Date: Tue, 17 Jun 2025 17:38:51 +0530 Subject: [PATCH 08/11] fixed some test failure errors Signed-off-by: Jagjeevan Kashid --- packages/prisma/zod-utils.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/prisma/zod-utils.ts b/packages/prisma/zod-utils.ts index d66c969051db67..62ed7ff91d122f 100644 --- a/packages/prisma/zod-utils.ts +++ b/packages/prisma/zod-utils.ts @@ -672,6 +672,7 @@ export const allManagedEventTypeProps: { [k in keyof Omit Date: Mon, 23 Jun 2025 19:08:43 +0530 Subject: [PATCH 09/11] Refactoring of roundrobin feature as suggested by Carina Signed-off-by: Jagjeevan Kashid --- .../test/lib/handleChildrenEventTypes.test.ts | 5 ----- .../components/EventTypeAppCardInterface.tsx | 21 +++++++++++++++++++ packages/app-store/salesforce/zod.ts | 1 + .../assignment/EventTeamAssignmentTab.tsx | 14 +------------ packages/features/eventtypes/lib/types.ts | 1 - packages/lib/server/getLuckyUser.test.ts | 15 +++++++++++-- packages/lib/server/getLuckyUser.ts | 13 ++++++++---- .../event-types/hooks/useEventTypeForm.ts | 1 - 8 files changed, 45 insertions(+), 26 deletions(-) diff --git a/apps/web/test/lib/handleChildrenEventTypes.test.ts b/apps/web/test/lib/handleChildrenEventTypes.test.ts index 77136ce2b82ac9..9c9b4a4c6bfe63 100644 --- a/apps/web/test/lib/handleChildrenEventTypes.test.ts +++ b/apps/web/test/lib/handleChildrenEventTypes.test.ts @@ -148,7 +148,6 @@ describe("handleChildrenEventTypes", () => { rrSegmentQueryValue: undefined, assignRRMembersUsingSegment: false, allowReschedulingCancelledBookings: false, - excludeSalesforceBookingsFromRR: false, }, }); expect(result.newUserIds).toEqual([4]); @@ -209,7 +208,6 @@ describe("handleChildrenEventTypes", () => { }, instantMeetingScheduleId: undefined, allowReschedulingCancelledBookings: false, - excludeSalesforceBookingsFromRR: false, }, where: { userId_parentId: { @@ -320,7 +318,6 @@ describe("handleChildrenEventTypes", () => { rrSegmentQueryValue: undefined, assignRRMembersUsingSegment: false, allowReschedulingCancelledBookings: false, - excludeSalesforceBookingsFromRR: false, }, }); expect(result.newUserIds).toEqual([4]); @@ -378,7 +375,6 @@ describe("handleChildrenEventTypes", () => { lockTimeZoneToggleOnBookingPage: false, requiresBookerEmailVerification: false, allowReschedulingCancelledBookings: false, - excludeSalesforceBookingsFromRR: false, }, where: { userId_parentId: { @@ -485,7 +481,6 @@ describe("handleChildrenEventTypes", () => { assignRRMembersUsingSegment: false, useEventLevelSelectedCalendars: false, allowReschedulingCancelledBookings: false, - excludeSalesforceBookingsFromRR: false, }, }); const { profileId, rrSegmentQueryValue, ...rest } = evType; diff --git a/packages/app-store/salesforce/components/EventTypeAppCardInterface.tsx b/packages/app-store/salesforce/components/EventTypeAppCardInterface.tsx index 46dc677fd3f1a6..8261c9fa3d2fc9 100644 --- a/packages/app-store/salesforce/components/EventTypeAppCardInterface.tsx +++ b/packages/app-store/salesforce/components/EventTypeAppCardInterface.tsx @@ -43,6 +43,7 @@ const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ const onBookingWriteToRecord = getAppData("onBookingWriteToRecord") ?? false; const onBookingWriteToRecordFields = getAppData("onBookingWriteToRecordFields") ?? {}; const ignoreGuests = getAppData("ignoreGuests") ?? false; + const excludeSalesforceBookingsFromRR = getAppData("excludeSalesforceBookingsFromRR") ?? false; const roundRobinSkipFallbackToLeadOwner = getAppData("roundRobinSkipFallbackToLeadOwner") ?? false; const onCancelWriteToEventRecord = getAppData("onCancelWriteToEventRecord") ?? false; const onCancelWriteToEventRecordFields = getAppData("onCancelWriteToEventRecordFields") ?? {}; @@ -440,6 +441,26 @@ const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ ) : null} + {/* Only show this toggle when the event type is Round Robin */} + {eventType.schedulingType === SchedulingType.ROUND_ROBIN && ( + + + { + setAppData("excludeSalesforceBookingsFromRR", checked); + }} + /> + + + )} + - - name="excludeSalesforceBookingsFromRR" - render={({ field: { value: excludeSalesforceBookingsFromRR, onChange } }) => ( - { - onChange(active); - }} - /> - )} - /> + { prismaMock.host.findMany.mockResolvedValue([]); prismaMock.booking.findMany.mockResolvedValue([]); + // Mock Salesforce app data with excludeSalesforceBookingsFromRR set to true + const mockEventTypeService = vi.spyOn(EventTypeService, "getEventTypeAppDataFromId"); + mockEventTypeService.mockResolvedValue({ excludeSalesforceBookingsFromRR: true }); + await getLuckyUser({ availableUsers: users, eventType: { id: 1, isRRWeightsEnabled: false, - excludeSalesforceBookingsFromRR: true, team: { rrResetInterval: RRResetInterval.MONTH }, }, allRRHosts: [], @@ -1461,6 +1465,8 @@ describe("attribute weights and virtual queues", () => { // Verify that the query excludes Salesforce assignments expect(queryArgs.where?.NOT?.assignmentReason?.some?.reasonEnum).toEqual("SALESFORCE_ASSIGNMENT"); + + mockEventTypeService.mockRestore(); }); it("should include Salesforce bookings in round robin when excludeSalesforceBookingsFromRR is false", async () => { @@ -1495,12 +1501,15 @@ describe("attribute weights and virtual queues", () => { prismaMock.host.findMany.mockResolvedValue([]); prismaMock.booking.findMany.mockResolvedValue([]); + // Mock Salesforce app data with excludeSalesforceBookingsFromRR set to false + const mockEventTypeService = vi.spyOn(EventTypeService, "getEventTypeAppDataFromId"); + mockEventTypeService.mockResolvedValue({ excludeSalesforceBookingsFromRR: false }); + await getLuckyUser({ availableUsers: users, eventType: { id: 1, isRRWeightsEnabled: false, - excludeSalesforceBookingsFromRR: false, team: { rrResetInterval: RRResetInterval.MONTH }, }, allRRHosts: [], @@ -1511,6 +1520,8 @@ describe("attribute weights and virtual queues", () => { // Verify that the query does NOT exclude Salesforce assignments expect(queryArgs.where?.NOT?.assignmentReason?.some?.reasonEnum).toBeUndefined(); + + mockEventTypeService.mockRestore(); }); }); diff --git a/packages/lib/server/getLuckyUser.ts b/packages/lib/server/getLuckyUser.ts index 4b79338ebdd7b6..603d5e56767fb4 100644 --- a/packages/lib/server/getLuckyUser.ts +++ b/packages/lib/server/getLuckyUser.ts @@ -9,6 +9,7 @@ import { acrossQueryValueCompatiblity } from "@calcom/lib/raqb/raqbUtils"; import { raqbQueryValueSchema } from "@calcom/lib/raqb/zod"; import { safeStringify } from "@calcom/lib/safeStringify"; import { BookingRepository } from "@calcom/lib/server/repository/booking"; +import { EventTypeService } from "@calcom/lib/server/service/eventType"; import prisma from "@calcom/prisma"; import type { Booking } from "@calcom/prisma/client"; import type { SelectedCalendar } from "@calcom/prisma/client"; @@ -72,7 +73,6 @@ interface GetLuckyUserParams { rrTimestampBasis: RRTimestampBasis; } | null; includeNoShowInRRCalculation: boolean; - excludeSalesforceBookingsFromRR?: boolean; }; // all routedTeamMemberIds or all hosts of event types allRRHosts: { @@ -633,6 +633,11 @@ async function fetchAllDataNeededForCalculations< const startTime = performance.now(); const { availableUsers, allRRHosts, eventType, meetingStartTime } = getLuckyUserParams; + + // Get Salesforce app data to check excludeSalesforceBookingsFromRR setting + const salesforceAppData = await EventTypeService.getEventTypeAppDataFromId(eventType.id, "salesforce"); + const excludeSalesforceBookingsFromRR = salesforceAppData?.excludeSalesforceBookingsFromRR ?? false; + const notAvailableHosts = (function getNotAvailableHosts() { const availableUserIds = new Set(availableUsers.map((user) => user.id)); return allRRHosts.reduce( @@ -689,7 +694,7 @@ async function fetchAllDataNeededForCalculations< virtualQueuesData: virtualQueuesData ?? null, interval, includeNoShowInRRCalculation: eventType.includeNoShowInRRCalculation, - excludeSalesforceBookingsFromRR: eventType.excludeSalesforceBookingsFromRR, + excludeSalesforceBookingsFromRR, rrTimestampBasis, meetingStartTime, }), @@ -700,7 +705,7 @@ async function fetchAllDataNeededForCalculations< virtualQueuesData: virtualQueuesData ?? null, interval, includeNoShowInRRCalculation: eventType.includeNoShowInRRCalculation, - excludeSalesforceBookingsFromRR: eventType.excludeSalesforceBookingsFromRR, + excludeSalesforceBookingsFromRR, rrTimestampBasis, meetingStartTime, }), @@ -713,7 +718,7 @@ async function fetchAllDataNeededForCalculations< virtualQueuesData: virtualQueuesData ?? null, interval, includeNoShowInRRCalculation: eventType.includeNoShowInRRCalculation, - excludeSalesforceBookingsFromRR: eventType.excludeSalesforceBookingsFromRR, + excludeSalesforceBookingsFromRR, rrTimestampBasis, meetingStartTime, }), diff --git a/packages/platform/atoms/event-types/hooks/useEventTypeForm.ts b/packages/platform/atoms/event-types/hooks/useEventTypeForm.ts index 79b4caa8e6d10a..68f7156fe8de1d 100644 --- a/packages/platform/atoms/event-types/hooks/useEventTypeForm.ts +++ b/packages/platform/atoms/event-types/hooks/useEventTypeForm.ts @@ -134,7 +134,6 @@ export const useEventTypeForm = ({ isRRWeightsEnabled: eventType.isRRWeightsEnabled, maxLeadThreshold: eventType.maxLeadThreshold, includeNoShowInRRCalculation: eventType.includeNoShowInRRCalculation, - excludeSalesforceBookingsFromRR: eventType.excludeSalesforceBookingsFromRR, useEventLevelSelectedCalendars: eventType.useEventLevelSelectedCalendars, customReplyToEmail: eventType.customReplyToEmail || null, calVideoSettings: eventType.calVideoSettings, From b95ed9648696caa4ebed125a6c35505adae3bd52 Mon Sep 17 00:00:00 2001 From: Jagjeevan Kashid Date: Mon, 23 Jun 2025 20:15:05 +0530 Subject: [PATCH 10/11] minor type error fix Signed-off-by: Jagjeevan Kashid --- .../salesforce/components/EventTypeAppCardInterface.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/app-store/salesforce/components/EventTypeAppCardInterface.tsx b/packages/app-store/salesforce/components/EventTypeAppCardInterface.tsx index 8261c9fa3d2fc9..8fade63933b6a5 100644 --- a/packages/app-store/salesforce/components/EventTypeAppCardInterface.tsx +++ b/packages/app-store/salesforce/components/EventTypeAppCardInterface.tsx @@ -447,7 +447,6 @@ const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ +

+ {t("exclude_salesforce_bookings_from_round_robin_description")} +

)} From dce0f332dda4e28f9963224a0f68b1ca33b794eb Mon Sep 17 00:00:00 2001 From: Jagjeevan Kashid Date: Tue, 24 Jun 2025 20:19:52 +0530 Subject: [PATCH 11/11] Update migration.sql --- .../migration.sql | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/prisma/migrations/20250617092831_add_exclude_salesforce_bookings_from_rr/migration.sql b/packages/prisma/migrations/20250617092831_add_exclude_salesforce_bookings_from_rr/migration.sql index 4bdca609b3b860..694cc95fd07205 100644 --- a/packages/prisma/migrations/20250617092831_add_exclude_salesforce_bookings_from_rr/migration.sql +++ b/packages/prisma/migrations/20250617092831_add_exclude_salesforce_bookings_from_rr/migration.sql @@ -1,2 +1 @@ --- AlterTable ALTER TABLE "EventType" ADD COLUMN "excludeSalesforceBookingsFromRR" BOOLEAN NOT NULL DEFAULT false;