mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-07 13:05:44 +00:00
enhance contract and booking services with server-side search and validation improvements
- Added parameter to and for server-side free-text search on contract reference, company name, and booking details. - Introduced new validation errors in for container clashes and space issues when creating bookings. - Implemented paginated dropdown settings retrieval in . - Updated to fetch active yards using a new method that handles pagination. - Enhanced with a method to fetch all records by walking through pages. - Refactored to support filtering and pagination in schedule listings. - Improved to return a paginated list of facilities. - Updated UI components in and to utilize debounced search inputs for better performance. - Added alerts in to inform users about booking constraints related to splits and capacity. - Enhanced to display notifications for split bookings and capacity usage.
This commit is contained in:
@@ -17,8 +17,21 @@ import {
|
||||
} from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { InjectDataSource } from '@nestjs/typeorm';
|
||||
import { DataSource, EntityManager, In, Not, QueryFailedError } from 'typeorm';
|
||||
import {
|
||||
DataSource,
|
||||
EntityManager,
|
||||
FindOptionsWhere,
|
||||
ILike,
|
||||
In,
|
||||
Not,
|
||||
QueryFailedError,
|
||||
Raw,
|
||||
} from 'typeorm';
|
||||
|
||||
import {
|
||||
buildPaginationMeta,
|
||||
normalizePagination,
|
||||
} from '../../common/utils/pagination.util';
|
||||
import { BookingsRepository } from '../bookings/bookings.repository';
|
||||
import { Booking } from '../bookings/entities/booking.entity';
|
||||
import { BookingContainer } from '../bookings/entities/booking-container.entity';
|
||||
@@ -50,6 +63,10 @@ import { CreateContainerTrainScheduleDto } from './dto/create-container-train-sc
|
||||
import { GetEligibleBookingsDto } from './dto/get-eligible-bookings.dto';
|
||||
import { GetEligibleBulkBookingsDto } from './dto/get-eligible-bulk-bookings.dto';
|
||||
import { GetEligibleContainerBookingsDto } from './dto/get-eligible-container-bookings.dto';
|
||||
import {
|
||||
ListTrainSchedulesQueryDto,
|
||||
TrainScheduleFreightType,
|
||||
} from './dto/list-train-schedules-query.dto';
|
||||
import { PinWagonsDto } from './dto/pin-wagons.dto';
|
||||
import { UpdateContainerItemDto } from './dto/update-container-item.dto';
|
||||
import { UpdateImportLoadingStatusDto } from './dto/update-import-loading-status.dto';
|
||||
@@ -2641,8 +2658,42 @@ export class TrainSchedulingService {
|
||||
return Object.assign(detail, { warehouseAutomation });
|
||||
}
|
||||
|
||||
async getContainerTrainSchedules() {
|
||||
const schedules = await this.trainSchedulesRepository.findAll({
|
||||
async getContainerTrainSchedules(query: ListTrainSchedulesQueryDto = {}) {
|
||||
const { page, pageSize, skip, take } = normalizePagination(query);
|
||||
|
||||
// Exact-match filters (enum/id semantics). Freight type is derived from
|
||||
// the bookings aboard — no column to match — so it rides on `id` as an
|
||||
// EXISTS fragment instead.
|
||||
const base: FindOptionsWhere<TrainSchedule> = {};
|
||||
if (query.status) base.status = query.status;
|
||||
if (query.originStationId) base.originStationId = query.originStationId;
|
||||
if (query.destinationStationId) base.destinationStationId = query.destinationStationId;
|
||||
if (query.freightType) base.id = this.scheduleFreightTypeFilter(query.freightType) as never;
|
||||
|
||||
// Search fans out across every human-recognizable label; each OR variant
|
||||
// repeats the base filters so the search never widens them.
|
||||
const term = query.search?.trim();
|
||||
let where: FindOptionsWhere<TrainSchedule> | FindOptionsWhere<TrainSchedule>[] =
|
||||
base;
|
||||
if (term) {
|
||||
const like = ILike(`%${term}%`);
|
||||
where = [
|
||||
{ ...base, reference: like as never },
|
||||
{ ...base, trainNumber: like as never },
|
||||
{ ...base, originStation: { label: like } },
|
||||
{ ...base, destinationStation: { label: like } },
|
||||
{ ...base, route: { originYard: { label: like } } },
|
||||
{ ...base, route: { destinationYard: { label: like } } },
|
||||
{ ...base, trainSet: { locomotive: { code: like } } },
|
||||
] as FindOptionsWhere<TrainSchedule>[];
|
||||
}
|
||||
|
||||
// Newest-created first (the client can re-sort; this is the default order).
|
||||
const sortBy = query.sortBy ?? 'createdAt';
|
||||
const sortOrder = query.sortOrder ?? 'DESC';
|
||||
|
||||
const [schedules, total] = await this.trainSchedulesRepository.findAndCount({
|
||||
where,
|
||||
relations: {
|
||||
trainSet: { locomotive: true, locomotives: { locomotive: true } },
|
||||
// Yards carry the route's display name used by mapScheduleListItem;
|
||||
@@ -2652,10 +2703,14 @@ export class TrainSchedulingService {
|
||||
destinationStation: true,
|
||||
scheduleBookings: { booking: true },
|
||||
},
|
||||
// Newest-created first (the client can re-sort; this is the default order).
|
||||
order: { createdAt: 'DESC', scheduledDepartureDate: 'DESC' },
|
||||
order: { [sortBy]: sortOrder } as never,
|
||||
skip,
|
||||
take,
|
||||
});
|
||||
return schedules.map((s) => this.mapScheduleListItem(s));
|
||||
return {
|
||||
items: schedules.map((s) => this.mapScheduleListItem(s)),
|
||||
meta: buildPaginationMeta(total, page, pageSize),
|
||||
};
|
||||
}
|
||||
|
||||
async getContainerTrainScheduleById(id: string) {
|
||||
@@ -3869,6 +3924,33 @@ export class TrainSchedulingService {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* WHERE fragment matching the DERIVED schedule freight type — the SQL mirror
|
||||
* of {@link resolveScheduleFreightType} (keep the two in sync). CONTAINER /
|
||||
* BULK = has bookings and every one is that kind; MIXED = both kinds aboard.
|
||||
* Schedules with no bookings (type null) match nothing. Applied to `id` so
|
||||
* the list query stays on findAndCount instead of a query-builder rewrite.
|
||||
*/
|
||||
private scheduleFreightTypeFilter(freightType: TrainScheduleFreightType) {
|
||||
const hasBookingOfType = (alias: string, cmp: '=' | '<>', param: string) =>
|
||||
'EXISTS (SELECT 1 FROM freight.train_schedule_bookings tsb ' +
|
||||
'JOIN freight.bookings b ON b.id = tsb.booking_id AND b.deleted_at IS NULL ' +
|
||||
`WHERE tsb.train_schedule_id = ${alias} AND tsb.deleted_at IS NULL ` +
|
||||
`AND b.freight_type ${cmp} :${param})`;
|
||||
if (freightType === 'MIXED') {
|
||||
return Raw(
|
||||
(alias) =>
|
||||
`${hasBookingOfType(alias, '=', 'ftContainer')} AND ${hasBookingOfType(alias, '=', 'ftBulk')}`,
|
||||
{ ftContainer: 'CONTAINER', ftBulk: 'BULK' },
|
||||
);
|
||||
}
|
||||
return Raw(
|
||||
(alias) =>
|
||||
`${hasBookingOfType(alias, '=', 'ftIs')} AND NOT ${hasBookingOfType(alias, '<>', 'ftNot')}`,
|
||||
{ ftIs: freightType, ftNot: freightType },
|
||||
);
|
||||
}
|
||||
|
||||
private resolveScheduleFreightType(
|
||||
schedule: import('../train-schedules/entities/train-schedule.entity').TrainSchedule,
|
||||
): 'CONTAINER' | 'BULK' | 'MIXED' | null {
|
||||
|
||||
Reference in New Issue
Block a user