mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-08 11:18:17 +00:00
feat(loading): notify loaded and left-behind containers, surface the CAS
A booking is routinely loaded in parts, and nothing told the customer which containers boarded and which stayed behind. The carriage acceptance sheet was the only record, and it both totalled up cargo still sitting in the yard and lived inside a Warehouse documents bundle that direct truck-to-train cargo has no business in. The sheet now marks each wagon Loaded or Not loaded and totals only the loaded ones. On load, the customer gets an in-app, SMS and email notice carrying the train number, route, departure time and both container lists — capped to a summary on SMS and email, complete in the inbox. Anything left behind also raises a warehouse-desk notice so somebody owns finding it space. That desk is addressed by a new warehouse_inventory:get_notification permission: a recipient selector, not a route guard, so ops can assign who gets pinged without granting access to anything. The GRN notice went out over SMS alone, to whatever phone number the gate clerk typed. Where the receive carries a booking it now resolves the company and delivers in-app, SMS and email, skipping the typed phone so the customer is not texted twice; manual and backlog receives keep the old path.
This commit is contained in:
@@ -1509,6 +1509,7 @@ export class WarehouseInventoryService {
|
||||
grnNumber: string;
|
||||
direction?: string | null;
|
||||
warehouseId?: string | null;
|
||||
bookingId?: string | null;
|
||||
};
|
||||
booking: {
|
||||
companyId?: string | null;
|
||||
@@ -1730,6 +1731,7 @@ export class WarehouseInventoryService {
|
||||
grnNumber,
|
||||
direction: dto.direction,
|
||||
warehouseId: dto.warehouseId,
|
||||
bookingId,
|
||||
},
|
||||
booking,
|
||||
bookingId,
|
||||
@@ -3107,6 +3109,7 @@ export class WarehouseInventoryService {
|
||||
grnNumber,
|
||||
direction: bookingDirection,
|
||||
warehouseId: dto.warehouseId,
|
||||
bookingId: dto.bookingId ?? null,
|
||||
});
|
||||
|
||||
return saved.id;
|
||||
@@ -6614,10 +6617,9 @@ export class WarehouseInventoryService {
|
||||
grnNumber: string;
|
||||
direction?: string | null;
|
||||
warehouseId?: string | null;
|
||||
/** Resolves the company, which unlocks in-app + email alongside the SMS. */
|
||||
bookingId?: string | null;
|
||||
}): Promise<void> {
|
||||
const phone = params.phone?.trim();
|
||||
if (!phone) return;
|
||||
|
||||
const ownerName = params.ownerName?.trim() || 'Customer';
|
||||
const bookingReference = params.bookingReference?.trim();
|
||||
const message =
|
||||
@@ -6627,6 +6629,47 @@ export class WarehouseInventoryService {
|
||||
(params.direction ? `Direction: ${params.direction}. ` : '') +
|
||||
`Thank you.`;
|
||||
|
||||
// A booking gives us the company, and with it the customer's inbox and
|
||||
// email — not just whatever phone number the gate clerk typed. Without one
|
||||
// (manual or backlog receive) the typed phone is all there is, so the
|
||||
// original SMS-only path stands.
|
||||
let companyId: string | null = null;
|
||||
if (params.bookingId) {
|
||||
try {
|
||||
const [row]: Array<{ companyId: string | null }> = await this.dataSource.query(
|
||||
`SELECT company_id AS "companyId"
|
||||
FROM freight.bookings
|
||||
WHERE id = $1 AND deleted_at IS NULL`,
|
||||
[params.bookingId],
|
||||
);
|
||||
companyId = row?.companyId ?? null;
|
||||
} catch (error) {
|
||||
this.logger.warn(`GRN ${params.grnNumber}: company lookup failed: ${String(error)}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (companyId) {
|
||||
try {
|
||||
await this.inbox.notify({
|
||||
recipients: { companyId },
|
||||
audience: NotificationAudience.PORTAL,
|
||||
type: NotificationType.DOCUMENT_ACTION,
|
||||
title: 'Cargo received — GRN issued',
|
||||
body: message,
|
||||
link: params.bookingId ? `/bookings/${params.bookingId}` : undefined,
|
||||
data: { grnNumber: params.grnNumber, bookingId: params.bookingId ?? null },
|
||||
});
|
||||
// Sends SMS *and* email to the company's own contacts, so the typed
|
||||
// phone below is skipped to avoid texting the customer twice.
|
||||
await sendCompanyChannels(this.dataSource, this.notifications, companyId, message);
|
||||
return;
|
||||
} catch (error) {
|
||||
this.logger.error(`Failed to notify company for GRN ${params.grnNumber}: ${String(error)}`);
|
||||
}
|
||||
}
|
||||
|
||||
const phone = params.phone?.trim();
|
||||
if (!phone) return;
|
||||
try {
|
||||
await this.notifications.directSend('sms', phone, message);
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user