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:
Marshal
2026-07-21 08:50:50 +00:00
parent 1647681840
commit 603537a20b
45 changed files with 1275 additions and 227 deletions

View File

@@ -0,0 +1,87 @@
import { PaginatedResponse } from '@edr/types';
import { Injectable } from '@nestjs/common';
import { Brackets, DataSource, In, Repository } from 'typeorm';
import { paginateQuery } from '../../../common/utils/pagination.util';
import { ListYardDistancesQueryDto } from '../dto/list-rule-engine-query.dto';
import { YardDistance } from '../entities/yard-distance.entity';
import { IYardDistancesRepository } from '../interfaces/yard-distances.repository.interface';
@Injectable()
export class YardDistancesRepository implements IYardDistancesRepository {
private readonly repo: Repository<YardDistance>;
constructor(private readonly dataSource: DataSource) {
this.repo = this.dataSource.getRepository(YardDistance);
}
findById(id: string): Promise<YardDistance | null> {
return this.repo.findOne({
where: { id },
relations: { fromYard: true, toYard: true },
});
}
findBetween(fromYardId: string, toYardId: string): Promise<YardDistance | null> {
return this.repo.findOne({
where: [
{ fromYardId, toYardId },
{ fromYardId: toYardId, toYardId: fromYardId },
],
});
}
findTouchingYards(yardIds: string[]): Promise<YardDistance[]> {
if (!yardIds.length) return Promise.resolve([]);
return this.repo.find({
where: [{ fromYardId: In(yardIds) }, { toYardId: In(yardIds) }],
});
}
/** Paged list with server-side search on either yard's label/code. */
findPaged(query: ListYardDistancesQueryDto): Promise<PaginatedResponse<YardDistance>> {
const qb = this.repo
.createQueryBuilder('yardDistance')
.leftJoinAndSelect('yardDistance.fromYard', 'fromYard')
.leftJoinAndSelect('yardDistance.toYard', 'toYard')
.orderBy(`yardDistance.${query.sortBy ?? 'createdAt'}`, query.sortOrder ?? 'ASC')
.addOrderBy('fromYard.label', 'ASC');
if (query.yardId) {
qb.andWhere(
new Brackets((w) =>
w
.where('yardDistance.fromYardId = :yardId', { yardId: query.yardId })
.orWhere('yardDistance.toYardId = :yardId', { yardId: query.yardId }),
),
);
}
if (query.search) {
qb.andWhere(
new Brackets((w) =>
w
.where('fromYard.label ILIKE :search', { search: `%${query.search}%` })
.orWhere('fromYard.code ILIKE :search', { search: `%${query.search}%` })
.orWhere('toYard.label ILIKE :search', { search: `%${query.search}%` })
.orWhere('toYard.code ILIKE :search', { search: `%${query.search}%` }),
),
);
}
return paginateQuery(qb, query);
}
async create(data: Partial<YardDistance>): Promise<YardDistance> {
const entity = this.repo.create(data);
const saved = await this.repo.save(entity);
return (await this.findById(saved.id)) ?? saved;
}
async update(id: string, data: Partial<YardDistance>): Promise<YardDistance | null> {
await this.repo.update(id, data);
return this.findById(id);
}
async softDelete(id: string): Promise<void> {
await this.repo.softDelete(id);
}
}