mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 23:28:11 +00:00
127 lines
5.8 KiB
TypeScript
127 lines
5.8 KiB
TypeScript
import { MigrationInterface, QueryRunner } from 'typeorm';
|
|
|
|
/**
|
|
* Simplify the rate + weight-limit configuration model:
|
|
*
|
|
* 1. Drop the effective_from / effective_to validity window from both
|
|
* `rates` and `weight_limit_rules`. Rates are now activated purely by
|
|
* the approval workflow (status = LIVE) and weight limits are always
|
|
* active for their container + direction. No time-travel scheduling.
|
|
*
|
|
* 2. Enforce "one rate per pattern" with partial unique indexes so the same
|
|
* configuration (e.g. FIRST_MILE for a given container type) cannot be
|
|
* duplicated. NULL scope columns are COALESCE-normalised because Postgres
|
|
* treats NULLs as distinct in a plain unique index.
|
|
*
|
|
* This migration is destructive on the date columns — existing effective_*
|
|
* values are dropped.
|
|
*/
|
|
export class SimplifyRatesAndWeightLimitRules1900000000000 implements MigrationInterface {
|
|
name = 'SimplifyRatesAndWeightLimitRules1900000000000';
|
|
|
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
// ── 1. De-duplicate existing data so the unique indexes can be created ──
|
|
// Keep the most recently-created row per pattern, soft-delete the rest.
|
|
await queryRunner.query(`
|
|
WITH ranked AS (
|
|
SELECT id,
|
|
row_number() OVER (
|
|
PARTITION BY rate_type,
|
|
COALESCE(container_type_id, '00000000-0000-0000-0000-000000000000'::uuid),
|
|
COALESCE(cargo_type_id, '00000000-0000-0000-0000-000000000000'::uuid),
|
|
COALESCE(trade_direction, ''),
|
|
rate_unit
|
|
ORDER BY created_at DESC, id DESC
|
|
) AS rn
|
|
FROM freight.rates
|
|
WHERE deleted_at IS NULL AND status <> 'SUPERSEDED'
|
|
)
|
|
UPDATE freight.rates r
|
|
SET deleted_at = now()
|
|
FROM ranked
|
|
WHERE r.id = ranked.id AND ranked.rn > 1;
|
|
`);
|
|
|
|
await queryRunner.query(`
|
|
WITH ranked AS (
|
|
SELECT id,
|
|
row_number() OVER (
|
|
PARTITION BY container_type_id, trade_direction
|
|
ORDER BY created_at DESC, id DESC
|
|
) AS rn
|
|
FROM freight.weight_limit_rules
|
|
WHERE deleted_at IS NULL
|
|
)
|
|
UPDATE freight.weight_limit_rules w
|
|
SET deleted_at = now()
|
|
FROM ranked
|
|
WHERE w.id = ranked.id AND ranked.rn > 1;
|
|
`);
|
|
|
|
// ── 2. Drop the effective-date indexes + columns ───────────────────────
|
|
await queryRunner.query(`DROP INDEX IF EXISTS freight."IDX_rates_effective_from";`);
|
|
await queryRunner.query(`DROP INDEX IF EXISTS freight."IDX_weight_limit_rules_effective_from";`);
|
|
// Indexes created by TypeORM's @Index carry generated hashed names — drop
|
|
// any index that references the effective_from column defensively.
|
|
await queryRunner.query(`
|
|
DO $$
|
|
DECLARE idx record;
|
|
BEGIN
|
|
FOR idx IN
|
|
SELECT indexname FROM pg_indexes
|
|
WHERE schemaname = 'freight'
|
|
AND tablename IN ('rates', 'weight_limit_rules')
|
|
AND indexdef ILIKE '%effective_from%'
|
|
LOOP
|
|
EXECUTE format('DROP INDEX IF EXISTS freight.%I', idx.indexname);
|
|
END LOOP;
|
|
END $$;
|
|
`);
|
|
|
|
await queryRunner.query(`ALTER TABLE freight.rates DROP COLUMN IF EXISTS effective_from;`);
|
|
await queryRunner.query(`ALTER TABLE freight.rates DROP COLUMN IF EXISTS effective_to;`);
|
|
await queryRunner.query(`ALTER TABLE freight.weight_limit_rules DROP COLUMN IF EXISTS effective_from;`);
|
|
await queryRunner.query(`ALTER TABLE freight.weight_limit_rules DROP COLUMN IF EXISTS effective_to;`);
|
|
|
|
// ── 3. One-rate-per-pattern partial unique indexes ─────────────────────
|
|
// The unit is part of the identity so a surcharge can legitimately carry two
|
|
// rows that bill different ways (e.g. reefer PER_CONTAINER + reefer PER_TON),
|
|
// while still blocking a true duplicate (same rateType + scope + unit).
|
|
await queryRunner.query(`
|
|
CREATE UNIQUE INDEX IF NOT EXISTS "UQ_rates_pattern"
|
|
ON freight.rates (
|
|
rate_type,
|
|
COALESCE(container_type_id, '00000000-0000-0000-0000-000000000000'::uuid),
|
|
COALESCE(cargo_type_id, '00000000-0000-0000-0000-000000000000'::uuid),
|
|
COALESCE(trade_direction, ''),
|
|
rate_unit
|
|
)
|
|
WHERE deleted_at IS NULL AND status <> 'SUPERSEDED';
|
|
`);
|
|
|
|
await queryRunner.query(`
|
|
CREATE UNIQUE INDEX IF NOT EXISTS "UQ_weight_limit_rules_pattern"
|
|
ON freight.weight_limit_rules (container_type_id, trade_direction)
|
|
WHERE deleted_at IS NULL;
|
|
`);
|
|
}
|
|
|
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
|
await queryRunner.query(`DROP INDEX IF EXISTS freight."UQ_rates_pattern";`);
|
|
await queryRunner.query(`DROP INDEX IF EXISTS freight."UQ_weight_limit_rules_pattern";`);
|
|
|
|
await queryRunner.query(`ALTER TABLE freight.rates ADD COLUMN IF NOT EXISTS effective_from date;`);
|
|
await queryRunner.query(`UPDATE freight.rates SET effective_from = COALESCE(effective_from, created_at::date);`);
|
|
await queryRunner.query(`ALTER TABLE freight.rates ALTER COLUMN effective_from SET NOT NULL;`);
|
|
await queryRunner.query(`ALTER TABLE freight.rates ADD COLUMN IF NOT EXISTS effective_to date;`);
|
|
|
|
await queryRunner.query(`ALTER TABLE freight.weight_limit_rules ADD COLUMN IF NOT EXISTS effective_from date;`);
|
|
await queryRunner.query(`ALTER TABLE freight.weight_limit_rules ADD COLUMN IF NOT EXISTS effective_to date;`);
|
|
|
|
await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_rates_effective_from" ON freight.rates (effective_from);`);
|
|
await queryRunner.query(
|
|
`CREATE INDEX IF NOT EXISTS "IDX_weight_limit_rules_effective_from" ON freight.weight_limit_rules (effective_from);`,
|
|
);
|
|
}
|
|
}
|