mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-27 00:52:50 +00:00
- Added `unplaced` method in `BookingNotifierService` to log warnings for bookings that cannot be placed on any train. - Introduced `getAvailableDays` method in `TrainSchedulingService` to retrieve distinct days with open departures for a given route. - Created `AvailableDaysQueryDto` for querying available days based on origin and destination yards. - Updated `TrainSchedulingController` to expose an endpoint for available days. - Modified frontend components to support day-level booking, allowing customers to select only a day without pinning to a specific train. - Removed references to train schedules in booking forms and review steps, emphasizing day selection. - Added a database migration to create an index for efficient querying of bookings by route and day.
86 lines
3.1 KiB
TypeScript
86 lines
3.1 KiB
TypeScript
import { Injectable, Logger } from '@nestjs/common';
|
|
|
|
import { Booking } from '../bookings/entities/booking.entity';
|
|
import { NotificationsService } from '../notifications/notifications.service';
|
|
import { PAYMENT_WINDOW_MS } from './booking-batch.constants';
|
|
|
|
@Injectable()
|
|
export class BookingNotifierService {
|
|
private readonly logger = new Logger(BookingNotifierService.name);
|
|
|
|
constructor(private readonly notifications: NotificationsService) {}
|
|
|
|
private ref(b: Booking): string {
|
|
return `${b.reference}${b.isGovernment ? ' (gov)' : ''}`;
|
|
}
|
|
|
|
private async notifyContact(
|
|
b: Booking,
|
|
message: string,
|
|
logLabel: string,
|
|
): Promise<void> {
|
|
this.logger.log(`${logLabel} — ${this.ref(b)}`);
|
|
const phone = b.company?.contactPersonPhone ?? b.company?.phone ?? null;
|
|
const email = b.company?.email ?? b.company?.generalManagerEmail ?? null;
|
|
|
|
if (phone) {
|
|
try {
|
|
await this.notifications.directSend('sms', phone, message);
|
|
} catch (err) {
|
|
this.logger.warn(`SMS failed for ${this.ref(b)}: ${(err as Error).message}`);
|
|
}
|
|
}
|
|
if (email) {
|
|
try {
|
|
await this.notifications.directSend('email', email, message);
|
|
} catch (err) {
|
|
this.logger.warn(`Email failed for ${this.ref(b)}: ${(err as Error).message}`);
|
|
}
|
|
}
|
|
if (!phone && !email) {
|
|
this.logger.warn(`No contact on file for ${this.ref(b)} — notification not sent`);
|
|
}
|
|
}
|
|
|
|
async payNow(b: Booking, deadline: Date): Promise<void> {
|
|
const payMinutes = Math.round(PAYMENT_WINDOW_MS / 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');
|
|
}
|
|
|
|
secured(b: Booking, reason: 'paid' | 'gov'): void {
|
|
const msg = `Booking ${b.reference ?? b.id} allocated on train schedule ${b.trainScheduleId ?? ''}${
|
|
reason === 'gov' ? ' (government)' : ''
|
|
}.`;
|
|
void this.notifyContact(b, msg, 'ALLOCATED');
|
|
}
|
|
|
|
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');
|
|
}
|
|
|
|
scheduleFull(b: Booking): void {
|
|
this.logger.warn(
|
|
`SCHEDULE FULL — ${this.ref(b)} could not be placed; change schedule, pick another day, or cancel.`,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Staff-facing warning when a pooled booking fits no train on its chosen day.
|
|
* It stays pending and is retried next batch; staff can add capacity or pin it
|
|
* to a train manually. Mirrors {@link scheduleFull} — no customer notification.
|
|
*/
|
|
unplaced(b: Booking, day: string): void {
|
|
this.logger.warn(
|
|
`UNPLACED — ${this.ref(b)} could not be placed on any train for ${day}; add capacity or assign it manually.`,
|
|
);
|
|
}
|
|
|
|
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');
|
|
}
|
|
}
|