mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 18:48:11 +00:00
Add train deactivation feature and import train number management
- Introduced DEACTIVATED status for trains, allowing staff to park trains indefinitely. - Implemented methods to deactivate and reactivate trains in the TrainBuilderService. - Added UI components for train deactivation and reactivation in TrainBuilderDetailPage. - Created a dropdown setting for admin-managed import train numbers, with corresponding migrations. - Updated yard code length to accommodate soft-delete suffix. - Enhanced train status handling to include DEACTIVATED state.
This commit is contained in:
@@ -153,6 +153,38 @@ export class TrainBuilderService {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Run numbers already claimed by live (non-deleted) trains, split by
|
||||
* direction. Legacy single `train_number` values are sorted into a side by
|
||||
* parity (even = import, odd = export) so the pickers can grey them out too.
|
||||
*/
|
||||
async usedTrainNumbers() {
|
||||
const rows: {
|
||||
import_train_number: string | null;
|
||||
export_train_number: string | null;
|
||||
train_number: string | null;
|
||||
}[] = await this.dataSource.query(
|
||||
`SELECT import_train_number, export_train_number, train_number
|
||||
FROM freight.trains
|
||||
WHERE deleted_at IS NULL`,
|
||||
);
|
||||
|
||||
const importTrainNumbers = new Set<string>();
|
||||
const exportTrainNumbers = new Set<string>();
|
||||
for (const row of rows) {
|
||||
if (row.import_train_number) importTrainNumbers.add(row.import_train_number);
|
||||
if (row.export_train_number) exportTrainNumbers.add(row.export_train_number);
|
||||
const legacy = row.train_number?.trim();
|
||||
if (legacy && /^\d+$/.test(legacy)) {
|
||||
(Number(legacy) % 2 === 0 ? importTrainNumbers : exportTrainNumbers).add(legacy);
|
||||
}
|
||||
}
|
||||
return {
|
||||
importTrainNumbers: [...importTrainNumbers].sort(),
|
||||
exportTrainNumbers: [...exportTrainNumbers].sort(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* One ACTIVE schedule per train for the page (prefer the DISPATCHED run,
|
||||
* else the earliest upcoming departure) — feeds the list's direction tint
|
||||
@@ -532,6 +564,50 @@ export class TrainBuilderService {
|
||||
return this.getComposition(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Park the train indefinitely (status DEACTIVATED). Blocked while it still
|
||||
* has a live (DRAFT/SCHEDULED/DISPATCHED) schedule. The consist stays
|
||||
* coupled; like UNDER_MAINTENANCE / OUT_OF_SERVICE the flag is staff-owned —
|
||||
* the scheduler never overwrites it and refuses the train for new schedules.
|
||||
*/
|
||||
async deactivate(id: string) {
|
||||
await this.dataSource.transaction(async (manager) => {
|
||||
const train = await manager.getRepository(Train).findOne({ where: { id } });
|
||||
if (!train) throw new NotFoundException(`Train ${id} not found`);
|
||||
if (train.status === Freight.TrainStatus.Deactivated) return;
|
||||
const active: { count: string }[] = await manager.query(
|
||||
`SELECT COUNT(*)::text AS count
|
||||
FROM freight.train_schedules ts
|
||||
JOIN freight.train_sets tset ON tset.id = ts.train_set_id
|
||||
WHERE tset.train_id = $1
|
||||
AND ts.deleted_at IS NULL
|
||||
AND ts.status IN ('DRAFT', 'SCHEDULED', 'DISPATCHED')`,
|
||||
[id],
|
||||
);
|
||||
if (Number(active[0]?.count ?? 0) > 0) {
|
||||
throw new ConflictException(
|
||||
'Train has active schedules; cancel them before deactivating the train',
|
||||
);
|
||||
}
|
||||
await manager
|
||||
.getRepository(Train)
|
||||
.update(id, { status: Freight.TrainStatus.Deactivated });
|
||||
});
|
||||
return this.getComposition(id);
|
||||
}
|
||||
|
||||
/** Reactivate a DEACTIVATED train back to AVAILABLE so it can be scheduled again. */
|
||||
async activate(id: string) {
|
||||
const train = await this.dataSource.getRepository(Train).findOne({ where: { id } });
|
||||
if (!train) throw new NotFoundException(`Train ${id} not found`);
|
||||
if (train.status === Freight.TrainStatus.Deactivated) {
|
||||
await this.dataSource
|
||||
.getRepository(Train)
|
||||
.update(id, { status: Freight.TrainStatus.Available });
|
||||
}
|
||||
return this.getComposition(id);
|
||||
}
|
||||
|
||||
/** Disband the train: release wagons and locomotives, then delete it. */
|
||||
async disband(id: string): Promise<void> {
|
||||
await this.dataSource.transaction(async (manager) => {
|
||||
|
||||
Reference in New Issue
Block a user