mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-07 13:05:44 +00:00
Two active Sebeta yards (LEGACY_DEST/'Sebeta' and SEBETA/'sebeta') split rates and routes across different yard ids, so route-scoped rate lookups missed. Migration repoints every yard reference to the survivor, retires the duplicate, and adds partial unique indexes on active label and code. Service now rejects case-insensitive duplicate labels on create/update - the old guard only compared generated codes, which missed labels whose existing code differs (LEGACY_DEST). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
68 lines
2.7 KiB
TypeScript
68 lines
2.7 KiB
TypeScript
import { MigrationInterface, QueryRunner } from "typeorm";
|
|
|
|
/**
|
|
* Two active "Sebeta" yards existed (code LEGACY_DEST label "Sebeta", and code
|
|
* SEBETA label "sebeta") — rates and routes pointed at one or the other, so a
|
|
* rate configured against one never matched a contract routed via the other.
|
|
* Merge them: keep the row all rates/distances/facilities reference
|
|
* (LEGACY_DEST), repoint every yard reference from the duplicate to it, retire
|
|
* the duplicate, and give the survivor the clean SEBETA code. Then make
|
|
* duplicate active yard labels/codes impossible at the DB level.
|
|
*/
|
|
export class MergeDuplicateSebetaYards3050000000000 implements MigrationInterface {
|
|
name = "MergeDuplicateSebetaYards3050000000000";
|
|
|
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
await queryRunner.query(`
|
|
DO $$
|
|
DECLARE
|
|
survivor uuid;
|
|
dupe uuid;
|
|
col record;
|
|
BEGIN
|
|
SELECT id INTO survivor FROM freight.yards
|
|
WHERE code = 'LEGACY_DEST' AND lower(trim(label)) = 'sebeta' AND deleted_at IS NULL;
|
|
SELECT id INTO dupe FROM freight.yards
|
|
WHERE code = 'SEBETA' AND deleted_at IS NULL;
|
|
IF survivor IS NULL OR dupe IS NULL OR survivor = dupe THEN
|
|
RETURN;
|
|
END IF;
|
|
|
|
-- Every yard-referencing column in the schema, so rows created between
|
|
-- authoring and running this migration are repointed too.
|
|
FOR col IN
|
|
SELECT table_name, column_name FROM information_schema.columns
|
|
WHERE table_schema = 'freight'
|
|
AND table_name <> 'yards'
|
|
AND (column_name LIKE '%yard_id%' OR column_name LIKE '%station_id%')
|
|
LOOP
|
|
EXECUTE format(
|
|
'UPDATE freight.%I SET %I = $1 WHERE %I = $2',
|
|
col.table_name, col.column_name, col.column_name
|
|
) USING survivor, dupe;
|
|
END LOOP;
|
|
|
|
UPDATE freight.yards
|
|
SET code = 'SEBETA@merged', label = 'sebeta@merged', deleted_at = now()
|
|
WHERE id = dupe;
|
|
UPDATE freight.yards SET code = 'SEBETA', label = 'Sebeta' WHERE id = survivor;
|
|
END $$;
|
|
`);
|
|
|
|
await queryRunner.query(`
|
|
CREATE UNIQUE INDEX IF NOT EXISTS "UQ_yards_label_active"
|
|
ON freight.yards (lower(trim(label))) WHERE deleted_at IS NULL
|
|
`);
|
|
await queryRunner.query(`
|
|
CREATE UNIQUE INDEX IF NOT EXISTS "UQ_yards_code_active"
|
|
ON freight.yards (lower(trim(code))) WHERE deleted_at IS NULL
|
|
`);
|
|
}
|
|
|
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
|
// Data repair — not reversible. The uniqueness indexes are the new invariant.
|
|
await queryRunner.query(`DROP INDEX IF EXISTS freight."UQ_yards_label_active"`);
|
|
await queryRunner.query(`DROP INDEX IF EXISTS freight."UQ_yards_code_active"`);
|
|
}
|
|
}
|