import { DataSource } from 'typeorm'; import { Logger } from '@nestjs/common'; import { NotificationAudience, NotificationType } from '@edr/types'; import { NotificationsService } from './notifications.service'; import { NotificationInboxService } from '../notification-inbox/notification-inbox.service'; import { companyNotifyEmailExpr, companyNotifyPhoneExpr, primaryContactUserJoin, } from './resolve-company-phone.util'; /** * Best-effort SMS + email fan-out to a company's contacts. Looks up the * company's phone/email and sends the message over both channels, swallowing * per-channel failures so a missing provider never breaks the caller's flow. */ export async function sendCompanyChannels( dataSource: DataSource, notifications: NotificationsService, companyId: string, message: string, ): Promise { const [contact]: Array<{ phone: string | null; email: string | null }> = await dataSource.query( `SELECT ${companyNotifyPhoneExpr('co')} AS phone, ${companyNotifyEmailExpr('co')} AS email FROM freight.companies co ${primaryContactUserJoin('co')} WHERE co.id = $1 AND co.deleted_at IS NULL`, [companyId], ); if (contact?.phone) { try { await notifications.directSend('sms', contact.phone, message); } catch { /* best-effort: SMS provider unavailable */ } } if (contact?.email) { try { await notifications.directSend('email', contact.email, message); } catch { /* best-effort: email provider unavailable */ } } } /** * Tell the customer their export carriage acceptance sheet is ready to * download from the portal — the sheet itself is generated on demand by * BookingsService.carriageAcceptanceSheet, never stored, so this is a * "ready" notice + link, not an attachment (the email pipeline carries text * only). Shared by every path that makes a booking's handover final: the * warehouse gate on receive, and direct truck-to-train on load (that cargo * never sees a warehouse, so its handover moment IS the load). */ export async function notifyCarriageAcceptanceReady( dataSource: DataSource, notifications: NotificationsService, inbox: NotificationInboxService, bookingId: string, logger: Logger, ): Promise { try { const [b]: Array<{ companyId: string | null; reference: string }> = await dataSource.query( `SELECT company_id AS "companyId", reference FROM freight.bookings WHERE id = $1 AND deleted_at IS NULL`, [bookingId], ); if (!b?.companyId) return; const body = `Your carriage acceptance sheet for booking ${b.reference} is ready to download from the portal.`; await inbox.notify({ recipients: { companyId: b.companyId }, audience: NotificationAudience.PORTAL, type: NotificationType.DOCUMENT_ACTION, title: 'Carriage acceptance sheet ready', body, link: `/bookings/${bookingId}`, data: { bookingId, reference: b.reference }, }); await sendCompanyChannels(dataSource, notifications, b.companyId, body); } catch (err) { logger.warn(`Carriage acceptance ready notify failed for ${bookingId}: ${(err as Error).message}`); } }