mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 00:38:11 +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:
@@ -78,8 +78,10 @@ export interface BookingListFilterOptions {
|
||||
createdTo?: string;
|
||||
scheduledFrom?: string;
|
||||
scheduledTo?: string;
|
||||
originYardId?: string;
|
||||
destinationYardId?: string;
|
||||
/** Any of these origin yards (OR). ANDed with `destinationYardId`. */
|
||||
originYardId?: string[];
|
||||
/** Any of these destination yards (OR). ANDed with `originYardId`. */
|
||||
destinationYardId?: string[];
|
||||
isGovernment?: 'true' | 'false';
|
||||
/** Shipping-line bookings vs ordinary customer bookings (exactly one owner is set). */
|
||||
customerKind?: 'SHIPPING_LINE' | 'CUSTOMER';
|
||||
@@ -1035,14 +1037,17 @@ export class BookingsRepository extends BaseRepository<Booking> {
|
||||
scheduledTo: options.scheduledTo,
|
||||
});
|
||||
}
|
||||
if (options.originYardId) {
|
||||
qb.andWhere('booking.origin_yard_id = :originYardId', {
|
||||
originYardId: options.originYardId,
|
||||
// Each end is its own OR-list, and the two ends AND together — so
|
||||
// "leaving Nagad or DMP" and "leaving Nagad, arriving Gelan" are both
|
||||
// expressible. `?.length` guards the empty array: `IN ()` is a syntax error.
|
||||
if (options.originYardId?.length) {
|
||||
qb.andWhere('booking.origin_yard_id IN (:...originYardIds)', {
|
||||
originYardIds: options.originYardId,
|
||||
});
|
||||
}
|
||||
if (options.destinationYardId) {
|
||||
qb.andWhere('booking.destination_yard_id = :destinationYardId', {
|
||||
destinationYardId: options.destinationYardId,
|
||||
if (options.destinationYardId?.length) {
|
||||
qb.andWhere('booking.destination_yard_id IN (:...destinationYardIds)', {
|
||||
destinationYardIds: options.destinationYardId,
|
||||
});
|
||||
}
|
||||
if (options.isGovernment === 'true') {
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
TRADE_DIRECTIONS,
|
||||
} from './create-booking.dto';
|
||||
import { PAYMENT_STATUSES } from '../entities/booking.entity';
|
||||
import { IdListParam } from '../../../common/dto/id-list.transform';
|
||||
|
||||
export class FilterBookingDto {
|
||||
@ApiPropertyOptional({ enum: BOOKING_STATUSES })
|
||||
@@ -96,15 +97,23 @@ export class FilterBookingDto {
|
||||
@IsDateString()
|
||||
scheduledTo?: string;
|
||||
|
||||
@ApiPropertyOptional({ format: 'uuid', description: 'Filter by origin yard' })
|
||||
@ApiPropertyOptional({
|
||||
description:
|
||||
'Filter by origin yard — one id or a comma-separated list; a booking matches if it leaves ANY of them.',
|
||||
})
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
originYardId?: string;
|
||||
@IdListParam()
|
||||
@IsUUID(undefined, { each: true })
|
||||
originYardId?: string[];
|
||||
|
||||
@ApiPropertyOptional({ format: 'uuid', description: 'Filter by destination yard' })
|
||||
@ApiPropertyOptional({
|
||||
description:
|
||||
'Filter by destination yard — one id or a comma-separated list; a booking matches if it arrives at ANY of them. Combined with originYardId by AND.',
|
||||
})
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
destinationYardId?: string;
|
||||
@IdListParam()
|
||||
@IsUUID(undefined, { each: true })
|
||||
destinationYardId?: string[];
|
||||
|
||||
@ApiPropertyOptional({ enum: ['true', 'false'], description: 'Filter government vs private bookings' })
|
||||
@IsOptional()
|
||||
|
||||
Reference in New Issue
Block a user