From 02ef4db0eb7e72ee9f94a0ab30137032c8e551f3 Mon Sep 17 00:00:00 2001 From: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com> Date: Fri, 27 Jun 2025 05:06:39 -0400 Subject: [PATCH] feat: Add `rerouting` to `AssignmentReasonRecorder` (#19252) * Add `rerouting` to `AssignmentReasonRecorder` * Pass session email as `rescheduledBy` * Write reassign reason * Edit message * Use `routingFormRoute` when rerouting * Pass isRerouting & reroutedByEmail to `routingFormRoute` method * Type fix * Fix test * Fix test * Fix test * Type fix * Refactor to use UserRepository * Show rerouted by email * Replace isReroutingCase * Fix rescheduledBy for new tab reschduling --------- Co-authored-by: Alex van Andel Co-authored-by: Hariom --- apps/web/components/dialog/RerouteDialog.tsx | 5 ++- .../dialog/__tests__/RerouteDialog.test.tsx | 35 ++++++++++++++----- .../features/bookings/lib/handleNewBooking.ts | 2 ++ .../AssignmentReasonRecorder.ts | 12 +++++-- 4 files changed, 42 insertions(+), 12 deletions(-) diff --git a/apps/web/components/dialog/RerouteDialog.tsx b/apps/web/components/dialog/RerouteDialog.tsx index e7e4d482d7..e52d149289 100644 --- a/apps/web/components/dialog/RerouteDialog.tsx +++ b/apps/web/components/dialog/RerouteDialog.tsx @@ -1,4 +1,5 @@ import { useMutation } from "@tanstack/react-query"; +import { useSession } from "next-auth/react"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { useState } from "react"; @@ -279,6 +280,7 @@ const NewRoutingManager = ({ const { t } = useLocale(); const router = useRouter(); const bookerUrl = useBookerUrl(); + const session = useSession(); const teamMemberIdsMatchingAttributeLogic = teamMembersMatchingAttributeLogic?.data ?.map((member) => member.id) @@ -305,7 +307,7 @@ const NewRoutingManager = ({ booking, currentResponse, searchParams: new URLSearchParams({ - // rescheduleReason + rescheduledBy: session?.data?.user?.email ?? "", }), }); @@ -469,6 +471,7 @@ const NewRoutingManager = ({ // TODO: Long term, we should refactor handleNewBooking and use a different route specific for this purpose, createBookingMutation.mutate({ rescheduleUid: booking.uid, + rescheduledBy: session?.data?.user?.email ?? undefined, // rescheduleReason, reroutingFormResponses: reroutingFormResponses, ...getTimeslotFields(), diff --git a/apps/web/components/dialog/__tests__/RerouteDialog.test.tsx b/apps/web/components/dialog/__tests__/RerouteDialog.test.tsx index b3c59d7595..538948e199 100644 --- a/apps/web/components/dialog/__tests__/RerouteDialog.test.tsx +++ b/apps/web/components/dialog/__tests__/RerouteDialog.test.tsx @@ -1,5 +1,7 @@ import { TooltipProvider } from "@radix-ui/react-tooltip"; import { act, fireEvent, render, screen } from "@testing-library/react"; +import type { Session } from "next-auth"; +import { SessionProvider } from "next-auth/react"; import { vi } from "vitest"; import { RouteActionType } from "@calcom/app-store/routing-forms/zod"; @@ -37,6 +39,15 @@ const mockOpen = vi.fn((_url: string) => { vi.stubGlobal("open", mockOpen); +const mockSession = { + expires: new Date(Date.now() + 2 * 86400).toISOString(), + user: { + id: 1, + name: "Test User", + email: "user@example.com", + }, +} as Session; + vi.mock("@calcom/app-store/routing-forms/components/FormInputFields", () => ({ default: vi.fn(({ response, form, setResponse, disabledFields }) => { return ( @@ -404,9 +415,11 @@ describe("RerouteDialog", () => { describe("New Routing tests", () => { test("when verify_new_route is clicked, the form is submitted", async () => { render( - - - + + + + + ); fireEvent.click(screen.getByText("verify_new_route")); @@ -425,9 +438,11 @@ describe("RerouteDialog", () => { describe("New tab rescheduling", () => { test("new tab is closed when new booking is rerouted", async () => { render( - - - + + + + + ); clickVerifyNewRouteButton(); clickRescheduleToTheNewEventWithDifferentTimeslotButton(); @@ -466,9 +481,11 @@ describe("RerouteDialog", () => { test("Rescheduling with same timeslot works", async () => { render( - - - + + + + + ); clickVerifyNewRouteButton(); clickRescheduleWithSameTimeslotOfChosenEventButton(); diff --git a/packages/features/bookings/lib/handleNewBooking.ts b/packages/features/bookings/lib/handleNewBooking.ts index 4934b6bcbe..113cb225f3 100644 --- a/packages/features/bookings/lib/handleNewBooking.ts +++ b/packages/features/bookings/lib/handleNewBooking.ts @@ -1396,6 +1396,8 @@ async function handler( routingFormResponseId, organizerId: organizerUser.id, teamId, + isRerouting: !!reroutingFormResponses, + reroutedByEmail: reqBody.rescheduledBy, }); } } diff --git a/packages/features/ee/round-robin/assignmentReason/AssignmentReasonRecorder.ts b/packages/features/ee/round-robin/assignmentReason/AssignmentReasonRecorder.ts index 62a38b15a1..710f28aad7 100644 --- a/packages/features/ee/round-robin/assignmentReason/AssignmentReasonRecorder.ts +++ b/packages/features/ee/round-robin/assignmentReason/AssignmentReasonRecorder.ts @@ -28,11 +28,15 @@ export default class AssignmentReasonRecorder { routingFormResponseId, organizerId, teamId, + isRerouting, + reroutedByEmail, }: { bookingId: number; routingFormResponseId: number; organizerId: number; teamId: number; + isRerouting: boolean; + reroutedByEmail?: string | null; }) { // Get the routing form data const routingFormResponse = await prisma.app_RoutingForms_FormResponse.findUnique({ @@ -113,8 +117,12 @@ export default class AssignmentReasonRecorder { } } - const reasonEnum = AssignmentReasonEnum.ROUTING_FORM_ROUTING; - const reasonString = attributeValues.join(", "); + const reasonEnum = isRerouting + ? AssignmentReasonEnum.REROUTED + : AssignmentReasonEnum.ROUTING_FORM_ROUTING; + const reasonString = `${ + isRerouting && reroutedByEmail ? `Rerouted by ${reroutedByEmail}` : "" + } ${attributeValues.join(", ")}`; await prisma.assignmentReason.create({ data: {