Files
edr-platform/apps/edr-freight-api/src/modules/wagons/wagons.service.ts
2026-06-08 16:31:28 +03:00

144 lines
5.6 KiB
TypeScript

import { Injectable, NotFoundException, ConflictException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, DataSource, FindOptionsOrder, FindOptionsWhere, ILike } 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';
import { Yard } from '../rule-engine/entities/yard.entity';
@Injectable()
export class WagonsService {
constructor(
@InjectRepository(Wagon)
private readonly wagonRepo: Repository<Wagon>,
@InjectRepository(Train)
private readonly trainRepo: Repository<Train>,
@InjectRepository(Yard)
private readonly yardRepo: Repository<Yard>,
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;
wagon.status = await this.statusForLocation(dto.currentLocationYardId, dto.status);
return this.wagonRepo.save(wagon);
}
async findAll(query: Record<string, string | undefined> = {}): Promise<Wagon[]> {
const where: FindOptionsWhere<Wagon>[] | FindOptionsWhere<Wagon> = [];
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 } : {}),
});
}
const sortBy = ['wagonNumber', 'tareWeight', 'maxPayloadWeight', 'status', 'sequenceNumber'].includes(query.sortBy ?? '')
? (query.sortBy as keyof Wagon)
: 'wagonNumber';
const sortOrder = query.sortOrder?.toUpperCase() === 'DESC' ? 'DESC' : 'ASC';
return this.wagonRepo.find({
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,
});
}
async findById(id: string): Promise<Wagon> {
const wagon = await this.wagonRepo.findOne({ where: { id }, relations: { currentLocationYard: true, wagonType: true } });
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.currentLocationYardId !== undefined) {
wagon.status = await this.statusForLocation(dto.currentLocationYardId, dto.status);
}
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 = 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();
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();
}
}
}