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,37 @@
import { BaseEntity } from '@edr/api-common';
import { Column, Entity, Index, JoinColumn, ManyToOne, OneToMany } from 'typeorm';
@Entity({ schema: 'freight', name: 'cargo_types' })
@Index(['isActive'])
@Index(['displayOrder'])
@Index(['parentGroupId'])
@Index(['code'])
export class CargoType extends BaseEntity {
@Column({ name: 'code', type: 'varchar', length: 50, unique: true, default: '' })
code!: string;
@Column({ name: 'cargo_type_name', type: 'varchar', length: 255 })
cargoTypeName!: string;
@Column({ name: 'parent_group_id', type: 'uuid', nullable: true })
parentGroupId?: string | null;
@Column({ name: 'show_free_text_box', type: 'boolean', default: false })
showFreeTextBox!: boolean;
@Column({ name: 'requires_director_approval', type: 'boolean', default: false })
requiresDirectorApproval!: boolean;
@Column({ name: 'is_active', type: 'boolean', default: true })
isActive!: boolean;
@Column({ name: 'display_order', type: 'int', default: 1 })
displayOrder!: number;
@ManyToOne(() => CargoType, (ct) => ct.children, { nullable: true, onDelete: 'SET NULL' })
@JoinColumn({ name: 'parent_group_id' })
parent?: CargoType | null;
@OneToMany(() => CargoType, (ct) => ct.parent)
children?: CargoType[];
}

View File

@@ -0,0 +1,23 @@
import { BaseEntity } from '@edr/api-common';
import { Column, Entity, Index, OneToMany } from 'typeorm';
import { WeightLimitRule } from './weight-limit-rule.entity';
@Entity({ schema: 'freight', name: 'container_types' })
@Index(['sizeCode'])
@Index(['isActive'])
export class ContainerType extends BaseEntity {
@Column({ name: 'size_code', type: 'varchar', length: 20, unique: true })
sizeCode!: string;
@Column({ name: 'description', type: 'varchar', length: 100, nullable: true })
description?: string | null;
@Column({ name: 'containers_per_wagon', type: 'int' })
containersPerWagon!: number;
@Column({ name: 'is_active', type: 'boolean', default: true })
isActive!: boolean;
@OneToMany(() => WeightLimitRule, (rule) => rule.containerType)
weightLimitRules?: WeightLimitRule[];
}

View File

@@ -0,0 +1,26 @@
import { BaseEntity } from '@edr/api-common';
import { Freight } from '@edr/types';
import { Column, Entity, Index } from 'typeorm';
@Entity({ schema: 'freight', name: 'priority_rules' })
@Index(['priorityType'])
@Index(['isActive'])
export class PriorityRule extends BaseEntity {
@Column({ name: 'priority_type', type: 'enum', enum: Freight.PriorityType, unique: true })
priorityType!: Freight.PriorityType;
@Column({ name: 'rule_name', type: 'varchar', length: 255 })
ruleName!: string;
@Column({ name: 'description', type: 'text', nullable: true })
description?: string | null;
@Column({ name: 'activation_condition', type: 'text', nullable: true })
activationCondition?: string | null;
@Column({ name: 'bonus_points', type: 'int', default: 0 })
bonusPoints!: number;
@Column({ name: 'is_active', type: 'boolean', default: false })
isActive!: boolean;
}

View File

@@ -0,0 +1,38 @@
import { BaseEntity } from '@edr/api-common';
import { Column, Entity, Index } from 'typeorm';
@Entity({ schema: 'freight', name: 'service_types' })
@Index(['isActive'])
@Index(['displayOrder'])
@Index(['code'])
export class ServiceType extends BaseEntity {
@Column({ name: 'code', type: 'varchar', length: 50, unique: true, default: '' })
code!: string;
@Column({ name: 'service_name', type: 'varchar', length: 255 })
serviceName!: string;
@Column({ name: 'description', type: 'text', nullable: true })
description?: string | null;
@Column({ name: 'can_be_booked_alone', type: 'boolean', default: true })
canBeBookedAlone!: boolean;
@Column({ name: 'includes_first_mile', type: 'boolean', default: false })
includesFirstMile!: boolean;
@Column({ name: 'includes_last_mile', type: 'boolean', default: false })
includesLastMile!: boolean;
@Column({ name: 'includes_customs', type: 'boolean', default: false })
includesCustoms!: boolean;
@Column({ name: 'priority_bonus_points', type: 'int', default: 0 })
priorityBonusPoints!: number;
@Column({ name: 'is_active', type: 'boolean', default: true })
isActive!: boolean;
@Column({ name: 'display_order', type: 'int', default: 1 })
displayOrder!: number;
}

View File

@@ -0,0 +1,23 @@
import { BaseEntity } from '@edr/api-common';
import { Column, Entity, Index, OneToMany } from 'typeorm';
import { Surcharge } from './surcharge.entity';
@Entity({ schema: 'freight', name: 'surcharge_types' })
@Index(['code'])
@Index(['isActive'])
export class SurchargeType extends BaseEntity {
@Column({ name: 'code', type: 'varchar', length: 50, unique: true })
code!: string;
@Column({ name: 'name', type: 'varchar', length: 100 })
name!: string;
@Column({ name: 'description', type: 'text', nullable: true })
description?: string | null;
@Column({ name: 'is_active', type: 'boolean', default: true })
isActive!: boolean;
@OneToMany(() => Surcharge, (s) => s.surchargeType)
surcharges?: Surcharge[];
}

View File

@@ -0,0 +1,52 @@
import { BaseEntity } from '@edr/api-common';
import { Freight } from '@edr/types';
import { Column, Entity, Index, JoinColumn, ManyToOne, OneToMany } from 'typeorm';
import { SurchargeType } from './surcharge-type.entity';
import { WeightLimitRule } from './weight-limit-rule.entity';
@Entity({ schema: 'freight', name: 'surcharges' })
@Index(['surchargeTypeId'])
@Index(['isActive'])
export class Surcharge extends BaseEntity {
@Column({ name: 'surcharge_type_id', type: 'uuid' })
surchargeTypeId!: string;
@ManyToOne(() => SurchargeType, (st) => st.surcharges)
@JoinColumn({ name: 'surcharge_type_id' })
surchargeType!: SurchargeType;
@Column({ name: 'fee_name', type: 'varchar', length: 255 })
feeName!: string;
@Column({ name: 'trigger_description', type: 'text', nullable: true })
triggerDescription?: string | null;
@Column({
name: 'calculation_method',
type: 'enum',
enum: Freight.CalculationMethod,
default: Freight.CalculationMethod.PER_TON,
})
calculationMethod!: Freight.CalculationMethod;
@Column({ name: 'rate', type: 'numeric', precision: 10, scale: 2 })
rate!: number;
@Column({ name: 'currency', type: 'char', length: 3, default: 'USD' })
currency!: string;
@Column({ name: 'apply_to_rail', type: 'boolean', default: false })
applyToRail!: boolean;
@Column({ name: 'apply_to_first_mile', type: 'boolean', default: false })
applyToFirstMile!: boolean;
@Column({ name: 'apply_to_last_mile', type: 'boolean', default: false })
applyToLastMile!: boolean;
@Column({ name: 'is_active', type: 'boolean', default: true })
isActive!: boolean;
@OneToMany(() => WeightLimitRule, (rule) => rule.surcharge)
weightLimitRules?: WeightLimitRule[];
}

View File

@@ -0,0 +1,45 @@
import { BaseEntity } from '@edr/api-common';
import { Freight } from '@edr/types';
import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm';
import { ContainerType } from './container-type.entity';
import { Surcharge } from './surcharge.entity';
@Entity({ schema: 'freight', name: 'weight_limit_rules' })
@Index(['containerTypeId'])
@Index(['surchargeId'])
@Index(['isActive'])
export class WeightLimitRule extends BaseEntity {
@Column({ name: 'container_type_id', type: 'uuid' })
containerTypeId!: string;
@ManyToOne(() => ContainerType, (ct) => ct.weightLimitRules)
@JoinColumn({ name: 'container_type_id' })
containerType!: ContainerType;
@Column({ name: 'trade_direction', type: 'enum', enum: Freight.TradeDirection })
tradeDirection!: Freight.TradeDirection;
@Column({ name: 'max_weight_tons', type: 'numeric', precision: 10, scale: 2 })
maxWeightTons!: number;
@Column({ name: 'warning_threshold_tons', type: 'numeric', precision: 10, scale: 2 })
warningThresholdTons!: number;
@Column({
name: 'exceeded_action',
type: 'enum',
enum: Freight.ExceededAction,
default: Freight.ExceededAction.WARNING_ONLY,
})
exceededAction!: Freight.ExceededAction;
@Column({ name: 'surcharge_id', type: 'uuid', nullable: true })
surchargeId?: string | null;
@ManyToOne(() => Surcharge, (s) => s.weightLimitRules, { nullable: true })
@JoinColumn({ name: 'surcharge_id' })
surcharge?: Surcharge | null;
@Column({ name: 'is_active', type: 'boolean', default: true })
isActive!: boolean;
}