Files
edr-platform/apps/edr-freight-api/src/modules/bookings/dto/create-booking.dto.ts

198 lines
5.0 KiB
TypeScript

import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { Transform, Type } from 'class-transformer';
import {
ArrayMinSize,
IsArray,
IsBoolean,
IsDateString,
IsIn,
IsInt,
IsNumber,
IsOptional,
IsString,
IsUUID,
Min,
Validate,
ValidateIf,
ValidateNested,
} from 'class-validator';
import { BOOKING_STATUSES, FREIGHT_TYPES } from '../entities/booking.entity';
import { BookingFreightShapeConstraint } from './validators/booking-freight.validator';
const CONTRACT_TYPES = ['NEW', 'RENEWAL'] as const;
const EQUIPMENT_RETURNS = ['WITH_RETURN', 'WITHOUT_RETURN', 'NA'] as const;
const TRADE_DIRECTIONS = ['IMPORT', 'EXPORT', 'DOMESTIC'] as const;
const PAYMENT_CURRENCIES = ['ETB', 'USD'] as const;
export {
BOOKING_STATUSES,
CONTRACT_TYPES,
EQUIPMENT_RETURNS,
FREIGHT_TYPES,
TRADE_DIRECTIONS,
PAYMENT_CURRENCIES,
};
export class CreateBookingContainerDto {
@ApiProperty({ format: 'uuid', description: 'FK to container_types.id' })
@IsUUID()
containerTypeId!: string;
@ApiProperty({ description: 'Quantity of containers', minimum: 1 })
@IsInt()
@Min(1)
@Transform(({ value }) => Number(value))
quantity!: number;
@ApiProperty({ description: 'VGM per container in tons', minimum: 0 })
@IsNumber()
@Min(0)
@Transform(({ value }) => Number(value))
vgmPerUnitTons!: number;
}
export class CreateBookingDto {
/** Class-level freight shape check (not a request field). */
@Validate(BookingFreightShapeConstraint)
freightShapeValidation?: boolean;
@ApiPropertyOptional({ description: 'Unique booking reference (auto-generated if omitted)' })
@IsOptional()
@IsString()
@Transform(({ value }) => (typeof value === 'string' ? value.trim() : value))
reference?: string;
// @ApiPropertyOptional({ format: 'uuid', description: 'Admin only: target customer (legacy)' })
// @IsOptional()
// @IsUUID()
// customerId?: string;
@ApiPropertyOptional({ format: 'uuid', description: 'Admin only: target company' })
@IsOptional()
@IsUUID()
companyId?: string;
@ApiPropertyOptional({ format: 'uuid' })
@IsOptional()
@IsUUID()
trainId?: string;
@ApiProperty({ example: '2026-06-15T00:00:00.000Z' })
@IsDateString()
scheduledDate!: string;
@ApiProperty({ enum: CONTRACT_TYPES })
@IsIn([...CONTRACT_TYPES])
contractType!: string;
@ApiPropertyOptional({ format: 'uuid' })
@IsOptional()
@IsUUID()
@Transform(({ value }) => (value === '' || value == null ? undefined : value))
previousContractId?: string;
@ApiProperty({ format: 'uuid', description: 'FK to service_types.id' })
@IsUUID()
serviceTypeId!: string;
@ApiPropertyOptional()
@IsOptional()
@IsString()
firstMilePickupAddress?: string;
@ApiPropertyOptional()
@IsOptional()
@IsString()
lastMileDeliveryAddress?: string;
@ApiProperty({ enum: EQUIPMENT_RETURNS })
@IsIn([...EQUIPMENT_RETURNS])
equipmentReturn!: string;
@ApiProperty({ format: 'uuid', description: 'FK to yards.id (origin)' })
@IsUUID()
originYardId!: string;
@ApiProperty({ format: 'uuid', description: 'FK to yards.id (destination)' })
@IsUUID()
destinationYardId!: string;
@ApiProperty({ enum: TRADE_DIRECTIONS })
@IsIn([...TRADE_DIRECTIONS])
tradeDirection!: string;
@ApiProperty({ enum: FREIGHT_TYPES, description: 'CONTAINER or BULK (mutually exclusive cargo shape)' })
@IsIn([...FREIGHT_TYPES])
freightType!: string;
@ApiPropertyOptional({
format: 'uuid',
description: 'Required for BULK; must be omitted for CONTAINER',
})
@ValidateIf((o) => o.freightType === 'BULK')
@IsUUID()
cargoTypeId?: string;
@ApiPropertyOptional({ maxLength: 200 })
@IsOptional()
@IsString()
cargoFreeText?: string;
@ApiPropertyOptional({ format: 'uuid', description: 'FK to shipping_lines.id' })
@IsOptional()
@IsUUID()
shippingLineId?: string;
@ApiProperty({ description: 'Total cargo weight VGM in tons', minimum: 0 })
@IsNumber()
@Min(0)
@Transform(({ value }) => Number(value))
cargoTotalWeightVgm!: number;
@ApiPropertyOptional({ default: false })
@IsOptional()
@IsBoolean()
@Transform(({ value }) => value === 'true' || value === true)
isHazardous?: boolean;
@ApiProperty({ enum: PAYMENT_CURRENCIES })
@IsIn([...PAYMENT_CURRENCIES])
paymentCurrency!: string;
@ApiPropertyOptional()
@IsOptional()
@IsString()
pnrCode?: string;
@ApiPropertyOptional()
@IsOptional()
@IsDateString()
startDate?: string;
@ApiPropertyOptional()
@IsOptional()
@IsDateString()
endDate?: string;
@ApiPropertyOptional()
@IsOptional()
@IsString()
financialTerms?: string;
@ApiPropertyOptional({
type: [CreateBookingContainerDto],
description: 'Required for CONTAINER (min 1 line); must be empty for BULK',
})
@ValidateIf((o) => o.freightType === 'CONTAINER')
@IsArray()
@ArrayMinSize(1)
@ValidateNested({ each: true })
@Type(() => CreateBookingContainerDto)
containers?: CreateBookingContainerDto[];
@ApiPropertyOptional({ default: false })
@IsOptional()
@IsBoolean()
@Transform(({ value }) => value === 'true' || value === true)
allowConsolidation?: boolean;
}