import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { Transform, Type } from 'class-transformer'; import { IsArray, IsBoolean, IsDateString, IsIn, IsInt, IsNumber, IsOptional, IsString, IsUUID, Matches, Min, ValidateNested, } from 'class-validator'; /** Per-shipment equipment return — "NA" stays contract-level only. */ const SHIPMENT_EQUIPMENT_RETURNS = ['WITH_RETURN', 'WITHOUT_RETURN'] as const; /** One physical container under a booking line — entered at booking time. */ export class CreateContainerUnitDto { @ApiProperty({ description: 'ISO 6346 container number, e.g. ABCD1234567' }) @IsString() @Transform(({ value }) => (typeof value === 'string' ? value.trim().toUpperCase() : value)) @Matches(/^[A-Z]{4}\d{7}$/, { message: 'containerNumber must match ISO container format, e.g. ABCD1234567', }) containerNumber!: string; @ApiPropertyOptional() @IsOptional() @IsString() sealNumber?: string; @ApiProperty({ description: 'VGM in tons', minimum: 0 }) @IsNumber() @Min(0) @Transform(({ value }) => Number(value)) vgmTons!: number; @ApiPropertyOptional({ default: false }) @IsOptional() @IsBoolean() @Transform(({ value }) => value === 'true' || value === true) isHazardous?: boolean; @ApiPropertyOptional({ default: false }) @IsOptional() @IsBoolean() @Transform(({ value }) => value === 'true' || value === true) isReefer?: boolean; } export class CreateBookingContainerLineDto { @ApiProperty({ description: '"20ft" | "40ft" — must be in the contract scope' }) @IsString() containerSize!: string; @ApiProperty({ minimum: 1 }) @IsInt() @Min(1) @Transform(({ value }) => Number(value)) quantity!: number; @ApiPropertyOptional({ minimum: 0 }) @IsOptional() @IsInt() @Min(0) @Transform(({ value }) => Number(value)) hazardousQuantity?: number; @ApiPropertyOptional({ minimum: 0 }) @IsOptional() @IsInt() @Min(0) @Transform(({ value }) => Number(value)) reeferQuantity?: number; @ApiPropertyOptional({ minimum: 0, description: 'How many units of this line ship with empty-container return (≤ quantity). ' + 'Only allowed when the contract was created WITH_RETURN (container freight).', }) @IsOptional() @IsInt() @Min(0) @Transform(({ value }) => Number(value)) returnQuantity?: number; @ApiProperty({ type: [CreateContainerUnitDto] }) @IsArray() @ValidateNested({ each: true }) @Type(() => CreateContainerUnitDto) units!: CreateContainerUnitDto[]; } export class CreateBulkLineDto { @ApiPropertyOptional({ format: 'uuid' }) @IsOptional() @IsUUID() cargoTypeId?: string | null; @ApiPropertyOptional({ minimum: 0 }) @IsOptional() @IsNumber() @Min(0) @Transform(({ value }) => Number(value)) cargoWeightTons?: number; @ApiPropertyOptional({ minimum: 0 }) @IsOptional() @IsInt() @Min(0) @Transform(({ value }) => Number(value)) itemCount?: number; @ApiPropertyOptional({ minimum: 0 }) @IsOptional() @IsInt() @Min(0) @Transform(({ value }) => Number(value)) hazardousQuantity?: number; @ApiPropertyOptional({ minimum: 0 }) @IsOptional() @IsInt() @Min(0) @Transform(({ value }) => Number(value)) reeferQuantity?: number; } /** Shipment booking created under a contract (Path A customer, Path B GL ET). */ export class CreateBookingUnderContractDto { @ApiPropertyOptional({ format: 'uuid', description: 'Required for GENERAL multi-route contracts; ONE_TIME auto-selected.', }) @IsOptional() @IsUUID() contractRouteId?: string; @ApiPropertyOptional({ description: 'Binding shipment day. Omitted for intercity (DOMESTIC) bookings — staff assign a passing train later.', example: '2026-07-15', }) @IsOptional() @IsDateString() scheduledDate?: string; @ApiPropertyOptional({ enum: SHIPMENT_EQUIPMENT_RETURNS, description: 'Per-shipment equipment return override; omitted → the contract default applies.', }) @IsOptional() @IsIn([...SHIPMENT_EQUIPMENT_RETURNS]) equipmentReturn?: string; @ApiPropertyOptional({ type: [CreateBookingContainerLineDto] }) @IsOptional() @IsArray() @ValidateNested({ each: true }) @Type(() => CreateBookingContainerLineDto) containers?: CreateBookingContainerLineDto[]; @ApiPropertyOptional({ type: [CreateBulkLineDto] }) @IsOptional() @IsArray() @ValidateNested({ each: true }) @Type(() => CreateBulkLineDto) bulkLines?: CreateBulkLineDto[]; @ApiPropertyOptional() @IsOptional() @IsString() notes?: string; }