Files
calendar/packages/app-store/_utils/CRMRoundRobinSkip.ts
T
Joe Au-YeungGitHubjoe@cal.com <j.auyeung419@gmail.com>Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>Alex van Andel
3784421f02 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>
2025-07-21 14:31:31 +00:00

73 lines
2.3 KiB
TypeScript

import type { Prisma } from "@prisma/client";
import type { z } from "zod";
import CrmManager from "@calcom/lib/crmManager/crmManager";
import logger from "@calcom/lib/logger";
import { prisma } from "@calcom/prisma";
import type { EventTypeAppMetadataSchema } from "@calcom/prisma/zod-utils";
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;
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;
const crm = await getCRMManagerWithRRLeadSkip(parsedEventTypeMetadata.data.apps);
if (!crm) return nullReturnValue;
const { crmManager, crmAppSlug } = crm;
const startTime = performance.now();
const contact = await crmManager.getContacts({ emails: bookerEmail, forRoundRobinSkip: true });
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,
recordId: contact[0].id ?? null,
};
}
async function getCRMManagerWithRRLeadSkip(apps: z.infer<typeof EventTypeAppMetadataSchema>) {
let crmRoundRobinLeadSkip;
for (const appKey in apps) {
const app = apps[appKey as keyof typeof apps];
if (
app.enabled &&
typeof app.appCategories === "object" &&
app.appCategories.some((category: string) => category === "crm") &&
app.roundRobinLeadSkip
) {
crmRoundRobinLeadSkip = app;
break;
}
}
if (!crmRoundRobinLeadSkip) return;
const crmCredential = await prisma.credential.findUnique({
where: {
id: crmRoundRobinLeadSkip.credentialId,
},
include: {
user: {
select: {
email: true,
},
},
},
});
if (!crmCredential) return;
return {
crmManager: new CrmManager(crmCredential, crmRoundRobinLeadSkip),
crmAppSlug: crmCredential.appId,
};
}