Files
edr-platform/apps/edr-freight-api/src/modules/train-scheduling/dto/create-container-train-schedule.dto.ts
Marshal e68bdb7a1a Enhance overview and train scheduling features
- Updated OverviewContractsTabPanel to include a new donut chart for freight type distribution.
- Modified OverviewOperationsTabPanel to improve data visualization with additional charts and refactored data handling.
- Introduced CreateScheduleWindowFields component for configuring booking windows in train scheduling.
- Added new API endpoints for allocation candidates and booking allocation in trainScheduling.service.
- Enhanced BookingRequestsPage to support allocation of paid bookings with a modal for selecting alternative dates.
- Updated QUERY_KEYS and URLS constants to accommodate new operations and features.
- Improved type definitions for overview and train scheduling to support new functionalities.
2026-08-03 21:06:59 +00:00

190 lines
5.2 KiB
TypeScript

import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { Type } from 'class-transformer';
import {
ArrayMinSize,
IsArray,
IsBoolean,
IsDateString,
IsInt,
IsNumber,
IsOptional,
IsUUID,
Max,
Min,
ValidateNested,
} from 'class-validator';
/**
* Per-schedule booking-window rule chosen AT CREATION, instead of inheriting the
* live global rules. Mirrors {@link UpdateScheduleWindowRuleDto}, plus the
* booking-close offset (which the post-creation override deliberately never
* touches). Every field is optional — an omitted field falls back to the global
* value, so staff can override just the one knob they care about.
*/
export class CreateScheduleWindowRuleDto {
@ApiPropertyOptional({ example: 8, description: 'Local EAT hour the booking desk opens each day' })
@IsOptional()
@Type(() => Number)
@IsInt()
@Min(0)
@Max(23)
windowOpenHour?: number;
@ApiPropertyOptional({
example: 17,
description:
'Local EAT hour the booking desk shuts each day. Equal to windowOpenHour = 24-hour desk',
})
@IsOptional()
@Type(() => Number)
@IsInt()
@Min(0)
@Max(23)
windowCloseHour?: number;
@ApiPropertyOptional({ example: 3, description: 'How long each booking cycle stays open, in hours' })
@IsOptional()
@Type(() => Number)
@IsNumber()
@Min(0.0166)
@Max(12)
windowDurationHours?: number;
@ApiPropertyOptional({ example: 30, description: 'Max staff document-review minutes after the window closes' })
@IsOptional()
@Type(() => Number)
@IsInt()
@Min(0)
docReviewMinutes?: number;
@ApiPropertyOptional({ example: 60, description: 'Customer payment window minutes' })
@IsOptional()
@Type(() => Number)
@IsInt()
@Min(1)
paymentWindowMinutes?: number;
@ApiPropertyOptional({
example: 3,
description: 'Days before departure the IMPORT/DOMESTIC booking window starts',
})
@IsOptional()
@Type(() => Number)
@IsInt()
@Min(0)
importWindowLeadDays?: number;
@ApiPropertyOptional({
example: 24,
description: 'Hours before departure the single FCFS EXPORT window opens',
})
@IsOptional()
@Type(() => Number)
@IsInt()
@Min(1)
exportBookingLeadHours?: number;
@ApiPropertyOptional({
example: 180,
nullable: true,
description:
'Minutes before departure the booking window closes; 0/null = close at departure. ' +
'Only the offset matching the schedule direction is used (import offset for ' +
'IMPORT/DOMESTIC, export offset for EXPORT).',
})
@IsOptional()
@Type(() => Number)
@IsInt()
@Min(0)
importCloseOffsetMinutes?: number | null;
@ApiPropertyOptional({
example: 1440,
nullable: true,
description: 'Minutes before departure an EXPORT booking window closes; 0/null = at departure',
})
@IsOptional()
@Type(() => Number)
@IsInt()
@Min(0)
exportCloseOffsetMinutes?: number | null;
}
export class CreateContainerTrainScheduleDto {
@ApiProperty({ format: 'uuid' })
@IsUUID()
routeId!: string;
@ApiProperty({ example: '2026-06-20T08:00:00.000Z' })
@IsDateString()
scheduleDate!: string;
@ApiPropertyOptional({
format: 'uuid',
description:
'Built train (Train Builder) to run this departure — its locomotive set is used. Provide either trainId or locomotiveIds.',
})
@IsOptional()
@IsUUID()
trainId?: string;
@ApiPropertyOptional({
type: [String],
format: 'uuid',
description:
'Hand-picked locomotives pulling the train (minimum 1). Ignored when trainId is provided.',
})
@IsOptional()
@IsArray()
@ArrayMinSize(1, { message: 'A train must be pulled by at least one locomotive' })
@IsUUID('all', { each: true })
locomotiveIds?: string[];
@ApiPropertyOptional({ description: 'Maximum total booking weight allowed on this train' })
@IsOptional()
@Type(() => Number)
@IsNumber()
@Min(1)
maxTrainWeightTons?: number;
@ApiPropertyOptional({ description: 'Maximum total wagon length allowed on this train' })
@IsOptional()
@Type(() => Number)
@IsNumber()
@Min(1)
maxTrainLengthMeters?: number;
@ApiPropertyOptional({ description: 'Maximum wagons allowed on this train' })
@IsOptional()
@Type(() => Number)
@IsInt()
@Min(1)
maxWagonsPerTrain?: number;
@ApiPropertyOptional({
description:
'Reverse the wagon order on this train: the physically-last wagon becomes ' +
'position 1. Frozen on the schedule; applied every time the wagon plan is ' +
'rebuilt so the stored train order and the schedule order stay in sync.',
default: false,
})
@IsOptional()
@IsBoolean()
reverseWagonOrder?: boolean;
@ApiPropertyOptional({
type: CreateScheduleWindowRuleDto,
description:
'Configure the booking window for THIS schedule instead of inheriting the live ' +
'global rules. Omit to use the global rules (the default). The values sent are ' +
'frozen onto the schedule as its rule snapshot, exactly as a post-creation ' +
'override would. Rejected for an IMPORT/DOMESTIC train that joins an existing ' +
'route+day group — those siblings share one window timeline, so edit the group ' +
"window instead of giving one member its own.",
})
@IsOptional()
@ValidateNested()
@Type(() => CreateScheduleWindowRuleDto)
windowRule?: CreateScheduleWindowRuleDto;
}