import { MigrationInterface, QueryRunner } from 'typeorm'; /** * Approval workflow for edits to LIVE rates. A LIVE rate is what pricing * charges, so it is never edited in place: the edit is filed here as PENDING * and the live row keeps its value until an approver applies it. * * `payload` holds the changed fields only; `previous_values` snapshots what * they were at submit time so the approver sees a real before→after diff. */ export class CreateRateChangeRequests2300000000000 implements MigrationInterface { name = 'CreateRateChangeRequests2300000000000'; public async up(queryRunner: QueryRunner): Promise { await queryRunner.query(` CREATE TABLE IF NOT EXISTS freight.rate_change_requests ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), rate_id uuid NOT NULL REFERENCES freight.rates (id), payload jsonb NOT NULL, previous_values jsonb NOT NULL, status varchar(10) NOT NULL DEFAULT 'PENDING', requested_by_user_id uuid NULL, decided_by_user_id uuid NULL, decided_at timestamptz NULL, decision_note text NULL, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), deleted_at timestamptz NULL ) `); await queryRunner.query(` CREATE INDEX IF NOT EXISTS idx_rcr_status ON freight.rate_change_requests (status) `); // At most one pending edit per rate — two racing requests would both pass // validation and the second would silently overwrite the first on approval. await queryRunner.query(` CREATE UNIQUE INDEX IF NOT EXISTS uq_rcr_one_pending_per_rate ON freight.rate_change_requests (rate_id) WHERE status = 'PENDING' AND deleted_at IS NULL `); } public async down(queryRunner: QueryRunner): Promise { await queryRunner.query(`DROP TABLE IF EXISTS freight.rate_change_requests`); } }