mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-28 19:30:57 +00:00
feat(api): accept several yards on each end of a route filter
Bookings, contracts and train schedules all validated originYardId / destinationYardId (originStationId / destinationStationId) as a single @IsUUID and matched with `=`, so a list could be narrowed to exactly one lane. The filter bar can now ask for several stations per end, and each end independently, which needs the same on the server. @IdListParam() is the shared transform: one id, a comma-separated list, or a repeated query param, always landing as a string[]. It yields undefined rather than [] when nothing usable is left — a repository that branches on `?.length` can then never hand TypeORM an empty array, which compiles to the syntax error IN (). It stays backwards compatible with the single-value form, so existing deep links and saved views are unaffected. Matching moves to IN (:...ids) — for contracts inside the two existing EXISTS subqueries, which keeps meaning "has a route from one of these origins" AND "has a route to one of these destinations", not necessarily the same route. All three statements were EXPLAIN-validated against edr_dev.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { IsIn, IsOptional, IsUUID } from 'class-validator';
|
||||
|
||||
import { IdListParam } from '../../../common/dto/id-list.transform';
|
||||
import { PaginationQueryDto } from '../../../common/dto/pagination-query.dto';
|
||||
import {
|
||||
TRAIN_SCHEDULE_STATUSES,
|
||||
@@ -50,15 +51,22 @@ export class ListTrainSchedulesQueryDto extends PaginationQueryDto {
|
||||
@IsIn(TRAIN_SCHEDULE_FREIGHT_TYPES as unknown as string[])
|
||||
freightType?: TrainScheduleFreightType;
|
||||
|
||||
/** Origin station/yard id (exact match). */
|
||||
@ApiPropertyOptional({ format: 'uuid' })
|
||||
/** Origin station/yard — one id or a comma-separated list; matches ANY of them. */
|
||||
@ApiPropertyOptional({
|
||||
description: 'Origin station/yard id, or a comma-separated list (matches any of them).',
|
||||
})
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
originStationId?: string;
|
||||
@IdListParam()
|
||||
@IsUUID(undefined, { each: true })
|
||||
originStationId?: string[];
|
||||
|
||||
/** Destination station/yard id (exact match). */
|
||||
@ApiPropertyOptional({ format: 'uuid' })
|
||||
/** Destination station/yard — one id or a comma-separated list; ANDed with the origin. */
|
||||
@ApiPropertyOptional({
|
||||
description:
|
||||
'Destination station/yard id, or a comma-separated list (matches any of them). ANDed with originStationId.',
|
||||
})
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
destinationStationId?: string;
|
||||
@IdListParam()
|
||||
@IsUUID(undefined, { each: true })
|
||||
destinationStationId?: string[];
|
||||
}
|
||||
|
||||
@@ -4570,8 +4570,15 @@ export class TrainSchedulingService {
|
||||
const base: FindOptionsWhere<TrainSchedule> = {};
|
||||
if (allowedDirections) base.direction = In(allowedDirections) as never;
|
||||
if (query.status) base.status = query.status;
|
||||
if (query.originStationId) base.originStationId = query.originStationId;
|
||||
if (query.destinationStationId) base.destinationStationId = query.destinationStationId;
|
||||
// Each end is an OR-list, the two ends AND together (origin-only and
|
||||
// destination-only are both valid queries). `?.length` guards the empty
|
||||
// array — `In([])` compiles to `IN ()`, a syntax error.
|
||||
if (query.originStationId?.length) {
|
||||
base.originStationId = In(query.originStationId) as never;
|
||||
}
|
||||
if (query.destinationStationId?.length) {
|
||||
base.destinationStationId = In(query.destinationStationId) as never;
|
||||
}
|
||||
if (query.freightType) base.id = this.scheduleFreightTypeFilter(query.freightType) as never;
|
||||
|
||||
// Search fans out across every human-recognizable label; each OR variant
|
||||
|
||||
Reference in New Issue
Block a user