mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-08 19:28:17 +00:00
enhance booking and contract notification systems
- Added detailed logging for socket connection events in useBookingWindowSocket. - Introduced new notification types for contract status and schedule updates. - Updated notification visuals to include new icons for contract status. - Enhanced notification href resolution for contract status and schedule updates. - Implemented booking lifecycle notifier service for customer and staff notifications. - Created contract notifier service for managing contract lifecycle notifications. - Added end-to-end tests for booking window socket functionality.
This commit is contained in:
@@ -1,13 +1,23 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import {
|
||||
NotificationAudience,
|
||||
NotificationType,
|
||||
NotifyInput,
|
||||
} from '@edr/types';
|
||||
|
||||
import { Booking } from '../bookings/entities/booking.entity';
|
||||
import { NotificationsService } from '../notifications/notifications.service';
|
||||
import { NotificationInboxService } from '../notification-inbox/notification-inbox.service';
|
||||
import { BATCH_TIMEZONE } from './booking-batch.constants';
|
||||
|
||||
@Injectable()
|
||||
export class BookingNotifierService {
|
||||
private readonly logger = new Logger(BookingNotifierService.name);
|
||||
|
||||
constructor(private readonly notifications: NotificationsService) {}
|
||||
constructor(
|
||||
private readonly notifications: NotificationsService,
|
||||
private readonly inbox: NotificationInboxService,
|
||||
) {}
|
||||
|
||||
private ref(b: Booking): string {
|
||||
return `${b.reference}${b.isGovernment ? ' (gov)' : ''}`;
|
||||
@@ -41,11 +51,34 @@ export class BookingNotifierService {
|
||||
}
|
||||
}
|
||||
|
||||
/** Persist + push an in-app item to all portal users of the booking's company. */
|
||||
private inApp(
|
||||
b: Booking,
|
||||
title: string,
|
||||
body: string,
|
||||
overrides: Partial<NotifyInput> = {},
|
||||
): void {
|
||||
if (!b.companyId) return; // government/unlinked bookings have no portal users
|
||||
void this.inbox.notify({
|
||||
recipients: { companyId: b.companyId },
|
||||
audience: NotificationAudience.PORTAL,
|
||||
type: NotificationType.SCHEDULE_UPDATE,
|
||||
title,
|
||||
body,
|
||||
link: `/bookings/${b.id}`,
|
||||
data: { bookingId: b.id, reference: b.reference },
|
||||
...overrides,
|
||||
});
|
||||
}
|
||||
|
||||
async payNow(b: Booking, deadline: Date): Promise<void> {
|
||||
const payMinutes = Math.max(1, Math.round((deadline.getTime() - Date.now()) / 60_000));
|
||||
const eat = deadline.toLocaleString('en-GB', { timeZone: 'Africa/Addis_Ababa' });
|
||||
const msg = `Pay within ${payMinutes} minute${payMinutes === 1 ? '' : 's'} to secure train slot ${b.reference ?? b.id}. Deadline: ${eat} EAT.`;
|
||||
await this.notifyContact(b, msg, 'PAY NOW');
|
||||
this.inApp(b, 'Payment window open', msg, {
|
||||
type: NotificationType.INVOICE_ISSUED,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -66,6 +99,9 @@ export class BookingNotifierService {
|
||||
`Pay within ${payMinutes} minute${payMinutes === 1 ? '' : 's'} to accept and ship ${offeredWagons} wagon${offeredWagons === 1 ? '' : 's'} now ` +
|
||||
`(the rest returns to your contract to book later). If you do not pay, the booking stays whole and you can rebook in the next window. Deadline: ${eat} EAT.`;
|
||||
await this.notifyContact(b, msg, 'PAY NOW (PARTIAL)');
|
||||
this.inApp(b, 'Partial allocation offer', msg, {
|
||||
type: NotificationType.INVOICE_ISSUED,
|
||||
});
|
||||
}
|
||||
|
||||
secured(b: Booking, reason: 'paid' | 'gov'): void {
|
||||
@@ -73,11 +109,13 @@ export class BookingNotifierService {
|
||||
reason === 'gov' ? ' (government)' : ''
|
||||
}.`;
|
||||
void this.notifyContact(b, msg, 'ALLOCATED');
|
||||
this.inApp(b, 'Wagon allocated', msg);
|
||||
}
|
||||
|
||||
expired(b: Booking): void {
|
||||
const msg = `Payment window expired for booking ${b.reference ?? b.id}. Reschedule or cancel — no re-approval needed.`;
|
||||
void this.notifyContact(b, msg, 'EXPIRED');
|
||||
this.inApp(b, 'Payment window expired', msg);
|
||||
}
|
||||
|
||||
scheduleFull(b: Booking): void {
|
||||
@@ -100,5 +138,42 @@ export class BookingNotifierService {
|
||||
displaced(b: Booking): void {
|
||||
const msg = `Booking ${b.reference ?? b.id} was displaced by a government booking. Move to another schedule or cancel.`;
|
||||
void this.notifyContact(b, msg, 'DISPLACED');
|
||||
this.inApp(b, 'Booking displaced', msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Staff rescheduled the train carrying this booking to a new departure date.
|
||||
* The booking stays on the train — only the date moved.
|
||||
*/
|
||||
rescheduled(b: Booking, newDeparture: Date): void {
|
||||
const when = newDeparture.toLocaleDateString('en-GB', { timeZone: BATCH_TIMEZONE });
|
||||
const msg = `Booking ${b.reference ?? b.id} has been rescheduled. New departure date: ${when}.`;
|
||||
void this.notifyContact(b, msg, 'RESCHEDULED');
|
||||
this.inApp(b, 'Booking rescheduled', msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Booking was removed from its train during a staff reschedule (not a government
|
||||
* pre-empt). It returns to eligible — the customer must rebook or reschedule.
|
||||
*/
|
||||
removedFromTrain(b: Booking): void {
|
||||
const msg =
|
||||
`Booking ${b.reference ?? b.id} has been removed from its train during rescheduling. ` +
|
||||
`Please rebook or select a new schedule from the portal.`;
|
||||
void this.notifyContact(b, msg, 'REMOVED FROM TRAIN');
|
||||
this.inApp(b, 'Removed from train', msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* The train carrying this booking was moved for maintenance to a new departure
|
||||
* date. The booking stays on the train — only the date moved.
|
||||
*/
|
||||
maintenanceMoved(b: Booking, newDeparture: Date): void {
|
||||
const when = newDeparture.toLocaleDateString('en-GB', { timeZone: BATCH_TIMEZONE });
|
||||
const msg =
|
||||
`The train for booking ${b.reference ?? b.id} was rescheduled for maintenance. ` +
|
||||
`New departure date: ${when}.`;
|
||||
void this.notifyContact(b, msg, 'MAINTENANCE RESCHEDULE');
|
||||
this.inApp(b, 'Train maintenance reschedule', msg);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user