Files
edr-platform/apps/edr-freight-api/src/migrations/1748514000000-AddRuleEngineTablesAndCodes.ts
2026-05-30 10:27:59 +03:00

294 lines
12 KiB
TypeScript

import {
MigrationInterface,
QueryRunner,
Table,
TableIndex,
TableForeignKey,
TableColumn,
} from 'typeorm';
export class AddRuleEngineTablesAndCodes1748514000000 implements MigrationInterface {
name = 'AddRuleEngineTablesAndCodes1748514000000';
public async up(queryRunner: QueryRunner): Promise<void> {
// ── 1. Add `code` column to existing tables ───────────────────────────
if (!(await queryRunner.hasColumn('freight.service_types', 'code'))) {
await queryRunner.addColumn(
'freight.service_types',
new TableColumn({
name: 'code',
type: 'varchar',
length: '50',
isNullable: true,
}),
);
}
await queryRunner.query(
`UPDATE freight.service_types SET code = upper(replace(service_name, ' ', '_')) WHERE code IS NULL OR code = ''`,
);
await queryRunner.query(
`UPDATE freight.service_types SET code = 'SERVICE_' || substring(id::text, 1, 8) WHERE code IS NULL`,
);
await queryRunner.query(
`ALTER TABLE freight.service_types ALTER COLUMN code SET NOT NULL`,
);
const serviceTypesCodeIdx = await queryRunner.query(
`SELECT 1 FROM pg_indexes WHERE schemaname = 'freight' AND indexname = 'idx_service_types_code' LIMIT 1`,
);
if (serviceTypesCodeIdx.length === 0) {
await queryRunner.createIndex(
'freight.service_types',
new TableIndex({ name: 'IDX_service_types_code', columnNames: ['code'], isUnique: true }),
);
}
if (!(await queryRunner.hasColumn('freight.cargo_types', 'code'))) {
await queryRunner.addColumn(
'freight.cargo_types',
new TableColumn({
name: 'code',
type: 'varchar',
length: '50',
isNullable: true,
}),
);
}
await queryRunner.query(
`UPDATE freight.cargo_types SET code = upper(replace(cargo_type_name, ' ', '_')) WHERE code IS NULL OR code = ''`,
);
await queryRunner.query(
`UPDATE freight.cargo_types SET code = 'CARGO_' || substring(id::text, 1, 8) WHERE code IS NULL`,
);
await queryRunner.query(
`ALTER TABLE freight.cargo_types ALTER COLUMN code SET NOT NULL`,
);
const cargoTypesCodeIdx = await queryRunner.query(
`SELECT 1 FROM pg_indexes WHERE schemaname = 'freight' AND indexname = 'idx_cargo_types_code' LIMIT 1`,
);
if (cargoTypesCodeIdx.length === 0) {
await queryRunner.createIndex(
'freight.cargo_types',
new TableIndex({ name: 'IDX_cargo_types_code', columnNames: ['code'], isUnique: true }),
);
}
// ── 2. surcharge_types ────────────────────────────────────────────────
if (!(await queryRunner.hasTable('freight.surcharge_types'))) await queryRunner.createTable(
new Table({
name: 'surcharge_types',
schema: 'freight',
columns: [
{ name: 'id', type: 'uuid', isPrimary: true, generationStrategy: 'uuid', default: 'uuid_generate_v4()' },
{ name: 'code', type: 'varchar', length: '50', isNullable: false },
{ name: 'name', type: 'varchar', length: '100', isNullable: false },
{ name: 'description', type: 'text', isNullable: true },
{ name: 'is_active', type: 'boolean', default: true },
{ name: 'created_at', type: 'timestamptz', default: 'now()' },
{ name: 'updated_at', type: 'timestamptz', default: 'now()' },
{ name: 'deleted_at', type: 'timestamptz', isNullable: true },
],
}),
true,
);
await queryRunner.createIndex(
'freight.surcharge_types',
new TableIndex({ name: 'IDX_surcharge_types_code', columnNames: ['code'], isUnique: true }),
);
await queryRunner.createIndex(
'freight.surcharge_types',
new TableIndex({ name: 'IDX_surcharge_types_is_active', columnNames: ['is_active'] }),
);
// ── 3. surcharges ─────────────────────────────────────────────────────
if (!(await queryRunner.hasTable('freight.surcharges'))) await queryRunner.createTable(
new Table({
name: 'surcharges',
schema: 'freight',
columns: [
{ name: 'id', type: 'uuid', isPrimary: true, generationStrategy: 'uuid', default: 'uuid_generate_v4()' },
{ name: 'surcharge_type_id', type: 'uuid', isNullable: false },
{ name: 'fee_name', type: 'varchar', length: '255', isNullable: false },
{ name: 'trigger_description', type: 'text', isNullable: true },
{
name: 'calculation_method',
type: 'enum',
enum: ['PER_TON', 'FLAT_FEE', 'PERCENTAGE'],
default: `'PER_TON'`,
},
{ name: 'rate', type: 'numeric', precision: 10, scale: 2, isNullable: false },
{ name: 'currency', type: 'char', length: '3', default: `'USD'` },
{ name: 'apply_to_rail', type: 'boolean', default: false },
{ name: 'apply_to_first_mile', type: 'boolean', default: false },
{ name: 'apply_to_last_mile', type: 'boolean', default: false },
{ name: 'is_active', type: 'boolean', default: true },
{ name: 'created_at', type: 'timestamptz', default: 'now()' },
{ name: 'updated_at', type: 'timestamptz', default: 'now()' },
{ name: 'deleted_at', type: 'timestamptz', isNullable: true },
],
}),
true,
);
await queryRunner.createForeignKey(
'freight.surcharges',
new TableForeignKey({
name: 'FK_surcharges_surcharge_type',
columnNames: ['surcharge_type_id'],
referencedTableName: 'freight.surcharge_types',
referencedColumnNames: ['id'],
onDelete: 'RESTRICT',
}),
);
await queryRunner.createIndex(
'freight.surcharges',
new TableIndex({ name: 'IDX_surcharges_surcharge_type_id', columnNames: ['surcharge_type_id'] }),
);
await queryRunner.createIndex(
'freight.surcharges',
new TableIndex({ name: 'IDX_surcharges_is_active', columnNames: ['is_active'] }),
);
// ── 4. container_types ────────────────────────────────────────────────
if (!(await queryRunner.hasTable('freight.container_types'))) await queryRunner.createTable(
new Table({
name: 'container_types',
schema: 'freight',
columns: [
{ name: 'id', type: 'uuid', isPrimary: true, generationStrategy: 'uuid', default: 'uuid_generate_v4()' },
{ name: 'size_code', type: 'varchar', length: '20', isNullable: false },
{ name: 'description', type: 'varchar', length: '100', isNullable: true },
{ name: 'containers_per_wagon', type: 'int', isNullable: false },
{ name: 'is_active', type: 'boolean', default: true },
{ name: 'created_at', type: 'timestamptz', default: 'now()' },
{ name: 'updated_at', type: 'timestamptz', default: 'now()' },
{ name: 'deleted_at', type: 'timestamptz', isNullable: true },
],
}),
true,
);
await queryRunner.createIndex(
'freight.container_types',
new TableIndex({ name: 'IDX_container_types_size_code', columnNames: ['size_code'], isUnique: true }),
);
await queryRunner.createIndex(
'freight.container_types',
new TableIndex({ name: 'IDX_container_types_is_active', columnNames: ['is_active'] }),
);
// ── 5. weight_limit_rules ─────────────────────────────────────────────
if (!(await queryRunner.hasTable('freight.weight_limit_rules'))) await queryRunner.createTable(
new Table({
name: 'weight_limit_rules',
schema: 'freight',
columns: [
{ name: 'id', type: 'uuid', isPrimary: true, generationStrategy: 'uuid', default: 'uuid_generate_v4()' },
{ name: 'container_type_id', type: 'uuid', isNullable: false },
{
name: 'trade_direction',
type: 'enum',
enum: ['IMPORT', 'EXPORT', 'BOTH'],
isNullable: false,
},
{ name: 'max_weight_tons', type: 'numeric', precision: 10, scale: 2, isNullable: false },
{ name: 'warning_threshold_tons', type: 'numeric', precision: 10, scale: 2, isNullable: false },
{
name: 'exceeded_action',
type: 'enum',
enum: ['WARNING_ONLY', 'HARD_BLOCK'],
default: `'WARNING_ONLY'`,
},
{ name: 'surcharge_id', type: 'uuid', isNullable: true },
{ name: 'is_active', type: 'boolean', default: true },
{ name: 'created_at', type: 'timestamptz', default: 'now()' },
{ name: 'updated_at', type: 'timestamptz', default: 'now()' },
{ name: 'deleted_at', type: 'timestamptz', isNullable: true },
],
}),
true,
);
await queryRunner.createForeignKey(
'freight.weight_limit_rules',
new TableForeignKey({
name: 'FK_weight_limit_rules_container_type',
columnNames: ['container_type_id'],
referencedTableName: 'freight.container_types',
referencedColumnNames: ['id'],
onDelete: 'RESTRICT',
}),
);
await queryRunner.createForeignKey(
'freight.weight_limit_rules',
new TableForeignKey({
name: 'FK_weight_limit_rules_surcharge',
columnNames: ['surcharge_id'],
referencedTableName: 'freight.surcharges',
referencedColumnNames: ['id'],
onDelete: 'SET NULL',
}),
);
await queryRunner.createIndex(
'freight.weight_limit_rules',
new TableIndex({ name: 'IDX_weight_limit_rules_container_type_id', columnNames: ['container_type_id'] }),
);
await queryRunner.createIndex(
'freight.weight_limit_rules',
new TableIndex({ name: 'IDX_weight_limit_rules_surcharge_id', columnNames: ['surcharge_id'] }),
);
await queryRunner.createIndex(
'freight.weight_limit_rules',
new TableIndex({ name: 'IDX_weight_limit_rules_is_active', columnNames: ['is_active'] }),
);
// ── 6. priority_rules ─────────────────────────────────────────────────
if (!(await queryRunner.hasTable('freight.priority_rules'))) await queryRunner.createTable(
new Table({
name: 'priority_rules',
schema: 'freight',
columns: [
{ name: 'id', type: 'uuid', isPrimary: true, generationStrategy: 'uuid', default: 'uuid_generate_v4()' },
{
name: 'priority_type',
type: 'enum',
enum: ['USD_PAYER', 'RAIL_AND_FORWARDING', 'GOVERNMENT_ACCOUNT', 'HIGH_VOLUME_SHIPMENT'],
isNullable: false,
},
{ name: 'rule_name', type: 'varchar', length: '255', isNullable: false },
{ name: 'description', type: 'text', isNullable: true },
{ name: 'activation_condition', type: 'text', isNullable: true },
{ name: 'bonus_points', type: 'int', default: 0 },
{ name: 'is_active', type: 'boolean', default: false },
{ name: 'created_at', type: 'timestamptz', default: 'now()' },
{ name: 'updated_at', type: 'timestamptz', default: 'now()' },
{ name: 'deleted_at', type: 'timestamptz', isNullable: true },
],
}),
true,
);
await queryRunner.createIndex(
'freight.priority_rules',
new TableIndex({ name: 'IDX_priority_rules_priority_type', columnNames: ['priority_type'], isUnique: true }),
);
await queryRunner.createIndex(
'freight.priority_rules',
new TableIndex({ name: 'IDX_priority_rules_is_active', columnNames: ['is_active'] }),
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropTable('freight.priority_rules', true);
await queryRunner.dropTable('freight.weight_limit_rules', true);
await queryRunner.dropTable('freight.container_types', true);
await queryRunner.dropTable('freight.surcharges', true);
await queryRunner.dropTable('freight.surcharge_types', true);
await queryRunner.dropIndex('freight.cargo_types', 'IDX_cargo_types_code');
await queryRunner.dropColumn('freight.cargo_types', 'code');
await queryRunner.dropIndex('freight.service_types', 'IDX_service_types_code');
await queryRunner.dropColumn('freight.service_types', 'code');
}
}