implement rule engine module with dynamic booking evaluation, 7 entities, and Postman endpoints

This commit is contained in:
marshal
2026-05-29 10:00:45 +03:00
parent 3b53042cf8
commit 800f036005
72 changed files with 2544 additions and 838 deletions

View File

@@ -0,0 +1,40 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { IsBoolean, IsInt, IsOptional, IsString, IsUUID, MaxLength, Min } from 'class-validator';
export class CreateCargoTypeDto {
@ApiProperty({ description: 'Machine-readable code, e.g. BULK, BREAK_BULK', maxLength: 50 })
@IsString()
@MaxLength(50)
code!: string;
@ApiProperty({ description: 'Cargo type display name', maxLength: 255 })
@IsString()
@MaxLength(255)
cargoTypeName!: string;
@ApiPropertyOptional({ description: 'Parent group ID for hierarchical cargo types' })
@IsOptional()
@IsUUID()
parentGroupId?: string;
@ApiPropertyOptional({ default: false })
@IsOptional()
@IsBoolean()
showFreeTextBox?: boolean;
@ApiPropertyOptional({ default: false })
@IsOptional()
@IsBoolean()
requiresDirectorApproval?: boolean;
@ApiPropertyOptional({ default: true })
@IsOptional()
@IsBoolean()
isActive?: boolean;
@ApiPropertyOptional({ default: 1 })
@IsOptional()
@IsInt()
@Min(1)
displayOrder?: number;
}

View File

@@ -0,0 +1,25 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { IsBoolean, IsInt, IsOptional, IsString, MaxLength, Min } from 'class-validator';
export class CreateContainerTypeDto {
@ApiProperty({ description: 'Size code, e.g. 20FT or 40FT', maxLength: 20 })
@IsString()
@MaxLength(20)
sizeCode!: string;
@ApiPropertyOptional({ description: 'Human-readable description', maxLength: 100 })
@IsOptional()
@IsString()
@MaxLength(100)
description?: string;
@ApiProperty({ description: 'Number of containers that fit per rail wagon (2 for 20FT, 1 for 40FT)' })
@IsInt()
@Min(1)
containersPerWagon!: number;
@ApiPropertyOptional({ default: true })
@IsOptional()
@IsBoolean()
isActive?: boolean;
}

View File

@@ -0,0 +1,34 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { IsBoolean, IsEnum, IsInt, IsOptional, IsString, MaxLength, Min } from 'class-validator';
import { Freight } from '@edr/types';
export class CreatePriorityRuleDto {
@ApiProperty({ enum: Freight.PriorityType, description: 'Priority type (unique per rule)' })
@IsEnum(Freight.PriorityType)
priorityType!: Freight.PriorityType;
@ApiProperty({ description: 'Human-readable rule name', maxLength: 255 })
@IsString()
@MaxLength(255)
ruleName!: string;
@ApiPropertyOptional({ description: 'Explanation of when this rule is triggered' })
@IsOptional()
@IsString()
description?: string;
@ApiPropertyOptional({ description: 'Technical expression describing the activation condition' })
@IsOptional()
@IsString()
activationCondition?: string;
@ApiProperty({ description: 'Points added to booking.priorityScore when this rule matches', default: 0 })
@IsInt()
@Min(0)
bonusPoints!: number;
@ApiPropertyOptional({ default: false })
@IsOptional()
@IsBoolean()
isActive?: boolean;
}

View File

@@ -0,0 +1,56 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { IsBoolean, IsInt, IsOptional, IsString, MaxLength, Min } from 'class-validator';
export class CreateServiceTypeDto {
@ApiProperty({ description: 'Machine-readable code, e.g. RAIL_ONLY', maxLength: 50 })
@IsString()
@MaxLength(50)
code!: string;
@ApiProperty({ description: 'Service type display name', maxLength: 255 })
@IsString()
@MaxLength(255)
serviceName!: string;
@ApiPropertyOptional({ description: 'Detailed description' })
@IsOptional()
@IsString()
description?: string;
@ApiPropertyOptional({ default: true })
@IsOptional()
@IsBoolean()
canBeBookedAlone?: boolean;
@ApiPropertyOptional({ default: false })
@IsOptional()
@IsBoolean()
includesFirstMile?: boolean;
@ApiPropertyOptional({ default: false })
@IsOptional()
@IsBoolean()
includesLastMile?: boolean;
@ApiPropertyOptional({ default: false })
@IsOptional()
@IsBoolean()
includesCustoms?: boolean;
@ApiPropertyOptional({ description: 'Priority bonus points awarded when this service is used', default: 0 })
@IsOptional()
@IsInt()
@Min(0)
priorityBonusPoints?: number;
@ApiPropertyOptional({ default: true })
@IsOptional()
@IsBoolean()
isActive?: boolean;
@ApiPropertyOptional({ default: 1 })
@IsOptional()
@IsInt()
@Min(1)
displayOrder?: number;
}

View File

@@ -0,0 +1,24 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { IsBoolean, IsOptional, IsString, MaxLength } from 'class-validator';
export class CreateSurchargeTypeDto {
@ApiProperty({ description: 'Unique code, e.g. HAZARDOUS, REFRIGERATED', maxLength: 50 })
@IsString()
@MaxLength(50)
code!: string;
@ApiProperty({ description: 'Display name', maxLength: 100 })
@IsString()
@MaxLength(100)
name!: string;
@ApiPropertyOptional({ description: 'Description of when this surcharge type is triggered' })
@IsOptional()
@IsString()
description?: string;
@ApiPropertyOptional({ default: true })
@IsOptional()
@IsBoolean()
isActive?: boolean;
}

View File

@@ -0,0 +1,63 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import {
IsBoolean,
IsEnum,
IsNumber,
IsOptional,
IsString,
IsUUID,
Length,
MaxLength,
Min,
} from 'class-validator';
import { Freight } from '@edr/types';
export class CreateSurchargeDto {
@ApiProperty({ description: 'FK to surcharge_types.id' })
@IsUUID()
surchargeTypeId!: string;
@ApiProperty({ description: 'Display name for this surcharge line item', maxLength: 255 })
@IsString()
@MaxLength(255)
feeName!: string;
@ApiPropertyOptional({ description: 'Human-readable description of when this surcharge is triggered' })
@IsOptional()
@IsString()
triggerDescription?: string;
@ApiProperty({ enum: Freight.CalculationMethod, default: Freight.CalculationMethod.PER_TON })
@IsEnum(Freight.CalculationMethod)
calculationMethod!: Freight.CalculationMethod;
@ApiProperty({ description: 'Rate amount (per ton, flat, or percentage)' })
@IsNumber()
@Min(0)
rate!: number;
@ApiProperty({ description: 'ISO 4217 currency code', default: 'USD' })
@IsString()
@Length(3, 3)
currency!: string;
@ApiPropertyOptional({ default: false })
@IsOptional()
@IsBoolean()
applyToRail?: boolean;
@ApiPropertyOptional({ default: false })
@IsOptional()
@IsBoolean()
applyToFirstMile?: boolean;
@ApiPropertyOptional({ default: false })
@IsOptional()
@IsBoolean()
applyToLastMile?: boolean;
@ApiPropertyOptional({ default: true })
@IsOptional()
@IsBoolean()
isActive?: boolean;
}

View File

@@ -0,0 +1,38 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { IsBoolean, IsEnum, IsNumber, IsOptional, IsUUID, Min } from 'class-validator';
import { Freight } from '@edr/types';
export class CreateWeightLimitRuleDto {
@ApiProperty({ description: 'FK to container_types.id' })
@IsUUID()
containerTypeId!: string;
@ApiProperty({ enum: Freight.TradeDirection, description: 'Trade direction this rule applies to' })
@IsEnum(Freight.TradeDirection)
tradeDirection!: Freight.TradeDirection;
@ApiProperty({ description: 'Maximum allowed weight in tons before surcharge is applied' })
@IsNumber()
@Min(0)
maxWeightTons!: number;
@ApiProperty({ description: 'Weight at which a warning is issued (must be ≤ maxWeightTons)' })
@IsNumber()
@Min(0)
warningThresholdTons!: number;
@ApiPropertyOptional({ enum: Freight.ExceededAction, default: Freight.ExceededAction.WARNING_ONLY })
@IsOptional()
@IsEnum(Freight.ExceededAction)
exceededAction?: Freight.ExceededAction;
@ApiPropertyOptional({ description: 'FK to surcharges.id — surcharge billed when max is exceeded' })
@IsOptional()
@IsUUID()
surchargeId?: string;
@ApiPropertyOptional({ default: true })
@IsOptional()
@IsBoolean()
isActive?: boolean;
}

View File

@@ -0,0 +1,4 @@
import { PartialType } from '@nestjs/mapped-types';
import { CreateCargoTypeDto } from './create-cargo-type.dto';
export class UpdateCargoTypeDto extends PartialType(CreateCargoTypeDto) {}

View File

@@ -0,0 +1,4 @@
import { PartialType } from '@nestjs/mapped-types';
import { CreateContainerTypeDto } from './create-container-type.dto';
export class UpdateContainerTypeDto extends PartialType(CreateContainerTypeDto) {}

View File

@@ -0,0 +1,4 @@
import { PartialType } from '@nestjs/mapped-types';
import { CreatePriorityRuleDto } from './create-priority-rule.dto';
export class UpdatePriorityRuleDto extends PartialType(CreatePriorityRuleDto) {}

View File

@@ -0,0 +1,4 @@
import { PartialType } from '@nestjs/mapped-types';
import { CreateServiceTypeDto } from './create-service-type.dto';
export class UpdateServiceTypeDto extends PartialType(CreateServiceTypeDto) {}

View File

@@ -0,0 +1,4 @@
import { PartialType } from '@nestjs/mapped-types';
import { CreateSurchargeTypeDto } from './create-surcharge-type.dto';
export class UpdateSurchargeTypeDto extends PartialType(CreateSurchargeTypeDto) {}

View File

@@ -0,0 +1,4 @@
import { PartialType } from '@nestjs/mapped-types';
import { CreateSurchargeDto } from './create-surcharge.dto';
export class UpdateSurchargeDto extends PartialType(CreateSurchargeDto) {}

View File

@@ -0,0 +1,4 @@
import { PartialType } from '@nestjs/mapped-types';
import { CreateWeightLimitRuleDto } from './create-weight-limit-rule.dto';
export class UpdateWeightLimitRuleDto extends PartialType(CreateWeightLimitRuleDto) {}