mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 08:20:58 +00:00
Direct truck-to-train export cargo skips the warehouse, so the existing carriage acceptance sheet ready notice (fired on warehouse receive) never reached these bookings. Their handover moment is the load itself. Extract notifyCarriageAcceptanceReady into a shared notifications util (was private to WarehouseInventoryService) and call it from BookingJourneyService.loadBooking for EXPORT + DIRECT_TO_TRAIN bookings, right after the GRN gate, before the load transaction proceeds.
86 lines
3.1 KiB
TypeScript
86 lines
3.1 KiB
TypeScript
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<void> {
|
|
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<void> {
|
|
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}`);
|
|
}
|
|
}
|