mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 23:28:11 +00:00
trains, wagons,containers and cargoes schema and API
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
import { IsUUID, IsOptional, IsInt, Min } from 'class-validator';
|
||||
|
||||
export class AssignWagonToTrainDto {
|
||||
@IsUUID()
|
||||
trainId!: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
sequenceNumber?: number;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { IsString, IsUUID, IsOptional, IsInt, Min, IsNumber, IsIn } from 'class-validator';
|
||||
|
||||
export class CreateWagonDto {
|
||||
@IsString()
|
||||
wagonNumber!: string;
|
||||
|
||||
@IsUUID()
|
||||
wagonTypeId!: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
trainId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
sequenceNumber?: number;
|
||||
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
tareWeight!: number;
|
||||
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
maxPayloadWeight!: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsIn(['AVAILABLE', 'ASSIGNED', 'MAINTENANCE', 'RETIRED'])
|
||||
status?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
notes?: string;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { IsArray, IsUUID } from 'class-validator';
|
||||
|
||||
export class ReorderWagonsDto {
|
||||
@IsArray()
|
||||
@IsUUID(4, { each: true })
|
||||
wagonIds!: string[];
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { PartialType } from '@nestjs/swagger';
|
||||
import { CreateWagonDto } from './create-wagon.dto';
|
||||
|
||||
export class UpdateWagonDto extends PartialType(CreateWagonDto) {}
|
||||
@@ -0,0 +1,41 @@
|
||||
// apps/edr-freight-api/src/modules/wagons/entities/wagon.entity.ts
|
||||
import { Entity, Column, ManyToOne, OneToMany, JoinColumn } from 'typeorm';
|
||||
import { BaseEntity } from '@edr/api-common';
|
||||
import { Train } from '../../trains/entities/train.entity';
|
||||
import { Container } from '../../container-management/entities/container.entity';
|
||||
|
||||
@Entity({ name: 'wagons', schema: 'freight' })
|
||||
export class Wagon extends BaseEntity {
|
||||
@Column({ unique: true, name: 'wagon_number' })
|
||||
wagonNumber!: string;
|
||||
|
||||
@Column({ name: 'wagon_type_id', type: 'uuid' })
|
||||
wagonTypeId!: string;
|
||||
|
||||
@Column({ name: 'train_id', type: 'uuid', nullable: true })
|
||||
trainId!: string | null;
|
||||
|
||||
@Column({ name: 'sequence_number', type: 'int', nullable: true })
|
||||
sequenceNumber!: number | null;
|
||||
|
||||
@Column({ name: 'tare_weight', type: 'decimal', precision: 10, scale: 2 })
|
||||
tareWeight!: number;
|
||||
|
||||
@Column({ name: 'max_payload_weight', type: 'decimal', precision: 10, scale: 2 })
|
||||
maxPayloadWeight!: number;
|
||||
|
||||
@Column({ type: 'varchar', default: 'AVAILABLE' })
|
||||
status!: string; // AVAILABLE, ASSIGNED, MAINTENANCE, RETIRED
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
notes!: string | null;
|
||||
|
||||
// Relationship to Train
|
||||
@ManyToOne(() => Train, (train) => train.wagons, { onDelete: 'SET NULL' })
|
||||
@JoinColumn({ name: 'train_id' })
|
||||
train!: Train | null;
|
||||
|
||||
// Relationship to Container
|
||||
@OneToMany(() => Container, (container) => container.wagon)
|
||||
containers!: Container[];
|
||||
}
|
||||
76
apps/edr-freight-api/src/modules/wagons/wagons.controller.ts
Normal file
76
apps/edr-freight-api/src/modules/wagons/wagons.controller.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
Param,
|
||||
ParseUUIDPipe,
|
||||
Patch,
|
||||
Post,
|
||||
} from '@nestjs/common';
|
||||
import { ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
import { CreateWagonDto } from './dto/create-wagon.dto';
|
||||
import { UpdateWagonDto } from './dto/update-wagon.dto';
|
||||
import { AssignWagonToTrainDto } from './dto/assign-wagon-to-train.dto';
|
||||
import { ReorderWagonsDto } from './dto/reorder-wagons.dto';
|
||||
import { WagonsService } from './wagons.service';
|
||||
|
||||
@ApiTags('wagons')
|
||||
@Controller('wagons')
|
||||
export class WagonsController {
|
||||
constructor(private readonly wagonsService: WagonsService) {}
|
||||
|
||||
@Post()
|
||||
@ApiOperation({ summary: 'Create a new wagon' })
|
||||
create(@Body() dto: CreateWagonDto) {
|
||||
return this.wagonsService.create(dto);
|
||||
}
|
||||
|
||||
@Get()
|
||||
@ApiOperation({ summary: 'List all wagons' })
|
||||
findAll() {
|
||||
return this.wagonsService.findAll();
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@ApiOperation({ summary: 'Get a wagon by ID' })
|
||||
findOne(@Param('id', ParseUUIDPipe) id: string) {
|
||||
return this.wagonsService.findById(id);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
@ApiOperation({ summary: 'Update a wagon' })
|
||||
update(@Param('id', ParseUUIDPipe) id: string, @Body() dto: UpdateWagonDto) {
|
||||
return this.wagonsService.update(id, dto);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@ApiOperation({ summary: 'Delete a wagon' })
|
||||
remove(@Param('id', ParseUUIDPipe) id: string) {
|
||||
return this.wagonsService.remove(id);
|
||||
}
|
||||
|
||||
@Post(':id/assign-train')
|
||||
@ApiOperation({ summary: 'Assign wagon to a train' })
|
||||
assignToTrain(@Param('id', ParseUUIDPipe) id: string, @Body() dto: AssignWagonToTrainDto) {
|
||||
return this.wagonsService.assignToTrain(id, dto);
|
||||
}
|
||||
|
||||
@Post(':id/unassign-train')
|
||||
@ApiOperation({ summary: 'Unassign wagon from train' })
|
||||
unassignFromTrain(@Param('id', ParseUUIDPipe) id: string) {
|
||||
return this.wagonsService.unassignFromTrain(id);
|
||||
}
|
||||
}
|
||||
|
||||
// Separate controller for train‑specific reorder (registered in module)
|
||||
@Controller('trains/:trainId/reorder-wagons')
|
||||
export class TrainWagonsReorderController {
|
||||
constructor(private readonly wagonsService: WagonsService) {}
|
||||
|
||||
@Post()
|
||||
@ApiOperation({ summary: 'Reorder wagons of a train' })
|
||||
reorder(@Param('trainId', ParseUUIDPipe) trainId: string, @Body() dto: ReorderWagonsDto) {
|
||||
return this.wagonsService.reorderWagons(trainId, dto);
|
||||
}
|
||||
}
|
||||
14
apps/edr-freight-api/src/modules/wagons/wagons.module.ts
Normal file
14
apps/edr-freight-api/src/modules/wagons/wagons.module.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { Wagon } from './entities/wagon.entity';
|
||||
import { Train } from '../trains/entities/train.entity';
|
||||
import { WagonsController, TrainWagonsReorderController } from './wagons.controller';
|
||||
import { WagonsService } from './wagons.service';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([Wagon, Train])],
|
||||
controllers: [WagonsController, TrainWagonsReorderController],
|
||||
providers: [WagonsService],
|
||||
exports: [WagonsService],
|
||||
})
|
||||
export class WagonsModule {}
|
||||
15
apps/edr-freight-api/src/modules/wagons/wagons.repository.ts
Normal file
15
apps/edr-freight-api/src/modules/wagons/wagons.repository.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { BaseRepository } from '@edr/api-common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { Wagon } from './entities/wagon.entity';
|
||||
|
||||
@Injectable()
|
||||
export class WagonsRepository extends BaseRepository<Wagon> {
|
||||
constructor(
|
||||
@InjectRepository(Wagon)
|
||||
repository: Repository<Wagon>,
|
||||
) {
|
||||
super(repository);
|
||||
}
|
||||
}
|
||||
101
apps/edr-freight-api/src/modules/wagons/wagons.service.ts
Normal file
101
apps/edr-freight-api/src/modules/wagons/wagons.service.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
import { Injectable, NotFoundException, ConflictException } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository, DataSource } from 'typeorm';
|
||||
import { CreateWagonDto } from './dto/create-wagon.dto';
|
||||
import { UpdateWagonDto } from './dto/update-wagon.dto';
|
||||
import { AssignWagonToTrainDto } from './dto/assign-wagon-to-train.dto';
|
||||
import { ReorderWagonsDto } from './dto/reorder-wagons.dto';
|
||||
import { Wagon } from './entities/wagon.entity';
|
||||
import { Train } from '../trains/entities/train.entity';
|
||||
|
||||
@Injectable()
|
||||
export class WagonsService {
|
||||
constructor(
|
||||
@InjectRepository(Wagon)
|
||||
private readonly wagonRepo: Repository<Wagon>,
|
||||
@InjectRepository(Train)
|
||||
private readonly trainRepo: Repository<Train>,
|
||||
private readonly dataSource: DataSource,
|
||||
) {}
|
||||
|
||||
async create(dto: CreateWagonDto): Promise<Wagon> {
|
||||
const wagon = this.wagonRepo.create(dto);
|
||||
// Convert undefined to null for nullable fields
|
||||
if (dto.trainId === undefined) wagon.trainId = null;
|
||||
if (dto.sequenceNumber === undefined) wagon.sequenceNumber = null;
|
||||
return this.wagonRepo.save(wagon);
|
||||
}
|
||||
|
||||
async findAll(): Promise<Wagon[]> {
|
||||
return this.wagonRepo.find({ order: { wagonNumber: 'ASC' } });
|
||||
}
|
||||
|
||||
async findById(id: string): Promise<Wagon> {
|
||||
const wagon = await this.wagonRepo.findOne({ where: { id } });
|
||||
if (!wagon) throw new NotFoundException(`Wagon ${id} not found`);
|
||||
return wagon;
|
||||
}
|
||||
|
||||
async update(id: string, dto: UpdateWagonDto): Promise<Wagon> {
|
||||
const wagon = await this.findById(id);
|
||||
Object.assign(wagon, dto);
|
||||
if (dto.trainId === undefined) wagon.trainId = null;
|
||||
if (dto.sequenceNumber === undefined) wagon.sequenceNumber = null;
|
||||
return this.wagonRepo.save(wagon);
|
||||
}
|
||||
|
||||
async remove(id: string): Promise<void> {
|
||||
const wagon = await this.findById(id);
|
||||
await this.wagonRepo.remove(wagon);
|
||||
}
|
||||
|
||||
async assignToTrain(wagonId: string, dto: AssignWagonToTrainDto): Promise<Wagon> {
|
||||
const wagon = await this.findById(wagonId);
|
||||
if (wagon.status === 'ASSIGNED') {
|
||||
throw new ConflictException('Wagon already assigned to a train');
|
||||
}
|
||||
|
||||
const train = await this.trainRepo.findOne({ where: { id: dto.trainId } });
|
||||
if (!train) throw new NotFoundException('Train not found');
|
||||
|
||||
let sequence: number | null = dto.sequenceNumber ?? null;
|
||||
if (sequence === null) {
|
||||
const maxSeq = await this.wagonRepo
|
||||
.createQueryBuilder('w')
|
||||
.select('MAX(w.sequenceNumber)', 'max')
|
||||
.where('w.trainId = :trainId', { trainId: train.id })
|
||||
.getRawOne();
|
||||
sequence = (maxSeq?.max ?? 0) + 1;
|
||||
}
|
||||
|
||||
wagon.trainId = train.id;
|
||||
wagon.sequenceNumber = sequence;
|
||||
wagon.status = 'ASSIGNED';
|
||||
return this.wagonRepo.save(wagon);
|
||||
}
|
||||
|
||||
async unassignFromTrain(wagonId: string): Promise<Wagon> {
|
||||
const wagon = await this.findById(wagonId);
|
||||
wagon.trainId = null;
|
||||
wagon.sequenceNumber = null;
|
||||
wagon.status = 'AVAILABLE';
|
||||
return this.wagonRepo.save(wagon);
|
||||
}
|
||||
|
||||
async reorderWagons(_trainId: string, dto: ReorderWagonsDto): Promise<void> {
|
||||
const queryRunner = this.dataSource.createQueryRunner();
|
||||
await queryRunner.connect();
|
||||
await queryRunner.startTransaction();
|
||||
try {
|
||||
for (let i = 0; i < dto.wagonIds.length; i++) {
|
||||
await queryRunner.manager.update(Wagon, dto.wagonIds[i], { sequenceNumber: i + 1 });
|
||||
}
|
||||
await queryRunner.commitTransaction();
|
||||
} catch (err) {
|
||||
await queryRunner.rollbackTransaction();
|
||||
throw err;
|
||||
} finally {
|
||||
await queryRunner.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user