Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 37 additions & 29 deletions packages/features/credentials/handleDeleteCredential.ts
Original file line number Diff line number Diff line change
Expand Up @@ -277,39 +277,47 @@ const handleDeleteCredential = async ({
},
});

for (const booking of unpaidBookings) {
await prisma.booking.update({
where: {
id: booking.id,
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",
},
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,
},
});

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.attendee.deleteMany({
where: {
bookingId: {
in: unpaidBookingsIds,
},
});

await prisma.bookingReference.updateMany({
where: {
bookingId: booking.id,
},
});
await prisma.bookingReference.updateMany({
where: {
bookingId: {
in: unpaidBookingsIds,
},
data: { deleted: true },
});

},
data: { deleted: true },
});
for (const booking of unpaidBookings) {
const attendeesListPromises = booking.attendees.map(async (attendee) => {
return {
name: attendee.name,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down Expand Up @@ -198,6 +199,7 @@ describe("createCRMEvent", () => {
};

prismaMock.booking.findUnique.mockResolvedValue(mockBooking);
prismaMock.credential.findMany.mockResolvedValueOnce([]);

const payload = JSON.stringify({
bookingUid: "booking-123",
Expand Down Expand Up @@ -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"));
Expand Down Expand Up @@ -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"));
Expand Down Expand Up @@ -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"));
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 },
]);
Expand Down
94 changes: 57 additions & 37 deletions packages/features/tasker/tasks/crm/createCRMEvent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,52 +111,72 @@ export async function createCRMEvent(payload: string): Promise<void> {
});

const errorPerApp: Record<AppSlug, UnknownError> = {};
// Find enabled CRM apps for the event type

// Parse apps and collect credential IDs for enabled CRM apps
const appInfoMap = new Map<string, { app: any; credentialId: number }>();
const credentialIds = new Set<number>();

for (const appSlug of Object.keys(eventTypeAppMetadata)) {
// 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 appData = eventTypeAppMetadata[appSlug as keyof typeof eventTypeAppMetadata];
const appDataSchema = appDataSchemas[appSlug as keyof typeof appDataSchemas];

const appParse = appDataSchema.safeParse(appData);
if (!appData || !appDataSchema) {
throw new Error(`Could not find appData or appDataSchema for ${appSlug}`);
}

if (!appParse.success) {
log.error(`Error parsing event type app data for bookingUid ${bookingUid}`, appParse?.error);
continue;
}
const appParse = appDataSchema.safeParse(appData);

const app = appParse.data;
const hasCrmCategory =
app.appCategories && app.appCategories.some((category: string) => category === "crm");
if (!appParse.success) {
log.error(`Error parsing event type app data for bookingUid ${bookingUid}`, appParse?.error);
continue;
}

if (!app.enabled || !app.credentialId || !hasCrmCategory) {
log.info(`Skipping CRM app ${appSlug}`, {
enabled: app.enabled,
credentialId: app.credentialId,
hasCrmCategory,
});
continue;
}
const app = appParse.data;
const hasCrmCategory =
app.appCategories && app.appCategories.some((category: string) => category === "crm");

const crmCredential = await prisma.credential.findUnique({
where: {
id: app.credentialId,
},
include: {
user: {
select: {
email: true,
},
},
},
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<number, (typeof crmCredentials)[number]>();
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 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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<number, (typeof enrichedUsers)[0]>();
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,
});
}

Expand Down
Loading