import { MigrationInterface, QueryRunner } from 'typeorm'; export class WarehouseBatch21790000000001 implements MigrationInterface { name = 'WarehouseBatch21790000000001'; public async up(queryRunner: QueryRunner): Promise { // ── Capacity columns (weight + volume) on warehouse / yard / zone ────── for (const table of ['warehouses', 'warehouse_yards', 'warehouse_zones']) { await queryRunner.query(` ALTER TABLE freight.${table} ADD COLUMN IF NOT EXISTS max_weight NUMERIC(14,3) NULL, ADD COLUMN IF NOT EXISTS max_volume NUMERIC(14,3) NULL, ADD COLUMN IF NOT EXISTS current_volume NUMERIC(14,3) NOT NULL DEFAULT 0; `); // Backfill max_weight from the Batch 1 capacity_weight column. await queryRunner.query(` UPDATE freight.${table} SET max_weight = capacity_weight WHERE max_weight IS NULL; `); } // ── Inventory lifecycle: migrate Batch 1 statuses to Batch 2 set ─────── await queryRunner.query(` ALTER TABLE freight.warehouse_inventory ALTER COLUMN status SET DEFAULT 'RECEIVED'; `); await queryRunner.query(` UPDATE freight.warehouse_inventory SET status = 'RECEIVED' WHERE status = 'ARRIVED_AT_WAREHOUSE'; `); await queryRunner.query(` UPDATE freight.warehouse_inventory SET status = 'STORED' WHERE status = 'UNDER_INSPECTION'; `); // ── New lifecycle timestamps ────────────────────────────────────────── await queryRunner.query(` ALTER TABLE freight.warehouse_inventory ADD COLUMN IF NOT EXISTS stored_at TIMESTAMPTZ NULL, ADD COLUMN IF NOT EXISTS reserved_at TIMESTAMPTZ NULL, ADD COLUMN IF NOT EXISTS loaded_at TIMESTAMPTZ NULL, ADD COLUMN IF NOT EXISTS dispatched_at TIMESTAMPTZ NULL; `); // booking_id becomes nullable (inventory can exist before booking linkage). await queryRunner.query(` ALTER TABLE freight.warehouse_inventory ALTER COLUMN booking_id DROP NOT NULL; `); // ── Movement history ────────────────────────────────────────────────── await queryRunner.query(` CREATE TABLE IF NOT EXISTS freight.warehouse_inventory_movement ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), inventory_id UUID NOT NULL REFERENCES freight.warehouse_inventory(id) ON DELETE CASCADE, from_warehouse_id UUID NOT NULL, from_yard_id UUID NOT NULL, from_zone_id UUID NOT NULL, to_warehouse_id UUID NOT NULL, to_yard_id UUID NOT NULL, to_zone_id UUID NOT NULL, remarks TEXT NULL, moved_by VARCHAR(120) NULL, moved_at TIMESTAMPTZ NOT NULL DEFAULT now(), 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_warehouse_inventory_movement_inventory_id ON freight.warehouse_inventory_movement(inventory_id); `); // ── Activity log ────────────────────────────────────────────────────── await queryRunner.query(` CREATE TABLE IF NOT EXISTS freight.warehouse_activity_log ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), inventory_id UUID NULL, warehouse_id UUID NULL, activity_type VARCHAR(40) NOT NULL, description TEXT NULL, performed_by VARCHAR(120) 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_warehouse_activity_log_inventory_id ON freight.warehouse_activity_log(inventory_id); `); await queryRunner.query(` CREATE INDEX IF NOT EXISTS idx_warehouse_activity_log_warehouse_id ON freight.warehouse_activity_log(warehouse_id); `); await queryRunner.query(` CREATE INDEX IF NOT EXISTS idx_warehouse_activity_log_activity_type ON freight.warehouse_activity_log(activity_type); `); } public async down(queryRunner: QueryRunner): Promise { await queryRunner.query(`DROP TABLE IF EXISTS freight.warehouse_activity_log;`); await queryRunner.query(`DROP TABLE IF EXISTS freight.warehouse_inventory_movement;`); await queryRunner.query(` ALTER TABLE freight.warehouse_inventory DROP COLUMN IF EXISTS stored_at, DROP COLUMN IF EXISTS reserved_at, DROP COLUMN IF EXISTS loaded_at, DROP COLUMN IF EXISTS dispatched_at; `); await queryRunner.query(` ALTER TABLE freight.warehouse_inventory ALTER COLUMN status SET DEFAULT 'RECEIVED'; `); for (const table of ['warehouses', 'warehouse_yards', 'warehouse_zones']) { await queryRunner.query(` ALTER TABLE freight.${table} DROP COLUMN IF EXISTS max_weight, DROP COLUMN IF EXISTS max_volume, DROP COLUMN IF EXISTS current_volume; `); } } }