mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 06:00:55 +00:00
Train scheduling API,Routes and UI
This commit is contained in:
@@ -16,6 +16,10 @@ export class CreateWagonDto {
|
||||
@Min(1)
|
||||
sequenceNumber?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
currentLocationYardId?: string;
|
||||
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
tareWeight!: number;
|
||||
@@ -25,10 +29,10 @@ export class CreateWagonDto {
|
||||
maxPayloadWeight!: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsIn(['AVAILABLE', 'ASSIGNED', 'MAINTENANCE', 'RETIRED'])
|
||||
@IsIn(['AVAILABLE', 'IMPORT_READY', 'EXPORT_READY', 'ASSIGNED', 'MAINTENANCE', 'RETIRED'])
|
||||
status?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
notes?: string;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ 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';
|
||||
import { Yard } from '../../rule-engine/entities/yard.entity';
|
||||
import { WagonType } from '../../wagon-types/entities/wagon-type.entity';
|
||||
|
||||
@Entity({ name: 'wagons', schema: 'freight' })
|
||||
export class Wagon extends BaseEntity {
|
||||
@@ -12,12 +14,23 @@ export class Wagon extends BaseEntity {
|
||||
@Column({ name: 'wagon_type_id', type: 'uuid' })
|
||||
wagonTypeId!: string;
|
||||
|
||||
@ManyToOne(() => WagonType, { onDelete: 'RESTRICT' })
|
||||
@JoinColumn({ name: 'wagon_type_id' })
|
||||
wagonType?: WagonType;
|
||||
|
||||
@Column({ name: 'train_id', type: 'uuid', nullable: true })
|
||||
trainId!: string | null;
|
||||
|
||||
@Column({ name: 'sequence_number', type: 'int', nullable: true })
|
||||
sequenceNumber!: number | null;
|
||||
|
||||
@Column({ name: 'current_location_yard_id', type: 'uuid', nullable: true })
|
||||
currentLocationYardId!: string | null;
|
||||
|
||||
@ManyToOne(() => Yard, { nullable: true, onDelete: 'SET NULL' })
|
||||
@JoinColumn({ name: 'current_location_yard_id' })
|
||||
currentLocationYard?: Yard | null;
|
||||
|
||||
@Column({ name: 'tare_weight', type: 'decimal', precision: 10, scale: 2 })
|
||||
tareWeight!: number;
|
||||
|
||||
@@ -25,7 +38,7 @@ export class Wagon extends BaseEntity {
|
||||
maxPayloadWeight!: number;
|
||||
|
||||
@Column({ type: 'varchar', default: 'AVAILABLE' })
|
||||
status!: string; // AVAILABLE, ASSIGNED, MAINTENANCE, RETIRED
|
||||
status!: string; // AVAILABLE, IMPORT_READY, EXPORT_READY, ASSIGNED, MAINTENANCE, RETIRED
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
notes!: string | null;
|
||||
@@ -38,4 +51,4 @@ export class Wagon extends BaseEntity {
|
||||
// Relationship to Container
|
||||
@OneToMany(() => Container, (container) => container.wagon)
|
||||
containers!: Container[];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,13 +2,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 { Yard } from '../rule-engine/entities/yard.entity';
|
||||
import { WagonsController, TrainWagonsReorderController } from './wagons.controller';
|
||||
import { WagonsService } from './wagons.service';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([Wagon, Train])],
|
||||
imports: [TypeOrmModule.forFeature([Wagon, Train, Yard])],
|
||||
controllers: [WagonsController, TrainWagonsReorderController],
|
||||
providers: [WagonsService],
|
||||
exports: [WagonsService],
|
||||
})
|
||||
export class WagonsModule {}
|
||||
export class WagonsModule {}
|
||||
|
||||
@@ -7,6 +7,7 @@ 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';
|
||||
import { Yard } from '../rule-engine/entities/yard.entity';
|
||||
|
||||
@Injectable()
|
||||
export class WagonsService {
|
||||
@@ -15,6 +16,8 @@ export class WagonsService {
|
||||
private readonly wagonRepo: Repository<Wagon>,
|
||||
@InjectRepository(Train)
|
||||
private readonly trainRepo: Repository<Train>,
|
||||
@InjectRepository(Yard)
|
||||
private readonly yardRepo: Repository<Yard>,
|
||||
private readonly dataSource: DataSource,
|
||||
) {}
|
||||
|
||||
@@ -23,6 +26,7 @@ export class WagonsService {
|
||||
// Convert undefined to null for nullable fields
|
||||
if (dto.trainId === undefined) wagon.trainId = null;
|
||||
if (dto.sequenceNumber === undefined) wagon.sequenceNumber = null;
|
||||
wagon.status = await this.statusForLocation(dto.currentLocationYardId, dto.status);
|
||||
return this.wagonRepo.save(wagon);
|
||||
}
|
||||
|
||||
@@ -31,12 +35,14 @@ export class WagonsService {
|
||||
const search = query.search?.trim();
|
||||
const status = query.status?.trim();
|
||||
const trainId = query.trainId?.trim();
|
||||
const currentLocationYardId = query.currentLocationYardId?.trim();
|
||||
|
||||
if (search) {
|
||||
where.push({
|
||||
wagonNumber: ILike(`%${search}%`),
|
||||
...(status ? { status } : {}),
|
||||
...(trainId ? { trainId } : {}),
|
||||
...(currentLocationYardId ? { currentLocationYardId } : {}),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -46,7 +52,8 @@ export class WagonsService {
|
||||
const sortOrder = query.sortOrder?.toUpperCase() === 'DESC' ? 'DESC' : 'ASC';
|
||||
|
||||
return this.wagonRepo.find({
|
||||
where: search ? where : { ...(status ? { status } : {}), ...(trainId ? { trainId } : {}) },
|
||||
where: search ? where : { ...(status ? { status } : {}), ...(trainId ? { trainId } : {}), ...(currentLocationYardId ? { currentLocationYardId } : {}) },
|
||||
relations: { currentLocationYard: true, wagonType: true },
|
||||
order: { [sortBy]: sortOrder } as FindOptionsOrder<Wagon>,
|
||||
skip: query.page && query.limit ? (Number(query.page) - 1) * Number(query.limit) : undefined,
|
||||
take: query.limit ? Number(query.limit) : undefined,
|
||||
@@ -54,7 +61,7 @@ export class WagonsService {
|
||||
}
|
||||
|
||||
async findById(id: string): Promise<Wagon> {
|
||||
const wagon = await this.wagonRepo.findOne({ where: { id } });
|
||||
const wagon = await this.wagonRepo.findOne({ where: { id }, relations: { currentLocationYard: true, wagonType: true } });
|
||||
if (!wagon) throw new NotFoundException(`Wagon ${id} not found`);
|
||||
return wagon;
|
||||
}
|
||||
@@ -62,6 +69,9 @@ export class WagonsService {
|
||||
async update(id: string, dto: UpdateWagonDto): Promise<Wagon> {
|
||||
const wagon = await this.findById(id);
|
||||
Object.assign(wagon, dto);
|
||||
if (dto.currentLocationYardId !== undefined) {
|
||||
wagon.status = await this.statusForLocation(dto.currentLocationYardId, dto.status);
|
||||
}
|
||||
return this.wagonRepo.save(wagon);
|
||||
}
|
||||
|
||||
@@ -99,10 +109,21 @@ export class WagonsService {
|
||||
const wagon = await this.findById(wagonId);
|
||||
wagon.trainId = null;
|
||||
wagon.sequenceNumber = null;
|
||||
wagon.status = 'AVAILABLE';
|
||||
wagon.status = await this.statusForLocation(wagon.currentLocationYardId, 'AVAILABLE');
|
||||
return this.wagonRepo.save(wagon);
|
||||
}
|
||||
|
||||
private async statusForLocation(yardId?: string | null, fallback = 'AVAILABLE') {
|
||||
if (!yardId) return fallback;
|
||||
|
||||
const yard = await this.yardRepo.findOne({ where: { id: yardId } });
|
||||
const country = yard?.country?.trim().toLowerCase();
|
||||
|
||||
if (country === 'ethiopia' || country === 'et') return 'EXPORT_READY';
|
||||
if (country === 'djibouti' || country === 'djoubti' || country === 'dj') return 'IMPORT_READY';
|
||||
return fallback;
|
||||
}
|
||||
|
||||
async reorderWagons(_trainId: string, dto: ReorderWagonsDto): Promise<void> {
|
||||
const queryRunner = this.dataSource.createQueryRunner();
|
||||
await queryRunner.connect();
|
||||
|
||||
Reference in New Issue
Block a user