add customs clearing filter and enhance clearance documents page for non-customs contracts

This commit is contained in:
Marshal
2026-07-12 14:14:21 +00:00
parent 84ba56f7d0
commit e7e5b93ffc
10 changed files with 417 additions and 10 deletions

View File

@@ -2258,7 +2258,7 @@ export class BookingBatchService implements OnModuleInit {
this.logger.log(
`[BATCH] ALLOCATED ${booking.reference} (${reason}) to train on schedule ${scheduleId}`,
);
this.notifier.secured(booking, reason);
this.notifier.secured(booking, reason, scheduleId);
void this.triggerWagonAllocation(scheduleId);
void this.markWagonAllocatedMilestone(booking.id);
// Customer tracking: freight payment settled (commercial pay-window path).

View File

@@ -9,6 +9,7 @@ import {
import { Booking } from '../bookings/entities/booking.entity';
import { NotificationsService } from '../notifications/notifications.service';
import { NotificationInboxService } from '../notification-inbox/notification-inbox.service';
import { TrainSchedulesRepository } from '../train-schedules/train-schedules.repository';
import { BATCH_TIMEZONE } from './booking-batch.constants';
@Injectable()
@@ -18,8 +19,37 @@ export class BookingNotifierService {
constructor(
private readonly notifications: NotificationsService,
private readonly inbox: NotificationInboxService,
private readonly trainSchedules: TrainSchedulesRepository,
) {}
/**
* Human-readable description of a train schedule for customer messages:
* reference (or train number) + route + departure date. Never leaks a UUID —
* falls back to a generic phrase when the schedule can't be loaded.
*/
private async scheduleLabel(scheduleId?: string | null): Promise<string> {
const fallback = 'your selected train';
if (!scheduleId) return fallback;
try {
const s = await this.trainSchedules.findByIdWithStations(scheduleId);
if (!s) return fallback;
const ref = s.reference ?? s.trainNumber ?? null;
const route =
s.originStation?.label && s.destinationStation?.label
? ` (${s.originStation.label}${s.destinationStation.label})`
: '';
const departure = s.scheduledDepartureDate
? `, departing ${new Date(s.scheduledDepartureDate).toLocaleDateString('en-GB', { timeZone: BATCH_TIMEZONE })}`
: '';
return ref ? `train ${ref}${route}${departure}` : `${fallback}${route}${departure}`;
} catch (err) {
this.logger.warn(
`scheduleLabel(${scheduleId}) failed: ${(err as Error).message}`,
);
return fallback;
}
}
private ref(b: Booking): string {
return `${b.reference}${b.isGovernment ? ' (gov)' : ''}`;
}
@@ -127,12 +157,15 @@ export class BookingNotifierService {
});
}
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');
this.inApp(b, 'Wagon allocated', msg);
secured(b: Booking, reason: 'paid' | 'gov', scheduleId?: string | null): void {
void (async () => {
const label = await this.scheduleLabel(scheduleId ?? b.trainScheduleId);
const msg = `Booking ${b.reference ?? b.id} allocated on ${label}${
reason === 'gov' ? ' (government)' : ''
}.`;
void this.notifyContact(b, msg, 'ALLOCATED');
this.inApp(b, 'Wagon allocated', msg);
})();
}
expired(b: Booking): void {