diff --git a/packages/features/credentials/handleDeleteCredential.ts b/packages/features/credentials/handleDeleteCredential.ts index ad43caab0a..c60a3b7219 100644 --- a/packages/features/credentials/handleDeleteCredential.ts +++ b/packages/features/credentials/handleDeleteCredential.ts @@ -277,39 +277,47 @@ const handleDeleteCredential = async ({ }, }); + const unpaidBookingsIds = unpaidBookings.map((booking) => booking.id); + const unpaidBookingsPaymentIds = unpaidBookings.flatMap((booking) => + booking.payment.map((payment) => payment.id) + ); + await prisma.booking.updateMany({ + where: { + id: { + in: unpaidBookingsIds, + }, + }, + data: { + status: BookingStatus.CANCELLED, + cancellationReason: "Payment method removed", + }, + }); + for (const paymentId of unpaidBookingsPaymentIds) { + await deletePayment(paymentId, credential); + } + await prisma.payment.deleteMany({ + where: { + id: { + in: unpaidBookingsPaymentIds, + }, + }, + }); + await prisma.attendee.deleteMany({ + where: { + bookingId: { + in: unpaidBookingsIds, + }, + }, + }); + await prisma.bookingReference.updateMany({ + where: { + bookingId: { + in: unpaidBookingsIds, + }, + }, + data: { deleted: true }, + }); for (const booking of unpaidBookings) { - await prisma.booking.update({ - where: { - id: booking.id, - }, - data: { - status: BookingStatus.CANCELLED, - cancellationReason: "Payment method removed", - }, - }); - - for (const payment of booking.payment) { - await deletePayment(payment.id, credential); - await prisma.payment.delete({ - where: { - id: payment.id, - }, - }); - } - - await prisma.attendee.deleteMany({ - where: { - bookingId: booking.id, - }, - }); - - await prisma.bookingReference.updateMany({ - where: { - bookingId: booking.id, - }, - data: { deleted: true }, - }); - const attendeesListPromises = booking.attendees.map(async (attendee) => { return { name: attendee.name, diff --git a/packages/features/tasker/tasks/crm/__tests__/createCRMEvent.test.ts b/packages/features/tasker/tasks/crm/__tests__/createCRMEvent.test.ts index be39032c95..e3689dc4aa 100644 --- a/packages/features/tasker/tasks/crm/__tests__/createCRMEvent.test.ts +++ b/packages/features/tasker/tasks/crm/__tests__/createCRMEvent.test.ts @@ -131,6 +131,7 @@ describe("createCRMEvent", () => { // Set up Prisma mocks with proper return values prismaMock.booking.findUnique.mockResolvedValueOnce(mockBooking); prismaMock.credential.findUnique.mockResolvedValueOnce(mockCredential); + prismaMock.credential.findMany.mockResolvedValueOnce([mockCredential]); prismaMock.bookingReference.createMany.mockResolvedValueOnce({ count: 1 }); prismaMock.bookingReference.findMany.mockResolvedValueOnce([]); const payload = JSON.stringify({ @@ -198,6 +199,7 @@ describe("createCRMEvent", () => { }; prismaMock.booking.findUnique.mockResolvedValue(mockBooking); + prismaMock.credential.findMany.mockResolvedValueOnce([]); const payload = JSON.stringify({ bookingUid: "booking-123", @@ -238,6 +240,7 @@ describe("createCRMEvent", () => { prismaMock.booking.findUnique.mockResolvedValue(mockBooking); prismaMock.credential.findUnique.mockResolvedValue(null); + prismaMock.credential.findMany.mockResolvedValueOnce([]); prismaMock.bookingReference.findMany.mockResolvedValueOnce([]); mockCreateEvent.mockRejectedValue(new Error("Salesforce API error")); @@ -288,6 +291,7 @@ describe("createCRMEvent", () => { prismaMock.booking.findUnique.mockResolvedValue(mockBooking); prismaMock.credential.findUnique.mockResolvedValue(mockCredential); + prismaMock.credential.findMany.mockResolvedValueOnce([mockCredential]); prismaMock.bookingReference.findMany.mockResolvedValueOnce([]); mockCreateEvent.mockRejectedValue(new RetryableError("Salesforce API Retryable error")); @@ -334,6 +338,7 @@ describe("createCRMEvent", () => { prismaMock.booking.findUnique.mockResolvedValue(mockBooking); prismaMock.credential.findUnique.mockResolvedValue(mockCredential); + prismaMock.credential.findMany.mockResolvedValueOnce([mockCredential]); prismaMock.bookingReference.findMany.mockResolvedValueOnce([]); mockCreateEvent.mockRejectedValue(new Error("Salesforce API error")); @@ -401,6 +406,11 @@ describe("createCRMEvent", () => { .mockResolvedValueOnce(mockSalesforceCredential) .mockResolvedValueOnce(mockHubspotCredential); + prismaMock.credential.findMany.mockResolvedValueOnce([ + mockSalesforceCredential, + mockHubspotCredential, + ]); + prismaMock.bookingReference.findMany.mockResolvedValueOnce([]); // Throw error for first app and resolve for second app @@ -462,6 +472,7 @@ describe("createCRMEvent", () => { prismaMock.booking.findUnique.mockResolvedValue(mockBooking); prismaMock.credential.findUnique.mockResolvedValueOnce(mockSalesforceCredential); + prismaMock.credential.findMany.mockResolvedValueOnce([mockSalesforceCredential]); prismaMock.bookingReference.findMany.mockResolvedValueOnce([ { id: 1, type: "salesforce_crm", uid: "sf-event-123", credentialId: 1, bookingId: 1 }, ]); diff --git a/packages/features/tasker/tasks/crm/createCRMEvent.ts b/packages/features/tasker/tasks/crm/createCRMEvent.ts index f8fe749492..edb783c656 100644 --- a/packages/features/tasker/tasks/crm/createCRMEvent.ts +++ b/packages/features/tasker/tasks/crm/createCRMEvent.ts @@ -111,52 +111,72 @@ export async function createCRMEvent(payload: string): Promise { }); const errorPerApp: Record = {}; - // Find enabled CRM apps for the event type + + // Parse apps and collect credential IDs for enabled CRM apps + const appInfoMap = new Map(); + const credentialIds = new Set(); + for (const appSlug of Object.keys(eventTypeAppMetadata)) { + const appData = eventTypeAppMetadata[appSlug as keyof typeof eventTypeAppMetadata]; + const appDataSchema = appDataSchemas[appSlug as keyof typeof appDataSchemas]; + + if (!appData || !appDataSchema) { + throw new Error(`Could not find appData or appDataSchema for ${appSlug}`); + } + + const appParse = appDataSchema.safeParse(appData); + + if (!appParse.success) { + log.error(`Error parsing event type app data for bookingUid ${bookingUid}`, appParse?.error); + continue; + } + + const app = appParse.data; + const hasCrmCategory = + app.appCategories && app.appCategories.some((category: string) => category === "crm"); + + if (!app.enabled || !app.credentialId || !hasCrmCategory) { + log.info(`Skipping CRM app ${appSlug}`, { + enabled: app.enabled, + credentialId: app.credentialId, + hasCrmCategory, + }); + continue; + } + + appInfoMap.set(appSlug, { app, credentialId: app.credentialId }); + credentialIds.add(app.credentialId); + } + + const crmCredentials = await prisma.credential.findMany({ + where: { + id: { + in: Array.from(credentialIds), + }, + }, + include: { + user: { + select: { + email: true, + }, + }, + }, + }); + + const crmCredentialMap = new Map(); + for (const credential of crmCredentials) { + crmCredentialMap.set(credential.id, credential); + } + //Find enabled CRM apps for the event type + for (const appSlug of Array.from(appInfoMap.keys())) { + const { app, credentialId } = appInfoMap.get(appSlug)!; // Try Catch per app to ensure all apps are tried even if any of them throws an error // If we want to retry for an error from this try catch, then that error must be thrown as a RetryableError try { - const appData = eventTypeAppMetadata[appSlug as keyof typeof eventTypeAppMetadata]; - const appDataSchema = appDataSchemas[appSlug as keyof typeof appDataSchemas]; - if (!appData || !appDataSchema) { - throw new Error(`Could not find appData or appDataSchema for ${appSlug}`); - } - - const appParse = appDataSchema.safeParse(appData); - - if (!appParse.success) { - log.error(`Error parsing event type app data for bookingUid ${bookingUid}`, appParse?.error); - continue; - } - - const app = appParse.data; - const hasCrmCategory = - app.appCategories && app.appCategories.some((category: string) => category === "crm"); - - if (!app.enabled || !app.credentialId || !hasCrmCategory) { - log.info(`Skipping CRM app ${appSlug}`, { - enabled: app.enabled, - credentialId: app.credentialId, - hasCrmCategory, - }); - continue; - } - - const crmCredential = await prisma.credential.findUnique({ - where: { - id: app.credentialId, - }, - include: { - user: { - select: { - email: true, - }, - }, - }, - }); + const crmCredential = crmCredentialMap.get(credentialId); if (!crmCredential) { - throw new Error(`Credential not found for credentialId: ${app.credentialId}`); + throw new Error(`Credential not found for credentialId: ${credentialId}`); } const existingBookingReferenceForTheCredential = existingBookingReferences.find( diff --git a/packages/trpc/server/routers/viewer/availability/team/listTeamAvailability.handler.ts b/packages/trpc/server/routers/viewer/availability/team/listTeamAvailability.handler.ts index 5d4e572dd9..779a9f1b79 100644 --- a/packages/trpc/server/routers/viewer/availability/team/listTeamAvailability.handler.ts +++ b/packages/trpc/server/routers/viewer/availability/team/listTeamAvailability.handler.ts @@ -71,13 +71,19 @@ async function getTeamMembers({ }); const userRepo = new UserRepository(prisma); + const users = memberships.map((membership) => membership.user); + const enrichedUsers = await userRepo.enrichUsersWithTheirProfileExcludingOrgMetadata(users); + const enrichedUserMap = new Map(); + enrichedUsers.forEach((enrichedUser) => { + enrichedUserMap.set(enrichedUser.id, enrichedUser); + }); const membershipWithUserProfile = []; for (const membership of memberships) { + const enrichedUser = enrichedUserMap.get(membership.user.id); + if (!enrichedUser) continue; membershipWithUserProfile.push({ ...membership, - user: await userRepo.enrichUserWithItsProfileExcludingOrgMetadata({ - user: membership.user, - }), + user: enrichedUser, }); }