import { MigrationInterface, QueryRunner } from "typeorm"; /** * Maker–checker for manual actions on shipping-line credit invoices. * * A shipping-line credit invoice is normally settled by the CBE webhook. Two * manual paths exist for finance: recording an offline payment (MARK_PAID) * and voiding an invoice raised in error (CANCEL, which releases its credits * back to the unbilled pool). Both erase or move real debt, so neither is a * single-person action: one permission raises the request, a different * permission — held by a chief, and never the requester themselves — approves * or rejects it. Rows are never deleted; decided requests are the audit trail. * * One PENDING row per invoice at a time (partial unique index): a second * request while one is undecided is a coordination failure, not a workflow. */ export class ShippingLineInvoiceApprovals3530000000000 implements MigrationInterface { public async up(queryRunner: QueryRunner): Promise { await queryRunner.query(` DO $$ BEGIN CREATE TYPE freight.shipping_line_invoice_approvals_action_enum AS ENUM ('MARK_PAID', 'CANCEL'); EXCEPTION WHEN duplicate_object THEN NULL; END $$ `); await queryRunner.query(` DO $$ BEGIN CREATE TYPE freight.shipping_line_invoice_approvals_status_enum AS ENUM ('PENDING', 'APPROVED', 'REJECTED'); EXCEPTION WHEN duplicate_object THEN NULL; END $$ `); await queryRunner.query(` CREATE TABLE IF NOT EXISTS freight.shipping_line_invoice_approvals ( id uuid PRIMARY KEY DEFAULT uuid_generate_v4(), invoice_id uuid NOT NULL REFERENCES freight.invoices (id), action freight.shipping_line_invoice_approvals_action_enum NOT NULL, status freight.shipping_line_invoice_approvals_status_enum NOT NULL DEFAULT 'PENDING', requested_by uuid NOT NULL, reason varchar(500) NOT NULL, payment_reference varchar(255), decided_by uuid, decided_at timestamptz, decision_note varchar(500), created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), deleted_at timestamptz ) `); await queryRunner.query(` CREATE INDEX IF NOT EXISTS idx_sl_invoice_approvals_invoice_status ON freight.shipping_line_invoice_approvals (invoice_id, status) `); // The workflow invariant, enforced where it cannot race: at most one // undecided request per invoice. await queryRunner.query(` CREATE UNIQUE INDEX IF NOT EXISTS uq_sl_invoice_approvals_one_pending ON freight.shipping_line_invoice_approvals (invoice_id) WHERE status = 'PENDING' AND deleted_at IS NULL `); } public async down(queryRunner: QueryRunner): Promise { await queryRunner.query( `DROP TABLE IF EXISTS freight.shipping_line_invoice_approvals`, ); await queryRunner.query( `DROP TYPE IF EXISTS freight.shipping_line_invoice_approvals_status_enum`, ); await queryRunner.query( `DROP TYPE IF EXISTS freight.shipping_line_invoice_approvals_action_enum`, ); } }