fix: Preview queue position and contact owner in preview (#17552)
* wip * Add preview mode in router * only fetch bookings of this month for weighted rr * test set up * only get bookings of current month * reverts test setup * first changes for weight adjustments * reset booking count + adjust calibration * depreciate weightAdjustment * get only bookings created this month * fix typo * make sure createdAt for hosts is correct * use earliest possibel date as fallback * add missing createdAt date to tests * fix typo * clean up changes in tests * fix typo * change end date to current date * fix: Fall back to empty host array when no hosts are found * fix: Restructure code a little * fixed test, incorrectly used now outdated var * perf: remove Dayjs from getLuckyUser * Refactor getHostsWithCalibration for optimised performance, intentionally break test as findMany is always an array * Better mock for host.findMany * Remove team-event-types.test.ts, move to appropriate package * TypeScript cannot auto-infer that an array is non-empty when assigning to a var * fix: Type Fixes and DistributionMethod enum add * Optimise tests * Added test to show that bookings made before a newHost was added affect the lucky user result * Throw error when the usersWithHighestPriority is empty, which should never happen * WIP * remove comment * update migrations * get attributes weights and virtual queue data * use attribute weights and use bookings of virtual queue only * clean up migrations * Add shortfall column and add tests * code clean up from feedback * wrapper function for getLuckyUser * code clean up * fetch routingFormResponse in handleNewBooking * fix type errors * fix type errors in tests * fix getAttributesQueryValue import for tests * fix totalWeight * add test for attributes weights and virtual queues * clean up code * add test for prepareQueuesAndAttributesData * remove console.log * use lazy import * Add more tests and more columns to matching members queue * fix issue from merge * always send usersAndTheirBookingShortfalls --------- Co-authored-by: CarinaWolli <wollencarina@gmail.com> Co-authored-by: Alex van Andel <me@alexvanandel.com>
This commit is contained in:
28 files changed
+2109
-871
No files matched your search
@@ -775,9 +775,9 @@ const RerouteDialogContentAndFooterWithFormResponse = ({
|
||||
>([]);
|
||||
|
||||
const findTeamMembersMatchingAttributeLogicMutation =
|
||||
trpc.viewer.appRoutingForms.findTeamMembersMatchingAttributeLogic.useMutation({
|
||||
trpc.viewer.routingForms.findTeamMembersMatchingAttributeLogic.useMutation({
|
||||
onSuccess(data) {
|
||||
setTeamMembersMatchingAttributeLogic(data.result);
|
||||
setTeamMembersMatchingAttributeLogic(data.result ? data.result.users : data.result);
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -164,28 +164,6 @@ vi.mock("@calcom/trpc/react", () => ({
|
||||
isPending: false,
|
||||
})),
|
||||
},
|
||||
findTeamMembersMatchingAttributeLogic: {
|
||||
useMutation: vi.fn(({ onSuccess }) => {
|
||||
return {
|
||||
mutate: vi.fn(() => {
|
||||
onSuccess({
|
||||
result: [
|
||||
{
|
||||
id: 1,
|
||||
name: "Matching User 1",
|
||||
email: "matching-user-1@example.com",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "Matching User 2",
|
||||
email: "matching-user-2@example.com",
|
||||
},
|
||||
],
|
||||
});
|
||||
}),
|
||||
};
|
||||
}),
|
||||
},
|
||||
},
|
||||
eventTypes: {
|
||||
get: {
|
||||
@@ -204,11 +182,37 @@ vi.mock("@calcom/trpc/react", () => ({
|
||||
})),
|
||||
},
|
||||
},
|
||||
routingForms: {
|
||||
findTeamMembersMatchingAttributeLogic: {
|
||||
useMutation: vi.fn(({ onSuccess }) => {
|
||||
return {
|
||||
mutate: vi.fn(() => {
|
||||
onSuccess({
|
||||
result: {
|
||||
users: [
|
||||
{
|
||||
id: 1,
|
||||
name: "Matching User 1",
|
||||
email: "matching-user-1@example.com",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "Matching User 2",
|
||||
email: "matching-user-2@example.com",
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
}),
|
||||
};
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
const mockMutateFn = vi.fn(({ __testOnSuccess }) => {
|
||||
const mockReactQueryMutateFn = vi.fn(({ __testOnSuccess }) => {
|
||||
__testOnSuccess({
|
||||
uid: "RESCHEDULED_BOOKING_UID_SAME_TIMESLOT",
|
||||
});
|
||||
@@ -218,7 +222,7 @@ vi.mock("@tanstack/react-query", () => ({
|
||||
useMutation: vi.fn(({ onSuccess }) => {
|
||||
return {
|
||||
mutate: vi.fn((payload) => {
|
||||
mockMutateFn({
|
||||
mockReactQueryMutateFn({
|
||||
...payload,
|
||||
__testOnSuccess: onSuccess,
|
||||
});
|
||||
@@ -441,7 +445,7 @@ describe("RerouteDialog", () => {
|
||||
);
|
||||
clickVerifyNewRouteButton();
|
||||
clickRescheduleWithSameTimeslotOfChosenEventButton();
|
||||
expect(mockMutateFn).toHaveBeenCalledWith(
|
||||
expect(mockReactQueryMutateFn).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
rescheduleUid: mockBooking.uid,
|
||||
// Shouldn't include the user who booked the booking
|
||||
|
||||
@@ -0,0 +1,246 @@
|
||||
import type { ParsedUrlQuery } from "querystring";
|
||||
|
||||
import { getCRMContactOwnerForRRLeadSkip } from "@calcom/app-store/_utils/CRMRoundRobinSkip";
|
||||
import { ROUTING_FORM_RESPONSE_ID_QUERY_STRING } from "@calcom/app-store/routing-forms/lib/constants";
|
||||
import { enabledAppSlugs } from "@calcom/app-store/routing-forms/lib/enabledApps";
|
||||
import type { AttributeRoutingConfig, LocalRoute } from "@calcom/app-store/routing-forms/types/types";
|
||||
import { zodRoutes as routesSchema } from "@calcom/app-store/routing-forms/zod";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import { safeStringify } from "@calcom/lib/safeStringify";
|
||||
import prisma from "@calcom/prisma";
|
||||
import type { Prisma } from "@calcom/prisma/client";
|
||||
import { SchedulingType } from "@calcom/prisma/enums";
|
||||
|
||||
const log = logger.getSubLogger({ prefix: ["getTeamMemberEmailFromCrm"] });
|
||||
|
||||
interface EventData {
|
||||
id: number;
|
||||
isInstantEvent: boolean;
|
||||
schedulingType: SchedulingType | null;
|
||||
metadata: Prisma.JsonValue | null;
|
||||
length: number;
|
||||
}
|
||||
|
||||
function getRoutingFormResponseIdFromQuery(query: ParsedUrlQuery) {
|
||||
const routingFormResponseIdAsNumber = Number(query[ROUTING_FORM_RESPONSE_ID_QUERY_STRING]);
|
||||
const routingFormResponseId = isNaN(routingFormResponseIdAsNumber) ? null : routingFormResponseIdAsNumber;
|
||||
return routingFormResponseId;
|
||||
}
|
||||
|
||||
async function getAttributeRoutingConfig(
|
||||
data:
|
||||
| {
|
||||
routingFormResponseId: number;
|
||||
eventTypeId: number;
|
||||
}
|
||||
| {
|
||||
route: Pick<LocalRoute, "attributeRoutingConfig">;
|
||||
}
|
||||
) {
|
||||
if ("route" in data) {
|
||||
return data.route.attributeRoutingConfig ?? null;
|
||||
}
|
||||
const { routingFormResponseId, eventTypeId } = data;
|
||||
const routingFormQuery = await prisma.app_RoutingForms_Form.findFirst({
|
||||
where: {
|
||||
responses: {
|
||||
some: {
|
||||
id: routingFormResponseId,
|
||||
},
|
||||
},
|
||||
},
|
||||
select: {
|
||||
routes: true,
|
||||
},
|
||||
});
|
||||
if (!routingFormQuery || !routingFormQuery?.routes) return null;
|
||||
const parsedRoutes = routesSchema.safeParse(routingFormQuery.routes);
|
||||
|
||||
if (!parsedRoutes.success || !parsedRoutes.data) return null;
|
||||
|
||||
// Find the route with the attributeRoutingConfig
|
||||
// FIXME: There could be multiple routes with same action.eventTypeId, we should actually ensure we have the chosenRouteId in here and use that route.
|
||||
const route = parsedRoutes.data.find((route) => {
|
||||
if ("action" in route) {
|
||||
return route.action.eventTypeId === eventTypeId;
|
||||
}
|
||||
});
|
||||
|
||||
if (!route || !("attributeRoutingConfig" in route)) return null;
|
||||
// Get attributeRoutingConfig for the form
|
||||
const attributeRoutingConfig = route.attributeRoutingConfig;
|
||||
|
||||
if (!attributeRoutingConfig) return null;
|
||||
return attributeRoutingConfig;
|
||||
}
|
||||
|
||||
function getEnabledRoutingFormAppSlugFromQuery(query: ParsedUrlQuery) {
|
||||
// Determine if a routing form enabled app is in the query. Then pass it to the proper handler
|
||||
// Routing form apps will have the format cal.appSlug
|
||||
let enabledRoutingFormApp;
|
||||
|
||||
for (const key of Object.keys(query)) {
|
||||
const keySplit = key.split(".");
|
||||
|
||||
const appSlug = keySplit[1];
|
||||
|
||||
if (enabledAppSlugs.includes(appSlug)) {
|
||||
enabledRoutingFormApp = appSlug;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return enabledRoutingFormApp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the owner of the contact directly from CRM
|
||||
*/
|
||||
async function getOwnerEmailFromCrm(eventData: EventData, email: string): Promise<string | null> {
|
||||
const crmContactOwnerEmail = await getCRMContactOwnerForRRLeadSkip(email, eventData.metadata);
|
||||
if (!crmContactOwnerEmail) return null;
|
||||
// Determine if the contactOwner is a part of the event type
|
||||
const contactOwnerQuery = await prisma.user.findFirst({
|
||||
where: {
|
||||
email: crmContactOwnerEmail,
|
||||
hosts: {
|
||||
some: {
|
||||
eventTypeId: eventData.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
if (!contactOwnerQuery) return null;
|
||||
return crmContactOwnerEmail;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles custom lookup field logic
|
||||
*/
|
||||
async function getTeamMemberEmailUsingRoutingFormHandler({
|
||||
bookerEmail,
|
||||
eventTypeId,
|
||||
attributeRoutingConfig,
|
||||
crmAppSlug,
|
||||
}: {
|
||||
bookerEmail: string;
|
||||
eventTypeId: number;
|
||||
attributeRoutingConfig: AttributeRoutingConfig | null;
|
||||
crmAppSlug: string;
|
||||
}) {
|
||||
const nullReturnValue = { email: null, skipContactOwner: false };
|
||||
|
||||
if (!attributeRoutingConfig) return nullReturnValue;
|
||||
|
||||
// If the skipContactOwner is enabled then don't return an team member email
|
||||
if (attributeRoutingConfig.skipContactOwner) return { ...nullReturnValue, skipContactOwner: true };
|
||||
|
||||
const appBookingFormHandler = (await import("@calcom/app-store/routing-forms/appBookingFormHandler"))
|
||||
.default;
|
||||
const appHandler = appBookingFormHandler[crmAppSlug];
|
||||
|
||||
if (!appHandler) return nullReturnValue;
|
||||
|
||||
const { email: userEmail } = await appHandler(bookerEmail, attributeRoutingConfig, eventTypeId);
|
||||
|
||||
if (!userEmail) return nullReturnValue;
|
||||
|
||||
// Determine if the user is a part of the event type
|
||||
const userQuery = await prisma.user.findFirst({
|
||||
where: {
|
||||
email: userEmail,
|
||||
hosts: {
|
||||
some: {
|
||||
eventTypeId: eventTypeId,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!userQuery) return nullReturnValue;
|
||||
|
||||
return { ...nullReturnValue, email: userEmail };
|
||||
}
|
||||
|
||||
async function getTeamMemberEmailForResponseOrContact({
|
||||
bookerEmail,
|
||||
eventTypeId,
|
||||
eventData,
|
||||
routingFormResponseId,
|
||||
chosenRoute,
|
||||
crmAppSlug,
|
||||
}: {
|
||||
bookerEmail: string;
|
||||
eventTypeId: number;
|
||||
eventData: EventData;
|
||||
routingFormResponseId?: number | null;
|
||||
/**
|
||||
* If provided, we won't go look for the route from DB.
|
||||
*/
|
||||
chosenRoute?: LocalRoute;
|
||||
crmAppSlug: string;
|
||||
}) {
|
||||
if (eventData.schedulingType !== SchedulingType.ROUND_ROBIN) return null;
|
||||
|
||||
const attributeRoutingConfigGetterData = routingFormResponseId
|
||||
? { routingFormResponseId, eventTypeId }
|
||||
: chosenRoute
|
||||
? { route: chosenRoute }
|
||||
: null;
|
||||
|
||||
// If we have found crmAppSlug, it means that the CRM App in the routing-form will handle the logic
|
||||
if (attributeRoutingConfigGetterData && crmAppSlug) {
|
||||
log.debug(
|
||||
"Using CRM App handler in routing-forms",
|
||||
safeStringify({ attributeRoutingConfigGetterData, crmAppSlug })
|
||||
);
|
||||
const attributeRoutingConfig = await getAttributeRoutingConfig(attributeRoutingConfigGetterData);
|
||||
const { email, skipContactOwner } = await getTeamMemberEmailUsingRoutingFormHandler({
|
||||
bookerEmail,
|
||||
eventTypeId,
|
||||
attributeRoutingConfig,
|
||||
crmAppSlug,
|
||||
});
|
||||
|
||||
if (skipContactOwner) return null;
|
||||
if (email) return email;
|
||||
} else {
|
||||
log.debug("Getting the contact owner email from CRM");
|
||||
return await getOwnerEmailFromCrm(eventData, bookerEmail);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export async function getTeamMemberEmailForResponseOrContactUsingUrlQuery({
|
||||
query,
|
||||
eventTypeId,
|
||||
eventData,
|
||||
chosenRoute,
|
||||
}: {
|
||||
query: ParsedUrlQuery;
|
||||
eventTypeId: number;
|
||||
eventData: EventData;
|
||||
chosenRoute?: LocalRoute;
|
||||
}) {
|
||||
// Without email no lookup is possible
|
||||
if (!query.email || typeof query.email !== "string") {
|
||||
return null;
|
||||
}
|
||||
|
||||
log.debug("getTeamMemberEmailForResponseOrContactUsingUrlQuery", safeStringify({ query }));
|
||||
|
||||
const crmAppSlug = getEnabledRoutingFormAppSlugFromQuery(query);
|
||||
if (!crmAppSlug) return null;
|
||||
|
||||
const routingFormResponseId = getRoutingFormResponseIdFromQuery(query);
|
||||
|
||||
return await getTeamMemberEmailForResponseOrContact({
|
||||
bookerEmail: query.email,
|
||||
eventTypeId,
|
||||
eventData,
|
||||
routingFormResponseId,
|
||||
chosenRoute,
|
||||
crmAppSlug,
|
||||
});
|
||||
}
|
||||
@@ -1,12 +1,6 @@
|
||||
import type { Prisma } from "@prisma/client";
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
import type { ParsedUrlQuery } from "querystring";
|
||||
import { z } from "zod";
|
||||
|
||||
import { getCRMContactOwnerForRRLeadSkip } from "@calcom/app-store/_utils/CRMRoundRobinSkip";
|
||||
import { ROUTING_FORM_RESPONSE_ID_QUERY_STRING } from "@calcom/app-store/routing-forms/lib/constants";
|
||||
import { enabledAppSlugs } from "@calcom/app-store/routing-forms/lib/enabledApps";
|
||||
import { zodRoutes as routesSchema } from "@calcom/app-store/routing-forms/zod";
|
||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
import type { GetBookingType } from "@calcom/features/bookings/lib/get-booking";
|
||||
import { getBookingForReschedule } from "@calcom/features/bookings/lib/get-booking";
|
||||
@@ -14,7 +8,6 @@ import { getSlugOrRequestedSlug, orgDomainConfig } from "@calcom/features/ee/org
|
||||
import slugify from "@calcom/lib/slugify";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { RedirectType } from "@calcom/prisma/client";
|
||||
import { SchedulingType } from "@calcom/prisma/enums";
|
||||
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
||||
|
||||
import { getTemporaryOrgRedirect } from "@lib/getTemporaryOrgRedirect";
|
||||
@@ -93,7 +86,9 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
||||
const ssr = await ssrInit(context);
|
||||
const fromRedirectOfNonOrgLink = context.query.orgRedirection === "true";
|
||||
const isUnpublished = team.parent ? !team.parent.slug : !team.slug;
|
||||
|
||||
const { getTeamMemberEmailForResponseOrContactUsingUrlQuery } = await import(
|
||||
"@calcom/web/lib/getTeamMemberEmailFromCrm"
|
||||
);
|
||||
return {
|
||||
props: {
|
||||
eventData: {
|
||||
@@ -117,148 +112,11 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
||||
isInstantMeeting: eventData && queryIsInstantMeeting ? true : false,
|
||||
themeBasis: null,
|
||||
orgBannerUrl: team.parent?.bannerUrl ?? "",
|
||||
teamMemberEmail: await handleGettingTeamMemberEmail(query, eventTypeId, eventData),
|
||||
teamMemberEmail: await getTeamMemberEmailForResponseOrContactUsingUrlQuery({
|
||||
query,
|
||||
eventTypeId,
|
||||
eventData,
|
||||
}),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
interface EventData {
|
||||
id: number;
|
||||
isInstantEvent: boolean;
|
||||
schedulingType: SchedulingType | null;
|
||||
metadata: Prisma.JsonValue | null;
|
||||
length: number;
|
||||
}
|
||||
|
||||
async function handleGettingTeamMemberEmail(
|
||||
query: ParsedUrlQuery,
|
||||
eventTypeId: number,
|
||||
eventData: EventData
|
||||
) {
|
||||
if (
|
||||
!query.email ||
|
||||
typeof query.email !== "string" ||
|
||||
eventData.schedulingType !== SchedulingType.ROUND_ROBIN
|
||||
)
|
||||
return null;
|
||||
|
||||
// Check if a routing form was completed and an routing form option is enabled
|
||||
if (
|
||||
ROUTING_FORM_RESPONSE_ID_QUERY_STRING in query &&
|
||||
Object.values(query).some((value) => value === "true")
|
||||
) {
|
||||
const { email, skipContactOwner } = await handleRoutingFormOption(query, eventTypeId);
|
||||
|
||||
if (skipContactOwner) return null;
|
||||
if (email) return email;
|
||||
} else {
|
||||
return await getTeamMemberEmail(eventData, query.email);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
async function handleRoutingFormOption(query: ParsedUrlQuery, eventTypeId: number) {
|
||||
const nullReturnValue = { email: null, skipContactOwner: false };
|
||||
|
||||
if (typeof query.email !== "string") return nullReturnValue;
|
||||
|
||||
const routingFormQuery = await prisma.app_RoutingForms_Form.findFirst({
|
||||
where: {
|
||||
responses: {
|
||||
some: {
|
||||
id: Number(query[ROUTING_FORM_RESPONSE_ID_QUERY_STRING]),
|
||||
},
|
||||
},
|
||||
},
|
||||
select: {
|
||||
routes: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!routingFormQuery || !routingFormQuery?.routes) return nullReturnValue;
|
||||
|
||||
const parsedRoutes = routesSchema.safeParse(routingFormQuery.routes);
|
||||
|
||||
if (!parsedRoutes.success || !parsedRoutes.data) return nullReturnValue;
|
||||
|
||||
// Find the route with the attributeRoutingConfig
|
||||
const route = parsedRoutes.data.find((route) => {
|
||||
if ("action" in route) {
|
||||
return route.action.eventTypeId === eventTypeId;
|
||||
}
|
||||
});
|
||||
|
||||
if (!route || !("attributeRoutingConfig" in route)) return nullReturnValue;
|
||||
|
||||
// Get attributeRoutingConfig for the form
|
||||
const attributeRoutingConfig = route.attributeRoutingConfig;
|
||||
|
||||
if (!attributeRoutingConfig) return nullReturnValue;
|
||||
|
||||
// If the skipContactOwner is enabled then don't return an team member email
|
||||
if (attributeRoutingConfig?.skipContactOwner) return { ...nullReturnValue, skipContactOwner: true };
|
||||
|
||||
// Determine if a routing form enabled app is in the query. Then pass it to the proper handler
|
||||
// Routing form apps will have the format cal.appSlug
|
||||
let enabledRoutingFormApp;
|
||||
|
||||
for (const key of Object.keys(query)) {
|
||||
const keySplit = key.split(".");
|
||||
|
||||
const appSlug = keySplit[1];
|
||||
|
||||
if (enabledAppSlugs.includes(appSlug)) {
|
||||
enabledRoutingFormApp = appSlug;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!enabledRoutingFormApp) return nullReturnValue;
|
||||
|
||||
const appBookingFormHandler = (await import("@calcom/app-store/routing-forms/appBookingFormHandler"))
|
||||
.default;
|
||||
const appHandler = appBookingFormHandler[enabledRoutingFormApp];
|
||||
|
||||
if (!appHandler) return nullReturnValue;
|
||||
|
||||
const { email: userEmail } = await appHandler(query.email, attributeRoutingConfig, eventTypeId);
|
||||
|
||||
if (!userEmail) return nullReturnValue;
|
||||
|
||||
// Determine if the user is a part of the event type
|
||||
const userQuery = await await prisma.user.findFirst({
|
||||
where: {
|
||||
email: userEmail,
|
||||
hosts: {
|
||||
some: {
|
||||
eventTypeId: eventTypeId,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!userQuery) return nullReturnValue;
|
||||
|
||||
return { ...nullReturnValue, email: userEmail };
|
||||
}
|
||||
|
||||
async function getTeamMemberEmail(eventData: EventData, email: string): Promise<string | null> {
|
||||
// Pre-requisites
|
||||
if (!eventData || !email || eventData.schedulingType !== SchedulingType.ROUND_ROBIN) return null;
|
||||
const crmContactOwnerEmail = await getCRMContactOwnerForRRLeadSkip(email, eventData.metadata);
|
||||
if (!crmContactOwnerEmail) return null;
|
||||
// Determine if the contactOwner is a part of the event type
|
||||
const contactOwnerQuery = await prisma.user.findFirst({
|
||||
where: {
|
||||
email: crmContactOwnerEmail,
|
||||
hosts: {
|
||||
some: {
|
||||
eventTypeId: eventData.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
if (!contactOwnerQuery) return null;
|
||||
return crmContactOwnerEmail;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { createNextApiHandler } from "@calcom/trpc/server/createNextApiHandler";
|
||||
import { routingFormsRouter } from "@calcom/trpc/server/routers/viewer/routing-forms/_router";
|
||||
|
||||
export default createNextApiHandler(routingFormsRouter);
|
||||
@@ -2680,6 +2680,8 @@
|
||||
"booking_limits_updated_successfully": "Booking limits updated successfully",
|
||||
"you_are_unauthorized_to_make_this_change_to_the_booking": "You are unauthorized to make this change to the booking",
|
||||
"matching_members": "Matching members",
|
||||
"matching_members_queue_using_attribute_weights": "Matching members queue (using attribute weights)",
|
||||
"matching_members_queue_using_event_assignee_weights": "Matching members queue (using event assignee weights)",
|
||||
"no_matching_members": "No matching members. It will fallback to using the team members assigned to the event type.",
|
||||
"no_matching_members_will_fallback_to_all_assigned_members": "No matching members. It will fallback to using the team members assigned to the event type. Consider adding a fallback or correcting the logic of using_fallback_members",
|
||||
"hide_calendar_event_details": "Hide calendar event details on shared calendars",
|
||||
@@ -2729,6 +2731,8 @@
|
||||
"add_new_field": "Add new field",
|
||||
"you_dont_have_access_to_reroute_this_booking": "You don't have access to reroute this booking",
|
||||
"form_response_not_found": "Form response not found",
|
||||
"contact_owner": "Contact owner",
|
||||
"contact_owner_not_found": "Not found",
|
||||
"using_fallback_members": "Using fallback members",
|
||||
"chosen_route": "Chosen Route",
|
||||
"attribute_logic_matched": "Attribute logic matched",
|
||||
@@ -2767,6 +2771,7 @@
|
||||
"booking_start_date": "Booking start date",
|
||||
"booking_created_date": "Booking created date",
|
||||
"booking_reassigned_to_host": "Booking reassigned to {{host}}",
|
||||
"no_contact_owner": "No contact owner",
|
||||
"routing_forms_created": "Routing Forms Created",
|
||||
"routing_forms_total_responses": "Total Responses",
|
||||
"routing_forms_total_responses_without_booking": "Total Responses Without Booking",
|
||||
|
||||
Reference in new issue
Block a user