train gate pass, Telebirr and Wafi

This commit is contained in:
hagiye
2026-07-01 16:23:52 +03:00
parent 38db9a4177
commit 14a00af341
41 changed files with 2807 additions and 151 deletions

View File

@@ -54,7 +54,6 @@ import {
type ImportDjiboutiDocumentType,
} from './entities/import-djibouti-operation.entity';
import {
IMPORT_DJIBOUTI_DOCUMENT_TYPES,
ImportDjiboutiActionDto,
UploadImportDjiboutiDocumentDto,
} from './dto/import-djibouti-operation.dto';
@@ -832,7 +831,7 @@ export class TrainSchedulingService {
}
async getImportDjiboutiOperation(scheduleId: string) {
const schedule = await this.getImportDjiboutiSchedule(scheduleId);
const schedule = await this.getDjiboutiGatepassSchedule(scheduleId);
const operation = await this.getOrCreateImportDjiboutiOperation(scheduleId);
return this.mapImportDjiboutiOperation(schedule, operation);
}
@@ -841,7 +840,7 @@ export class TrainSchedulingService {
scheduleId: string,
dto: UploadImportDjiboutiDocumentDto,
) {
const schedule = await this.getImportDjiboutiSchedule(scheduleId);
const schedule = await this.getDjiboutiGatepassSchedule(scheduleId);
const operation = await this.getOrCreateImportDjiboutiOperation(scheduleId);
const documents = {
...(operation.documents ?? {}),
@@ -865,21 +864,30 @@ export class TrainSchedulingService {
}
async grantImportDjiboutiGatepass(scheduleId: string, dto: ImportDjiboutiActionDto = {}) {
const schedule = await this.getImportDjiboutiSchedule(scheduleId);
const schedule = await this.getDjiboutiGatepassSchedule(scheduleId);
const operation = await this.getOrCreateImportDjiboutiOperation(scheduleId);
const missing = this.missingImportDjiboutiDocuments(operation);
if (missing.length) {
throw new BadRequestException(`Gatepass cannot be granted until documents are uploaded: ${missing.join(', ')}`);
const securedAt = dto.securedAt ? new Date(dto.securedAt) : new Date();
const documents = { ...(operation.documents ?? {}) };
if (dto.fileId || dto.fileUrl || dto.reference || dto.notes) {
documents.GATE_PASS = {
fileId: dto.fileId ?? null,
fileUrl: dto.fileUrl ?? null,
reference: dto.reference ?? null,
uploadedAt: new Date().toISOString(),
uploadedBy: dto.performedBy ?? null,
notes: dto.notes ?? null,
};
}
await this.dataSource.getRepository(ImportDjiboutiOperation).update(operation.id, {
gatepassGrantedAt: operation.gatepassGrantedAt ?? new Date(),
documents,
gatepassGrantedAt: securedAt,
performedBy: dto.performedBy ?? operation.performedBy ?? null,
notes: dto.notes ?? operation.notes ?? null,
});
console.log(
`[NOTIFY] Import gatepass granted for train ${schedule.trainNumber ?? schedule.id}; loading may proceed.`,
`[NOTIFY] Gate pass secured for train ${schedule.trainNumber ?? schedule.id}; Djibouti Port entry is allowed.`,
);
return this.getImportDjiboutiOperation(schedule.id);
}
@@ -1299,16 +1307,28 @@ export class TrainSchedulingService {
}
private async getImportDjiboutiSchedule(scheduleId: string): Promise<TrainSchedule> {
const schedule = await this.getDjiboutiGatepassSchedule(scheduleId);
if (!this.isImportDjiboutiSchedule(schedule)) {
throw new BadRequestException('This action applies only to IMPORT schedules originating from Djibouti');
}
return schedule;
}
private async getDjiboutiGatepassSchedule(scheduleId: string): Promise<TrainSchedule> {
const schedule = await this.trainSchedulesRepository.findByIdWithFullGraph(scheduleId);
if (!schedule) {
throw new NotFoundException(`Train schedule ${scheduleId} not found`);
}
if (!this.isImportDjiboutiSchedule(schedule)) {
throw new BadRequestException('Batch 7 actions apply only to IMPORT schedules originating from Djibouti');
if (!this.isDjiboutiGatepassSchedule(schedule)) {
throw new BadRequestException('Gate pass applies only to trains entering Djibouti Port on import or export routes');
}
return schedule;
}
private isDjiboutiGatepassSchedule(schedule: TrainSchedule): boolean {
return this.isImportDjiboutiSchedule(schedule) || this.isExportDjiboutiSchedule(schedule);
}
private isImportDjiboutiSchedule(schedule: TrainSchedule): boolean {
const direction =
(schedule.direction as 'IMPORT' | 'EXPORT' | 'DOMESTIC' | null) ??
@@ -1323,6 +1343,20 @@ export class TrainSchedulingService {
);
}
private isExportDjiboutiSchedule(schedule: TrainSchedule): boolean {
const direction =
(schedule.direction as 'IMPORT' | 'EXPORT' | 'DOMESTIC' | null) ??
(schedule.originStation && schedule.destinationStation
? deriveScheduleDirection(schedule.originStation, schedule.destinationStation)
: null);
return (
direction === 'EXPORT' &&
this.isDjiboutiPortDestination(
`${schedule.destinationStation?.code ?? ''} ${schedule.destinationStation?.label ?? ''}`,
)
);
}
private async getOrCreateImportDjiboutiOperation(scheduleId: string): Promise<ImportDjiboutiOperation> {
const repo = this.dataSource.getRepository(ImportDjiboutiOperation);
const existing = await repo.findOne({ where: { trainScheduleId: scheduleId } });
@@ -1331,8 +1365,8 @@ export class TrainSchedulingService {
}
private missingImportDjiboutiDocuments(operation?: ImportDjiboutiOperation | null): ImportDjiboutiDocumentType[] {
const documents = operation?.documents ?? {};
return IMPORT_DJIBOUTI_DOCUMENT_TYPES.filter((type) => !documents[type]);
void operation;
return [];
}
private assertImportDjiboutiGatepassGranted(operation?: ImportDjiboutiOperation | null): void {
@@ -1343,6 +1377,7 @@ export class TrainSchedulingService {
private mapImportDjiboutiOperation(schedule: TrainSchedule, operation: ImportDjiboutiOperation) {
const missingDocuments = this.missingImportDjiboutiDocuments(operation);
const gatepassStatus = operation.gatepassGrantedAt ? 'SECURED' : 'NOT_SECURED';
return {
trainScheduleId: schedule.id,
trainNumber: schedule.trainNumber ?? null,
@@ -1350,6 +1385,7 @@ export class TrainSchedulingService {
status: {
documentsComplete: missingDocuments.length === 0,
missingDocuments,
gatepassStatus,
gatepassGranted: Boolean(operation.gatepassGrantedAt),
readyForLoading: Boolean(operation.readyForLoadingAt),
loadedOnTrain: Boolean(operation.loadedOnTrainAt),
@@ -1358,6 +1394,8 @@ export class TrainSchedulingService {
},
documents: operation.documents ?? {},
gatepassGrantedAt: operation.gatepassGrantedAt ?? null,
gatepassSecuredAt: operation.gatepassGrantedAt ?? null,
gatepassStatus,
readyForLoadingAt: operation.readyForLoadingAt ?? null,
loadedOnTrainAt: operation.loadedOnTrainAt ?? null,
departedFromDjiboutiAt: operation.departedFromDjiboutiAt ?? null,