import { MigrationInterface, QueryRunner } from 'typeorm'; /** * Adds train_schedules.reference: a human-facing unique schedule number * S-YYYY-NNNNN (per-year sequence, like bookings' BK-YYYY-NNNNNN). * * - Adds the nullable column. * - Backfills existing rows: within each created-at year, numbers rows by * created_at ascending (oldest → S--00001). Deterministic order. * - Adds a partial unique index (NULLs allowed so a future insert can stage * the row before the app stamps its reference). */ export class AddTrainScheduleReference2030000000000 implements MigrationInterface { public async up(queryRunner: QueryRunner): Promise { await queryRunner.query(` ALTER TABLE freight.train_schedules ADD COLUMN IF NOT EXISTS reference VARCHAR(20); `); // Backfill per-year, ordered by created_at (oldest = 00001). Uses the row's // own created-at year as the reference year so historical rows keep a // sensible number. await queryRunner.query(` WITH numbered AS ( SELECT id, EXTRACT(YEAR FROM created_at)::int AS yr, ROW_NUMBER() OVER ( PARTITION BY EXTRACT(YEAR FROM created_at) ORDER BY created_at ASC, id ASC ) AS seq FROM freight.train_schedules WHERE reference IS NULL ) UPDATE freight.train_schedules ts SET reference = 'S-' || numbered.yr || '-' || LPAD(numbered.seq::text, 5, '0') FROM numbered WHERE ts.id = numbered.id; `); await queryRunner.query(` CREATE UNIQUE INDEX IF NOT EXISTS ux_train_schedules_reference ON freight.train_schedules (reference) WHERE reference IS NOT NULL; `); } public async down(queryRunner: QueryRunner): Promise { await queryRunner.query(` DROP INDEX IF EXISTS freight.ux_train_schedules_reference; `); await queryRunner.query(` ALTER TABLE freight.train_schedules DROP COLUMN IF EXISTS reference; `); } }