mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 15:48:11 +00:00
add yard distances management to rule engine
- Introduced new yard distances resource with CRUD operations. - Created migration for yard distances table with necessary constraints. - Implemented service and repository for yard distances handling. - Added controller for API endpoints to manage yard distances. - Updated rule engine configuration to include yard distances. - Enhanced rule engine resource page to support yard distance selection. - Updated contracts and train builder pages to handle new yard distance logic. - Added error handling utility for better error message extraction.
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
import { PaginatedResponse } from '@edr/types';
|
||||
import {
|
||||
BadRequestException,
|
||||
ConflictException,
|
||||
Inject,
|
||||
Injectable,
|
||||
NotFoundException,
|
||||
} from '@nestjs/common';
|
||||
import { CreateYardDistanceDto } from '../dto/create-yard-distance.dto';
|
||||
import { ListYardDistancesQueryDto } from '../dto/list-rule-engine-query.dto';
|
||||
import { UpdateYardDistanceDto } from '../dto/update-yard-distance.dto';
|
||||
import { YardDistance } from '../entities/yard-distance.entity';
|
||||
import {
|
||||
IYardDistancesRepository,
|
||||
YARD_DISTANCES_REPOSITORY,
|
||||
} from '../interfaces/yard-distances.repository.interface';
|
||||
import { IYardsRepository, YARDS_REPOSITORY } from '../interfaces/yards.repository.interface';
|
||||
|
||||
/**
|
||||
* Flat row shape for the backoffice config table: the yard relations stay for
|
||||
* API consumers, plus label fields the generic rule-engine grid can render.
|
||||
*/
|
||||
export type YardDistanceRow = YardDistance & {
|
||||
fromYardLabel: string;
|
||||
toYardLabel: string;
|
||||
};
|
||||
|
||||
const yardDisplay = (yard?: { label?: string; code?: string } | null): string =>
|
||||
yard?.label ?? yard?.code ?? '—';
|
||||
|
||||
const toRow = (entity: YardDistance): YardDistanceRow =>
|
||||
Object.assign(entity, {
|
||||
fromYardLabel: yardDisplay(entity.fromYard),
|
||||
toYardLabel: yardDisplay(entity.toYard),
|
||||
});
|
||||
|
||||
@Injectable()
|
||||
export class YardDistancesService {
|
||||
constructor(
|
||||
@Inject(YARD_DISTANCES_REPOSITORY)
|
||||
private readonly repository: IYardDistancesRepository,
|
||||
@Inject(YARDS_REPOSITORY)
|
||||
private readonly yardsRepository: IYardsRepository,
|
||||
) {}
|
||||
|
||||
async findAll(query: ListYardDistancesQueryDto): Promise<PaginatedResponse<YardDistanceRow>> {
|
||||
const page = await this.repository.findPaged(query);
|
||||
return { ...page, items: page.items.map(toRow) };
|
||||
}
|
||||
|
||||
async findById(id: string): Promise<YardDistanceRow> {
|
||||
const entity = await this.repository.findById(id);
|
||||
if (!entity) throw new NotFoundException(`Yard distance ${id} not found`);
|
||||
return toRow(entity);
|
||||
}
|
||||
|
||||
async create(dto: CreateYardDistanceDto): Promise<YardDistanceRow> {
|
||||
await this.assertValidPair(dto.fromYardId, dto.toYardId);
|
||||
|
||||
const created = await this.repository.create({
|
||||
fromYardId: dto.fromYardId,
|
||||
toYardId: dto.toYardId,
|
||||
distanceKm: dto.distanceKm.toFixed(2),
|
||||
});
|
||||
return toRow(created);
|
||||
}
|
||||
|
||||
async update(id: string, dto: UpdateYardDistanceDto): Promise<YardDistanceRow> {
|
||||
const existing = await this.findById(id);
|
||||
|
||||
const fromYardId = dto.fromYardId ?? existing.fromYardId;
|
||||
const toYardId = dto.toYardId ?? existing.toYardId;
|
||||
if (fromYardId !== existing.fromYardId || toYardId !== existing.toYardId) {
|
||||
await this.assertValidPair(fromYardId, toYardId, id);
|
||||
}
|
||||
|
||||
const updated = await this.repository.update(id, {
|
||||
fromYardId,
|
||||
toYardId,
|
||||
...(dto.distanceKm != null ? { distanceKm: dto.distanceKm.toFixed(2) } : {}),
|
||||
});
|
||||
if (!updated) throw new NotFoundException(`Yard distance ${id} not found`);
|
||||
return toRow(updated);
|
||||
}
|
||||
|
||||
async remove(id: string): Promise<void> {
|
||||
await this.findById(id);
|
||||
await this.repository.softDelete(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Both yards must exist and differ, and the pair must not already be
|
||||
* configured in either direction — distances are symmetric, so an A→B row
|
||||
* already covers B→A.
|
||||
*/
|
||||
private async assertValidPair(
|
||||
fromYardId: string,
|
||||
toYardId: string,
|
||||
ignoreId?: string,
|
||||
): Promise<void> {
|
||||
if (fromYardId === toYardId) {
|
||||
throw new BadRequestException('From and to yards must be different');
|
||||
}
|
||||
|
||||
const [fromYard, toYard] = await Promise.all([
|
||||
this.yardsRepository.findById(fromYardId),
|
||||
this.yardsRepository.findById(toYardId),
|
||||
]);
|
||||
if (!fromYard) throw new BadRequestException(`Yard ${fromYardId} does not exist`);
|
||||
if (!toYard) throw new BadRequestException(`Yard ${toYardId} does not exist`);
|
||||
|
||||
const existing = await this.repository.findBetween(fromYardId, toYardId);
|
||||
if (existing && existing.id !== ignoreId) {
|
||||
throw new ConflictException(
|
||||
`A distance between ${fromYard.label} and ${toYard.label} is already configured`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user