import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { Transform } from 'class-transformer'; import { IsIn, IsNumber, IsOptional, IsString, IsUUID, MaxLength, Min } from 'class-validator'; import { RATE_APPLIES_TO, RATE_TRIGGERS, RATE_UNITS, } from '../entities/rate.entity'; // DOMESTIC is accepted only for FUEL rates (an intercity fuel lane). const TRADE_DIRECTIONS = ['IMPORT', 'EXPORT', 'BOTH', 'DOMESTIC'] as const; // ETB is accepted only for last-mile rates; the service forces USD elsewhere. const CURRENCIES = ['USD', 'ETB'] as const; export const INTERCITY_KINDS = ['CONTAINER', 'BULK'] as const; export const CARGO_KINDS = ['CONTAINER', 'BULK'] as const; export class CreateRateDto { @ApiProperty({ enum: RATE_APPLIES_TO, description: 'Friendly category the rate applies to' }) @IsIn([...RATE_APPLIES_TO]) appliesTo!: string; @ApiProperty({ enum: RATE_TRIGGERS, description: 'What makes this rate apply. ALWAYS = base freight; anything else is a surcharge.', }) @IsIn([...RATE_TRIGGERS]) trigger!: string; @ApiPropertyOptional({ description: 'FK to container_types.id — set for container/intercity-container rates' }) @IsOptional() @IsUUID() containerTypeId?: string; @ApiPropertyOptional({ description: 'FK to cargo_types.id (bulk leaf commodity) — set for bulk/intercity-bulk rates' }) @IsOptional() @IsUUID() cargoTypeId?: string; @ApiPropertyOptional({ enum: TRADE_DIRECTIONS, description: 'Trade direction. Null = direction-agnostic' }) @IsOptional() @IsIn([...TRADE_DIRECTIONS]) tradeDirection?: string; @ApiPropertyOptional({ enum: INTERCITY_KINDS, description: 'Whether an intercity rate covers containers or bulk. Required when appliesTo = INTERCITY; ignored otherwise. Not stored — it selects the INTERCITY_CONTAINER / INTERCITY_BULK rate type.', }) @IsOptional() @IsIn([...INTERCITY_KINDS]) intercityKind?: string; @ApiPropertyOptional({ enum: CARGO_KINDS, description: 'Whether a customs clearance / cancellation rate covers containers or bulk. Required when trigger = CUSTOMS_CLEARANCE or CANCELLATION. Not stored — container fees carry a containerTypeId, bulk fees a cargoTypeId.', }) @IsOptional() @IsIn([...CARGO_KINDS]) cargoKind?: string; @ApiPropertyOptional({ description: 'FK to yards.id — origin of the leg this rate prices. Required for base freight (bulk/container/intercity), rejected for surcharges and first/last mile.', }) @IsOptional() @IsUUID() originYardId?: string; @ApiPropertyOptional({ description: 'FK to yards.id — destination of the leg this rate prices. Required for base freight (bulk/container/intercity), rejected for surcharges and first/last mile.', }) @IsOptional() @IsUUID() destinationYardId?: string; @ApiPropertyOptional({ description: 'FK to shipping_line_companies.id — set to price this rate for one shipping line only. Omitted/null = the standard rate every customer pays. A line rate overrides the standard one for that line\'s bookings.', }) @IsOptional() @IsUUID() shippingLineCompanyId?: string; @ApiPropertyOptional({ enum: CURRENCIES }) @IsOptional() @IsIn([...CURRENCIES]) currency?: string; @ApiProperty({ description: 'Numeric rate value', minimum: 0 }) @IsNumber() @Min(0) @Transform(({ value }) => Number(value)) rateValue!: number; @ApiPropertyOptional({ enum: RATE_UNITS, description: 'Unit basis for the rate. Optional for shapes with a forced unit (overweight is always PER_TON — the admin form hides the field and omits it); required otherwise.', }) @IsOptional() @IsIn([...RATE_UNITS]) rateUnit?: string; @ApiPropertyOptional({ description: 'FUEL rates billed PER_LITER only: liters the surcharge covers — price = baseLiters × rateValue, once per booking. Required there, rejected elsewhere.', minimum: 0, }) @IsOptional() @IsNumber() @Min(0) @Transform(({ value }) => (value === null || value === undefined || value === '' ? undefined : Number(value))) baseLiters?: number; @ApiPropertyOptional({ description: 'Distance band start (km, inclusive). Container last-mile rates only (rateUnit = PER_KM).', minimum: 0, }) @IsOptional() @IsNumber() @Min(0) @Transform(({ value }) => (value === null || value === undefined || value === '' ? undefined : Number(value))) minKm?: number; @ApiPropertyOptional({ description: 'Distance band end (km, exclusive). Null/omitted = open-ended band. Container last-mile rates only.', minimum: 0, }) @IsOptional() @IsNumber() @Min(0) @Transform(({ value }) => (value === null || value === undefined || value === '' ? undefined : Number(value))) maxKm?: number; } export class SubmitRateForApprovalDto { @ApiPropertyOptional({ description: 'Optional note for the approval request', maxLength: 500 }) @IsOptional() @IsString() @MaxLength(500) note?: string; }