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.
This commit is contained in:
Marshal
2026-08-03 21:06:59 +00:00
parent 488c2465be
commit e68bdb7a1a
30 changed files with 1580 additions and 82 deletions

View File

@@ -9,9 +9,107 @@ import {
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()
@@ -73,4 +171,19 @@ export class CreateContainerTrainScheduleDto {
@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;
}