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 <me@alexvanandel.com>
Co-authored-by: Hariom <hariombalhara@gmail.com>
This commit is contained in:
Joe Au-Yeung
2025-06-27 10:06:39 +01:00
committed by GitHub
co-authored by Alex van Andel Hariom
parent f8d48a341d
commit 02ef4db0eb
4 changed files with 42 additions and 12 deletions
+4 -1
View File
@@ -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(),
@@ -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(
<TooltipProvider>
<RerouteDialog isOpenDialog={true} setIsOpenDialog={mockSetIsOpenDialog} booking={mockBooking} />
</TooltipProvider>
<SessionProvider session={mockSession}>
<TooltipProvider>
<RerouteDialog isOpenDialog={true} setIsOpenDialog={mockSetIsOpenDialog} booking={mockBooking} />
</TooltipProvider>
</SessionProvider>
);
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(
<TooltipProvider>
<RerouteDialog isOpenDialog={true} setIsOpenDialog={mockSetIsOpenDialog} booking={mockBooking} />
</TooltipProvider>
<SessionProvider session={mockSession}>
<TooltipProvider>
<RerouteDialog isOpenDialog={true} setIsOpenDialog={mockSetIsOpenDialog} booking={mockBooking} />
</TooltipProvider>
</SessionProvider>
);
clickVerifyNewRouteButton();
clickRescheduleToTheNewEventWithDifferentTimeslotButton();
@@ -466,9 +481,11 @@ describe("RerouteDialog", () => {
test("Rescheduling with same timeslot works", async () => {
render(
<TooltipProvider>
<RerouteDialog isOpenDialog={true} setIsOpenDialog={mockSetIsOpenDialog} booking={mockBooking} />
</TooltipProvider>
<SessionProvider session={mockSession}>
<TooltipProvider>
<RerouteDialog isOpenDialog={true} setIsOpenDialog={mockSetIsOpenDialog} booking={mockBooking} />
</TooltipProvider>
</SessionProvider>
);
clickVerifyNewRouteButton();
clickRescheduleWithSameTimeslotOfChosenEventButton();
@@ -1396,6 +1396,8 @@ async function handler(
routingFormResponseId,
organizerId: organizerUser.id,
teamId,
isRerouting: !!reroutingFormResponses,
reroutedByEmail: reqBody.rescheduledBy,
});
}
}
@@ -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: {