mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-28 04:20:55 +00:00
implement wagon maintenance feature: add functionality to detach wagons and move them to maintenance status
This commit is contained in:
@@ -10,11 +10,6 @@ import {
|
||||
} from 'class-validator';
|
||||
|
||||
export class BuildTrainDto {
|
||||
@ApiProperty({ example: '81001', description: 'Operator-assigned train code (unique)' })
|
||||
@IsString()
|
||||
@MaxLength(32)
|
||||
code!: string;
|
||||
|
||||
@ApiProperty({ example: '8001', description: 'EXPORT run number (odd, unique across trains)' })
|
||||
@IsString()
|
||||
@MaxLength(20)
|
||||
|
||||
@@ -85,6 +85,16 @@ export class TrainBuilderController {
|
||||
return this.trainBuilderService.removeWagon(id, wagonId);
|
||||
}
|
||||
|
||||
@Post(':id/wagons/:wagonId/maintenance')
|
||||
@FleetManage()
|
||||
@ApiOperation({ summary: 'Detach one wagon and move it to MAINTENANCE status' })
|
||||
sendWagonToMaintenance(
|
||||
@Param('id', ParseUUIDPipe) id: string,
|
||||
@Param('wagonId', ParseUUIDPipe) wagonId: string,
|
||||
) {
|
||||
return this.trainBuilderService.sendWagonToMaintenance(id, wagonId);
|
||||
}
|
||||
|
||||
@Post(':id/reorder-wagons')
|
||||
@FleetManage()
|
||||
@ApiOperation({ summary: 'Persist a drag-reorder of the full consist' })
|
||||
|
||||
@@ -59,11 +59,7 @@ export class TrainBuilderService {
|
||||
}
|
||||
|
||||
const trainId = await this.dataSource.transaction(async (manager) => {
|
||||
const code = dto.code.trim();
|
||||
const existing = await manager.getRepository(Train).findOne({ where: { code } });
|
||||
if (existing) {
|
||||
throw new ConflictException(`Train code ${code} is already in use`);
|
||||
}
|
||||
const code = await this.generateTrainCode(manager);
|
||||
|
||||
// Friendly 409 before the partial unique indexes (the race-proof backstop):
|
||||
// the typed pair may not collide with any train's pair or legacy number.
|
||||
@@ -418,6 +414,33 @@ export class TrainBuilderService {
|
||||
return this.getComposition(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detach one wagon AND flag it for maintenance: it leaves the consist and
|
||||
* moves to MAINTENANCE status (not AVAILABLE), so it is not re-coupled until
|
||||
* it clears maintenance. The freed sequence gap is closed.
|
||||
*/
|
||||
async sendWagonToMaintenance(id: string, wagonId: string) {
|
||||
await this.dataSource.transaction(async (manager) => {
|
||||
const train = await this.getEditableTrain(manager, id);
|
||||
const wagon = await manager.getRepository(Wagon).findOne({ where: { id: wagonId } });
|
||||
if (!wagon || wagon.trainId !== train.id) {
|
||||
throw new NotFoundException(`Wagon ${wagonId} is not part of this train`);
|
||||
}
|
||||
if (wagon.currentTrainScheduleId) {
|
||||
throw new ConflictException(
|
||||
`Wagon ${wagon.wagonNumber} is pinned to an active schedule and cannot be removed`,
|
||||
);
|
||||
}
|
||||
await manager.getRepository(Wagon).update(wagon.id, {
|
||||
trainId: null,
|
||||
sequenceNumber: null,
|
||||
status: WagonStatus.Maintenance,
|
||||
});
|
||||
await this.resequenceWagons(manager, train.id);
|
||||
});
|
||||
return this.getComposition(id);
|
||||
}
|
||||
|
||||
/** Persist a drag-reorder: `wagonIds` is the full consist in its new order. */
|
||||
async reorderWagons(id: string, dto: ReorderTrainWagonsDto) {
|
||||
await this.dataSource.transaction(async (manager) => {
|
||||
@@ -469,6 +492,29 @@ export class TrainBuilderService {
|
||||
|
||||
// ---------------------------------------------------------------- internals
|
||||
|
||||
/**
|
||||
* System-assigned train code `TR-NNNNN`. Draws the next number from the
|
||||
* highest existing `TR-` code and probes past any manual collision so the
|
||||
* unique constraint never rejects the build.
|
||||
*/
|
||||
private async generateTrainCode(manager: EntityManager): Promise<string> {
|
||||
const [row]: { max_seq: string | null }[] = await manager.query(
|
||||
`SELECT MAX(CAST(SUBSTRING(code FROM '^TR-([0-9]+)$') AS INTEGER)) AS max_seq
|
||||
FROM freight.trains
|
||||
WHERE code ~ '^TR-[0-9]+$'`,
|
||||
);
|
||||
let seq = Number(row?.max_seq ?? 0) + 1;
|
||||
for (let attempt = 0; attempt < 50; attempt += 1) {
|
||||
const code = `TR-${String(seq).padStart(5, '0')}`;
|
||||
const exists = await manager
|
||||
.getRepository(Train)
|
||||
.findOne({ where: { code }, withDeleted: true });
|
||||
if (!exists) return code;
|
||||
seq += 1;
|
||||
}
|
||||
throw new ConflictException('Could not allocate a unique train code');
|
||||
}
|
||||
|
||||
private mapSummary(train: Train, activeSchedule: ActiveScheduleRef | null) {
|
||||
const locomotives = [...(train.locomotives ?? [])]
|
||||
.sort((a, b) => a.sequenceNo - b.sequenceNo)
|
||||
|
||||
Reference in New Issue
Block a user