mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-31 22:27:37 +00:00
147 lines
4.9 KiB
TypeScript
147 lines
4.9 KiB
TypeScript
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;
|
||
}
|