feat: include record IDs in Salesforce assignment reason strings (#22561)
* feat: include record IDs in Salesforce assignment reason strings - Add recordId parameter to assignmentReasonHandler function - Include Contact ID, Lead ID, and Account ID in assignment reason strings - Update entire call chain to pass record IDs from CRM service - Maintain backward compatibility with optional recordId parameter Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com> * fix: resolve lint warnings in assignment reason handler implementation - Change Record<string, any> to Record<string, unknown> in BookingHandlerInput type - Remove unused eventTypeId variable in getAttributeRoutingConfig function Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com> * fix: revert to Record<string, any> with ESLint disable for BookingHandlerInput - Revert from Record<string, unknown> to Record<string, any> to maintain type compatibility - Add ESLint disable comment to suppress no-explicit-any warning - Maintains consistency with handleNewRecurringBooking.ts pattern Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com> * feat: pass CRM record ID from booker state to handleNewBooking - Add crmRecordId field to booker store interface and initialization - Update mapBookingToMutationInput to include record ID from booker state - Modify handleNewBooking to extract record ID from bookingData parameter - Add crmRecordId to BookingCreateBody schema in Prisma layer - Follow existing pattern for CRM fields (teamMemberEmail, crmOwnerRecordType, crmAppSlug) - Ensures record ID flows: booker store → booking form → mapBookingToMutationInput → handleNewBooking This replaces the previous backend CRM service extraction approach with frontend booker state approach as requested by the user. Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com> * Pass crmRecordId as prop --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: Alex van Andel <me@alexvanandel.com>
This commit is contained in:
22 files changed
+147
-36
No files matched your search
@@ -122,12 +122,14 @@ export async function getCRMData(
|
||||
const crmContactOwnerEmail = query["cal.crmContactOwnerEmail"];
|
||||
const crmContactOwnerRecordType = query["cal.crmContactOwnerRecordType"];
|
||||
const crmAppSlugParam = query["cal.crmAppSlug"];
|
||||
const crmRecordIdParam = query["cal.crmRecordId"];
|
||||
|
||||
let teamMemberEmail = Array.isArray(crmContactOwnerEmail) ? crmContactOwnerEmail[0] : crmContactOwnerEmail;
|
||||
let crmOwnerRecordType = Array.isArray(crmContactOwnerRecordType)
|
||||
? crmContactOwnerRecordType[0]
|
||||
: crmContactOwnerRecordType;
|
||||
let crmAppSlug = Array.isArray(crmAppSlugParam) ? crmAppSlugParam[0] : crmAppSlugParam;
|
||||
let crmRecordId = Array.isArray(crmRecordIdParam) ? crmRecordIdParam[0] : crmRecordIdParam;
|
||||
|
||||
if (!teamMemberEmail || !crmOwnerRecordType || !crmAppSlug) {
|
||||
const { getTeamMemberEmailForResponseOrContactUsingUrlQuery } = await import(
|
||||
@@ -137,6 +139,7 @@ export async function getCRMData(
|
||||
email,
|
||||
recordType,
|
||||
crmAppSlug: crmAppSlugQuery,
|
||||
recordId: crmRecordIdQuery,
|
||||
} = await getTeamMemberEmailForResponseOrContactUsingUrlQuery({
|
||||
query,
|
||||
eventData,
|
||||
@@ -145,11 +148,13 @@ export async function getCRMData(
|
||||
teamMemberEmail = email ?? undefined;
|
||||
crmOwnerRecordType = recordType ?? undefined;
|
||||
crmAppSlug = crmAppSlugQuery ?? undefined;
|
||||
crmRecordId = crmRecordIdQuery ?? undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
teamMemberEmail,
|
||||
crmOwnerRecordType,
|
||||
crmAppSlug,
|
||||
crmRecordId,
|
||||
};
|
||||
}
|
||||
@@ -29,9 +29,9 @@ function mockGetCRMContactOwnerForRRLeadSkip({
|
||||
}) {
|
||||
vi.mocked(getCRMContactOwnerForRRLeadSkip).mockImplementation((_bookerEmail, _eventMetadata) => {
|
||||
if (_bookerEmail === bookerEmail) {
|
||||
return Promise.resolve({ email: teamMemberEmail, recordType: null, crmAppSlug: null });
|
||||
return Promise.resolve({ email: teamMemberEmail, recordType: null, crmAppSlug: null, recordId: null });
|
||||
}
|
||||
return Promise.resolve({ email: null, recordType: null, crmAppSlug: null });
|
||||
return Promise.resolve({ email: null, recordType: null, crmAppSlug: null, recordId: null });
|
||||
});
|
||||
}
|
||||
|
||||
@@ -45,9 +45,9 @@ function mockBookingFormHandler({
|
||||
vi.mocked(bookingFormHandlers.salesforce).mockImplementation(
|
||||
(_bookerEmail, _attributeRoutingConfig, _eventTypeId) => {
|
||||
if (_bookerEmail === bookerEmail) {
|
||||
return Promise.resolve({ email: teamMemberEmail, recordType: null });
|
||||
return Promise.resolve({ email: teamMemberEmail, recordType: null, recordId: null });
|
||||
}
|
||||
return Promise.resolve({ email: null, recordType: null });
|
||||
return Promise.resolve({ email: null, recordType: null, recordId: null });
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -92,6 +92,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
||||
const crmContactOwnerEmail = query["cal.crmContactOwnerEmail"];
|
||||
const crmContactOwnerRecordType = query["cal.crmContactOwnerRecordType"];
|
||||
const crmAppSlugParam = query["cal.crmAppSlug"];
|
||||
const crmRecordIdParam = query["cal.crmRecordId"];
|
||||
|
||||
// Handle string[] type from query params
|
||||
let teamMemberEmail = Array.isArray(crmContactOwnerEmail) ? crmContactOwnerEmail[0] : crmContactOwnerEmail;
|
||||
@@ -101,6 +102,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
||||
: crmContactOwnerRecordType;
|
||||
|
||||
let crmAppSlug = Array.isArray(crmAppSlugParam) ? crmAppSlugParam[0] : crmAppSlugParam;
|
||||
let crmRecordId = Array.isArray(crmRecordIdParam) ? crmRecordIdParam[0] : crmRecordIdParam;
|
||||
|
||||
if (!teamMemberEmail || !crmOwnerRecordType || !crmAppSlug) {
|
||||
const { getTeamMemberEmailForResponseOrContactUsingUrlQuery } = await import(
|
||||
@@ -110,6 +112,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
||||
email,
|
||||
recordType,
|
||||
crmAppSlug: crmAppSlugQuery,
|
||||
recordId,
|
||||
} = await getTeamMemberEmailForResponseOrContactUsingUrlQuery({
|
||||
query,
|
||||
eventData,
|
||||
@@ -118,6 +121,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
||||
teamMemberEmail = email ?? undefined;
|
||||
crmOwnerRecordType = recordType ?? undefined;
|
||||
crmAppSlug = crmAppSlugQuery ?? undefined;
|
||||
crmRecordId = recordId ?? undefined;
|
||||
}
|
||||
|
||||
const organizationSettings = getOrganizationSEOSettings(team);
|
||||
@@ -167,6 +171,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
||||
teamMemberEmail,
|
||||
crmOwnerRecordType,
|
||||
crmAppSlug,
|
||||
crmRecordId,
|
||||
isSEOIndexable: allowSEOIndexing,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -34,6 +34,7 @@ function Type({
|
||||
teamMemberEmail,
|
||||
crmOwnerRecordType,
|
||||
crmAppSlug,
|
||||
crmRecordId,
|
||||
isEmbed,
|
||||
useApiV2,
|
||||
}: PageProps) {
|
||||
@@ -64,6 +65,7 @@ function Type({
|
||||
teamMemberEmail={teamMemberEmail}
|
||||
crmOwnerRecordType={crmOwnerRecordType}
|
||||
crmAppSlug={crmAppSlug}
|
||||
crmRecordId={crmRecordId}
|
||||
/>
|
||||
</main>
|
||||
</BookingPageErrorBoundary>
|
||||
|
||||
@@ -10,8 +10,13 @@ import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
||||
export async function getCRMContactOwnerForRRLeadSkip(
|
||||
bookerEmail: string,
|
||||
eventTypeMetadata: Prisma.JsonValue
|
||||
): Promise<{ email: string | null; recordType: string | null; crmAppSlug: string | null }> {
|
||||
const nullReturnValue = { email: null, recordType: null, crmAppSlug: "" };
|
||||
): Promise<{
|
||||
email: string | null;
|
||||
recordType: string | null;
|
||||
crmAppSlug: string | null;
|
||||
recordId: string | null;
|
||||
}> {
|
||||
const nullReturnValue = { email: null, recordType: null, crmAppSlug: "", recordId: null };
|
||||
const parsedEventTypeMetadata = EventTypeMetaDataSchema.safeParse(eventTypeMetadata);
|
||||
if (!parsedEventTypeMetadata.success || !parsedEventTypeMetadata.data?.apps) return nullReturnValue;
|
||||
|
||||
@@ -24,7 +29,12 @@ export async function getCRMContactOwnerForRRLeadSkip(
|
||||
const endTime = performance.now();
|
||||
logger.info(`Fetching from CRM took ${endTime - startTime}ms`);
|
||||
if (!contact?.length || !contact[0].ownerEmail) return nullReturnValue;
|
||||
return { email: contact[0].ownerEmail ?? null, recordType: contact[0].recordType ?? null, crmAppSlug };
|
||||
return {
|
||||
email: contact[0].ownerEmail ?? null,
|
||||
recordType: contact[0].recordType ?? null,
|
||||
crmAppSlug,
|
||||
recordId: contact[0].id ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
async function getCRMManagerWithRRLeadSkip(apps: z.infer<typeof EventTypeAppMetadataSchema>) {
|
||||
|
||||
@@ -5,7 +5,7 @@ type AppBookingFormHandler = (
|
||||
attendeeEmail: string,
|
||||
attributeRoutingConfig: AttributeRoutingConfig,
|
||||
eventTypeId: number
|
||||
) => Promise<{ email: string | null; recordType: string | null }>;
|
||||
) => Promise<{ email: string | null; recordType: string | null; recordId: string | null }>;
|
||||
|
||||
const appBookingFormHandler: Record<string, AppBookingFormHandler> = {
|
||||
salesforce: routingFormBookingFormHandler,
|
||||
|
||||
@@ -40,10 +40,16 @@ export default async function routerGetCrmContactOwnerEmail({
|
||||
const eventTypeMetadata = eventType.metadata;
|
||||
if (!eventTypeMetadata) return null;
|
||||
|
||||
let contactOwner: { email: string | null; recordType: string | null; crmAppSlug: string | null } = {
|
||||
let contactOwner: {
|
||||
email: string | null;
|
||||
recordType: string | null;
|
||||
crmAppSlug: string | null;
|
||||
recordId: string | null;
|
||||
} = {
|
||||
email: null,
|
||||
recordType: null,
|
||||
crmAppSlug: null,
|
||||
recordId: null,
|
||||
};
|
||||
// Determine if there is a CRM option enabled in the chosen route
|
||||
for (const appSlug of enabledAppSlugs) {
|
||||
|
||||
@@ -267,6 +267,7 @@ describe("handleResponse", () => {
|
||||
email: "owner@example.com",
|
||||
recordType: "contact",
|
||||
crmAppSlug: "hubspot",
|
||||
recordId: "123",
|
||||
});
|
||||
vi.mocked(findTeamMembersMatchingAttributeLogic).mockResolvedValue({
|
||||
teamMembersMatchingAttributeLogic: [{ userId: 123, result: "MATCH" as any }],
|
||||
|
||||
@@ -96,6 +96,7 @@ const _handleResponse = async ({
|
||||
let crmContactOwnerEmail: string | null = null;
|
||||
let crmContactOwnerRecordType: string | null = null;
|
||||
let crmAppSlug: string | null = null;
|
||||
let crmRecordId: string | null = null;
|
||||
let timeTaken: Record<string, number | null> = {};
|
||||
if (chosenRoute) {
|
||||
if (isRouter(chosenRoute)) {
|
||||
@@ -119,6 +120,7 @@ const _handleResponse = async ({
|
||||
crmContactOwnerEmail = contactOwnerQuery?.email ?? null;
|
||||
crmContactOwnerRecordType = contactOwnerQuery?.recordType ?? null;
|
||||
crmAppSlug = contactOwnerQuery?.crmAppSlug ?? null;
|
||||
crmRecordId = contactOwnerQuery?.recordId ?? null;
|
||||
})(),
|
||||
(async () => {
|
||||
const teamMembersMatchingAttributeLogicWithResult =
|
||||
@@ -212,6 +214,7 @@ const _handleResponse = async ({
|
||||
crmContactOwnerEmail,
|
||||
crmContactOwnerRecordType,
|
||||
crmAppSlug,
|
||||
crmRecordId,
|
||||
attributeRoutingConfig: chosenRoute
|
||||
? "attributeRoutingConfig" in chosenRoute
|
||||
? chosenRoute.attributeRoutingConfig
|
||||
|
||||
@@ -8,27 +8,52 @@ export async function assignmentReasonHandler({
|
||||
recordType,
|
||||
teamMemberEmail,
|
||||
routingFormResponseId,
|
||||
recordId,
|
||||
}: {
|
||||
recordType: string;
|
||||
teamMemberEmail: string;
|
||||
routingFormResponseId: number;
|
||||
recordId?: string;
|
||||
}) {
|
||||
const returnObject = { reasonEnum: AssignmentReasonEnum.SALESFORCE_ASSIGNMENT };
|
||||
|
||||
switch (recordType) {
|
||||
case SalesforceRecordEnum.CONTACT:
|
||||
return { ...returnObject, assignmentReason: `Salesforce contact owner: ${teamMemberEmail}` };
|
||||
return {
|
||||
...returnObject,
|
||||
assignmentReason: `Salesforce contact owner: ${teamMemberEmail}${
|
||||
recordId ? ` (Contact ID: ${recordId})` : ""
|
||||
}`,
|
||||
};
|
||||
case SalesforceRecordEnum.LEAD:
|
||||
return { ...returnObject, assignmentReason: `Salesforce lead owner: ${teamMemberEmail}` };
|
||||
return {
|
||||
...returnObject,
|
||||
assignmentReason: `Salesforce lead owner: ${teamMemberEmail}${
|
||||
recordId ? ` (Lead ID: ${recordId})` : ""
|
||||
}`,
|
||||
};
|
||||
case SalesforceRecordEnum.ACCOUNT:
|
||||
return { ...returnObject, assignmentReason: `Salesforce account owner: ${teamMemberEmail}` };
|
||||
return {
|
||||
...returnObject,
|
||||
assignmentReason: `Salesforce account owner: ${teamMemberEmail}${
|
||||
recordId ? ` (Account ID: ${recordId})` : ""
|
||||
}`,
|
||||
};
|
||||
case RoutingReasons.ACCOUNT_LOOKUP_FIELD:
|
||||
const assignmentReason = await handleAccountLookupFieldReason(routingFormResponseId, teamMemberEmail);
|
||||
const assignmentReason = await handleAccountLookupFieldReason(
|
||||
routingFormResponseId,
|
||||
teamMemberEmail,
|
||||
recordId
|
||||
);
|
||||
return { ...returnObject, assignmentReason };
|
||||
}
|
||||
}
|
||||
|
||||
async function handleAccountLookupFieldReason(routingFormResponseId: number, teamMemberEmail: string) {
|
||||
async function handleAccountLookupFieldReason(
|
||||
routingFormResponseId: number,
|
||||
teamMemberEmail: string,
|
||||
recordId?: string
|
||||
) {
|
||||
const routingFormResponse = await prisma.app_RoutingForms_FormResponse.findUnique({
|
||||
where: {
|
||||
id: routingFormResponseId,
|
||||
@@ -67,6 +92,8 @@ async function handleAccountLookupFieldReason(routingFormResponseId: number, tea
|
||||
const accountLookupFieldName = salesforceConfig?.rrSKipToAccountLookupFieldName;
|
||||
|
||||
return accountLookupFieldName
|
||||
? `Salesforce account lookup field: ${accountLookupFieldName} - ${teamMemberEmail}`
|
||||
? `Salesforce account lookup field: ${accountLookupFieldName} - ${teamMemberEmail}${
|
||||
recordId ? ` (Account ID: ${recordId})` : ""
|
||||
}`
|
||||
: undefined;
|
||||
}
|
||||
@@ -16,7 +16,7 @@ const routingFormBookingFormHandler = async (
|
||||
!salesforceSettings.rrSkipToAccountLookupField ||
|
||||
!salesforceSettings.rrSKipToAccountLookupFieldName
|
||||
)
|
||||
return { email: null, recordType: null };
|
||||
return { email: null, recordType: null, recordId: null };
|
||||
|
||||
const appData = await EventTypeService.getEventTypeAppDataFromId(eventTypeId, "salesforce");
|
||||
|
||||
@@ -24,7 +24,7 @@ const routingFormBookingFormHandler = async (
|
||||
|
||||
const credential = await CredentialRepository.findFirstByIdWithKeyAndUser({ id: credentialId });
|
||||
|
||||
if (!credential) return { email: null, recordType: null };
|
||||
if (!credential) return { email: null, recordType: null, recordId: null };
|
||||
|
||||
const crm = new SalesforceCRMService(credential, {});
|
||||
|
||||
@@ -34,7 +34,11 @@ const routingFormBookingFormHandler = async (
|
||||
SalesforceRecordEnum.ACCOUNT
|
||||
);
|
||||
|
||||
return { email: userLookupEmail?.email ?? null, recordType: RoutingReasons.ACCOUNT_LOOKUP_FIELD as string };
|
||||
return {
|
||||
email: userLookupEmail?.email ?? null,
|
||||
recordType: RoutingReasons.ACCOUNT_LOOKUP_FIELD as string,
|
||||
recordId: null,
|
||||
};
|
||||
};
|
||||
|
||||
export default routingFormBookingFormHandler;
|
||||
@@ -36,6 +36,7 @@ type StoreInitializeType = {
|
||||
teamMemberEmail?: string | null;
|
||||
crmOwnerRecordType?: string | null;
|
||||
crmAppSlug?: string | null;
|
||||
crmRecordId?: string | null;
|
||||
isPlatform?: boolean;
|
||||
allowUpdatingUrlParams?: boolean;
|
||||
};
|
||||
@@ -163,6 +164,7 @@ export type BookerStore = {
|
||||
teamMemberEmail?: string | null;
|
||||
crmOwnerRecordType?: string | null;
|
||||
crmAppSlug?: string | null;
|
||||
crmRecordId?: string | null;
|
||||
isPlatform?: boolean;
|
||||
allowUpdatingUrlParams?: boolean;
|
||||
};
|
||||
@@ -306,6 +308,7 @@ export const useBookerStore = createWithEqualityFn<BookerStore>((set, get) => ({
|
||||
teamMemberEmail,
|
||||
crmOwnerRecordType,
|
||||
crmAppSlug,
|
||||
crmRecordId,
|
||||
isPlatform = false,
|
||||
allowUpdatingUrlParams = true,
|
||||
}: StoreInitializeType) => {
|
||||
@@ -324,7 +327,8 @@ export const useBookerStore = createWithEqualityFn<BookerStore>((set, get) => ({
|
||||
get().rescheduledBy === rescheduledBy &&
|
||||
get().teamMemberEmail === teamMemberEmail &&
|
||||
get().crmOwnerRecordType === crmOwnerRecordType &&
|
||||
get().crmAppSlug
|
||||
get().crmAppSlug === crmAppSlug &&
|
||||
get().crmRecordId === crmRecordId
|
||||
)
|
||||
return;
|
||||
set({
|
||||
@@ -347,6 +351,7 @@ export const useBookerStore = createWithEqualityFn<BookerStore>((set, get) => ({
|
||||
teamMemberEmail,
|
||||
crmOwnerRecordType,
|
||||
crmAppSlug,
|
||||
crmRecordId,
|
||||
isPlatform,
|
||||
allowUpdatingUrlParams,
|
||||
});
|
||||
@@ -456,6 +461,7 @@ export const useInitializeBookerStore = ({
|
||||
teamMemberEmail,
|
||||
crmOwnerRecordType,
|
||||
crmAppSlug,
|
||||
crmRecordId,
|
||||
isPlatform = false,
|
||||
allowUpdatingUrlParams = true,
|
||||
}: StoreInitializeType) => {
|
||||
@@ -479,6 +485,7 @@ export const useInitializeBookerStore = ({
|
||||
teamMemberEmail,
|
||||
crmOwnerRecordType,
|
||||
crmAppSlug,
|
||||
crmRecordId,
|
||||
isPlatform,
|
||||
allowUpdatingUrlParams,
|
||||
});
|
||||
@@ -501,6 +508,7 @@ export const useInitializeBookerStore = ({
|
||||
teamMemberEmail,
|
||||
crmOwnerRecordType,
|
||||
crmAppSlug,
|
||||
crmRecordId,
|
||||
isPlatform,
|
||||
allowUpdatingUrlParams,
|
||||
]);
|
||||
|
||||
@@ -100,6 +100,7 @@ export interface BookerProps {
|
||||
teamMemberEmail?: string | null;
|
||||
crmOwnerRecordType?: string | null;
|
||||
crmAppSlug?: string | null;
|
||||
crmRecordId?: string | null;
|
||||
areInstantMeetingParametersSet?: boolean | null;
|
||||
userLocale?: string | null;
|
||||
hasValidLicense?: boolean;
|
||||
|
||||
@@ -27,6 +27,7 @@ export type BookingOptions = {
|
||||
teamMemberEmail?: string | null;
|
||||
crmOwnerRecordType?: string | null;
|
||||
crmAppSlug?: string | null;
|
||||
crmRecordId?: string | null;
|
||||
orgSlug?: string;
|
||||
routingFormSearchParams?: RoutingFormSearchParams;
|
||||
isDryRunProp?: boolean;
|
||||
@@ -49,6 +50,7 @@ export const mapBookingToMutationInput = ({
|
||||
teamMemberEmail,
|
||||
crmOwnerRecordType,
|
||||
crmAppSlug,
|
||||
crmRecordId,
|
||||
orgSlug,
|
||||
routingFormSearchParams,
|
||||
isDryRunProp,
|
||||
@@ -86,6 +88,7 @@ export const mapBookingToMutationInput = ({
|
||||
teamMemberEmail,
|
||||
crmOwnerRecordType,
|
||||
crmAppSlug,
|
||||
crmRecordId,
|
||||
orgSlug,
|
||||
routedTeamMemberIds,
|
||||
routingFormResponseId,
|
||||
|
||||
@@ -378,6 +378,7 @@ export type PlatformParams = {
|
||||
};
|
||||
|
||||
export type BookingHandlerInput = {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
bookingData: Record<string, any>;
|
||||
userId?: number;
|
||||
// These used to come from headers but now we're passing them as params
|
||||
@@ -597,6 +598,7 @@ async function handler(
|
||||
});
|
||||
|
||||
const contactOwnerEmail = skipContactOwner ? null : contactOwnerFromReq;
|
||||
const crmRecordId: string | undefined = reqBody.crmRecordId ?? undefined;
|
||||
|
||||
let routingFormResponse = null;
|
||||
|
||||
@@ -1397,6 +1399,7 @@ async function handler(
|
||||
teamMemberEmail: contactOwnerEmail,
|
||||
recordType: reqBody.crmOwnerRecordType,
|
||||
routingFormResponseId,
|
||||
recordId: crmRecordId,
|
||||
});
|
||||
} else if (routingFormResponseId && teamId) {
|
||||
assignmentReason = await AssignmentReasonRecorder.routingFormRoute({
|
||||
|
||||
@@ -149,18 +149,25 @@ export default class AssignmentReasonRecorder {
|
||||
teamMemberEmail,
|
||||
recordType,
|
||||
routingFormResponseId,
|
||||
recordId,
|
||||
}: {
|
||||
bookingId: number;
|
||||
crmAppSlug: string;
|
||||
teamMemberEmail: string;
|
||||
recordType: string;
|
||||
routingFormResponseId: number;
|
||||
recordId?: string;
|
||||
}) {
|
||||
const appAssignmentReasonHandler = (await import("./appAssignmentReasonHandler")).default;
|
||||
const appHandler = appAssignmentReasonHandler[crmAppSlug];
|
||||
if (!appHandler) return;
|
||||
|
||||
const crmRoutingReason = await appHandler({ recordType, teamMemberEmail, routingFormResponseId });
|
||||
const crmRoutingReason = await appHandler({
|
||||
recordType,
|
||||
teamMemberEmail,
|
||||
routingFormResponseId,
|
||||
recordId,
|
||||
});
|
||||
|
||||
if (!crmRoutingReason || !crmRoutingReason.assignmentReason) return;
|
||||
|
||||
|
||||
@@ -5,10 +5,12 @@ type AppAssignmentReasonHandler = ({
|
||||
recordType,
|
||||
teamMemberEmail,
|
||||
routingFormResponseId,
|
||||
recordId,
|
||||
}: {
|
||||
recordType: string;
|
||||
teamMemberEmail: string;
|
||||
routingFormResponseId: number;
|
||||
recordId?: string;
|
||||
}) => Promise<{ assignmentReason: string | undefined; reasonEnum: AssignmentReasonEnum } | undefined>;
|
||||
|
||||
const appBookingFormHandler: Record<string, AppAssignmentReasonHandler> = {
|
||||
|
||||
@@ -21,7 +21,7 @@ interface EventData {
|
||||
length: number;
|
||||
}
|
||||
|
||||
const returnNullValue = { email: null, recordType: null, crmAppSlug: null };
|
||||
const returnNullValue = { email: null, recordType: null, crmAppSlug: null, recordId: null };
|
||||
|
||||
async function findUserByEmailWhoIsAHostOfEventType({
|
||||
email,
|
||||
@@ -57,7 +57,7 @@ async function getAttributeRoutingConfig(
|
||||
if ("route" in data) {
|
||||
return data.route.attributeRoutingConfig ?? null;
|
||||
}
|
||||
const { routingFormResponseId, eventTypeId } = data;
|
||||
const { routingFormResponseId } = data;
|
||||
|
||||
const routingFormResponseQuery = await prisma.app_RoutingForms_FormResponse.findUnique({
|
||||
where: {
|
||||
@@ -117,7 +117,12 @@ function getEnabledRoutingFormAppSlugFromQuery(query: ParsedUrlQuery) {
|
||||
async function getOwnerEmailFromCrm(
|
||||
eventData: EventData,
|
||||
email: string
|
||||
): Promise<{ email: string | null; recordType: string | null; crmAppSlug: string | null }> {
|
||||
): Promise<{
|
||||
email: string | null;
|
||||
recordType: string | null;
|
||||
crmAppSlug: string | null;
|
||||
recordId: string | null;
|
||||
}> {
|
||||
const crmContactOwner = await getCRMContactOwnerForRRLeadSkip(email, eventData.metadata);
|
||||
|
||||
if (!crmContactOwner?.email) return returnNullValue;
|
||||
@@ -159,7 +164,7 @@ async function getTeamMemberEmailUsingRoutingFormHandler({
|
||||
attributeRoutingConfig: AttributeRoutingConfig | null;
|
||||
crmAppSlug: string;
|
||||
}) {
|
||||
const nullReturnValue = { email: null, skipContactOwner: false, recordType: "" };
|
||||
const nullReturnValue = { email: null, skipContactOwner: false, recordType: "", recordId: "" };
|
||||
|
||||
if (!attributeRoutingConfig) return nullReturnValue;
|
||||
|
||||
@@ -171,7 +176,11 @@ async function getTeamMemberEmailUsingRoutingFormHandler({
|
||||
const appHandler = appBookingFormHandler[crmAppSlug];
|
||||
if (!appHandler) return nullReturnValue;
|
||||
|
||||
const { email: userEmail, recordType } = await appHandler(bookerEmail, attributeRoutingConfig, eventTypeId);
|
||||
const {
|
||||
email: userEmail,
|
||||
recordType,
|
||||
recordId,
|
||||
} = await appHandler(bookerEmail, attributeRoutingConfig, eventTypeId);
|
||||
|
||||
if (!userEmail) return nullReturnValue;
|
||||
|
||||
@@ -180,7 +189,7 @@ async function getTeamMemberEmailUsingRoutingFormHandler({
|
||||
|
||||
if (!userQuery) return nullReturnValue;
|
||||
|
||||
return { ...nullReturnValue, email: userEmail, recordType };
|
||||
return { ...nullReturnValue, email: userEmail, recordType, recordId };
|
||||
}
|
||||
|
||||
async function getTeamMemberEmailForResponseOrContact({
|
||||
@@ -198,7 +207,12 @@ async function getTeamMemberEmailForResponseOrContact({
|
||||
*/
|
||||
chosenRoute?: LocalRoute;
|
||||
crmAppSlug?: string;
|
||||
}) {
|
||||
}): Promise<{
|
||||
email: string | null;
|
||||
recordType: string | null;
|
||||
crmAppSlug: string | null;
|
||||
recordId: string | null;
|
||||
}> {
|
||||
const eventTypeId = eventData.id;
|
||||
if (eventData.schedulingType !== SchedulingType.ROUND_ROBIN) return returnNullValue;
|
||||
|
||||
@@ -215,15 +229,17 @@ async function getTeamMemberEmailForResponseOrContact({
|
||||
safeStringify({ attributeRoutingConfigGetterData, crmAppSlug })
|
||||
);
|
||||
const attributeRoutingConfig = await getAttributeRoutingConfig(attributeRoutingConfigGetterData);
|
||||
const { email, skipContactOwner, recordType } = await getTeamMemberEmailUsingRoutingFormHandler({
|
||||
bookerEmail,
|
||||
eventTypeId,
|
||||
attributeRoutingConfig,
|
||||
crmAppSlug,
|
||||
});
|
||||
const { email, skipContactOwner, recordType, recordId } = await getTeamMemberEmailUsingRoutingFormHandler(
|
||||
{
|
||||
bookerEmail,
|
||||
eventTypeId,
|
||||
attributeRoutingConfig,
|
||||
crmAppSlug,
|
||||
}
|
||||
);
|
||||
|
||||
if (skipContactOwner) return returnNullValue;
|
||||
if (email) return { email, recordType, crmAppSlug };
|
||||
if (email) return { email, recordType, crmAppSlug, recordId };
|
||||
} else {
|
||||
log.debug("Getting the contact owner email from CRM");
|
||||
return await getOwnerEmailFromCrm(eventData, bookerEmail);
|
||||
@@ -240,7 +256,12 @@ export async function getTeamMemberEmailForResponseOrContactUsingUrlQuery({
|
||||
query: ParsedUrlQuery;
|
||||
eventData: EventData;
|
||||
chosenRoute?: LocalRoute;
|
||||
}) {
|
||||
}): Promise<{
|
||||
email: string | null;
|
||||
recordType: string | null;
|
||||
crmAppSlug: string | null;
|
||||
recordId: string | null;
|
||||
}> {
|
||||
// Without email no lookup is possible
|
||||
if (!query.email || typeof query.email !== "string") {
|
||||
return returnNullValue;
|
||||
|
||||
@@ -158,6 +158,7 @@ export const BookerPlatformWrapper = (
|
||||
teamMemberEmail,
|
||||
crmAppSlug,
|
||||
crmOwnerRecordType,
|
||||
crmRecordId: props.crmRecordId,
|
||||
eventId: event?.data?.id,
|
||||
rescheduleUid: props.rescheduleUid ?? null,
|
||||
bookingUid: props.bookingUid ?? null,
|
||||
|
||||
@@ -64,7 +64,6 @@ export const BookerWebWrapper = (props: BookerWebWrapperAtomProps) => {
|
||||
// This event isn't processed by BookingPageTagManager because BookingPageTagManager hasn't loaded when it is fired. I think we should have a queue in fire method to handle this.
|
||||
sdkActionManager?.fire("navigatedToBooker", {});
|
||||
}, []);
|
||||
|
||||
useInitializeBookerStore({
|
||||
...props,
|
||||
eventId: props.entity.eventTypeId ?? event?.data?.id,
|
||||
|
||||
@@ -62,6 +62,7 @@ export const useHandleBookEvent = ({
|
||||
const teamMemberEmail = useBookerStore((state) => state.teamMemberEmail);
|
||||
const crmOwnerRecordType = useBookerStore((state) => state.crmOwnerRecordType);
|
||||
const crmAppSlug = useBookerStore((state) => state.crmAppSlug);
|
||||
const crmRecordId = useBookerStore((state) => state.crmRecordId);
|
||||
const handleError = (err: any) => {
|
||||
const errorMessage = err?.message ? t(err.message) : t("can_you_try_again");
|
||||
showToast(errorMessage, "error");
|
||||
@@ -108,6 +109,7 @@ export const useHandleBookEvent = ({
|
||||
teamMemberEmail,
|
||||
crmOwnerRecordType,
|
||||
crmAppSlug,
|
||||
crmRecordId,
|
||||
orgSlug: orgSlug ? orgSlug : undefined,
|
||||
routingFormSearchParams,
|
||||
isDryRunProp: isBookingDryRun,
|
||||
|
||||
@@ -23,6 +23,7 @@ export const bookingCreateBodySchema = z.object({
|
||||
orgSlug: z.string().optional(),
|
||||
teamMemberEmail: z.string().nullish(),
|
||||
crmOwnerRecordType: z.string().nullish(),
|
||||
crmRecordId: z.string().nullish(),
|
||||
routedTeamMemberIds: z.array(z.number()).nullish(),
|
||||
routingFormResponseId: z.number().optional(),
|
||||
skipContactOwner: z.boolean().optional(),
|
||||
|
||||
Reference in new issue
Block a user