import { eq } from 'drizzle-orm'; import { db } from '@/lib/db'; import { userPortRoles, roles } from '@/lib/db/schema/users'; import type { RolePermissions } from '@/lib/db/schema/users'; import { createNotification } from '@/lib/services/notifications.service'; import { getSetting } from '@/lib/services/settings.service'; import { getQueue } from '@/lib/queue'; import { logger } from '@/lib/logger'; interface InquiryNotificationParams { portId: string; portSlug: string; interestId: string; clientFullName: string; clientEmail: string; clientPhone: string; mooringNumber: string | null; firstName: string; } /** * Sends inquiry notifications to all relevant parties: * 1. Confirmation email to the client * 2. In-app + email notifications to CRM users with interests.view permission * 3. Email to any external recipients configured in system settings * * All operations are fire-and-forget (errors are logged, not thrown). */ export async function sendInquiryNotifications(params: InquiryNotificationParams): Promise { const { portId, portSlug, interestId, clientFullName, clientEmail, clientPhone, mooringNumber, firstName, } = params; // 1. Queue client confirmation email try { const contactEmailSetting = await getSetting('inquiry_contact_email', portId); const contactEmail = typeof contactEmailSetting?.value === 'string' ? contactEmailSetting.value : 'sales@portnimara.com'; const emailQueue = getQueue('email'); await emailQueue.add('send-inquiry-confirmation', { to: clientEmail, firstName, mooringNumber, contactEmail, }); } catch (err) { logger.error({ err, interestId }, 'Failed to queue client confirmation email'); } // 2. Notify CRM users with interests.view permission on this port try { const usersWithAccess = await findUsersWithInterestsPermission(portId); const crmUrl = `/${portSlug}/interests/${interestId}`; for (const userId of usersWithAccess) { try { await createNotification({ portId, userId, type: 'new_registration', title: 'New Interest Registered', description: `${clientFullName} has registered interest${mooringNumber ? ` in Berth ${mooringNumber}` : ''} via the website`, link: crmUrl, entityType: 'interest', entityId: interestId, dedupeKey: `inquiry-${interestId}`, }); } catch (err) { logger.error({ err, userId, interestId }, 'Failed to create notification for user'); } } } catch (err) { logger.error({ err, interestId }, 'Failed to notify CRM users'); } // 3. Notify external recipients try { const recipientsSetting = await getSetting('inquiry_notification_recipients', portId); const externalEmails: string[] = Array.isArray(recipientsSetting?.value) ? recipientsSetting.value : []; if (externalEmails.length > 0) { const emailQueue = getQueue('email'); const appUrl = process.env.APP_URL ?? ''; const crmUrl = `${appUrl}/${portSlug}/interests/${interestId}`; for (const externalEmail of externalEmails) { await emailQueue.add('send-inquiry-sales-notification', { to: externalEmail, fullName: clientFullName, email: clientEmail, phone: clientPhone, mooringNumber, crmUrl, }); } } } catch (err) { logger.error({ err, interestId }, 'Failed to notify external recipients'); } } /** * Finds all user IDs on a port whose role grants `interests.view` permission. */ async function findUsersWithInterestsPermission(portId: string): Promise { const assignments = await db .select({ userId: userPortRoles.userId, permissions: roles.permissions, }) .from(userPortRoles) .innerJoin(roles, eq(userPortRoles.roleId, roles.id)) .where(eq(userPortRoles.portId, portId)); const userIds = new Set(); for (const row of assignments) { const perms = row.permissions as RolePermissions | null; if (perms?.interests?.view) { userIds.add(row.userId); } } return Array.from(userIds); }