import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { IsString, IsEmail, IsNumber, Min, IsOptional, IsEnum, IsObject, MinLength, MaxLength, } from 'class-validator'; /** * Create CSV Booking DTO * * Request body for creating a new CSV-based booking request * This is sent by the user after selecting a rate from CSV search results */ export class CreateCsvBookingDto { @ApiProperty({ description: 'Carrier/Company name', example: 'SSC Consolidation', }) @IsString() @MinLength(2) @MaxLength(200) carrierName: string; @ApiProperty({ description: 'Carrier email address for booking request', example: 'bookings@sscconsolidation.com', }) @IsEmail() carrierEmail: string; @ApiProperty({ description: 'Origin port code (UN/LOCODE)', example: 'NLRTM', }) @IsString() @MinLength(5) @MaxLength(5) origin: string; @ApiProperty({ description: 'Destination port code (UN/LOCODE)', example: 'USNYC', }) @IsString() @MinLength(5) @MaxLength(5) destination: string; @ApiProperty({ description: 'Volume in cubic meters (CBM)', example: 25.5, minimum: 0.01, }) @IsNumber() @Min(0.01) volumeCBM: number; @ApiProperty({ description: 'Weight in kilograms', example: 3500, minimum: 1, }) @IsNumber() @Min(1) weightKG: number; @ApiProperty({ description: 'Number of pallets', example: 10, minimum: 0, }) @IsNumber() @Min(0) palletCount: number; @ApiProperty({ description: 'Price in USD', example: 1850.5, minimum: 0, }) @IsNumber() @Min(0) priceUSD: number; @ApiProperty({ description: 'Price in EUR', example: 1665.45, minimum: 0, }) @IsNumber() @Min(0) priceEUR: number; @ApiProperty({ description: 'Primary currency', enum: ['USD', 'EUR'], example: 'USD', }) @IsEnum(['USD', 'EUR']) primaryCurrency: string; @ApiPropertyOptional({ description: 'Freight charge total (in freightCurrency)', example: 420.0, minimum: 0, }) @IsOptional() @IsNumber() @Min(0) freightTotal?: number; @ApiPropertyOptional({ description: 'Currency of the freight charge', enum: ['USD', 'EUR'], example: 'USD', }) @IsOptional() @IsEnum(['USD', 'EUR']) freightCurrency?: string; @ApiPropertyOptional({ description: 'FOB charges total (in fobCurrency)', example: 245.0, minimum: 0, }) @IsOptional() @IsNumber() @Min(0) fobTotal?: number; @ApiPropertyOptional({ description: 'Currency of the FOB charges', enum: ['USD', 'EUR'], example: 'EUR', }) @IsOptional() @IsEnum(['USD', 'EUR']) fobCurrency?: string; @ApiProperty({ description: 'Transit time in days', example: 28, minimum: 1, }) @IsNumber() @Min(1) transitDays: number; @ApiProperty({ description: 'Container type', example: 'LCL', }) @IsString() @MinLength(2) @MaxLength(50) containerType: string; @ApiPropertyOptional({ description: 'Additional notes or requirements', example: 'Please handle with care - fragile goods', }) @IsOptional() @IsString() @MaxLength(1000) notes?: string; @ApiPropertyOptional({ description: 'Selected options & services as a JSON string (multipart), e.g. {"insurance":true}', example: '{"insurance":true,"customsStop":true}', }) @IsOptional() @IsString() @MaxLength(2000) options?: string; // Documents will be handled via file upload interceptor // Not included in DTO validation but processed separately } /** * Update CSV Booking Details DTO * * Editable cargo characteristics before payment. Carrier, route and price are * derived from the selected rate and cannot be changed here. */ export class UpdateCsvBookingDetailsDto { @ApiPropertyOptional({ description: 'Total volume in cubic meters (CBM)', example: 12.5 }) @IsOptional() @IsNumber() @Min(0.01) volumeCBM?: number; @ApiPropertyOptional({ description: 'Total weight in kilograms', example: 1500 }) @IsOptional() @IsNumber() @Min(0.01) weightKG?: number; @ApiPropertyOptional({ description: 'Number of pallets', example: 5 }) @IsOptional() @IsNumber() @Min(0) palletCount?: number; @ApiPropertyOptional({ description: 'Additional notes', example: 'Please handle with care' }) @IsOptional() @IsString() @MaxLength(2000) notes?: string; // Recomputed pricing (sent when cargo details change). Optional; when omitted // the price is left unchanged. @ApiPropertyOptional({ description: 'Recomputed total price in USD' }) @IsOptional() @IsNumber() @Min(0) priceUSD?: number; @ApiPropertyOptional({ description: 'Recomputed total price in EUR' }) @IsOptional() @IsNumber() @Min(0) priceEUR?: number; @ApiPropertyOptional({ description: 'Primary currency (USD/EUR)' }) @IsOptional() @IsString() @MaxLength(3) primaryCurrency?: string; @ApiPropertyOptional({ description: 'Recomputed freight total' }) @IsOptional() @IsNumber() @Min(0) freightTotal?: number; @ApiPropertyOptional({ description: 'Freight currency' }) @IsOptional() @IsString() @MaxLength(3) freightCurrency?: string; @ApiPropertyOptional({ description: 'Recomputed FOB total' }) @IsOptional() @IsNumber() @Min(0) fobTotal?: number; @ApiPropertyOptional({ description: 'FOB currency' }) @IsOptional() @IsString() @MaxLength(3) fobCurrency?: string; } /** * Update CSV Booking Rate DTO * * Full re-selection of a rate before payment (carrier + route + container + * transit + cargo + price). Used when the user re-runs the search and picks a * (possibly different) rate for a PENDING_PAYMENT booking. */ export class UpdateCsvBookingRateDto { @ApiProperty({ example: 'SSC Consolidation' }) @IsString() @MinLength(2) @MaxLength(200) carrierName: string; @ApiProperty({ example: 'bookings@example.com' }) @IsEmail() carrierEmail: string; @ApiProperty({ example: 'FRLIO' }) @IsString() @MaxLength(5) origin: string; @ApiProperty({ example: 'AUADL' }) @IsString() @MaxLength(5) destination: string; @ApiProperty({ example: 'LCL' }) @IsString() @MaxLength(50) containerType: string; @ApiProperty({ example: 46 }) @IsNumber() @Min(1) transitDays: number; @ApiProperty({ example: 12.5 }) @IsNumber() @Min(0.01) volumeCBM: number; @ApiProperty({ example: 1500 }) @IsNumber() @Min(0.01) weightKG: number; @ApiProperty({ example: 5 }) @IsNumber() @Min(0) palletCount: number; @ApiProperty({ example: 0 }) @IsNumber() @Min(0) priceUSD: number; @ApiProperty({ example: 258 }) @IsNumber() @Min(0) priceEUR: number; @ApiProperty({ example: 'EUR' }) @IsString() @MaxLength(3) primaryCurrency: string; @ApiPropertyOptional({ example: 150 }) @IsOptional() @IsNumber() @Min(0) freightTotal?: number; @ApiPropertyOptional({ example: 'EUR' }) @IsOptional() @IsString() @MaxLength(3) freightCurrency?: string; @ApiPropertyOptional({ example: 108 }) @IsOptional() @IsNumber() @Min(0) fobTotal?: number; @ApiPropertyOptional({ example: 'EUR' }) @IsOptional() @IsString() @MaxLength(3) fobCurrency?: string; @ApiPropertyOptional({ example: 'Handle with care' }) @IsOptional() @IsString() @MaxLength(2000) notes?: string; @ApiPropertyOptional({ description: 'Selected options & services (customs, insurance, DG, handling…)', example: { insurance: true, customsStop: true }, }) @IsOptional() @IsObject() options?: Record; } /** * Document DTO for response */ export class CsvBookingDocumentDto { @ApiProperty({ description: 'Document unique ID', example: '123e4567-e89b-12d3-a456-426614174000', }) id: string; @ApiProperty({ description: 'Document type', enum: [ 'BILL_OF_LADING', 'PACKING_LIST', 'COMMERCIAL_INVOICE', 'CERTIFICATE_OF_ORIGIN', 'OTHER', ], example: 'BILL_OF_LADING', }) type: string; @ApiProperty({ description: 'Original file name', example: 'bill-of-lading.pdf', }) fileName: string; @ApiProperty({ description: 'File storage path or URL', example: '/uploads/documents/123e4567-e89b-12d3-a456-426614174000.pdf', }) filePath: string; @ApiProperty({ description: 'File MIME type', example: 'application/pdf', }) mimeType: string; @ApiProperty({ description: 'File size in bytes', example: 245678, }) size: number; @ApiProperty({ description: 'Upload timestamp', example: '2025-10-23T14:30:00Z', }) uploadedAt: Date; } /** * CSV Booking Response DTO * * Response when creating or retrieving a CSV booking */ export class CsvBookingResponseDto { @ApiProperty({ description: 'Booking unique ID', example: '123e4567-e89b-12d3-a456-426614174000', }) id: string; @ApiPropertyOptional({ description: 'Booking number (e.g. XPD-2026-W75VPT)', example: 'XPD-2026-W75VPT', }) bookingNumber?: string; @ApiProperty({ description: 'User ID who created the booking', example: '987fcdeb-51a2-43e8-9c6d-8b9a1c2d3e4f', }) userId: string; @ApiProperty({ description: 'Organization ID', example: 'a1234567-0000-4000-8000-000000000001', }) organizationId: string; @ApiProperty({ description: 'Carrier/Company name', example: 'SSC Consolidation', }) carrierName: string; @ApiProperty({ description: 'Carrier email address', example: 'bookings@sscconsolidation.com', }) carrierEmail: string; @ApiProperty({ description: 'Origin port code', example: 'NLRTM', }) origin: string; @ApiProperty({ description: 'Destination port code', example: 'USNYC', }) destination: string; @ApiProperty({ description: 'Volume in CBM', example: 25.5, }) volumeCBM: number; @ApiProperty({ description: 'Weight in KG', example: 3500, }) weightKG: number; @ApiProperty({ description: 'Number of pallets', example: 10, }) palletCount: number; @ApiProperty({ description: 'Price in USD', example: 1850.5, }) priceUSD: number; @ApiProperty({ description: 'Price in EUR', example: 1665.45, }) priceEUR: number; @ApiProperty({ description: 'Primary currency', enum: ['USD', 'EUR'], example: 'USD', }) primaryCurrency: string; @ApiProperty({ description: 'Transit time in days', example: 28, }) transitDays: number; @ApiProperty({ description: 'Container type', example: 'LCL', }) containerType: string; @ApiProperty({ description: 'Booking status', enum: ['PENDING_PAYMENT', 'PENDING', 'ACCEPTED', 'REJECTED', 'CANCELLED'], example: 'PENDING_PAYMENT', }) status: string; @ApiProperty({ description: 'Uploaded documents', type: [CsvBookingDocumentDto], }) documents: CsvBookingDocumentDto[]; @ApiProperty({ description: 'Confirmation token for accept/reject actions', example: 'abc123-def456-ghi789', }) confirmationToken: string; @ApiProperty({ description: 'Booking request timestamp', example: '2025-10-23T14:30:00Z', }) requestedAt: Date; @ApiProperty({ description: 'Response timestamp (when accepted/rejected)', example: '2025-10-24T09:15:00Z', nullable: true, }) respondedAt: Date | null; @ApiPropertyOptional({ description: 'Additional notes', example: 'Please handle with care', }) notes?: string; @ApiPropertyOptional({ description: 'Rejection reason (if rejected)', example: 'No capacity available for requested dates', }) rejectionReason?: string; @ApiProperty({ description: 'Route description (origin → destination)', example: 'NLRTM → USNYC', }) routeDescription: string; @ApiProperty({ description: 'Whether the booking is expired (7+ days pending)', example: false, }) isExpired: boolean; @ApiProperty({ description: 'Price in the primary currency', example: 1850.5, }) price: number; @ApiPropertyOptional({ description: 'Commission rate in percent', example: 5, }) commissionRate?: number; @ApiPropertyOptional({ description: 'Commission amount in EUR', example: 313.27, }) commissionAmountEur?: number; @ApiPropertyOptional({ description: 'Freight charge total (in freightCurrency)', example: 420.0, }) freightTotal?: number; @ApiPropertyOptional({ description: 'Currency of the freight charge', example: 'USD', }) freightCurrency?: string; @ApiPropertyOptional({ description: 'FOB charges total (in fobCurrency)', example: 245.0, }) fobTotal?: number; @ApiPropertyOptional({ description: 'Currency of the FOB charges', example: 'EUR', }) fobCurrency?: string; @ApiPropertyOptional({ description: 'Selected options & services (customs, insurance, DG, handling…)', example: { insurance: true }, }) options?: Record; } /** * Update CSV Booking Status DTO * * Request body for accepting/rejecting a booking */ export class UpdateCsvBookingStatusDto { @ApiPropertyOptional({ description: 'Rejection reason (required when rejecting)', example: 'No capacity available', }) @IsOptional() @IsString() @MaxLength(500) rejectionReason?: string; } /** * CSV Booking List Response DTO * * Paginated list of bookings */ export class CsvBookingListResponseDto { @ApiProperty({ description: 'Array of bookings', type: [CsvBookingResponseDto], }) bookings: CsvBookingResponseDto[]; @ApiProperty({ description: 'Total number of bookings', example: 42, }) total: number; @ApiProperty({ description: 'Current page number', example: 1, }) page: number; @ApiProperty({ description: 'Number of items per page', example: 10, }) limit: number; @ApiProperty({ description: 'Total number of pages', example: 5, }) totalPages: number; } /** * CSV Booking Statistics DTO * * Statistics for user's or organization's bookings */ export class CsvBookingStatsDto { @ApiProperty({ description: 'Number of bookings awaiting payment', example: 1, }) pendingPayment: number; @ApiProperty({ description: 'Number of pending bookings', example: 5, }) pending: number; @ApiProperty({ description: 'Number of accepted bookings', example: 12, }) accepted: number; @ApiProperty({ description: 'Number of rejected bookings', example: 2, }) rejected: number; @ApiProperty({ description: 'Number of cancelled bookings', example: 1, }) cancelled: number; @ApiProperty({ description: 'Total number of bookings', example: 20, }) total: number; }