mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 16:28:12 +00:00
fix(rule-engine): merge duplicate Sebeta yards, block dupes
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>
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
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"`);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user