import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { Transform } from 'class-transformer'; import { IsDateString, IsIn, IsNumber, IsOptional, IsString, MaxLength, Min, } from 'class-validator'; import { TARGET_DIMENSIONS, TARGET_METRICS, TARGET_PERIOD_TYPES, TargetDimension, TargetMetric, TargetPeriodType, } from '../entities/operations-target.entity'; const toNumber = ({ value }: { value: unknown }) => value === '' || value == null ? value : Number(value); export class CreateOperationsTargetDto { @ApiProperty({ enum: TARGET_PERIOD_TYPES }) @IsIn(TARGET_PERIOD_TYPES as unknown as string[]) periodType!: TargetPeriodType; @ApiProperty({ example: '2026-08-01', description: 'Any date inside the bucket — normalised to the bucket start on write.', }) @IsDateString() periodStart!: string; @ApiProperty({ enum: TARGET_METRICS }) @IsIn(TARGET_METRICS as unknown as string[]) metric!: TargetMetric; @ApiProperty({ enum: TARGET_DIMENSIONS }) @IsIn(TARGET_DIMENSIONS as unknown as string[]) dimension!: TargetDimension; @ApiProperty({ example: 'CONTAINER_IMPORT_MULTIMODAL', description: 'Category key, container-class key or yard code — not a display label.', }) @IsString() @MaxLength(60) dimensionKey!: string; @ApiProperty({ example: 1200 }) @Transform(toNumber) @IsNumber() @Min(0) plannedValue!: number; @ApiPropertyOptional({ description: 'Station targets only: which cargo category this station plan covers. Ignored for the ' + 'other dimensions, whose key already carries the category.', example: 'CONTAINER_IMPORT_MULTIMODAL', }) @IsOptional() // `'' ?? null` is `''`, and an empty string matches neither the unique // index's `COALESCE(cargo_category, '')` nor the report's join — it reads as // a category that does not exist. Blank means absent. @Transform(({ value }) => (value === '' ? null : value)) @IsString() @MaxLength(60) cargoCategory?: string | null; @ApiPropertyOptional() @IsOptional() @IsString() @MaxLength(500) note?: string; }