mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-28 19:30:57 +00:00
148 lines
5.5 KiB
TypeScript
148 lines
5.5 KiB
TypeScript
import { WagonStatus } from '@edr/types';
|
|
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 { ListWagonsQueryDto } from './dto/list-wagons-query.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,
|
|
status: dto.status ?? WagonStatus.Available,
|
|
});
|
|
// Convert undefined to null for nullable fields
|
|
if (dto.trainId === undefined) wagon.trainId = null;
|
|
if (dto.sequenceNumber === undefined) wagon.sequenceNumber = null;
|
|
if (dto.currentYardId === undefined) wagon.currentYardId = null;
|
|
return this.wagonRepo.save(wagon);
|
|
}
|
|
|
|
async findAll(query: ListWagonsQueryDto = {}): Promise<Wagon[]> {
|
|
const where: FindOptionsWhere<Wagon>[] | FindOptionsWhere<Wagon> = [];
|
|
const search = query.search?.trim();
|
|
const trainId = query.trainId?.trim();
|
|
const wagonTypeId = query.wagonTypeId?.trim();
|
|
const filters: FindOptionsWhere<Wagon> = {
|
|
...(query.status ? { status: query.status } : {}),
|
|
...(query.currentYardId ? { currentYardId: query.currentYardId } : {}),
|
|
...(trainId ? { trainId } : {}),
|
|
...(wagonTypeId ? { wagonTypeId } : {}),
|
|
};
|
|
|
|
if (search) {
|
|
where.push({
|
|
wagonNumber: ILike(`%${search}%`),
|
|
...filters,
|
|
});
|
|
}
|
|
|
|
const sortBy = ['wagonNumber', 'tareWeight', 'maxPayloadWeight', 'status', 'currentYardId', '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 : filters,
|
|
relations: { currentYard: 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: { currentYard: 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);
|
|
// `findById` eager-loads `currentYard`; when the DTO changes the scalar FK
|
|
// TypeORM otherwise re-derives `current_yard_id` from the STALE relation
|
|
// object (the old yard) on save and silently reverts the change. Drop the
|
|
// relation so the scalar `currentYardId` wins.
|
|
if (dto.currentYardId !== undefined) {
|
|
wagon.currentYard = null;
|
|
}
|
|
await this.wagonRepo.save(wagon);
|
|
// Re-read with the relation so the response reflects the new yard label
|
|
// instead of the stale relation object loaded before the assign.
|
|
return this.findById(id);
|
|
}
|
|
|
|
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 === WagonStatus.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 = WagonStatus.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 = WagonStatus.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();
|
|
}
|
|
}
|
|
}
|