mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-28 04:20:55 +00:00
implement update train details feature: add DTO, service method, and UI modal for editing train name and run numbers
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
import { ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { IsNotEmpty, IsOptional, IsString, MaxLength } from 'class-validator';
|
||||
|
||||
/**
|
||||
* Edit a built train's display identity: its name and its fixed import/export
|
||||
* run numbers. Composition (yard, locomotives, wagons) has its own endpoints.
|
||||
* Omitted fields keep their current value; an empty trainName clears the name.
|
||||
*/
|
||||
export class UpdateTrainDetailsDto {
|
||||
@ApiPropertyOptional({ description: 'Display name; empty string clears it' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(100)
|
||||
trainName?: string;
|
||||
|
||||
@ApiPropertyOptional({ description: 'Fixed IMPORT (even) run number' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@MaxLength(20)
|
||||
importTrainNumber?: string;
|
||||
|
||||
@ApiPropertyOptional({ description: 'Fixed EXPORT (odd) run number' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@MaxLength(20)
|
||||
exportTrainNumber?: string;
|
||||
}
|
||||
@@ -45,7 +45,7 @@ export class Train extends BaseEntity {
|
||||
trainNumber?: string;
|
||||
|
||||
@Column({ name: 'train_name', type: 'varchar', length: 100, nullable: true })
|
||||
trainName?: string;
|
||||
trainName?: string | null;
|
||||
|
||||
@Column({ name: 'route_id', type: 'uuid', nullable: true })
|
||||
routeId?: string;
|
||||
|
||||
@@ -19,6 +19,7 @@ import { AssignTrainWagonsDto } from './dto/assign-train-wagons.dto';
|
||||
import { BuildTrainDto } from './dto/build-train.dto';
|
||||
import { ListBuiltTrainsQueryDto } from './dto/list-built-trains-query.dto';
|
||||
import { ReorderTrainWagonsDto } from './dto/reorder-train-wagons.dto';
|
||||
import { UpdateTrainDetailsDto } from './dto/update-train-details.dto';
|
||||
import { UpdateTrainLocomotivesDto } from './dto/update-train-locomotives.dto';
|
||||
import { UpdateTrainYardDto } from './dto/update-train-yard.dto';
|
||||
import { TrainBuilderService } from './train-builder.service';
|
||||
@@ -59,6 +60,18 @@ export class TrainBuilderController {
|
||||
return this.trainBuilderService.setLocomotives(id, dto);
|
||||
}
|
||||
|
||||
@Patch(':id/details')
|
||||
@FleetManage()
|
||||
@ApiOperation({
|
||||
summary: "Edit the train's name and fixed import/export run numbers",
|
||||
})
|
||||
updateDetails(
|
||||
@Param('id', ParseUUIDPipe) id: string,
|
||||
@Body() dto: UpdateTrainDetailsDto,
|
||||
) {
|
||||
return this.trainBuilderService.updateDetails(id, dto);
|
||||
}
|
||||
|
||||
@Patch(':id/yard')
|
||||
@FleetManage()
|
||||
@ApiOperation({
|
||||
|
||||
@@ -17,6 +17,7 @@ import { AssignTrainWagonsDto } from './dto/assign-train-wagons.dto';
|
||||
import { BuildTrainDto } from './dto/build-train.dto';
|
||||
import { ListBuiltTrainsQueryDto } from './dto/list-built-trains-query.dto';
|
||||
import { ReorderTrainWagonsDto } from './dto/reorder-train-wagons.dto';
|
||||
import { UpdateTrainDetailsDto } from './dto/update-train-details.dto';
|
||||
import { UpdateTrainLocomotivesDto } from './dto/update-train-locomotives.dto';
|
||||
import { TrainLocomotive } from './entities/train-locomotive.entity';
|
||||
import { Train } from './entities/train.entity';
|
||||
@@ -331,6 +332,53 @@ export class TrainBuilderService {
|
||||
return this.getComposition(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit a built train's display identity: name and fixed import/export run
|
||||
* numbers. Mirrors the build-time number rules — the pair may not collide
|
||||
* with any other train's pair or legacy number (friendly 409 ahead of the
|
||||
* partial unique indexes). Blocked while the train is out on a dispatched
|
||||
* run, like every other composition edit.
|
||||
*/
|
||||
async updateDetails(id: string, dto: UpdateTrainDetailsDto) {
|
||||
await this.dataSource.transaction(async (manager) => {
|
||||
const train = await this.getEditableTrain(manager, id);
|
||||
|
||||
const patch: Partial<Train> = {};
|
||||
if (dto.trainName !== undefined) {
|
||||
patch.trainName = dto.trainName.trim() || null;
|
||||
}
|
||||
const importTrainNumber = dto.importTrainNumber?.trim();
|
||||
const exportTrainNumber = dto.exportTrainNumber?.trim();
|
||||
if (importTrainNumber) patch.importTrainNumber = importTrainNumber;
|
||||
if (exportTrainNumber) patch.exportTrainNumber = exportTrainNumber;
|
||||
|
||||
if (importTrainNumber || exportTrainNumber) {
|
||||
const nextImport = importTrainNumber ?? train.importTrainNumber ?? '';
|
||||
const nextExport = exportTrainNumber ?? train.exportTrainNumber ?? '';
|
||||
const numberClash: { code: string }[] = await manager.query(
|
||||
`SELECT code FROM freight.trains
|
||||
WHERE deleted_at IS NULL
|
||||
AND id != $3
|
||||
AND (import_train_number IN ($1, $2)
|
||||
OR export_train_number IN ($1, $2)
|
||||
OR train_number IN ($1, $2))
|
||||
LIMIT 1`,
|
||||
[nextImport, nextExport, train.id],
|
||||
);
|
||||
if (numberClash.length) {
|
||||
throw new ConflictException(
|
||||
`Train number ${nextImport}/${nextExport} is already used by train ${numberClash[0].code}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(patch).length) {
|
||||
await manager.getRepository(Train).update(train.id, patch);
|
||||
}
|
||||
});
|
||||
return this.getComposition(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Relocate the train to another yard. The consist moves as one unit: every
|
||||
* coupled locomotive and wagon follows to the new yard (so their current
|
||||
|
||||
Reference in New Issue
Block a user