diff --git a/.claude/skills/edr-db/SKILL.md b/.claude/skills/edr-db/SKILL.md
index 47333128d..dde5f4b8a 100644
--- a/.claude/skills/edr-db/SKILL.md
+++ b/.claude/skills/edr-db/SKILL.md
@@ -11,7 +11,7 @@ One script, runs from anywhere in the repo (resolves `pg` from `apps/edr-freight
node .claude/skills/edr-db/query.cjs "SELECT ... " # run SQL, console.table output
node .claude/skills/edr-db/query.cjs explain "SELECT ..." # EXPLAIN-validate only (no rows touched)
node .claude/skills/edr-db/query.cjs columns
# freight. column list
-node .claude/skills/edr-db/query.cjs migrations [like] # public.migrations rows (newest first)
+node .claude/skills/edr-db/query.cjs migrations [like] # migration rows, newest first (freight.migrations + iam.typeorm_migrations)
node .claude/skills/edr-db/query.cjs drift # bare column names, for diffing vs the entity
```
@@ -31,7 +31,11 @@ defaulting to the shared dev database (`edr_dev`).
(`WHERE NOT EXISTS` guards) — watch-mode API instances race `migrationsRun`,
and non-idempotent statements have double-run here before.
- Timestamps for new migrations: must be unique across `src/migrations/` AND
- higher than `SELECT max(timestamp) FROM public.migrations`.
+ higher than `SELECT max(timestamp) FROM freight.migrations`.
+- Freight and IAM keep separate histories: `freight.migrations` for
+ `apps/edr-freight-api/src/migrations/*`, `iam.typeorm_migrations` for the
+ `@tria-plc/iamapi-common` migrations. `public.migrations` is the pre-split
+ table, left in place for rollback — never write to it.
## Diagnosing a pasted 400/500 (the recurring loop)
diff --git a/.claude/skills/edr-db/query.cjs b/.claude/skills/edr-db/query.cjs
index b4797bd45..6f41f555b 100644
--- a/.claude/skills/edr-db/query.cjs
+++ b/.claude/skills/edr-db/query.cjs
@@ -8,7 +8,7 @@
* node .claude/skills/edr-db/query.cjs "SELECT ... " run SQL (console.table)
* node .claude/skills/edr-db/query.cjs explain "SELECT..." EXPLAIN-validate only
* node .claude/skills/edr-db/query.cjs columns list freight. columns
- * node .claude/skills/edr-db/query.cjs migrations [like] public.migrations rows
+ * node .claude/skills/edr-db/query.cjs migrations [like] freight/iam migration rows
* node .claude/skills/edr-db/query.cjs drift columns vs entity check helper
*
* Connection: DB_HOST/DB_PORT/DB_USER/DB_PASSWORD/DB_NAME env vars, falling
@@ -52,12 +52,27 @@ async function main() {
console.table(r.rows);
} else if (first === 'migrations') {
const like = rest[0] ? `%${rest[0]}%` : '%';
- const r = await c.query(
- `SELECT id, timestamp, name FROM public.migrations
- WHERE name ILIKE $1 ORDER BY id DESC LIMIT 40`,
- [like],
- );
- console.table(r.rows);
+ // Histories are split per owner: freight.migrations (this app) and
+ // iam.typeorm_migrations (@tria-plc/iamapi-common). public.migrations is the
+ // pre-split table, kept for rollback — read it only if the split has not
+ // been applied to this DB yet.
+ const sources = [
+ ['freight', 'freight.migrations'],
+ ['iam', 'iam.typeorm_migrations'],
+ ['legacy', 'public.migrations'],
+ ];
+ const rows = [];
+ for (const [owner, table] of sources) {
+ const present = await c.query(`SELECT to_regclass($1) IS NOT NULL AS ok`, [table]);
+ if (!present.rows[0].ok) continue;
+ const r = await c.query(
+ `SELECT id, timestamp, name FROM ${table}
+ WHERE name ILIKE $1 ORDER BY id DESC LIMIT 40`,
+ [like],
+ );
+ rows.push(...r.rows.map((row) => ({ owner, ...row })));
+ }
+ console.table(rows);
} else if (first === 'drift') {
// Quick drift signal: DB columns for the table. Compare by eye against
// the entity's @Column names; a recorded-but-absent column = drift.
diff --git a/apps/edr-freight-api/src/config/database.config.ts b/apps/edr-freight-api/src/config/database.config.ts
index 0591cd0b1..f699b193d 100644
--- a/apps/edr-freight-api/src/config/database.config.ts
+++ b/apps/edr-freight-api/src/config/database.config.ts
@@ -118,30 +118,91 @@ const iamMigrationsGlob = join(
);
const freightMigrationsGlob = join(__dirname, "../migrations/*.js");
-export function buildDataSourceOptions(): DataSourceOptions {
+/**
+ * Migration history is split per owner instead of sharing one `public.migrations`
+ * table:
+ *
+ * - IAM migrations ship with `@tria-plc/iamapi-common`, target the `iam` schema
+ * and are recorded in `iam.typeorm_migrations` — the same table the package's
+ * own CLI (`pnpm iam:migration:run|show|revert`) uses, so both paths agree on
+ * what has been applied.
+ * - Freight migrations are recorded in `freight.migrations`.
+ *
+ * `public.migrations` is the pre-split table; `src/scripts/migrate.ts` adopts its
+ * rows into the two tables above on first run and then leaves it untouched.
+ */
+export const IAM_MIGRATIONS = {
+ schema: "iam",
+ table: "typeorm_migrations",
+} as const;
+
+export const FREIGHT_MIGRATIONS = {
+ schema: "freight",
+ table: "migrations",
+} as const;
+
+export const LEGACY_MIGRATIONS = {
+ schema: "public",
+ table: "migrations",
+} as const;
+
+function buildConnectionOptions() {
return {
- type: "postgres",
+ type: "postgres" as const,
host: process.env.DB_HOST ?? "localhost",
port: parseInt(process.env.DB_PORT ?? "5433", 10),
username: process.env.DB_USER ?? "postgres",
password: process.env.DB_PASSWORD ?? "",
database: process.env.DB_NAME ?? "edr_freight",
- schema: "public",
// NOTE: do NOT pass `extra.options: '-c search_path=...'`. That sends the
// Postgres startup `options` parameter, which connection poolers (PgBouncer /
// proxies fronting the remote edr_dev DB) reject with
// `08P01 unsupported startup parameter in options: search_path`.
// The search_path is instead applied per-connection via a pool `connect`
- // handler in app.module.ts (see setPoolSearchPath).
- entities: [__dirname + "/../**/*.entity.{ts,js}", ...iamEntities],
- migrations: [
- iamMigrationsGlob,
- freightMigrationsGlob,
- ],
- migrationsTransactionMode: "each",
+ // handler (app.module.ts `setPoolSearchPath`, migrate.ts `applySearchPath`).
synchronize: false,
logging:
- process.env.TYPEORM_LOGGING === "true" ? true : ["error", "warn"],
+ process.env.TYPEORM_LOGGING === "true"
+ ? true
+ : (["error", "warn"] as DataSourceOptions["logging"]),
+ };
+}
+
+/**
+ * Runtime options for the API (and the seed scripts using `AppDataSource`).
+ * Carries no migrations: migrations run only through `src/scripts/migrate.ts`,
+ * which uses the two dedicated DataSources below.
+ */
+export function buildDataSourceOptions(): DataSourceOptions {
+ return {
+ ...buildConnectionOptions(),
+ schema: "public",
+ entities: [__dirname + "/../**/*.entity.{ts,js}", ...iamEntities],
+ migrations: [],
+ };
+}
+
+/** IAM migrations only, recorded in `iam.typeorm_migrations`. */
+export function buildIamMigrationDataSourceOptions(): DataSourceOptions {
+ return {
+ ...buildConnectionOptions(),
+ schema: IAM_MIGRATIONS.schema,
+ entities: [],
+ migrations: [iamMigrationsGlob],
+ migrationsTableName: IAM_MIGRATIONS.table,
+ migrationsTransactionMode: "each",
+ };
+}
+
+/** Freight migrations only, recorded in `freight.migrations`. */
+export function buildFreightMigrationDataSourceOptions(): DataSourceOptions {
+ return {
+ ...buildConnectionOptions(),
+ schema: FREIGHT_MIGRATIONS.schema,
+ entities: [],
+ migrations: [freightMigrationsGlob],
+ migrationsTableName: FREIGHT_MIGRATIONS.table,
+ migrationsTransactionMode: "each",
};
}
diff --git a/apps/edr-freight-api/src/config/ensure-postgres-schemas.ts b/apps/edr-freight-api/src/config/ensure-postgres-schemas.ts
index 99fbcab61..0f359a942 100644
--- a/apps/edr-freight-api/src/config/ensure-postgres-schemas.ts
+++ b/apps/edr-freight-api/src/config/ensure-postgres-schemas.ts
@@ -10,6 +10,13 @@ export const APPLICATION_SCHEMAS = [
export const APPLICATION_SEARCH_PATH = APPLICATION_SCHEMAS.join(",");
+/**
+ * Extensions the migrations call into but never create themselves — both the IAM
+ * package's migrations and the freight baseline default columns to
+ * `uuid_generate_v4()` / `gen_random_uuid()`.
+ */
+export const APPLICATION_EXTENSIONS = ["uuid-ossp", "pgcrypto"] as const;
+
/**
* TypeORM creates the migrations table before any migration runs. If `public` was
* dropped, current_schema() is null and CREATE TABLE migrations fails.
@@ -42,6 +49,20 @@ export async function ensurePostgresSchemas(
}
}
+ // Best effort: creating an extension needs elevated rights the app user may not
+ // have. On an established database they are already installed and this is a
+ // no-op, so a failure here is only fatal for a brand-new database — where the
+ // first migration will fail loudly on the missing function anyway.
+ for (const extension of APPLICATION_EXTENSIONS) {
+ try {
+ await bootstrap.query(`CREATE EXTENSION IF NOT EXISTS "${extension}"`);
+ } catch (err) {
+ console.warn(
+ `could not ensure extension "${extension}": ${(err as Error).message}`,
+ );
+ }
+ }
+
await bootstrap.query(
`SET search_path TO ${APPLICATION_SEARCH_PATH}`,
);
diff --git a/apps/edr-freight-api/src/migrations/1748427600000-AddServiceTypesAndCargoTypes.ts b/apps/edr-freight-api/src/migrations/1748427600000-AddServiceTypesAndCargoTypes.ts
deleted file mode 100644
index 08ce634eb..000000000
--- a/apps/edr-freight-api/src/migrations/1748427600000-AddServiceTypesAndCargoTypes.ts
+++ /dev/null
@@ -1,233 +0,0 @@
-import { MigrationInterface, QueryRunner, Table, TableIndex, TableForeignKey } from "typeorm";
-
-export class AddServiceTypesAndCargoTypes1748427600000 implements MigrationInterface {
- name = "AddServiceTypesAndCargoTypes1748427600000";
-
- public async up(queryRunner: QueryRunner): Promise {
- // Create service_types table
- if (!(await queryRunner.hasTable("freight.service_types"))) await queryRunner.createTable(
- new Table({
- name: "service_types",
- schema: "freight",
- columns: [
- {
- name: "id",
- type: "uuid",
- isPrimary: true,
- generationStrategy: "uuid",
- default: "uuid_generate_v4()",
- },
- {
- name: "service_name",
- type: "varchar",
- length: "255",
- isNullable: false,
- },
- {
- name: "description",
- type: "text",
- isNullable: true,
- },
- {
- name: "can_be_booked_alone",
- type: "boolean",
- default: true,
- isNullable: false,
- },
- {
- name: "includes_first_mile",
- type: "boolean",
- default: false,
- isNullable: false,
- },
- {
- name: "includes_last_mile",
- type: "boolean",
- default: false,
- isNullable: false,
- },
- {
- name: "includes_customs",
- type: "boolean",
- default: false,
- isNullable: false,
- },
- {
- name: "priority_bonus_points",
- type: "int",
- default: 0,
- isNullable: false,
- },
- {
- name: "is_active",
- type: "boolean",
- default: true,
- isNullable: false,
- },
- {
- name: "display_order",
- type: "int",
- default: 1,
- isNullable: false,
- },
- {
- name: "created_at",
- type: "timestamptz",
- default: "now()",
- isNullable: false,
- },
- {
- name: "updated_at",
- type: "timestamptz",
- default: "now()",
- isNullable: false,
- },
- {
- name: "deleted_at",
- type: "timestamptz",
- isNullable: true,
- },
- ],
- }),
- true,
- );
-
- // Create indexes for service_types
- const table = await queryRunner.getTable("freight.service_types");
- if (table && !table.indices.some((idx) => idx.name === "IDX_SERVICE_TYPES_IS_ACTIVE")) {
- await queryRunner.createIndex(
- "freight.service_types",
- new TableIndex({
- name: "IDX_SERVICE_TYPES_IS_ACTIVE",
- columnNames: ["is_active"],
- }),
- );
- }
- if (table && !table.indices.some((idx) => idx.name === "IDX_SERVICE_TYPES_DISPLAY_ORDER")) {
- await queryRunner.createIndex(
- "freight.service_types",
- new TableIndex({
- name: "IDX_SERVICE_TYPES_DISPLAY_ORDER",
- columnNames: ["display_order"],
- }),
- );
- }
-
- // Create cargo_types table
- if (!(await queryRunner.hasTable("freight.cargo_types"))) await queryRunner.createTable(
- new Table({
- name: "cargo_types",
- schema: "freight",
- columns: [
- {
- name: "id",
- type: "uuid",
- isPrimary: true,
- generationStrategy: "uuid",
- default: "uuid_generate_v4()",
- },
- {
- name: "cargo_type_name",
- type: "varchar",
- length: "255",
- isNullable: false,
- },
- {
- name: "parent_group_id",
- type: "uuid",
- isNullable: true,
- },
- {
- name: "show_free_text_box",
- type: "boolean",
- default: false,
- isNullable: false,
- },
- {
- name: "requires_director_approval",
- type: "boolean",
- default: false,
- isNullable: false,
- },
- {
- name: "is_active",
- type: "boolean",
- default: true,
- isNullable: false,
- },
- {
- name: "display_order",
- type: "int",
- default: 1,
- isNullable: false,
- },
- {
- name: "created_at",
- type: "timestamptz",
- default: "now()",
- isNullable: false,
- },
- {
- name: "updated_at",
- type: "timestamptz",
- default: "now()",
- isNullable: false,
- },
- {
- name: "deleted_at",
- type: "timestamptz",
- isNullable: true,
- },
- ],
- }),
- true,
- );
-
- // Create indexes for cargo_types
- await queryRunner.createIndex(
- "freight.cargo_types",
- new TableIndex({
- name: "IDX_CARGO_TYPES_IS_ACTIVE",
- columnNames: ["is_active"],
- }),
- );
- await queryRunner.createIndex(
- "freight.cargo_types",
- new TableIndex({
- name: "IDX_CARGO_TYPES_DISPLAY_ORDER",
- columnNames: ["display_order"],
- }),
- );
- await queryRunner.createIndex(
- "freight.cargo_types",
- new TableIndex({
- name: "IDX_CARGO_TYPES_PARENT_GROUP_ID",
- columnNames: ["parent_group_id"],
- }),
- );
-
- // Create self-referencing foreign key for cargo_types
- await queryRunner.createForeignKey(
- "freight.cargo_types",
- new TableForeignKey({
- name: "FK_CARGO_TYPES_PARENT_GROUP",
- columnNames: ["parent_group_id"],
- referencedSchema: "freight",
- referencedTableName: "cargo_types",
- referencedColumnNames: ["id"],
- onDelete: "SET NULL",
- }),
- );
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- // Drop foreign key first
- await queryRunner.dropForeignKey("freight.cargo_types", "FK_CARGO_TYPES_PARENT_GROUP");
-
- // Drop cargo_types table
- await queryRunner.dropTable("freight.cargo_types", true);
-
- // Drop service_types table
- await queryRunner.dropTable("freight.service_types", true);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1748514000000-AddRuleEngineTablesAndCodes.ts b/apps/edr-freight-api/src/migrations/1748514000000-AddRuleEngineTablesAndCodes.ts
deleted file mode 100644
index e1a9f6aeb..000000000
--- a/apps/edr-freight-api/src/migrations/1748514000000-AddRuleEngineTablesAndCodes.ts
+++ /dev/null
@@ -1,293 +0,0 @@
-import {
- MigrationInterface,
- QueryRunner,
- Table,
- TableIndex,
- TableForeignKey,
- TableColumn,
-} from 'typeorm';
-
-export class AddRuleEngineTablesAndCodes1748514000000 implements MigrationInterface {
- name = 'AddRuleEngineTablesAndCodes1748514000000';
-
- public async up(queryRunner: QueryRunner): Promise {
- // ── 1. Add `code` column to existing tables ───────────────────────────
-
- if (!(await queryRunner.hasColumn('freight.service_types', 'code'))) {
- await queryRunner.addColumn(
- 'freight.service_types',
- new TableColumn({
- name: 'code',
- type: 'varchar',
- length: '50',
- isNullable: true,
- }),
- );
- }
- await queryRunner.query(
- `UPDATE freight.service_types SET code = upper(replace(service_name, ' ', '_')) WHERE code IS NULL OR code = ''`,
- );
- await queryRunner.query(
- `UPDATE freight.service_types SET code = 'SERVICE_' || substring(id::text, 1, 8) WHERE code IS NULL`,
- );
- await queryRunner.query(
- `ALTER TABLE freight.service_types ALTER COLUMN code SET NOT NULL`,
- );
- const serviceTypesCodeIdx = await queryRunner.query(
- `SELECT 1 FROM pg_indexes WHERE schemaname = 'freight' AND indexname = 'idx_service_types_code' LIMIT 1`,
- );
- if (serviceTypesCodeIdx.length === 0) {
- await queryRunner.createIndex(
- 'freight.service_types',
- new TableIndex({ name: 'IDX_service_types_code', columnNames: ['code'], isUnique: true }),
- );
- }
-
- if (!(await queryRunner.hasColumn('freight.cargo_types', 'code'))) {
- await queryRunner.addColumn(
- 'freight.cargo_types',
- new TableColumn({
- name: 'code',
- type: 'varchar',
- length: '50',
- isNullable: true,
- }),
- );
- }
- await queryRunner.query(
- `UPDATE freight.cargo_types SET code = upper(replace(cargo_type_name, ' ', '_')) WHERE code IS NULL OR code = ''`,
- );
- await queryRunner.query(
- `UPDATE freight.cargo_types SET code = 'CARGO_' || substring(id::text, 1, 8) WHERE code IS NULL`,
- );
- await queryRunner.query(
- `ALTER TABLE freight.cargo_types ALTER COLUMN code SET NOT NULL`,
- );
- const cargoTypesCodeIdx = await queryRunner.query(
- `SELECT 1 FROM pg_indexes WHERE schemaname = 'freight' AND indexname = 'idx_cargo_types_code' LIMIT 1`,
- );
- if (cargoTypesCodeIdx.length === 0) {
- await queryRunner.createIndex(
- 'freight.cargo_types',
- new TableIndex({ name: 'IDX_cargo_types_code', columnNames: ['code'], isUnique: true }),
- );
- }
-
- // ── 2. surcharge_types ────────────────────────────────────────────────
-
- if (!(await queryRunner.hasTable('freight.surcharge_types'))) await queryRunner.createTable(
- new Table({
- name: 'surcharge_types',
- schema: 'freight',
- columns: [
- { name: 'id', type: 'uuid', isPrimary: true, generationStrategy: 'uuid', default: 'uuid_generate_v4()' },
- { name: 'code', type: 'varchar', length: '50', isNullable: false },
- { name: 'name', type: 'varchar', length: '100', isNullable: false },
- { name: 'description', type: 'text', isNullable: true },
- { name: 'is_active', type: 'boolean', default: true },
- { name: 'created_at', type: 'timestamptz', default: 'now()' },
- { name: 'updated_at', type: 'timestamptz', default: 'now()' },
- { name: 'deleted_at', type: 'timestamptz', isNullable: true },
- ],
- }),
- true,
- );
- await queryRunner.createIndex(
- 'freight.surcharge_types',
- new TableIndex({ name: 'IDX_surcharge_types_code', columnNames: ['code'], isUnique: true }),
- );
- await queryRunner.createIndex(
- 'freight.surcharge_types',
- new TableIndex({ name: 'IDX_surcharge_types_is_active', columnNames: ['is_active'] }),
- );
-
- // ── 3. surcharges ─────────────────────────────────────────────────────
-
- if (!(await queryRunner.hasTable('freight.surcharges'))) await queryRunner.createTable(
- new Table({
- name: 'surcharges',
- schema: 'freight',
- columns: [
- { name: 'id', type: 'uuid', isPrimary: true, generationStrategy: 'uuid', default: 'uuid_generate_v4()' },
- { name: 'surcharge_type_id', type: 'uuid', isNullable: false },
- { name: 'fee_name', type: 'varchar', length: '255', isNullable: false },
- { name: 'trigger_description', type: 'text', isNullable: true },
- {
- name: 'calculation_method',
- type: 'enum',
- enum: ['PER_TON', 'FLAT_FEE', 'PERCENTAGE'],
- default: `'PER_TON'`,
- },
- { name: 'rate', type: 'numeric', precision: 10, scale: 2, isNullable: false },
- { name: 'currency', type: 'char', length: '3', default: `'USD'` },
- { name: 'apply_to_rail', type: 'boolean', default: false },
- { name: 'apply_to_first_mile', type: 'boolean', default: false },
- { name: 'apply_to_last_mile', type: 'boolean', default: false },
- { name: 'is_active', type: 'boolean', default: true },
- { name: 'created_at', type: 'timestamptz', default: 'now()' },
- { name: 'updated_at', type: 'timestamptz', default: 'now()' },
- { name: 'deleted_at', type: 'timestamptz', isNullable: true },
- ],
- }),
- true,
- );
- await queryRunner.createForeignKey(
- 'freight.surcharges',
- new TableForeignKey({
- name: 'FK_surcharges_surcharge_type',
- columnNames: ['surcharge_type_id'],
- referencedTableName: 'freight.surcharge_types',
- referencedColumnNames: ['id'],
- onDelete: 'RESTRICT',
- }),
- );
- await queryRunner.createIndex(
- 'freight.surcharges',
- new TableIndex({ name: 'IDX_surcharges_surcharge_type_id', columnNames: ['surcharge_type_id'] }),
- );
- await queryRunner.createIndex(
- 'freight.surcharges',
- new TableIndex({ name: 'IDX_surcharges_is_active', columnNames: ['is_active'] }),
- );
-
- // ── 4. container_types ────────────────────────────────────────────────
-
- if (!(await queryRunner.hasTable('freight.container_types'))) await queryRunner.createTable(
- new Table({
- name: 'container_types',
- schema: 'freight',
- columns: [
- { name: 'id', type: 'uuid', isPrimary: true, generationStrategy: 'uuid', default: 'uuid_generate_v4()' },
- { name: 'size_code', type: 'varchar', length: '20', isNullable: false },
- { name: 'description', type: 'varchar', length: '100', isNullable: true },
- { name: 'containers_per_wagon', type: 'int', isNullable: false },
- { name: 'is_active', type: 'boolean', default: true },
- { name: 'created_at', type: 'timestamptz', default: 'now()' },
- { name: 'updated_at', type: 'timestamptz', default: 'now()' },
- { name: 'deleted_at', type: 'timestamptz', isNullable: true },
- ],
- }),
- true,
- );
- await queryRunner.createIndex(
- 'freight.container_types',
- new TableIndex({ name: 'IDX_container_types_size_code', columnNames: ['size_code'], isUnique: true }),
- );
- await queryRunner.createIndex(
- 'freight.container_types',
- new TableIndex({ name: 'IDX_container_types_is_active', columnNames: ['is_active'] }),
- );
-
- // ── 5. weight_limit_rules ─────────────────────────────────────────────
-
- if (!(await queryRunner.hasTable('freight.weight_limit_rules'))) await queryRunner.createTable(
- new Table({
- name: 'weight_limit_rules',
- schema: 'freight',
- columns: [
- { name: 'id', type: 'uuid', isPrimary: true, generationStrategy: 'uuid', default: 'uuid_generate_v4()' },
- { name: 'container_type_id', type: 'uuid', isNullable: false },
- {
- name: 'trade_direction',
- type: 'enum',
- enum: ['IMPORT', 'EXPORT', 'BOTH'],
- isNullable: false,
- },
- { name: 'max_weight_tons', type: 'numeric', precision: 10, scale: 2, isNullable: false },
- { name: 'warning_threshold_tons', type: 'numeric', precision: 10, scale: 2, isNullable: false },
- {
- name: 'exceeded_action',
- type: 'enum',
- enum: ['WARNING_ONLY', 'HARD_BLOCK'],
- default: `'WARNING_ONLY'`,
- },
- { name: 'surcharge_id', type: 'uuid', isNullable: true },
- { name: 'is_active', type: 'boolean', default: true },
- { name: 'created_at', type: 'timestamptz', default: 'now()' },
- { name: 'updated_at', type: 'timestamptz', default: 'now()' },
- { name: 'deleted_at', type: 'timestamptz', isNullable: true },
- ],
- }),
- true,
- );
- await queryRunner.createForeignKey(
- 'freight.weight_limit_rules',
- new TableForeignKey({
- name: 'FK_weight_limit_rules_container_type',
- columnNames: ['container_type_id'],
- referencedTableName: 'freight.container_types',
- referencedColumnNames: ['id'],
- onDelete: 'RESTRICT',
- }),
- );
- await queryRunner.createForeignKey(
- 'freight.weight_limit_rules',
- new TableForeignKey({
- name: 'FK_weight_limit_rules_surcharge',
- columnNames: ['surcharge_id'],
- referencedTableName: 'freight.surcharges',
- referencedColumnNames: ['id'],
- onDelete: 'SET NULL',
- }),
- );
- await queryRunner.createIndex(
- 'freight.weight_limit_rules',
- new TableIndex({ name: 'IDX_weight_limit_rules_container_type_id', columnNames: ['container_type_id'] }),
- );
- await queryRunner.createIndex(
- 'freight.weight_limit_rules',
- new TableIndex({ name: 'IDX_weight_limit_rules_surcharge_id', columnNames: ['surcharge_id'] }),
- );
- await queryRunner.createIndex(
- 'freight.weight_limit_rules',
- new TableIndex({ name: 'IDX_weight_limit_rules_is_active', columnNames: ['is_active'] }),
- );
-
- // ── 6. priority_rules ─────────────────────────────────────────────────
-
- if (!(await queryRunner.hasTable('freight.priority_rules'))) await queryRunner.createTable(
- new Table({
- name: 'priority_rules',
- schema: 'freight',
- columns: [
- { name: 'id', type: 'uuid', isPrimary: true, generationStrategy: 'uuid', default: 'uuid_generate_v4()' },
- {
- name: 'priority_type',
- type: 'enum',
- enum: ['USD_PAYER', 'RAIL_AND_FORWARDING', 'GOVERNMENT_ACCOUNT', 'HIGH_VOLUME_SHIPMENT'],
- isNullable: false,
- },
- { name: 'rule_name', type: 'varchar', length: '255', isNullable: false },
- { name: 'description', type: 'text', isNullable: true },
- { name: 'activation_condition', type: 'text', isNullable: true },
- { name: 'bonus_points', type: 'int', default: 0 },
- { name: 'is_active', type: 'boolean', default: false },
- { name: 'created_at', type: 'timestamptz', default: 'now()' },
- { name: 'updated_at', type: 'timestamptz', default: 'now()' },
- { name: 'deleted_at', type: 'timestamptz', isNullable: true },
- ],
- }),
- true,
- );
- await queryRunner.createIndex(
- 'freight.priority_rules',
- new TableIndex({ name: 'IDX_priority_rules_priority_type', columnNames: ['priority_type'], isUnique: true }),
- );
- await queryRunner.createIndex(
- 'freight.priority_rules',
- new TableIndex({ name: 'IDX_priority_rules_is_active', columnNames: ['is_active'] }),
- );
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.dropTable('freight.priority_rules', true);
- await queryRunner.dropTable('freight.weight_limit_rules', true);
- await queryRunner.dropTable('freight.container_types', true);
- await queryRunner.dropTable('freight.surcharges', true);
- await queryRunner.dropTable('freight.surcharge_types', true);
- await queryRunner.dropIndex('freight.cargo_types', 'IDX_cargo_types_code');
- await queryRunner.dropColumn('freight.cargo_types', 'code');
- await queryRunner.dropIndex('freight.service_types', 'IDX_service_types_code');
- await queryRunner.dropColumn('freight.service_types', 'code');
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1748550000000-CreateFreightLegacyBaseline.ts b/apps/edr-freight-api/src/migrations/1748550000000-CreateFreightLegacyBaseline.ts
deleted file mode 100644
index 2455727bf..000000000
--- a/apps/edr-freight-api/src/migrations/1748550000000-CreateFreightLegacyBaseline.ts
+++ /dev/null
@@ -1,88 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-/**
- * Pre-ITMLS baseline for fresh databases. Older environments created `freight.bookings`
- * via synchronize or manual SQL; ItmlsFullSchemaRewrite only ALTERs that table.
- */
-export class CreateFreightLegacyBaseline1748550000000 implements MigrationInterface {
- name = 'CreateFreightLegacyBaseline1748550000000';
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`CREATE SCHEMA IF NOT EXISTS freight`);
-
- await queryRunner.query(`
- DO $$ BEGIN
- CREATE TYPE freight.train_status AS ENUM (
- 'AVAILABLE', 'SCHEDULED', 'IN_SERVICE', 'UNDER_MAINTENANCE', 'OUT_OF_SERVICE'
- );
- EXCEPTION WHEN duplicate_object THEN NULL;
- END $$;
- `);
-
- await queryRunner.query(`
- CREATE TABLE IF NOT EXISTS freight.trains (
- id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
- code VARCHAR(32) NOT NULL UNIQUE,
- capacity_tons NUMERIC(10, 2) NOT NULL DEFAULT 0,
- status freight.train_status NOT NULL DEFAULT 'AVAILABLE',
- notes TEXT,
- created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
- updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
- deleted_at TIMESTAMPTZ
- );
- `);
-
- await queryRunner.query(`
- CREATE TABLE IF NOT EXISTS freight.bookings (
- id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
- reference VARCHAR(64) NOT NULL UNIQUE,
- customer_id UUID NOT NULL,
- train_id UUID,
- status VARCHAR(40) NOT NULL DEFAULT 'DRAFT',
- scheduled_date TIMESTAMPTZ NOT NULL DEFAULT now(),
- total_amount NUMERIC(14, 2) NOT NULL DEFAULT 0,
- payment_status VARCHAR(20) NOT NULL DEFAULT 'PENDING',
- contract_type VARCHAR(20) NOT NULL DEFAULT 'SPOT',
- previous_contract_id UUID,
- trade_direction VARCHAR(10) NOT NULL DEFAULT 'IMPORT',
- equipment_return VARCHAR(20) NOT NULL DEFAULT 'RETURN',
- first_mile_pickup_address TEXT,
- last_mile_delivery_address TEXT,
- cargo_total_weight_vgm NUMERIC(12, 3) NOT NULL DEFAULT 0,
- is_hazardous BOOLEAN NOT NULL DEFAULT false,
- payment_currency VARCHAR(5) NOT NULL DEFAULT 'USD',
- start_date DATE,
- end_date DATE,
- financial_terms TEXT,
- version_number INT NOT NULL DEFAULT 1,
- approved_by_staff_id UUID,
- approved_by_staff_at TIMESTAMPTZ,
- signed_by_director_id UUID,
- signed_by_director_at TIMESTAMPTZ,
- signed_by_ceo_id UUID,
- signed_by_ceo_at TIMESTAMPTZ,
- priority_score INT NOT NULL DEFAULT 0,
- allow_consolidation BOOLEAN NOT NULL DEFAULT false,
- consolidation_partner_id UUID,
- origin_station VARCHAR(255),
- destination_station VARCHAR(255),
- service_type VARCHAR(100),
- freight_type VARCHAR(100),
- freight_subtype VARCHAR(255),
- containers JSONB,
- first_mile_enabled BOOLEAN DEFAULT false,
- last_mile_enabled BOOLEAN DEFAULT false,
- is_refrigerated BOOLEAN DEFAULT false,
- created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
- updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
- deleted_at TIMESTAMPTZ
- );
- `);
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`DROP TABLE IF EXISTS freight.bookings CASCADE`);
- await queryRunner.query(`DROP TABLE IF EXISTS freight.trains CASCADE`);
- await queryRunner.query(`DROP TYPE IF EXISTS freight.train_status`);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1748600000000-ItmlsFullSchemaRewrite.ts b/apps/edr-freight-api/src/migrations/1748600000000-ItmlsFullSchemaRewrite.ts
deleted file mode 100644
index ce5b38c15..000000000
--- a/apps/edr-freight-api/src/migrations/1748600000000-ItmlsFullSchemaRewrite.ts
+++ /dev/null
@@ -1,544 +0,0 @@
-import {
- MigrationInterface,
- QueryRunner,
- Table,
- TableForeignKey,
- TableIndex,
- TableUnique,
-} from 'typeorm';
-
-export class ItmlsFullSchemaRewrite1748600000000 implements MigrationInterface {
- name = 'ItmlsFullSchemaRewrite1748600000000';
-
- public async up(queryRunner: QueryRunner): Promise {
- // ── container_types ───────────────────────────────────────────────────
- await queryRunner.query(`
- DO $$ BEGIN
- ALTER TABLE freight.container_types RENAME COLUMN size_code TO code;
- EXCEPTION WHEN undefined_column THEN NULL;
- END $$;
- `);
- await queryRunner.query(`
- DO $$ BEGIN
- ALTER TABLE freight.container_types RENAME COLUMN description TO label;
- EXCEPTION WHEN undefined_column THEN NULL;
- END $$;
- `);
- await queryRunner.query(`
- ALTER TABLE freight.container_types
- ADD COLUMN IF NOT EXISTS size_ft SMALLINT,
- ADD COLUMN IF NOT EXISTS is_reefer BOOLEAN NOT NULL DEFAULT false,
- ADD COLUMN IF NOT EXISTS is_open_top BOOLEAN NOT NULL DEFAULT false,
- ADD COLUMN IF NOT EXISTS display_order INT NOT NULL DEFAULT 1;
- `);
- await queryRunner.query(`
- ALTER TABLE freight.container_types
- ADD COLUMN IF NOT EXISTS wagons_per_unit NUMERIC(4,2);
- `);
- await queryRunner.query(`
- UPDATE freight.container_types
- SET wagons_per_unit = CASE
- WHEN containers_per_wagon > 0 THEN ROUND(1.0 / containers_per_wagon, 2)
- ELSE 1.00
- END
- WHERE wagons_per_unit IS NULL;
- `);
- await queryRunner.query(`
- UPDATE freight.container_types
- SET size_ft = CASE WHEN code LIKE '40%' OR code LIKE '%40%' THEN 40 ELSE 20 END
- WHERE size_ft IS NULL;
- `);
- await queryRunner.query(`
- ALTER TABLE freight.container_types
- ALTER COLUMN wagons_per_unit SET NOT NULL,
- DROP COLUMN IF EXISTS containers_per_wagon;
- `);
-
- // ── weight_limit_rules ──────────────────────────────────────────────────
- await queryRunner.query(`
- DO $$ BEGIN
- ALTER TABLE freight.weight_limit_rules RENAME COLUMN max_weight_tons TO max_vgm_tons;
- EXCEPTION WHEN undefined_column THEN NULL;
- END $$;
- `);
- await queryRunner.query(`
- ALTER TABLE freight.weight_limit_rules
- ALTER COLUMN max_vgm_tons TYPE NUMERIC(8,3);
- `);
- await queryRunner.query(`
- ALTER TABLE freight.weight_limit_rules
- ADD COLUMN IF NOT EXISTS effective_from DATE NOT NULL DEFAULT CURRENT_DATE,
- ADD COLUMN IF NOT EXISTS effective_to DATE;
- `);
- await queryRunner.query(`
- ALTER TABLE freight.weight_limit_rules
- DROP COLUMN IF EXISTS warning_threshold_tons,
- DROP COLUMN IF EXISTS exceeded_action,
- DROP COLUMN IF EXISTS surcharge_id,
- DROP COLUMN IF EXISTS is_active;
- `);
-
- // ── priority_rules ────────────────────────────────────────────────────
- await queryRunner.query(`
- ALTER TABLE freight.priority_rules
- ADD COLUMN IF NOT EXISTS code VARCHAR(40),
- ADD COLUMN IF NOT EXISTS label VARCHAR(100),
- ADD COLUMN IF NOT EXISTS score INT NOT NULL DEFAULT 0,
- ADD COLUMN IF NOT EXISTS condition_currency VARCHAR(5);
- `);
- await queryRunner.query(`
- UPDATE freight.priority_rules
- SET code = COALESCE(code, upper(priority_type::text)),
- label = COALESCE(label, rule_name),
- score = COALESCE(score, bonus_points)
- WHERE code IS NULL OR label IS NULL;
- `);
- await queryRunner.query(`
- ALTER TABLE freight.priority_rules
- DROP COLUMN IF EXISTS priority_type,
- DROP COLUMN IF EXISTS rule_name,
- DROP COLUMN IF EXISTS bonus_points,
- DROP COLUMN IF EXISTS activation_condition,
- DROP COLUMN IF EXISTS description;
- `);
- await queryRunner.query(`
- ALTER TABLE freight.priority_rules
- ALTER COLUMN code SET NOT NULL,
- ALTER COLUMN label SET NOT NULL;
- `);
- await queryRunner.createIndex(
- 'freight.priority_rules',
- new TableIndex({ name: 'UQ_priority_rules_code', columnNames: ['code'], isUnique: true }),
- );
-
- // ── rates (before surcharge_types.rate_id) ────────────────────────────
- await queryRunner.createTable(
- new Table({
- name: 'rates',
- schema: 'freight',
- columns: [
- { name: 'id', type: 'uuid', isPrimary: true, generationStrategy: 'uuid', default: 'uuid_generate_v4()' },
- { name: 'rate_type', type: 'varchar', length: '50' },
- { name: 'container_type_id', type: 'uuid', isNullable: true },
- { name: 'trade_direction', type: 'varchar', length: '10', isNullable: true },
- { name: 'currency', type: 'varchar', length: '5' },
- { name: 'rate_value', type: 'numeric', precision: 14, scale: 4 },
- { name: 'rate_unit', type: 'varchar', length: '30' },
- { name: 'status', type: 'varchar', length: '20', default: "'DRAFT'" },
- { name: 'proposed_by_staff_id', type: 'uuid' },
- { name: 'approved_by_ceo_id', type: 'uuid', isNullable: true },
- { name: 'approved_at', type: 'timestamptz', isNullable: true },
- { name: 'effective_from', type: 'date' },
- { name: 'effective_to', type: 'date', isNullable: true },
- { name: 'created_at', type: 'timestamptz', default: 'now()' },
- { name: 'updated_at', type: 'timestamptz', default: 'now()' },
- { name: 'deleted_at', type: 'timestamptz', isNullable: true },
- ],
- }),
- true,
- );
-
- // ── surcharge_types ───────────────────────────────────────────────────
- await queryRunner.query(`
- DO $$ BEGIN
- ALTER TABLE freight.surcharge_types RENAME COLUMN name TO label;
- EXCEPTION WHEN undefined_column THEN NULL;
- END $$;
- `);
- await queryRunner.query(`
- ALTER TABLE freight.surcharge_types
- ADD COLUMN IF NOT EXISTS trigger_condition VARCHAR(50),
- ADD COLUMN IF NOT EXISTS rate_id UUID;
- `);
- await queryRunner.query(`ALTER TABLE freight.surcharge_types DROP COLUMN IF EXISTS description`);
-
- await queryRunner.query(`DROP TABLE IF EXISTS freight.surcharges CASCADE`);
-
- // ── yards ─────────────────────────────────────────────────────────────
- await queryRunner.createTable(
- new Table({
- name: 'yards',
- schema: 'freight',
- columns: [
- { name: 'id', type: 'uuid', isPrimary: true, generationStrategy: 'uuid', default: 'uuid_generate_v4()' },
- { name: 'code', type: 'varchar', length: '20' },
- { name: 'label', type: 'varchar', length: '100' },
- { name: 'country', type: 'varchar', length: '50' },
- { name: 'is_active', type: 'boolean', default: true },
- { name: 'display_order', type: 'int', default: 1 },
- { name: 'created_at', type: 'timestamptz', default: 'now()' },
- { name: 'updated_at', type: 'timestamptz', default: 'now()' },
- { name: 'deleted_at', type: 'timestamptz', isNullable: true },
- ],
- }),
- true,
- );
- await queryRunner.createIndex(
- 'freight.yards',
- new TableIndex({ name: 'UQ_yards_code', columnNames: ['code'], isUnique: true }),
- );
-
- // ── shipping_lines ────────────────────────────────────────────────────
- await queryRunner.createTable(
- new Table({
- name: 'shipping_lines',
- schema: 'freight',
- columns: [
- { name: 'id', type: 'uuid', isPrimary: true, generationStrategy: 'uuid', default: 'uuid_generate_v4()' },
- { name: 'code', type: 'varchar', length: '20' },
- { name: 'label', type: 'varchar', length: '100' },
- { name: 'mapped_to_code', type: 'varchar', length: '20', isNullable: true },
- { name: 'show_extra_fee_notice', type: 'boolean', default: false },
- { name: 'is_active', type: 'boolean', default: true },
- { name: 'created_at', type: 'timestamptz', default: 'now()' },
- { name: 'updated_at', type: 'timestamptz', default: 'now()' },
- { name: 'deleted_at', type: 'timestamptz', isNullable: true },
- ],
- }),
- true,
- );
-
- // ── approval_rules ────────────────────────────────────────────────────
- await queryRunner.createTable(
- new Table({
- name: 'approval_rules',
- schema: 'freight',
- columns: [
- { name: 'id', type: 'uuid', isPrimary: true, generationStrategy: 'uuid', default: 'uuid_generate_v4()' },
- { name: 'requires_director_approval', type: 'boolean' },
- { name: 'step_order', type: 'smallint' },
- { name: 'required_role', type: 'varchar', length: '30' },
- { name: 'action_label', type: 'varchar', length: '50' },
- { name: 'blocks_role', type: 'varchar', length: '30', isNullable: true },
- { name: 'created_at', type: 'timestamptz', default: 'now()' },
- { name: 'updated_at', type: 'timestamptz', default: 'now()' },
- { name: 'deleted_at', type: 'timestamptz', isNullable: true },
- ],
- }),
- true,
- );
- await queryRunner.createUniqueConstraint(
- 'freight.approval_rules',
- new TableUnique({
- name: 'UQ_approval_rules_chain_step',
- columnNames: ['requires_director_approval', 'step_order'],
- }),
- );
-
- // ── bookings ────────────────────────────────────────────────────────
- await queryRunner.query(`
- ALTER TABLE freight.bookings
- ADD COLUMN IF NOT EXISTS origin_yard_id UUID,
- ADD COLUMN IF NOT EXISTS destination_yard_id UUID,
- ADD COLUMN IF NOT EXISTS service_type_id UUID,
- ADD COLUMN IF NOT EXISTS cargo_type_id UUID,
- ADD COLUMN IF NOT EXISTS cargo_free_text VARCHAR(200),
- ADD COLUMN IF NOT EXISTS shipping_line_id UUID,
- ADD COLUMN IF NOT EXISTS pnr_code VARCHAR(50),
- ADD COLUMN IF NOT EXISTS customer_signed_at TIMESTAMPTZ,
- ADD COLUMN IF NOT EXISTS fully_executed_at TIMESTAMPTZ;
- `);
-
- await queryRunner.query(`
- INSERT INTO freight.yards (id, code, label, country, is_active, display_order, created_at, updated_at)
- VALUES
- (uuid_generate_v4(), 'KALITY', 'Kality Rail Terminal', 'Ethiopia', true, 1, now(), now()),
- (uuid_generate_v4(), 'MOJO', 'Mojo Dry Port', 'Ethiopia', true, 2, now(), now()),
- (uuid_generate_v4(), 'DIRE_DAWA', 'Dire Dawa Yard', 'Ethiopia', true, 3, now(), now()),
- (uuid_generate_v4(), 'DJIB_PORT', 'Djibouti Port Terminal', 'Djibouti', true, 4, now(), now()),
- (uuid_generate_v4(), 'NAGAD', 'Nagad Terminal, Djibouti', 'Djibouti', true, 5, now(), now()),
- (uuid_generate_v4(), 'LEGACY_ORIGIN', 'Legacy Origin', 'Ethiopia', true, 99, now(), now()),
- (uuid_generate_v4(), 'LEGACY_DEST', 'Legacy Destination', 'Ethiopia', true, 100, now(), now())
- ON CONFLICT (code) DO NOTHING;
- `);
-
- await queryRunner.query(`
- INSERT INTO freight.service_types (id, code, service_name, can_be_booked_alone, includes_first_mile, includes_last_mile, includes_customs, priority_bonus_points, is_active, display_order, created_at, updated_at)
- SELECT uuid_generate_v4(), 'RAIL', 'Rail Transport Only', true, false, false, false, 0, true, 1, now(), now()
- WHERE NOT EXISTS (SELECT 1 FROM freight.service_types LIMIT 1);
- `);
- await queryRunner.query(`
- INSERT INTO freight.cargo_types (id, code, cargo_type_name, requires_director_approval, show_free_text_box, is_active, display_order, created_at, updated_at)
- SELECT uuid_generate_v4(), 'GENERAL', 'General Cargo', false, false, true, 1, now(), now()
- WHERE NOT EXISTS (SELECT 1 FROM freight.cargo_types LIMIT 1);
- `);
-
- const hasServiceTypeCol = await queryRunner.query(`
- SELECT 1 FROM information_schema.columns
- WHERE table_schema = 'freight' AND table_name = 'bookings' AND column_name = 'service_type'
- LIMIT 1;
- `);
-
- if (hasServiceTypeCol.length > 0) {
- await queryRunner.query(`
- UPDATE freight.bookings b
- SET service_type_id = st.id
- FROM freight.service_types st
- WHERE b.service_type_id IS NULL
- AND (
- st.code = b.service_type
- OR upper(replace(st.service_name, ' ', '_')) = upper(b.service_type)
- OR st.code = upper(replace(b.service_type, ' ', '_'))
- );
- `);
- await queryRunner.query(`
- UPDATE freight.bookings b
- SET cargo_type_id = ct.id
- FROM freight.cargo_types ct
- WHERE b.cargo_type_id IS NULL
- AND (
- ct.code = upper(b.freight_type)
- OR ct.code = upper(concat(b.freight_type, '_', coalesce(b.freight_subtype, '')))
- );
- `);
- await queryRunner.query(`
- UPDATE freight.bookings b
- SET cargo_free_text = b.freight_subtype
- WHERE b.cargo_free_text IS NULL AND b.freight_subtype IS NOT NULL;
- `);
- await queryRunner.query(`
- UPDATE freight.bookings b
- SET origin_yard_id = y.id
- FROM freight.yards y
- WHERE b.origin_yard_id IS NULL
- AND (y.label ILIKE b.origin_station OR y.code = upper(replace(b.origin_station, ' ', '_')));
- `);
- await queryRunner.query(`
- UPDATE freight.bookings b
- SET destination_yard_id = y.id
- FROM freight.yards y
- WHERE b.destination_yard_id IS NULL
- AND (y.label ILIKE b.destination_station OR y.code = upper(replace(b.destination_station, ' ', '_')));
- `);
- }
-
- const defaultServiceTypeId = await queryRunner.query(
- `SELECT id FROM freight.service_types ORDER BY display_order LIMIT 1`,
- );
- const defaultCargoTypeId = await queryRunner.query(
- `SELECT id FROM freight.cargo_types ORDER BY display_order LIMIT 1`,
- );
- const legacyOriginId = await queryRunner.query(
- `SELECT id FROM freight.yards WHERE code = 'LEGACY_ORIGIN' LIMIT 1`,
- );
- const legacyDestId = await queryRunner.query(
- `SELECT id FROM freight.yards WHERE code = 'LEGACY_DEST' LIMIT 1`,
- );
-
- if (defaultServiceTypeId[0]?.id) {
- await queryRunner.query(
- `UPDATE freight.bookings SET service_type_id = $1 WHERE service_type_id IS NULL`,
- [defaultServiceTypeId[0].id],
- );
- }
- if (defaultCargoTypeId[0]?.id) {
- await queryRunner.query(
- `UPDATE freight.bookings SET cargo_type_id = $1 WHERE cargo_type_id IS NULL`,
- [defaultCargoTypeId[0].id],
- );
- }
- if (legacyOriginId[0]?.id) {
- await queryRunner.query(
- `UPDATE freight.bookings SET origin_yard_id = $1 WHERE origin_yard_id IS NULL`,
- [legacyOriginId[0].id],
- );
- }
- if (legacyDestId[0]?.id) {
- await queryRunner.query(
- `UPDATE freight.bookings SET destination_yard_id = $1 WHERE destination_yard_id IS NULL`,
- [legacyDestId[0].id],
- );
- }
-
- const nullBookings = await queryRunner.query(
- `SELECT COUNT(*)::int AS cnt FROM freight.bookings WHERE service_type_id IS NULL OR cargo_type_id IS NULL OR origin_yard_id IS NULL OR destination_yard_id IS NULL`,
- );
- if (nullBookings[0]?.cnt > 0) {
- await queryRunner.query(`DELETE FROM freight.bookings WHERE service_type_id IS NULL OR cargo_type_id IS NULL OR origin_yard_id IS NULL OR destination_yard_id IS NULL`);
- }
-
- await queryRunner.query(`
- ALTER TABLE freight.bookings
- ALTER COLUMN service_type_id SET NOT NULL,
- ALTER COLUMN cargo_type_id SET NOT NULL,
- ALTER COLUMN origin_yard_id SET NOT NULL,
- ALTER COLUMN destination_yard_id SET NOT NULL;
- `);
-
- await queryRunner.query(`
- ALTER TABLE freight.bookings
- DROP COLUMN IF EXISTS origin_station,
- DROP COLUMN IF EXISTS destination_station,
- DROP COLUMN IF EXISTS service_type,
- DROP COLUMN IF EXISTS freight_type,
- DROP COLUMN IF EXISTS freight_subtype,
- DROP COLUMN IF EXISTS containers,
- DROP COLUMN IF EXISTS first_mile_enabled,
- DROP COLUMN IF EXISTS last_mile_enabled,
- DROP COLUMN IF EXISTS is_refrigerated;
- `);
-
- // ── booking_container ─────────────────────────────────────────────────
- await queryRunner.createTable(
- new Table({
- name: 'booking_container',
- schema: 'freight',
- columns: [
- { name: 'id', type: 'uuid', isPrimary: true, generationStrategy: 'uuid', default: 'uuid_generate_v4()' },
- { name: 'booking_id', type: 'uuid' },
- { name: 'container_type_id', type: 'uuid' },
- { name: 'quantity', type: 'smallint' },
- { name: 'vgm_per_unit_tons', type: 'numeric', precision: 10, scale: 3 },
- { name: 'total_vgm_tons', type: 'numeric', precision: 12, scale: 3 },
- { name: 'wagons_required', type: 'numeric', precision: 6, scale: 2 },
- { name: 'weight_limit_rule_id', type: 'uuid', isNullable: true },
- { name: 'is_overweight', type: 'boolean', default: false },
- { name: 'overweight_excess_tons', type: 'numeric', precision: 10, scale: 3, isNullable: true },
- { name: 'created_at', type: 'timestamptz', default: 'now()' },
- { name: 'updated_at', type: 'timestamptz', default: 'now()' },
- { name: 'deleted_at', type: 'timestamptz', isNullable: true },
- ],
- }),
- true,
- );
-
- await queryRunner.createTable(
- new Table({
- name: 'booking_rate_snapshot',
- schema: 'freight',
- columns: [
- { name: 'id', type: 'uuid', isPrimary: true, generationStrategy: 'uuid', default: 'uuid_generate_v4()' },
- { name: 'booking_id', type: 'uuid' },
- { name: 'rate_id', type: 'uuid' },
- { name: 'rate_type', type: 'varchar', length: '50' },
- { name: 'rate_value', type: 'numeric', precision: 14, scale: 4 },
- { name: 'rate_unit', type: 'varchar', length: '30' },
- { name: 'currency', type: 'varchar', length: '5' },
- { name: 'snapshotted_at', type: 'timestamptz' },
- { name: 'created_at', type: 'timestamptz', default: 'now()' },
- { name: 'updated_at', type: 'timestamptz', default: 'now()' },
- { name: 'deleted_at', type: 'timestamptz', isNullable: true },
- ],
- }),
- true,
- );
-
- await queryRunner.createTable(
- new Table({
- name: 'booking_cargo_modifier',
- schema: 'freight',
- columns: [
- { name: 'id', type: 'uuid', isPrimary: true, generationStrategy: 'uuid', default: 'uuid_generate_v4()' },
- { name: 'booking_id', type: 'uuid' },
- { name: 'surcharge_type_id', type: 'uuid' },
- { name: 'trigger_value', type: 'numeric', precision: 14, scale: 4, isNullable: true },
- { name: 'calculated_amount', type: 'numeric', precision: 14, scale: 2 },
- { name: 'rate_snapshot_id', type: 'uuid' },
- { name: 'created_at', type: 'timestamptz', default: 'now()' },
- { name: 'updated_at', type: 'timestamptz', default: 'now()' },
- { name: 'deleted_at', type: 'timestamptz', isNullable: true },
- ],
- }),
- true,
- );
-
- await queryRunner.createTable(
- new Table({
- name: 'booking_approval_step',
- schema: 'freight',
- columns: [
- { name: 'id', type: 'uuid', isPrimary: true, generationStrategy: 'uuid', default: 'uuid_generate_v4()' },
- { name: 'booking_id', type: 'uuid' },
- { name: 'approval_rule_id', type: 'uuid' },
- { name: 'step_order', type: 'smallint' },
- { name: 'required_role', type: 'varchar', length: '30' },
- { name: 'status', type: 'varchar', length: '20', default: "'PENDING'" },
- { name: 'actioned_by_staff_id', type: 'uuid', isNullable: true },
- { name: 'actioned_at', type: 'timestamptz', isNullable: true },
- { name: 'remarks', type: 'text', isNullable: true },
- { name: 'created_at', type: 'timestamptz', default: 'now()' },
- { name: 'updated_at', type: 'timestamptz', default: 'now()' },
- { name: 'deleted_at', type: 'timestamptz', isNullable: true },
- ],
- }),
- true,
- );
-
- // Foreign keys
- await queryRunner.createForeignKey(
- 'freight.surcharge_types',
- new TableForeignKey({
- name: 'FK_surcharge_types_rate_id',
- columnNames: ['rate_id'],
- referencedTableName: 'rates',
- referencedSchema: 'freight',
- referencedColumnNames: ['id'],
- }),
- );
- await queryRunner.createForeignKey(
- 'freight.booking_container',
- new TableForeignKey({
- columnNames: ['booking_id'],
- referencedTableName: 'bookings',
- referencedSchema: 'freight',
- referencedColumnNames: ['id'],
- onDelete: 'CASCADE',
- }),
- );
- await queryRunner.createForeignKey(
- 'freight.bookings',
- new TableForeignKey({
- columnNames: ['origin_yard_id'],
- referencedTableName: 'yards',
- referencedSchema: 'freight',
- referencedColumnNames: ['id'],
- }),
- );
- await queryRunner.createForeignKey(
- 'freight.bookings',
- new TableForeignKey({
- columnNames: ['destination_yard_id'],
- referencedTableName: 'yards',
- referencedSchema: 'freight',
- referencedColumnNames: ['id'],
- }),
- );
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.dropTable('freight.booking_approval_step', true);
- await queryRunner.dropTable('freight.booking_cargo_modifier', true);
- await queryRunner.dropTable('freight.booking_rate_snapshot', true);
- await queryRunner.dropTable('freight.booking_container', true);
-
- await queryRunner.query(`
- ALTER TABLE freight.bookings
- ADD COLUMN IF NOT EXISTS origin_station VARCHAR(255),
- ADD COLUMN IF NOT EXISTS destination_station VARCHAR(255),
- ADD COLUMN IF NOT EXISTS service_type VARCHAR(30),
- ADD COLUMN IF NOT EXISTS freight_type VARCHAR(20),
- ADD COLUMN IF NOT EXISTS freight_subtype VARCHAR(100),
- ADD COLUMN IF NOT EXISTS containers JSONB,
- ADD COLUMN IF NOT EXISTS first_mile_enabled BOOLEAN DEFAULT false,
- ADD COLUMN IF NOT EXISTS last_mile_enabled BOOLEAN DEFAULT false,
- ADD COLUMN IF NOT EXISTS is_refrigerated BOOLEAN DEFAULT false;
- `);
- await queryRunner.query(`
- ALTER TABLE freight.bookings
- DROP COLUMN IF EXISTS origin_yard_id,
- DROP COLUMN IF EXISTS destination_yard_id,
- DROP COLUMN IF EXISTS service_type_id,
- DROP COLUMN IF EXISTS cargo_type_id,
- DROP COLUMN IF EXISTS cargo_free_text,
- DROP COLUMN IF EXISTS shipping_line_id,
- DROP COLUMN IF EXISTS pnr_code,
- DROP COLUMN IF EXISTS customer_signed_at,
- DROP COLUMN IF EXISTS fully_executed_at;
- `);
-
- await queryRunner.dropTable('freight.approval_rules', true);
- await queryRunner.dropTable('freight.shipping_lines', true);
- await queryRunner.dropTable('freight.yards', true);
- await queryRunner.dropTable('freight.rates', true);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1748700000000-AddBookingsConfigForeignKeys.ts b/apps/edr-freight-api/src/migrations/1748700000000-AddBookingsConfigForeignKeys.ts
deleted file mode 100644
index 79f3cfb94..000000000
--- a/apps/edr-freight-api/src/migrations/1748700000000-AddBookingsConfigForeignKeys.ts
+++ /dev/null
@@ -1,94 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-export class AddBookingsConfigForeignKeys1748700000000 implements MigrationInterface {
- name = 'AddBookingsConfigForeignKeys1748700000000';
-
- public async up(queryRunner: QueryRunner): Promise {
- // Ensure parent config rows exist for backfill
- await queryRunner.query(`
- INSERT INTO freight.service_types (id, code, service_name, can_be_booked_alone, includes_first_mile, includes_last_mile, includes_customs, priority_bonus_points, is_active, display_order, created_at, updated_at)
- SELECT uuid_generate_v4(), 'RAIL', 'Rail Transport Only', true, false, false, false, 0, true, 1, now(), now()
- WHERE NOT EXISTS (SELECT 1 FROM freight.service_types LIMIT 1);
- `);
- await queryRunner.query(`
- INSERT INTO freight.cargo_types (id, code, cargo_type_name, requires_director_approval, show_free_text_box, is_active, display_order, created_at, updated_at)
- SELECT uuid_generate_v4(), 'GENERAL', 'General Cargo', false, false, true, 1, now(), now()
- WHERE NOT EXISTS (SELECT 1 FROM freight.cargo_types LIMIT 1);
- `);
-
- // Clear orphan shipping_line references (nullable FK)
- await queryRunner.query(`
- UPDATE freight.bookings b
- SET shipping_line_id = NULL
- WHERE b.shipping_line_id IS NOT NULL
- AND NOT EXISTS (
- SELECT 1 FROM freight.shipping_lines sl WHERE sl.id = b.shipping_line_id
- );
- `);
-
- // Backfill required FK columns
- await queryRunner.query(`
- UPDATE freight.bookings
- SET service_type_id = (SELECT id FROM freight.service_types ORDER BY display_order LIMIT 1)
- WHERE service_type_id IS NULL
- OR NOT EXISTS (SELECT 1 FROM freight.service_types st WHERE st.id = service_type_id);
- `);
- await queryRunner.query(`
- UPDATE freight.bookings
- SET cargo_type_id = (SELECT id FROM freight.cargo_types ORDER BY display_order LIMIT 1)
- WHERE cargo_type_id IS NULL
- OR NOT EXISTS (SELECT 1 FROM freight.cargo_types ct WHERE ct.id = cargo_type_id);
- `);
-
- await queryRunner.query(`
- DO $$ BEGIN
- ALTER TABLE freight.bookings
- ADD CONSTRAINT "FK_bookings_service_type_id"
- FOREIGN KEY (service_type_id)
- REFERENCES freight.service_types(id)
- ON DELETE RESTRICT;
- EXCEPTION
- WHEN duplicate_object THEN NULL;
- END $$;
- `);
-
- await queryRunner.query(`
- DO $$ BEGIN
- ALTER TABLE freight.bookings
- ADD CONSTRAINT "FK_bookings_cargo_type_id"
- FOREIGN KEY (cargo_type_id)
- REFERENCES freight.cargo_types(id)
- ON DELETE RESTRICT;
- EXCEPTION
- WHEN duplicate_object THEN NULL;
- END $$;
- `);
-
- await queryRunner.query(`
- DO $$ BEGIN
- ALTER TABLE freight.bookings
- ADD CONSTRAINT "FK_bookings_shipping_line_id"
- FOREIGN KEY (shipping_line_id)
- REFERENCES freight.shipping_lines(id)
- ON DELETE SET NULL;
- EXCEPTION
- WHEN duplicate_object THEN NULL;
- END $$;
- `);
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- ALTER TABLE freight.bookings
- DROP CONSTRAINT IF EXISTS "FK_bookings_shipping_line_id";
- `);
- await queryRunner.query(`
- ALTER TABLE freight.bookings
- DROP CONSTRAINT IF EXISTS "FK_bookings_cargo_type_id";
- `);
- await queryRunner.query(`
- ALTER TABLE freight.bookings
- DROP CONSTRAINT IF EXISTS "FK_bookings_service_type_id";
- `);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1748800000000-AddBookingsRemainingForeignKeys.ts b/apps/edr-freight-api/src/migrations/1748800000000-AddBookingsRemainingForeignKeys.ts
deleted file mode 100644
index 50d052a24..000000000
--- a/apps/edr-freight-api/src/migrations/1748800000000-AddBookingsRemainingForeignKeys.ts
+++ /dev/null
@@ -1,331 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-export class AddBookingsRemainingForeignKeys1748800000000 implements MigrationInterface {
- name = 'AddBookingsRemainingForeignKeys1748800000000';
-
- public async up(queryRunner: QueryRunner): Promise {
- const publicCustomersExists = await queryRunner.query(`
- SELECT EXISTS (
- SELECT 1 FROM information_schema.tables
- WHERE table_schema = 'public' AND table_name = 'customers'
- ) AS exists
- `);
- const hasPublicCustomers = Boolean(publicCustomersExists[0]?.exists);
-
- // ── freight.bookings: nullable FK cleanup ─────────────────────────────
- await queryRunner.query(`
- UPDATE freight.bookings b
- SET train_id = NULL
- WHERE b.train_id IS NOT NULL
- AND NOT EXISTS (SELECT 1 FROM freight.trains t WHERE t.id = b.train_id);
- `);
- await queryRunner.query(`
- UPDATE freight.bookings b
- SET previous_contract_id = NULL
- WHERE b.previous_contract_id IS NOT NULL
- AND NOT EXISTS (SELECT 1 FROM freight.bookings pb WHERE pb.id = b.previous_contract_id);
- `);
- await queryRunner.query(`
- UPDATE freight.bookings b
- SET consolidation_partner_id = NULL
- WHERE b.consolidation_partner_id IS NOT NULL
- AND NOT EXISTS (SELECT 1 FROM freight.bookings pb WHERE pb.id = b.consolidation_partner_id);
- `);
-
- // Legacy DBs only: public.customers is created/moved in MoveCustomersToFreightSchema (174890).
- if (hasPublicCustomers) {
- await queryRunner.query(`
- DELETE FROM freight.booking_cargo_modifier bcm
- USING freight.bookings b
- WHERE bcm.booking_id = b.id
- AND NOT EXISTS (SELECT 1 FROM public.customers c WHERE c.id = b.customer_id);
- `);
- await queryRunner.query(`
- DELETE FROM freight.booking_approval_step bas
- USING freight.bookings b
- WHERE bas.booking_id = b.id
- AND NOT EXISTS (SELECT 1 FROM public.customers c WHERE c.id = b.customer_id);
- `);
- await queryRunner.query(`
- DELETE FROM freight.booking_rate_snapshot brs
- USING freight.bookings b
- WHERE brs.booking_id = b.id
- AND NOT EXISTS (SELECT 1 FROM public.customers c WHERE c.id = b.customer_id);
- `);
- await queryRunner.query(`
- DELETE FROM freight.booking_container bc
- USING freight.bookings b
- WHERE bc.booking_id = b.id
- AND NOT EXISTS (SELECT 1 FROM public.customers c WHERE c.id = b.customer_id);
- `);
- await queryRunner.query(`
- DELETE FROM freight.bookings b
- WHERE NOT EXISTS (SELECT 1 FROM public.customers c WHERE c.id = b.customer_id);
- `);
-
- await queryRunner.query(`
- DO $$ BEGIN
- ALTER TABLE freight.bookings
- ADD CONSTRAINT "FK_bookings_customer_id"
- FOREIGN KEY (customer_id)
- REFERENCES public.customers(id)
- ON DELETE RESTRICT;
- EXCEPTION
- WHEN duplicate_object THEN NULL;
- END $$;
- `);
- }
-
- // ── freight.bookings FKs ────────────────────────────────────────────
- await queryRunner.query(`
- DO $$ BEGIN
- ALTER TABLE freight.bookings
- ADD CONSTRAINT "FK_bookings_train_id"
- FOREIGN KEY (train_id)
- REFERENCES freight.trains(id)
- ON DELETE SET NULL;
- EXCEPTION
- WHEN duplicate_object THEN NULL;
- END $$;
- `);
-
- await queryRunner.query(`
- DO $$ BEGIN
- ALTER TABLE freight.bookings
- ADD CONSTRAINT "FK_bookings_previous_contract_id"
- FOREIGN KEY (previous_contract_id)
- REFERENCES freight.bookings(id)
- ON DELETE SET NULL;
- EXCEPTION
- WHEN duplicate_object THEN NULL;
- END $$;
- `);
-
- await queryRunner.query(`
- DO $$ BEGIN
- ALTER TABLE freight.bookings
- ADD CONSTRAINT "FK_bookings_consolidation_partner_id"
- FOREIGN KEY (consolidation_partner_id)
- REFERENCES freight.bookings(id)
- ON DELETE SET NULL;
- EXCEPTION
- WHEN duplicate_object THEN NULL;
- END $$;
- `);
-
- // ── freight.booking_container ─────────────────────────────────────────
- await queryRunner.query(`
- UPDATE freight.booking_container bc
- SET weight_limit_rule_id = NULL
- WHERE bc.weight_limit_rule_id IS NOT NULL
- AND NOT EXISTS (
- SELECT 1 FROM freight.weight_limit_rules wlr WHERE wlr.id = bc.weight_limit_rule_id
- );
- `);
-
- await queryRunner.query(`
- DELETE FROM freight.booking_container bc
- WHERE NOT EXISTS (SELECT 1 FROM freight.container_types ct WHERE ct.id = bc.container_type_id);
- `);
-
- await queryRunner.query(`
- DO $$ BEGIN
- ALTER TABLE freight.booking_container
- ADD CONSTRAINT "FK_booking_container_container_type_id"
- FOREIGN KEY (container_type_id)
- REFERENCES freight.container_types(id)
- ON DELETE RESTRICT;
- EXCEPTION
- WHEN duplicate_object THEN NULL;
- END $$;
- `);
-
- await queryRunner.query(`
- DO $$ BEGIN
- ALTER TABLE freight.booking_container
- ADD CONSTRAINT "FK_booking_container_weight_limit_rule_id"
- FOREIGN KEY (weight_limit_rule_id)
- REFERENCES freight.weight_limit_rules(id)
- ON DELETE SET NULL;
- EXCEPTION
- WHEN duplicate_object THEN NULL;
- END $$;
- `);
-
- // ── freight.booking_rate_snapshot ─────────────────────────────────────
- await queryRunner.query(`
- DELETE FROM freight.booking_cargo_modifier bcm
- USING freight.booking_rate_snapshot brs
- WHERE bcm.rate_snapshot_id = brs.id
- AND (
- NOT EXISTS (SELECT 1 FROM freight.bookings b WHERE b.id = brs.booking_id)
- OR NOT EXISTS (SELECT 1 FROM freight.rates r WHERE r.id = brs.rate_id)
- );
- `);
- await queryRunner.query(`
- DELETE FROM freight.booking_rate_snapshot brs
- WHERE NOT EXISTS (SELECT 1 FROM freight.bookings b WHERE b.id = brs.booking_id)
- OR NOT EXISTS (SELECT 1 FROM freight.rates r WHERE r.id = brs.rate_id);
- `);
-
- await queryRunner.query(`
- DO $$ BEGIN
- ALTER TABLE freight.booking_rate_snapshot
- ADD CONSTRAINT "FK_booking_rate_snapshot_booking_id"
- FOREIGN KEY (booking_id)
- REFERENCES freight.bookings(id)
- ON DELETE CASCADE;
- EXCEPTION
- WHEN duplicate_object THEN NULL;
- END $$;
- `);
-
- await queryRunner.query(`
- DO $$ BEGIN
- ALTER TABLE freight.booking_rate_snapshot
- ADD CONSTRAINT "FK_booking_rate_snapshot_rate_id"
- FOREIGN KEY (rate_id)
- REFERENCES freight.rates(id)
- ON DELETE RESTRICT;
- EXCEPTION
- WHEN duplicate_object THEN NULL;
- END $$;
- `);
-
- // ── freight.booking_approval_step ─────────────────────────────────────
- await queryRunner.query(`
- DELETE FROM freight.booking_approval_step bas
- WHERE NOT EXISTS (SELECT 1 FROM freight.bookings b WHERE b.id = bas.booking_id)
- OR NOT EXISTS (SELECT 1 FROM freight.approval_rules ar WHERE ar.id = bas.approval_rule_id);
- `);
-
- await queryRunner.query(`
- DO $$ BEGIN
- ALTER TABLE freight.booking_approval_step
- ADD CONSTRAINT "FK_booking_approval_step_booking_id"
- FOREIGN KEY (booking_id)
- REFERENCES freight.bookings(id)
- ON DELETE CASCADE;
- EXCEPTION
- WHEN duplicate_object THEN NULL;
- END $$;
- `);
-
- await queryRunner.query(`
- DO $$ BEGIN
- ALTER TABLE freight.booking_approval_step
- ADD CONSTRAINT "FK_booking_approval_step_approval_rule_id"
- FOREIGN KEY (approval_rule_id)
- REFERENCES freight.approval_rules(id)
- ON DELETE RESTRICT;
- EXCEPTION
- WHEN duplicate_object THEN NULL;
- END $$;
- `);
-
- // ── freight.booking_cargo_modifier ────────────────────────────────────
- await queryRunner.query(`
- DELETE FROM freight.booking_cargo_modifier bcm
- WHERE NOT EXISTS (SELECT 1 FROM freight.bookings b WHERE b.id = bcm.booking_id)
- OR NOT EXISTS (SELECT 1 FROM freight.surcharge_types st WHERE st.id = bcm.surcharge_type_id)
- OR NOT EXISTS (
- SELECT 1 FROM freight.booking_rate_snapshot brs WHERE brs.id = bcm.rate_snapshot_id
- );
- `);
-
- await queryRunner.query(`
- DO $$ BEGIN
- ALTER TABLE freight.booking_cargo_modifier
- ADD CONSTRAINT "FK_booking_cargo_modifier_booking_id"
- FOREIGN KEY (booking_id)
- REFERENCES freight.bookings(id)
- ON DELETE CASCADE;
- EXCEPTION
- WHEN duplicate_object THEN NULL;
- END $$;
- `);
-
- await queryRunner.query(`
- DO $$ BEGIN
- ALTER TABLE freight.booking_cargo_modifier
- ADD CONSTRAINT "FK_booking_cargo_modifier_surcharge_type_id"
- FOREIGN KEY (surcharge_type_id)
- REFERENCES freight.surcharge_types(id)
- ON DELETE RESTRICT;
- EXCEPTION
- WHEN duplicate_object THEN NULL;
- END $$;
- `);
-
- await queryRunner.query(`
- DO $$ BEGIN
- ALTER TABLE freight.booking_cargo_modifier
- ADD CONSTRAINT "FK_booking_cargo_modifier_rate_snapshot_id"
- FOREIGN KEY (rate_snapshot_id)
- REFERENCES freight.booking_rate_snapshot(id)
- ON DELETE RESTRICT;
- EXCEPTION
- WHEN duplicate_object THEN NULL;
- END $$;
- `);
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- ALTER TABLE freight.booking_cargo_modifier
- DROP CONSTRAINT IF EXISTS "FK_booking_cargo_modifier_rate_snapshot_id";
- `);
- await queryRunner.query(`
- ALTER TABLE freight.booking_cargo_modifier
- DROP CONSTRAINT IF EXISTS "FK_booking_cargo_modifier_surcharge_type_id";
- `);
- await queryRunner.query(`
- ALTER TABLE freight.booking_cargo_modifier
- DROP CONSTRAINT IF EXISTS "FK_booking_cargo_modifier_booking_id";
- `);
-
- await queryRunner.query(`
- ALTER TABLE freight.booking_approval_step
- DROP CONSTRAINT IF EXISTS "FK_booking_approval_step_approval_rule_id";
- `);
- await queryRunner.query(`
- ALTER TABLE freight.booking_approval_step
- DROP CONSTRAINT IF EXISTS "FK_booking_approval_step_booking_id";
- `);
-
- await queryRunner.query(`
- ALTER TABLE freight.booking_rate_snapshot
- DROP CONSTRAINT IF EXISTS "FK_booking_rate_snapshot_rate_id";
- `);
- await queryRunner.query(`
- ALTER TABLE freight.booking_rate_snapshot
- DROP CONSTRAINT IF EXISTS "FK_booking_rate_snapshot_booking_id";
- `);
-
- await queryRunner.query(`
- ALTER TABLE freight.booking_container
- DROP CONSTRAINT IF EXISTS "FK_booking_container_weight_limit_rule_id";
- `);
- await queryRunner.query(`
- ALTER TABLE freight.booking_container
- DROP CONSTRAINT IF EXISTS "FK_booking_container_container_type_id";
- `);
-
- await queryRunner.query(`
- ALTER TABLE freight.bookings
- DROP CONSTRAINT IF EXISTS "FK_bookings_consolidation_partner_id";
- `);
- await queryRunner.query(`
- ALTER TABLE freight.bookings
- DROP CONSTRAINT IF EXISTS "FK_bookings_previous_contract_id";
- `);
- await queryRunner.query(`
- ALTER TABLE freight.bookings
- DROP CONSTRAINT IF EXISTS "FK_bookings_train_id";
- `);
- await queryRunner.query(`
- ALTER TABLE freight.bookings
- DROP CONSTRAINT IF EXISTS "FK_bookings_customer_id";
- `);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1748900000000-MoveCustomersToFreightSchema.ts b/apps/edr-freight-api/src/migrations/1748900000000-MoveCustomersToFreightSchema.ts
deleted file mode 100644
index 36e848e64..000000000
--- a/apps/edr-freight-api/src/migrations/1748900000000-MoveCustomersToFreightSchema.ts
+++ /dev/null
@@ -1,185 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-export class MoveCustomersToFreightSchema1748900000000 implements MigrationInterface {
- name = 'MoveCustomersToFreightSchema1748900000000';
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- CREATE TABLE IF NOT EXISTS freight.customers (
- id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
- user_id UUID NOT NULL,
- first_name VARCHAR(100) NOT NULL,
- last_name VARCHAR(100) NOT NULL,
- email VARCHAR(150) NOT NULL UNIQUE,
- phone VARCHAR(20) NOT NULL,
- company_name VARCHAR(200) NOT NULL,
- company_email VARCHAR(150) NOT NULL,
- company_phone VARCHAR(20) NOT NULL,
- company_location VARCHAR(100) NOT NULL,
- company_address TEXT NOT NULL,
- customer_type VARCHAR(32),
- status VARCHAR(32),
- contact_person_name VARCHAR(100) NOT NULL,
- contact_person_phone VARCHAR(20) NOT NULL,
- tin_number VARCHAR(10) NOT NULL UNIQUE,
- vat_number VARCHAR(50),
- fan_number VARCHAR(16) NOT NULL UNIQUE,
- general_manager_name VARCHAR(100) NOT NULL,
- general_manager_email VARCHAR(150) NOT NULL,
- general_manager_phone VARCHAR(20) NOT NULL,
- poa_name VARCHAR(100),
- poa_phone VARCHAR(20),
- poa_address TEXT,
- poa_email VARCHAR(150),
- poa_location VARCHAR(100),
- notes TEXT,
- 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_freight_customers_email"
- ON freight.customers (email);
- `);
- // await queryRunner.query(`
- // CREATE INDEX IF NOT EXISTS "IDX_freight_customers_user_id"
- // ON freight.customers (user_id);
- //`);
-
- // Copy rows from public.customers when that legacy table exists
- await queryRunner.query(`
- DO $$
- DECLARE
- has_public boolean;
- has_user_id boolean;
- has_userid boolean;
- BEGIN
- SELECT EXISTS (
- SELECT 1 FROM information_schema.tables
- WHERE table_schema = 'public' AND table_name = 'customers'
- ) INTO has_public;
-
- IF NOT has_public THEN
- RETURN;
- END IF;
-
- SELECT EXISTS (
- SELECT 1 FROM information_schema.columns
- WHERE table_schema = 'public' AND table_name = 'customers' AND column_name = 'user_id'
- ) INTO has_user_id;
-
- SELECT EXISTS (
- SELECT 1 FROM information_schema.columns
- WHERE table_schema = 'public' AND table_name = 'customers' AND column_name = 'userid'
- ) INTO has_userid;
-
- IF has_user_id THEN
- INSERT INTO freight.customers (
- id, user_id, first_name, last_name, email, phone,
- company_name, company_email, company_phone, company_location, company_address,
- contact_person_name, contact_person_phone, tin_number, vat_number, fan_number,
- general_manager_name, general_manager_email, general_manager_phone,
- poa_name, poa_phone, poa_address, poa_email, poa_location, notes,
- created_at, updated_at
- )
- SELECT
- id, user_id, first_name, last_name, email, phone,
- company_name, company_email, company_phone, company_location, company_address,
- contact_person_name, contact_person_phone, tin_number, vat_number, fan_number,
- general_manager_name, general_manager_email, general_manager_phone,
- poa_name, poa_phone, poa_address, poa_email, poa_location, notes,
- COALESCE(created_at, now()), COALESCE(updated_at, now())
- FROM public.customers
- ON CONFLICT (id) DO NOTHING;
- ELSIF has_userid THEN
- INSERT INTO freight.customers (
- id, user_id, first_name, last_name, email, phone,
- company_name, company_email, company_phone, company_location, company_address,
- contact_person_name, contact_person_phone, tin_number, vat_number, fan_number,
- general_manager_name, general_manager_email, general_manager_phone,
- poa_name, poa_phone, poa_address, poa_email, poa_location, notes,
- created_at, updated_at
- )
- SELECT
- id, userid, firstname, lastname, email, phone,
- companyname, companyemail, companyphone, companylocation, companyaddress,
- contactpersonname, contactpersonphone, tinnumber, vatnumber, fannumber,
- generalmanagername, generalmanageremail, generalmanagerphone,
- poaname, poaphone, poaaddress, poaemail, poalocation, notes,
- COALESCE("createdAt", now()), COALESCE("updatedAt", now())
- FROM public.customers
- ON CONFLICT (id) DO NOTHING;
- END IF;
- END $$;
- `);
-
- await queryRunner.query(`
- ALTER TABLE freight.bookings
- DROP CONSTRAINT IF EXISTS "FK_bookings_customer_id";
- `);
-
- await queryRunner.query(`
- DELETE FROM freight.booking_cargo_modifier bcm
- USING freight.bookings b
- WHERE bcm.booking_id = b.id
- AND NOT EXISTS (SELECT 1 FROM freight.customers c WHERE c.id = b.customer_id);
- `);
- await queryRunner.query(`
- DELETE FROM freight.booking_approval_step bas
- USING freight.bookings b
- WHERE bas.booking_id = b.id
- AND NOT EXISTS (SELECT 1 FROM freight.customers c WHERE c.id = b.customer_id);
- `);
- await queryRunner.query(`
- DELETE FROM freight.booking_rate_snapshot brs
- USING freight.bookings b
- WHERE brs.booking_id = b.id
- AND NOT EXISTS (SELECT 1 FROM freight.customers c WHERE c.id = b.customer_id);
- `);
- await queryRunner.query(`
- DELETE FROM freight.booking_container bc
- USING freight.bookings b
- WHERE bc.booking_id = b.id
- AND NOT EXISTS (SELECT 1 FROM freight.customers c WHERE c.id = b.customer_id);
- `);
- await queryRunner.query(`
- DELETE FROM freight.bookings b
- WHERE NOT EXISTS (SELECT 1 FROM freight.customers c WHERE c.id = b.customer_id);
- `);
-
- await queryRunner.query(`
- DO $$ BEGIN
- ALTER TABLE freight.bookings
- ADD CONSTRAINT "FK_bookings_customer_id"
- FOREIGN KEY (customer_id)
- REFERENCES freight.customers(id)
- ON DELETE RESTRICT;
- EXCEPTION
- WHEN duplicate_object THEN NULL;
- END $$;
- `);
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- ALTER TABLE freight.bookings
- DROP CONSTRAINT IF EXISTS "FK_bookings_customer_id";
- `);
-
- await queryRunner.query(`
- DO $$ BEGIN
- ALTER TABLE freight.bookings
- ADD CONSTRAINT "FK_bookings_customer_id"
- FOREIGN KEY (customer_id)
- REFERENCES public.customers(id)
- ON DELETE RESTRICT;
- EXCEPTION
- WHEN duplicate_object THEN NULL;
- END $$;
- `);
-
- await queryRunner.query(`DROP TABLE IF EXISTS freight.customers CASCADE;`);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1749000000000-NormalizeWeightLimitTradeDirectionBoth.ts b/apps/edr-freight-api/src/migrations/1749000000000-NormalizeWeightLimitTradeDirectionBoth.ts
deleted file mode 100644
index 5dc1d6315..000000000
--- a/apps/edr-freight-api/src/migrations/1749000000000-NormalizeWeightLimitTradeDirectionBoth.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-/**
- * Align weight_limit_rules.trade_direction with app code: IMPORT, EXPORT, BOTH (not ANY).
- */
-export class NormalizeWeightLimitTradeDirectionBoth1749000000000
- implements MigrationInterface
-{
- name = 'NormalizeWeightLimitTradeDirectionBoth1749000000000';
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- DO $$ BEGIN
- UPDATE freight.weight_limit_rules
- SET trade_direction = 'BOTH'
- WHERE trade_direction::text = 'ANY';
-
- UPDATE freight.weight_limit_rules
- SET trade_direction = 'IMPORT'
- WHERE trade_direction IS NULL;
- EXCEPTION WHEN undefined_table OR undefined_column THEN NULL;
- END $$;
- `);
- }
-
- public async down(_queryRunner: QueryRunner): Promise {
- // No-op: ANY is not a valid enum value in PostgreSQL.
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1749100000000-CreateFreightFilesTable.ts b/apps/edr-freight-api/src/migrations/1749100000000-CreateFreightFilesTable.ts
deleted file mode 100644
index ebb194833..000000000
--- a/apps/edr-freight-api/src/migrations/1749100000000-CreateFreightFilesTable.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-export class CreateFreightFilesTable1749100000000 implements MigrationInterface {
- name = 'CreateFreightFilesTable1749100000000';
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- CREATE TABLE IF NOT EXISTS freight.files (
- id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
- resource_id UUID NOT NULL,
- resource VARCHAR(100) NOT NULL,
- code VARCHAR(100) NOT NULL,
- name VARCHAR(500) NOT NULL,
- url TEXT NOT NULL,
- size INTEGER NOT NULL,
- mime_type VARCHAR(255) NOT NULL,
- 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_freight_files_resource"
- ON freight.files (resource_id, resource);
- `);
-
- await queryRunner.query(`
- CREATE INDEX IF NOT EXISTS "IDX_freight_files_resource_code"
- ON freight.files (resource_id, resource, code);
- `);
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`DROP TABLE IF EXISTS freight.files CASCADE`);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1749200000000-BookingFlowRefactor.ts b/apps/edr-freight-api/src/migrations/1749200000000-BookingFlowRefactor.ts
deleted file mode 100644
index 162672727..000000000
--- a/apps/edr-freight-api/src/migrations/1749200000000-BookingFlowRefactor.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-export class BookingFlowRefactor1749200000000 implements MigrationInterface {
- name = 'BookingFlowRefactor1749200000000';
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- CREATE TABLE IF NOT EXISTS freight.booking_review_note (
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
- booking_id UUID NOT NULL REFERENCES freight.bookings(id) ON DELETE CASCADE,
- author_id UUID,
- note TEXT NOT NULL,
- type VARCHAR(30) NOT NULL,
- created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
- updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
- deleted_at TIMESTAMPTZ
- );
- CREATE INDEX IF NOT EXISTS idx_booking_review_note_booking_id
- ON freight.booking_review_note(booking_id);
- `);
-
- await queryRunner.query(`
- ALTER TABLE freight.bookings
- ADD COLUMN IF NOT EXISTS marketing_approved_by_id UUID,
- ADD COLUMN IF NOT EXISTS marketing_approved_at TIMESTAMPTZ,
- ADD COLUMN IF NOT EXISTS contract_summary TEXT,
- ADD COLUMN IF NOT EXISTS locked_at TIMESTAMPTZ;
- `);
-
- await queryRunner.query(`
- UPDATE freight.bookings SET status = 'SUBMITTED'
- WHERE status IN ('RFQ_SUBMITTED', 'QUOTATION_SENT', 'QUOTATION_APPROVED');
- UPDATE freight.bookings SET status = 'REJECTED'
- WHERE status = 'QUOTATION_REJECTED';
- UPDATE freight.bookings SET status = 'CANCELLED'
- WHERE status = 'CANCELLED';
- `);
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- ALTER TABLE freight.bookings
- DROP COLUMN IF EXISTS locked_at,
- DROP COLUMN IF EXISTS contract_summary,
- DROP COLUMN IF EXISTS marketing_approved_at,
- DROP COLUMN IF EXISTS marketing_approved_by_id;
- `);
- await queryRunner.query(`DROP TABLE IF EXISTS freight.booking_review_note;`);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1749200000000-CreateCompaniesModule.ts b/apps/edr-freight-api/src/migrations/1749200000000-CreateCompaniesModule.ts
deleted file mode 100644
index c02c9fb5b..000000000
--- a/apps/edr-freight-api/src/migrations/1749200000000-CreateCompaniesModule.ts
+++ /dev/null
@@ -1,115 +0,0 @@
-import { MigrationInterface, QueryRunner, Table, TableIndex, TableUnique } from 'typeorm';
-
-export class CreateCompaniesModule1749200000000 implements MigrationInterface {
- name = 'CreateCompaniesModule1749200000000';
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.createTable(
- new Table({
- schema: 'freight',
- name: 'companies',
- columns: [
- { name: 'id', type: 'uuid', isPrimary: true, generationStrategy: 'uuid', default: 'gen_random_uuid()' },
- { name: 'name', type: 'varchar', length: '200' },
- { name: 'type', type: 'varchar', length: '32' },
- { name: 'status', type: 'varchar', length: '32', default: "'pending'" },
- { name: 'tin', type: 'varchar', length: '10', isUnique: true },
- { name: 'vat_number', type: 'varchar', length: '50', isNullable: true },
- { name: 'business_license', type: 'varchar', length: '100', isNullable: true },
- { name: 'fan_number', type: 'varchar', length: '16', isNullable: true },
- { name: 'country', type: 'varchar', length: '32', default: "'Ethiopia'" },
- { name: 'address', type: 'text', isNullable: true },
- { name: 'phone', type: 'varchar', length: '20', isNullable: true },
- { name: 'email', type: 'varchar', length: '150', isNullable: true },
- { name: 'website', type: 'varchar', length: '200', isNullable: true },
- { name: 'attributes', type: 'jsonb', isNullable: true },
- { name: 'created_at', type: 'timestamptz', default: 'now()' },
- { name: 'updated_at', type: 'timestamptz', default: 'now()' },
- { name: 'deleted_at', type: 'timestamptz', isNullable: true },
- ],
- }),
- true,
- );
-
- await queryRunner.createTable(
- new Table({
- schema: 'freight',
- name: 'external_profiles',
- columns: [
- { name: 'id', type: 'uuid', isPrimary: true, generationStrategy: 'uuid', default: 'gen_random_uuid()' },
- { name: 'user_id', type: 'uuid' },
- { name: 'company_id', type: 'uuid' },
- { name: 'first_name', type: 'varchar', length: '100' },
- { name: 'last_name', type: 'varchar', length: '100' },
- { name: 'email', type: 'varchar', length: '150', isUnique: true },
- { name: 'phone', type: 'varchar', length: '20', isNullable: true },
- { name: 'national_id', type: 'varchar', length: '50', isNullable: true },
- { name: 'job_title', type: 'varchar', length: '100', isNullable: true },
- { name: 'is_primary_contact', type: 'boolean', default: false },
- { name: 'created_at', type: 'timestamptz', default: 'now()' },
- { name: 'updated_at', type: 'timestamptz', default: 'now()' },
- { name: 'deleted_at', type: 'timestamptz', isNullable: true },
- ],
- foreignKeys: [
- {
- columnNames: ['company_id'],
- referencedTableName: 'companies',
- referencedSchema: 'freight',
- referencedColumnNames: ['id'],
- },
- ],
- }),
- true,
- );
-
- await queryRunner.createTable(
- new Table({
- schema: 'freight',
- name: 'ff_clients',
- columns: [
- { name: 'id', type: 'uuid', isPrimary: true, generationStrategy: 'uuid', default: 'gen_random_uuid()' },
- { name: 'forwarder_company_id', type: 'uuid' },
- { name: 'client_company_id', type: 'uuid' },
- { name: 'relationship_type', type: 'varchar', length: '32', default: "'managed_account'" },
- { name: 'can_book_on_behalf', type: 'boolean', default: true },
- { name: 'can_view_documents', type: 'boolean', default: true },
- { name: 'created_at', type: 'timestamptz', default: 'now()' },
- { name: 'updated_at', type: 'timestamptz', default: 'now()' },
- { name: 'deleted_at', type: 'timestamptz', isNullable: true },
- ],
- foreignKeys: [
- {
- columnNames: ['forwarder_company_id'],
- referencedTableName: 'companies',
- referencedSchema: 'freight',
- referencedColumnNames: ['id'],
- },
- {
- columnNames: ['client_company_id'],
- referencedTableName: 'companies',
- referencedSchema: 'freight',
- referencedColumnNames: ['id'],
- },
- ],
- }),
- true,
- );
-
- await queryRunner.createIndex('freight.companies', new TableIndex({ columnNames: ['tin'] }));
- await queryRunner.createIndex('freight.companies', new TableIndex({ columnNames: ['type'] }));
- await queryRunner.createIndex('freight.external_profiles', new TableIndex({ columnNames: ['user_id'] }));
- await queryRunner.createIndex('freight.external_profiles', new TableIndex({ columnNames: ['company_id'] }));
- await queryRunner.createIndex('freight.ff_clients', new TableIndex({ columnNames: ['forwarder_company_id'] }));
- await queryRunner.createIndex('freight.ff_clients', new TableIndex({ columnNames: ['client_company_id'] }));
-
- await queryRunner.createUniqueConstraint('freight.ff_clients', new TableUnique({
- columnNames: ['forwarder_company_id', 'client_company_id'],
- }));
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.dropTable('freight.ff_clients');
- await queryRunner.dropTable('freight.external_profiles');
- await queryRunner.dropTable('freight.companies');
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1749300000000-AddBookingFreightType.ts b/apps/edr-freight-api/src/migrations/1749300000000-AddBookingFreightType.ts
deleted file mode 100644
index 795d93fc3..000000000
--- a/apps/edr-freight-api/src/migrations/1749300000000-AddBookingFreightType.ts
+++ /dev/null
@@ -1,71 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-export class AddBookingFreightType1749300000000 implements MigrationInterface {
- name = 'AddBookingFreightType1749300000000';
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- ALTER TABLE freight.bookings
- ADD COLUMN IF NOT EXISTS freight_type VARCHAR(20);
- `);
-
- await queryRunner.query(`
- UPDATE freight.bookings b
- SET freight_type = 'CONTAINER'
- WHERE EXISTS (
- SELECT 1 FROM freight.booking_container bc WHERE bc.booking_id = b.id
- );
- `);
-
- await queryRunner.query(`
- UPDATE freight.bookings b
- SET freight_type = 'BULK'
- WHERE freight_type IS NULL
- AND b.cargo_type_id IS NOT NULL
- AND EXISTS (
- SELECT 1 FROM freight.cargo_types ct
- WHERE ct.id = b.cargo_type_id AND ct.requires_director_approval = true
- );
- `);
-
- await queryRunner.query(`
- UPDATE freight.bookings
- SET freight_type = 'CONTAINER'
- WHERE freight_type IS NULL;
- `);
-
- await queryRunner.query(`
- ALTER TABLE freight.bookings
- ALTER COLUMN cargo_type_id DROP NOT NULL;
- `);
-
- await queryRunner.query(`
- ALTER TABLE freight.bookings
- ALTER COLUMN freight_type SET NOT NULL;
- `);
-
- await queryRunner.query(`
- ALTER TABLE freight.bookings
- ADD CONSTRAINT chk_bookings_freight_type
- CHECK (freight_type IN ('CONTAINER', 'BULK'));
- `);
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- ALTER TABLE freight.bookings DROP CONSTRAINT IF EXISTS chk_bookings_freight_type;
- `);
- await queryRunner.query(`
- ALTER TABLE freight.bookings DROP COLUMN IF EXISTS freight_type;
- `);
- await queryRunner.query(`
- UPDATE freight.bookings SET cargo_type_id = (
- SELECT id FROM freight.cargo_types LIMIT 1
- ) WHERE cargo_type_id IS NULL;
- `);
- await queryRunner.query(`
- ALTER TABLE freight.bookings
- ALTER COLUMN cargo_type_id SET NOT NULL;
- `);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1749300000000-AddFanNumberToCompanies.ts b/apps/edr-freight-api/src/migrations/1749300000000-AddFanNumberToCompanies.ts
deleted file mode 100644
index 4df7ea4ce..000000000
--- a/apps/edr-freight-api/src/migrations/1749300000000-AddFanNumberToCompanies.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-export class AddFanNumberToCompanies1749300000000 implements MigrationInterface {
- name = 'AddFanNumberToCompanies1749300000000';
-
- public async up(queryRunner: QueryRunner): Promise {
- // fan_number may already exist when CreateCompaniesModule ran with the full schema
- await queryRunner.query(`
- ALTER TABLE freight.companies
- ADD COLUMN IF NOT EXISTS fan_number varchar(16) NULL;
- `);
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- ALTER TABLE freight.companies
- DROP COLUMN IF EXISTS fan_number;
- `);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1749400000000-AddContractSignatures.ts b/apps/edr-freight-api/src/migrations/1749400000000-AddContractSignatures.ts
deleted file mode 100644
index 8126b91ca..000000000
--- a/apps/edr-freight-api/src/migrations/1749400000000-AddContractSignatures.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-export class AddContractSignatures1749400000000 implements MigrationInterface {
- name = 'AddContractSignatures1749400000000';
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- ALTER TABLE freight.bookings
- ADD COLUMN IF NOT EXISTS contract_template_key VARCHAR(80),
- ADD COLUMN IF NOT EXISTS contract_generated_at TIMESTAMPTZ,
- ADD COLUMN IF NOT EXISTS pricing_breakdown JSONB;
- `);
-
- await queryRunner.query(`
- CREATE TABLE IF NOT EXISTS freight.booking_contract_signatures (
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
- booking_id UUID NOT NULL REFERENCES freight.bookings(id) ON DELETE CASCADE,
- signer_role VARCHAR(20) NOT NULL,
- signer_user_id UUID,
- signer_display_name VARCHAR(200) NOT NULL,
- signed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
- signature_file_id UUID REFERENCES freight.files(id) ON DELETE SET NULL,
- consent_text TEXT,
- ip_address VARCHAR(64),
- created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
- updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
- deleted_at TIMESTAMPTZ,
- CONSTRAINT uq_booking_contract_signatures_role
- UNIQUE (booking_id, signer_role)
- );
- CREATE INDEX IF NOT EXISTS idx_booking_contract_signatures_booking_id
- ON freight.booking_contract_signatures(booking_id);
- `);
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`DROP TABLE IF EXISTS freight.booking_contract_signatures;`);
- await queryRunner.query(`
- ALTER TABLE freight.bookings
- DROP COLUMN IF EXISTS pricing_breakdown,
- DROP COLUMN IF EXISTS contract_generated_at,
- DROP COLUMN IF EXISTS contract_template_key;
- `);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1749400000000-AddTrainScheduling.ts b/apps/edr-freight-api/src/migrations/1749400000000-AddTrainScheduling.ts
deleted file mode 100644
index 5e61797da..000000000
--- a/apps/edr-freight-api/src/migrations/1749400000000-AddTrainScheduling.ts
+++ /dev/null
@@ -1,153 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-export class AddTrainScheduling1749400000000 implements MigrationInterface {
- name = 'AddTrainScheduling1749400000000';
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- CREATE TABLE IF NOT EXISTS freight.wagon_types (
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
- code VARCHAR(32) NOT NULL UNIQUE,
- name VARCHAR(100) NOT NULL,
- capacity_tons NUMERIC(10,3) NOT NULL,
- length_meters NUMERIC(10,3) NOT NULL,
- max_wagons_per_train INT NULL,
- supported_load_types TEXT[] NOT NULL DEFAULT '{}',
- is_active BOOLEAN NOT NULL DEFAULT true,
- created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
- updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
- deleted_at TIMESTAMPTZ NULL
- );
- `);
-
- await queryRunner.query(`
- CREATE TABLE IF NOT EXISTS freight.locomotives (
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
- code VARCHAR(32) NOT NULL UNIQUE,
- name VARCHAR(100) NULL,
- max_pull_weight_tons NUMERIC(10,3) NOT NULL,
- status VARCHAR(20) NOT NULL DEFAULT 'AVAILABLE',
- available_from TIMESTAMPTZ NULL,
- created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
- updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
- deleted_at TIMESTAMPTZ NULL
- );
- `);
-
- await queryRunner.query(`
- CREATE TABLE IF NOT EXISTS freight.train_sets (
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
- locomotive_id UUID NOT NULL,
- total_weight_tons NUMERIC(10,3) NOT NULL,
- total_length_meters NUMERIC(10,3) NOT NULL,
- wagon_count INT NOT NULL,
- status VARCHAR(20) NOT NULL DEFAULT 'DRAFT',
- created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
- updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
- deleted_at TIMESTAMPTZ NULL,
- CONSTRAINT fk_train_sets_locomotive FOREIGN KEY (locomotive_id)
- REFERENCES freight.locomotives(id)
- );
- `);
-
- await queryRunner.query(`
- CREATE TABLE IF NOT EXISTS freight.train_set_wagons (
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
- train_set_id UUID NOT NULL,
- wagon_type_id UUID NOT NULL,
- sequence_no INT NOT NULL,
- capacity_tons NUMERIC(10,3) NOT NULL,
- length_meters NUMERIC(10,3) NOT NULL,
- assigned_weight_tons NUMERIC(10,3) NOT NULL DEFAULT 0,
- created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
- updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
- deleted_at TIMESTAMPTZ NULL,
- CONSTRAINT uq_train_set_wagons_sequence UNIQUE (train_set_id, sequence_no),
- CONSTRAINT fk_train_set_wagons_train_set FOREIGN KEY (train_set_id)
- REFERENCES freight.train_sets(id) ON DELETE CASCADE,
- CONSTRAINT fk_train_set_wagons_wagon_type FOREIGN KEY (wagon_type_id)
- REFERENCES freight.wagon_types(id)
- );
- `);
-
- await queryRunner.query(`
- CREATE TABLE IF NOT EXISTS freight.train_schedules (
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
- train_set_id UUID NOT NULL UNIQUE,
- origin_station_id UUID NOT NULL,
- destination_station_id UUID NOT NULL,
- scheduled_departure_date TIMESTAMPTZ NOT NULL,
- scheduled_arrival_date TIMESTAMPTZ NULL,
- status VARCHAR(20) NOT NULL DEFAULT 'DRAFT',
- created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
- updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
- deleted_at TIMESTAMPTZ NULL,
- CONSTRAINT fk_train_schedules_train_set FOREIGN KEY (train_set_id)
- REFERENCES freight.train_sets(id),
- CONSTRAINT fk_train_schedules_origin FOREIGN KEY (origin_station_id)
- REFERENCES freight.yards(id),
- CONSTRAINT fk_train_schedules_destination FOREIGN KEY (destination_station_id)
- REFERENCES freight.yards(id)
- );
- `);
-
- await queryRunner.query(`
- CREATE TABLE IF NOT EXISTS freight.train_schedule_bookings (
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
- train_schedule_id UUID NOT NULL,
- booking_id UUID NOT NULL UNIQUE,
- created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
- updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
- deleted_at TIMESTAMPTZ NULL,
- CONSTRAINT uq_train_schedule_booking UNIQUE (train_schedule_id, booking_id),
- CONSTRAINT fk_train_schedule_bookings_schedule FOREIGN KEY (train_schedule_id)
- REFERENCES freight.train_schedules(id) ON DELETE CASCADE,
- CONSTRAINT fk_train_schedule_bookings_booking FOREIGN KEY (booking_id)
- REFERENCES freight.bookings(id)
- );
- `);
-
- await queryRunner.query(`
- CREATE TABLE IF NOT EXISTS freight.wagon_booking_allocations (
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
- train_set_wagon_id UUID NOT NULL,
- booking_id UUID NOT NULL,
- allocated_weight_tons NUMERIC(10,3) NOT NULL,
- created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
- updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
- deleted_at TIMESTAMPTZ NULL,
- CONSTRAINT fk_wagon_booking_allocations_wagon FOREIGN KEY (train_set_wagon_id)
- REFERENCES freight.train_set_wagons(id) ON DELETE CASCADE,
- CONSTRAINT fk_wagon_booking_allocations_booking FOREIGN KEY (booking_id)
- REFERENCES freight.bookings(id)
- );
- `);
-
- await queryRunner.query(`
- CREATE INDEX IF NOT EXISTS idx_locomotives_status
- ON freight.locomotives(status);
- `);
- await queryRunner.query(`
- CREATE INDEX IF NOT EXISTS idx_train_sets_status
- ON freight.train_sets(status);
- `);
- await queryRunner.query(`
- CREATE INDEX IF NOT EXISTS idx_train_schedules_departure_status
- ON freight.train_schedules(scheduled_departure_date, status);
- `);
- await queryRunner.query(`
- CREATE INDEX IF NOT EXISTS idx_wagon_booking_allocations_booking
- ON freight.wagon_booking_allocations(booking_id);
- `);
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`DROP TABLE IF EXISTS freight.wagon_booking_allocations;`);
- await queryRunner.query(`DROP TABLE IF EXISTS freight.train_schedule_bookings;`);
- await queryRunner.query(`DROP TABLE IF EXISTS freight.train_schedules;`);
- await queryRunner.query(`DROP TABLE IF EXISTS freight.train_set_wagons;`);
- await queryRunner.query(`DROP TABLE IF EXISTS freight.train_sets;`);
- await queryRunner.query(`DROP TABLE IF EXISTS freight.locomotives;`);
- await queryRunner.query(`DROP TABLE IF EXISTS freight.wagon_types;`);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1749500000000-AddCompanyIdToBookings.ts b/apps/edr-freight-api/src/migrations/1749500000000-AddCompanyIdToBookings.ts
deleted file mode 100644
index 25fbe1806..000000000
--- a/apps/edr-freight-api/src/migrations/1749500000000-AddCompanyIdToBookings.ts
+++ /dev/null
@@ -1,59 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-export class AddCompanyIdToBookings1749500000000 implements MigrationInterface {
- name = 'AddCompanyIdToBookings1749500000000';
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- ALTER TABLE freight.bookings
- ALTER COLUMN customer_id DROP NOT NULL;
- `);
-
- await queryRunner.query(`
- ALTER TABLE freight.bookings
- ADD COLUMN IF NOT EXISTS company_id UUID;
- `);
-
- await queryRunner.query(`
- CREATE INDEX IF NOT EXISTS idx_bookings_company_id
- ON freight.bookings(company_id);
- `);
-
- await queryRunner.query(`
- DO $$
- BEGIN
- IF NOT EXISTS (
- SELECT 1 FROM pg_constraint WHERE conname = 'FK_bookings_company_id'
- ) THEN
- ALTER TABLE freight.bookings
- ADD CONSTRAINT "FK_bookings_company_id"
- FOREIGN KEY (company_id)
- REFERENCES freight.companies(id);
- END IF;
- END $$;
- `);
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- ALTER TABLE freight.bookings
- DROP CONSTRAINT IF EXISTS "FK_bookings_company_id";
- `);
-
- await queryRunner.query(`
- UPDATE freight.bookings SET customer_id = company_id WHERE customer_id IS NULL AND company_id IS NOT NULL;
- `);
-
- await queryRunner.query(`
- ALTER TABLE freight.bookings
- ALTER COLUMN customer_id SET NOT NULL;
- `);
- await queryRunner.query(`
- DROP INDEX IF EXISTS freight.idx_bookings_company_id;
- `);
- await queryRunner.query(`
- ALTER TABLE freight.bookings
- DROP COLUMN IF EXISTS company_id;
- `);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1749600000000-AddBlocksRoleToApprovalStep.ts b/apps/edr-freight-api/src/migrations/1749600000000-AddBlocksRoleToApprovalStep.ts
deleted file mode 100644
index a1830c370..000000000
--- a/apps/edr-freight-api/src/migrations/1749600000000-AddBlocksRoleToApprovalStep.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-export class AddBlocksRoleToApprovalStep1749600000000 implements MigrationInterface {
- name = 'AddBlocksRoleToApprovalStep1749600000000';
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- ALTER TABLE freight.booking_approval_step
- ADD COLUMN IF NOT EXISTS blocks_role VARCHAR(30) NULL;
- `);
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- ALTER TABLE freight.booking_approval_step
- DROP COLUMN IF EXISTS blocks_role;
- `);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1749700000000-SeedDefaultApprovalRules.ts b/apps/edr-freight-api/src/migrations/1749700000000-SeedDefaultApprovalRules.ts
deleted file mode 100644
index f972f50c2..000000000
--- a/apps/edr-freight-api/src/migrations/1749700000000-SeedDefaultApprovalRules.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-/**
- * Seed ITMLS US-06 approval chains if missing (standard + bulk).
- */
-export class SeedDefaultApprovalRules1749700000000 implements MigrationInterface {
- name = 'SeedDefaultApprovalRules1749700000000';
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- INSERT INTO freight.approval_rules
- (id, requires_director_approval, step_order, required_role, action_label, blocks_role, created_at, updated_at)
- SELECT uuid_generate_v4(), false, 1, 'LINE_STAFF', 'Review & Approve', NULL, now(), now()
- WHERE NOT EXISTS (
- SELECT 1 FROM freight.approval_rules
- WHERE requires_director_approval = false AND step_order = 1 AND deleted_at IS NULL
- );
-
- INSERT INTO freight.approval_rules
- (id, requires_director_approval, step_order, required_role, action_label, blocks_role, created_at, updated_at)
- SELECT uuid_generate_v4(), false, 2, 'DIRECTOR', 'Final Signature', 'LINE_STAFF', now(), now()
- WHERE NOT EXISTS (
- SELECT 1 FROM freight.approval_rules
- WHERE requires_director_approval = false AND step_order = 2 AND deleted_at IS NULL
- );
-
- INSERT INTO freight.approval_rules
- (id, requires_director_approval, step_order, required_role, action_label, blocks_role, created_at, updated_at)
- SELECT uuid_generate_v4(), true, 1, 'DIRECTOR', 'Review & Approve', 'LINE_STAFF', now(), now()
- WHERE NOT EXISTS (
- SELECT 1 FROM freight.approval_rules
- WHERE requires_director_approval = true AND step_order = 1 AND deleted_at IS NULL
- );
-
- INSERT INTO freight.approval_rules
- (id, requires_director_approval, step_order, required_role, action_label, blocks_role, created_at, updated_at)
- SELECT uuid_generate_v4(), true, 2, 'CEO', 'Final Signature', NULL, now(), now()
- WHERE NOT EXISTS (
- SELECT 1 FROM freight.approval_rules
- WHERE requires_director_approval = true AND step_order = 2 AND deleted_at IS NULL
- );
- `);
- }
-
- public async down(_queryRunner: QueryRunner): Promise {
- // Keep seeded rules on rollback to avoid breaking in-flight bookings.
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1749800000000-AddShippingLinesCodeUniqueIndex.ts b/apps/edr-freight-api/src/migrations/1749800000000-AddShippingLinesCodeUniqueIndex.ts
deleted file mode 100644
index 374fea724..000000000
--- a/apps/edr-freight-api/src/migrations/1749800000000-AddShippingLinesCodeUniqueIndex.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import { MigrationInterface, QueryRunner, TableIndex } from 'typeorm';
-
-/**
- * shipping_lines was created without a unique index on code; seeder upserts require it.
- */
-export class AddShippingLinesCodeUniqueIndex1749800000000 implements MigrationInterface {
- name = 'AddShippingLinesCodeUniqueIndex1749800000000';
-
- public async up(queryRunner: QueryRunner): Promise {
- const existing = await queryRunner.query(
- `SELECT 1 FROM pg_indexes WHERE schemaname = 'freight' AND tablename = 'shipping_lines' AND indexdef ILIKE '%UNIQUE%code%' LIMIT 1`,
- );
- if (existing.length === 0) {
- await queryRunner.createIndex(
- 'freight.shipping_lines',
- new TableIndex({
- name: 'UQ_shipping_lines_code',
- columnNames: ['code'],
- isUnique: true,
- }),
- );
- }
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.dropIndex('freight.shipping_lines', 'UQ_shipping_lines_code');
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1749900000000-CreateFileUploadSettingsTables.ts b/apps/edr-freight-api/src/migrations/1749900000000-CreateFileUploadSettingsTables.ts
deleted file mode 100644
index cbbf914d4..000000000
--- a/apps/edr-freight-api/src/migrations/1749900000000-CreateFileUploadSettingsTables.ts
+++ /dev/null
@@ -1,63 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-/**
- * file_upload_settings / file_upload_fields entities had no migration; seeder requires both tables.
- */
-export class CreateFileUploadSettingsTables1749900000000 implements MigrationInterface {
- name = 'CreateFileUploadSettingsTables1749900000000';
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- CREATE TABLE IF NOT EXISTS freight.file_upload_settings (
- id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
- code VARCHAR(128) NOT NULL,
- label VARCHAR(256) NOT NULL,
- description TEXT,
- entity VARCHAR(32) NOT NULL DEFAULT 'other',
- created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
- updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
- deleted_at TIMESTAMPTZ
- );
- `);
-
- await queryRunner.query(`
- CREATE UNIQUE INDEX IF NOT EXISTS "UQ_file_upload_settings_code"
- ON freight.file_upload_settings (code);
- `);
-
- await queryRunner.query(`
- CREATE TABLE IF NOT EXISTS freight.file_upload_fields (
- id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
- setting_id UUID NOT NULL,
- file_key VARCHAR(128) NOT NULL,
- file_label VARCHAR(256) NOT NULL,
- help_text TEXT,
- is_required BOOLEAN NOT NULL DEFAULT false,
- is_multiple BOOLEAN NOT NULL DEFAULT false,
- max_files INTEGER NOT NULL DEFAULT 1,
- allowed_extensions TEXT[] NOT NULL DEFAULT '{}'::text[],
- max_size_mb INTEGER NOT NULL DEFAULT 10,
- display_order INTEGER NOT NULL DEFAULT 0,
- created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
- updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
- deleted_at TIMESTAMPTZ,
- CONSTRAINT "CHK_file_upload_fields_max_files" CHECK (max_files > 0),
- CONSTRAINT "CHK_file_upload_fields_max_size_mb" CHECK (max_size_mb > 0),
- CONSTRAINT "FK_file_upload_fields_setting"
- FOREIGN KEY (setting_id)
- REFERENCES freight.file_upload_settings(id)
- ON DELETE CASCADE
- );
- `);
-
- await queryRunner.query(`
- CREATE UNIQUE INDEX IF NOT EXISTS "UQ_file_upload_fields_setting_file_key"
- ON freight.file_upload_fields (setting_id, file_key);
- `);
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`DROP TABLE IF EXISTS freight.file_upload_fields CASCADE`);
- await queryRunner.query(`DROP TABLE IF EXISTS freight.file_upload_settings CASCADE`);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1750000000000-AddCompanyContactColumns.ts b/apps/edr-freight-api/src/migrations/1750000000000-AddCompanyContactColumns.ts
deleted file mode 100644
index 56d41edf9..000000000
--- a/apps/edr-freight-api/src/migrations/1750000000000-AddCompanyContactColumns.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-export class AddCompanyContactColumns1750000000000 implements MigrationInterface {
- name = 'AddCompanyContactColumns1750000000000';
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`ALTER TABLE freight.companies ADD COLUMN IF NOT EXISTS contact_person_name VARCHAR(100);`);
- await queryRunner.query(`ALTER TABLE freight.companies ADD COLUMN IF NOT EXISTS contact_person_phone VARCHAR(20);`);
- await queryRunner.query(`ALTER TABLE freight.companies ADD COLUMN IF NOT EXISTS general_manager_name VARCHAR(100);`);
- await queryRunner.query(`ALTER TABLE freight.companies ADD COLUMN IF NOT EXISTS general_manager_email VARCHAR(150);`);
- await queryRunner.query(`ALTER TABLE freight.companies ADD COLUMN IF NOT EXISTS general_manager_phone VARCHAR(20);`);
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`ALTER TABLE freight.companies DROP COLUMN IF EXISTS general_manager_phone;`);
- await queryRunner.query(`ALTER TABLE freight.companies DROP COLUMN IF EXISTS general_manager_email;`);
- await queryRunner.query(`ALTER TABLE freight.companies DROP COLUMN IF EXISTS general_manager_name;`);
- await queryRunner.query(`ALTER TABLE freight.companies DROP COLUMN IF EXISTS contact_person_phone;`);
- await queryRunner.query(`ALTER TABLE freight.companies DROP COLUMN IF EXISTS contact_person_name;`);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1750000000000-AddTrainExtendedColumns.ts b/apps/edr-freight-api/src/migrations/1750000000000-AddTrainExtendedColumns.ts
deleted file mode 100644
index b249bd198..000000000
--- a/apps/edr-freight-api/src/migrations/1750000000000-AddTrainExtendedColumns.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-/**
- * Train entity gained extended fields; baseline trains table only had code/capacity/status/notes.
- */
-export class AddTrainExtendedColumns1750000000000 implements MigrationInterface {
- name = 'AddTrainExtendedColumns1750000000000';
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- ALTER TABLE freight.trains
- ADD COLUMN IF NOT EXISTS train_number VARCHAR(20),
- ADD COLUMN IF NOT EXISTS train_name VARCHAR(100),
- ADD COLUMN IF NOT EXISTS route_id UUID,
- ADD COLUMN IF NOT EXISTS origin_station_id UUID,
- ADD COLUMN IF NOT EXISTS destination_station_id UUID,
- ADD COLUMN IF NOT EXISTS departure_time TIMESTAMPTZ,
- ADD COLUMN IF NOT EXISTS arrival_time TIMESTAMPTZ,
- ADD COLUMN IF NOT EXISTS locomotive_number VARCHAR(50),
- ADD COLUMN IF NOT EXISTS remarks TEXT;
- `);
-
- await queryRunner.query(`
- CREATE UNIQUE INDEX IF NOT EXISTS "UQ_trains_train_number"
- ON freight.trains (train_number)
- WHERE train_number IS NOT NULL;
- `);
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`DROP INDEX IF EXISTS freight."UQ_trains_train_number"`);
- await queryRunner.query(`
- ALTER TABLE freight.trains
- DROP COLUMN IF EXISTS remarks,
- DROP COLUMN IF EXISTS locomotive_number,
- DROP COLUMN IF EXISTS arrival_time,
- DROP COLUMN IF EXISTS departure_time,
- DROP COLUMN IF EXISTS destination_station_id,
- DROP COLUMN IF EXISTS origin_station_id,
- DROP COLUMN IF EXISTS route_id,
- DROP COLUMN IF EXISTS train_name,
- DROP COLUMN IF EXISTS train_number;
- `);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1750000000000-CreateFacilitiesTable.ts b/apps/edr-freight-api/src/migrations/1750000000000-CreateFacilitiesTable.ts
deleted file mode 100644
index 32e1ff653..000000000
--- a/apps/edr-freight-api/src/migrations/1750000000000-CreateFacilitiesTable.ts
+++ /dev/null
@@ -1,138 +0,0 @@
-import { MigrationInterface, QueryRunner, Table, TableIndex } from 'typeorm';
-
-export class CreateFacilitiesTable1750000000000 implements MigrationInterface {
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.createTable(
- new Table({
- schema: 'freight',
- name: 'facilities',
- columns: [
- {
- name: 'id',
- type: 'uuid',
- isPrimary: true,
- generationStrategy: 'uuid',
- default: 'gen_random_uuid()',
- },
- {
- name: 'code',
- type: 'varchar',
- length: '40',
- isUnique: true,
- },
- {
- name: 'name',
- type: 'varchar',
- length: '160',
- },
- {
- name: 'description',
- type: 'text',
- isNullable: true,
- },
- {
- name: 'facility_type',
- type: 'varchar',
- length: '32',
- },
- {
- name: 'facility_status',
- type: 'varchar',
- length: '32',
- default: "'ACTIVE'",
- },
- {
- name: 'location_name',
- type: 'varchar',
- length: '200',
- isNullable: true,
- },
- {
- name: 'country',
- type: 'varchar',
- length: '100',
- isNullable: true,
- },
- {
- name: 'city',
- type: 'varchar',
- length: '100',
- isNullable: true,
- },
- {
- name: 'address',
- type: 'text',
- isNullable: true,
- },
- {
- name: 'latitude',
- type: 'numeric',
- precision: 10,
- scale: 8,
- isNullable: true,
- },
- {
- name: 'longitude',
- type: 'numeric',
- precision: 11,
- scale: 8,
- isNullable: true,
- },
- {
- name: 'capacity',
- type: 'numeric',
- precision: 14,
- scale: 3,
- isNullable: true,
- },
- {
- name: 'is_active',
- type: 'boolean',
- default: true,
- },
- {
- name: 'notes',
- type: 'text',
- isNullable: true,
- },
- {
- name: 'created_at',
- type: 'timestamp',
- default: 'CURRENT_TIMESTAMP',
- },
- {
- name: 'updated_at',
- type: 'timestamp',
- default: 'CURRENT_TIMESTAMP',
- },
- {
- name: 'deleted_at',
- type: 'timestamp',
- isNullable: true,
- },
- ],
- }),
- );
-
- await queryRunner.createIndex(
- 'freight.facilities',
- new TableIndex({
- name: 'idx_facilities_code',
- columnNames: ['code'],
- isUnique: true,
- }),
- );
-
- await queryRunner.createIndex(
- 'freight.facilities',
- new TableIndex({
- name: 'idx_facilities_status',
- columnNames: ['facility_status'],
- }),
- );
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.dropTable('freight.facilities');
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1750000000001-AddFacilityIdToWarehouses.ts b/apps/edr-freight-api/src/migrations/1750000000001-AddFacilityIdToWarehouses.ts
deleted file mode 100644
index 82f2d92fd..000000000
--- a/apps/edr-freight-api/src/migrations/1750000000001-AddFacilityIdToWarehouses.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-import { MigrationInterface, QueryRunner, TableColumn, TableForeignKey } from 'typeorm';
-
-export class AddFacilityIdToWarehouses1750000000001 implements MigrationInterface {
- public async up(queryRunner: QueryRunner): Promise {
- const table = await queryRunner.getTable('freight.warehouses');
- if (!table) {
- // warehouses table doesn't exist yet, skip this migration
- return;
- }
-
- const hasColumn = table.columns.some((col) => col.name === 'facility_id');
- if (hasColumn) {
- // Column already exists, skip
- return;
- }
-
- await queryRunner.addColumn(
- 'freight.warehouses',
- new TableColumn({
- name: 'facility_id',
- type: 'uuid',
- isNullable: true,
- }),
- );
-
- await queryRunner.createForeignKey(
- 'freight.warehouses',
- new TableForeignKey({
- columnNames: ['facility_id'],
- referencedColumnNames: ['id'],
- referencedTableName: 'facilities',
- referencedSchema: 'freight',
- onDelete: 'SET NULL',
- }),
- );
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- const table = await queryRunner.getTable('freight.warehouses');
- if (!table) {
- return;
- }
- const foreignKey = table.foreignKeys.find((fk) => fk.columnNames.includes('facility_id'));
- if (foreignKey) {
- await queryRunner.dropForeignKey('freight.warehouses', foreignKey);
- }
- const hasColumn = table.columns.some((col) => col.name === 'facility_id');
- if (hasColumn) {
- await queryRunner.dropColumn('freight.warehouses', 'facility_id');
- }
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1750000000002-AddProofOfDeliveryToCargoes.ts b/apps/edr-freight-api/src/migrations/1750000000002-AddProofOfDeliveryToCargoes.ts
deleted file mode 100644
index 67957f437..000000000
--- a/apps/edr-freight-api/src/migrations/1750000000002-AddProofOfDeliveryToCargoes.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-import { MigrationInterface, QueryRunner, TableColumn } from 'typeorm';
-
-/**
- * Proof of Delivery (customer pickup) capture on cargoes:
- * receiver name, delivered/picked-up timestamp, and delivery remarks.
- */
-export class AddProofOfDeliveryToCargoes1750000000002 implements MigrationInterface {
- public async up(queryRunner: QueryRunner): Promise {
- const table = await queryRunner.getTable('freight.cargoes');
- if (!table) {
- // cargoes table doesn't exist yet, skip this migration
- return;
- }
-
- const columnsToAdd = [
- { name: 'receiver_name', type: 'varchar', isNullable: true },
- { name: 'delivered_at', type: 'timestamp', isNullable: true },
- { name: 'delivery_remarks', type: 'text', isNullable: true },
- ];
-
- const columnsToCreate = columnsToAdd.filter(
- (col) => !table.columns.some((c) => c.name === col.name),
- );
-
- if (columnsToCreate.length > 0) {
- await queryRunner.addColumns(
- 'freight.cargoes',
- columnsToCreate.map((col) => new TableColumn(col)),
- );
- }
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- const table = await queryRunner.getTable('freight.cargoes');
- if (!table) {
- return;
- }
-
- const columnNames = ['receiver_name', 'delivered_at', 'delivery_remarks'];
- const columnsToRemove = columnNames.filter((name) =>
- table.columns.some((c) => c.name === name),
- );
-
- if (columnsToRemove.length > 0) {
- await queryRunner.dropColumns('freight.cargoes', columnsToRemove);
- }
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1750000000003-AddWarehouseInspection.ts b/apps/edr-freight-api/src/migrations/1750000000003-AddWarehouseInspection.ts
deleted file mode 100644
index 88ef8d3a5..000000000
--- a/apps/edr-freight-api/src/migrations/1750000000003-AddWarehouseInspection.ts
+++ /dev/null
@@ -1,75 +0,0 @@
-import { MigrationInterface, QueryRunner, Table, TableColumn } from 'typeorm';
-
-/**
- * Batch 4.5 — warehouse inspection reports + inventory inspection status.
- */
-export class AddWarehouseInspection1750000000003 implements MigrationInterface {
- public async up(queryRunner: QueryRunner): Promise {
- // inventory.inspection_status
- const inventoryTable = await queryRunner.getTable('freight.warehouse_inventory');
- if (inventoryTable) {
- const hasColumn = inventoryTable.columns.some((col) => col.name === 'inspection_status');
- if (!hasColumn) {
- await queryRunner.addColumn(
- 'freight.warehouse_inventory',
- new TableColumn({ name: 'inspection_status', type: 'varchar', length: '20', isNullable: true }),
- );
- }
- }
-
- // warehouse_inspection_reports table
- const inspectionTable = await queryRunner.getTable('freight.warehouse_inspection_reports');
- if (!inspectionTable) {
- await queryRunner.createTable(
- new Table({
- schema: 'freight',
- name: 'warehouse_inspection_reports',
- columns: [
- { name: 'id', type: 'uuid', isPrimary: true, default: 'uuid_generate_v4()' },
- { name: 'inventory_id', type: 'uuid' },
- { name: 'booking_id', type: 'uuid', isNullable: true },
- { name: 'customer_id', type: 'uuid', isNullable: true },
- { name: 'report_type', type: 'varchar', length: '32', default: "'INSPECTION'" },
- { name: 'inspection_status', type: 'varchar', length: '20', default: "'NEEDS_REVIEW'" },
- { name: 'has_damage', type: 'boolean', default: false },
- { name: 'damage_description', type: 'text', isNullable: true },
- { name: 'has_weight_loss', type: 'boolean', default: false },
- { name: 'expected_weight', type: 'numeric', precision: 14, scale: 3, isNullable: true },
- { name: 'actual_weight', type: 'numeric', precision: 14, scale: 3, isNullable: true },
- { name: 'weight_loss', type: 'numeric', precision: 14, scale: 3, isNullable: true },
- { name: 'weight_loss_unit', type: 'varchar', length: '12', isNullable: true },
- { name: 'has_missing_items', type: 'boolean', default: false },
- { name: 'missing_items_description', type: 'text', isNullable: true },
- { name: 'remarks', type: 'text', isNullable: true },
- { name: 'inspected_by_id', type: 'uuid', isNullable: true },
- { name: 'inspected_at', type: 'timestamptz', isNullable: true },
- { name: 'created_at', type: 'timestamptz', default: 'now()' },
- { name: 'updated_at', type: 'timestamptz', default: 'now()' },
- { name: 'deleted_at', type: 'timestamptz', isNullable: true },
- ],
- indices: [
- { name: 'idx_wir_inventory', columnNames: ['inventory_id'] },
- { name: 'idx_wir_booking', columnNames: ['booking_id'] },
- { name: 'idx_wir_status', columnNames: ['inspection_status'] },
- ],
- }),
- true,
- );
- }
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- const inspectionTable = await queryRunner.getTable('freight.warehouse_inspection_reports');
- if (inspectionTable) {
- await queryRunner.dropTable('freight.warehouse_inspection_reports', true);
- }
-
- const inventoryTable = await queryRunner.getTable('freight.warehouse_inventory');
- if (inventoryTable) {
- const hasColumn = inventoryTable.columns.some((col) => col.name === 'inspection_status');
- if (hasColumn) {
- await queryRunner.dropColumn('freight.warehouse_inventory', 'inspection_status');
- }
- }
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1750100000000-AddRoutesAndExtendLocomotives.ts b/apps/edr-freight-api/src/migrations/1750100000000-AddRoutesAndExtendLocomotives.ts
deleted file mode 100644
index 421f66ef9..000000000
--- a/apps/edr-freight-api/src/migrations/1750100000000-AddRoutesAndExtendLocomotives.ts
+++ /dev/null
@@ -1,87 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-export class AddRoutesAndExtendLocomotives1750100000000 implements MigrationInterface {
- name = 'AddRoutesAndExtendLocomotives1750100000000';
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- ALTER TABLE freight.locomotives
- ADD COLUMN IF NOT EXISTS locomotive_type VARCHAR(20) NOT NULL DEFAULT 'DIESEL',
- ADD COLUMN IF NOT EXISTS max_train_length_meters NUMERIC(10,3) NOT NULL DEFAULT 760,
- ADD COLUMN IF NOT EXISTS power_kw NUMERIC(10,3) NULL,
- ADD COLUMN IF NOT EXISTS traction_force_kn NUMERIC(10,3) NULL,
- ADD COLUMN IF NOT EXISTS max_speed_kmh NUMERIC(10,3) NULL;
- `);
-
- await queryRunner.query(`
- UPDATE freight.locomotives
- SET status = 'OUT_OF_SERVICE'
- WHERE status = 'INACTIVE';
- `);
-
- await queryRunner.query(`
- CREATE TABLE IF NOT EXISTS freight.routes (
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
- name VARCHAR(120) NOT NULL UNIQUE,
- origin_yard_id UUID NOT NULL REFERENCES freight.yards(id),
- destination_yard_id UUID NOT NULL REFERENCES freight.yards(id),
- is_active BOOLEAN NOT NULL DEFAULT true,
- created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
- updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
- deleted_at TIMESTAMPTZ NULL
- );
- `);
-
- await queryRunner.query(`
- CREATE TABLE IF NOT EXISTS freight.route_milestones (
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
- route_id UUID NOT NULL REFERENCES freight.routes(id) ON DELETE CASCADE,
- yard_id UUID NOT NULL REFERENCES freight.yards(id),
- sequence_no INT NOT NULL,
- created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
- updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
- deleted_at TIMESTAMPTZ NULL,
- CONSTRAINT uq_route_milestones_route_sequence UNIQUE (route_id, sequence_no)
- );
- `);
-
- await queryRunner.query(`
- CREATE INDEX IF NOT EXISTS idx_routes_origin_yard_id
- ON freight.routes(origin_yard_id);
- `);
- await queryRunner.query(`
- CREATE INDEX IF NOT EXISTS idx_routes_destination_yard_id
- ON freight.routes(destination_yard_id);
- `);
- await queryRunner.query(`
- CREATE INDEX IF NOT EXISTS idx_routes_is_active
- ON freight.routes(is_active);
- `);
- await queryRunner.query(`
- CREATE INDEX IF NOT EXISTS idx_route_milestones_route_id
- ON freight.route_milestones(route_id);
- `);
- await queryRunner.query(`
- CREATE INDEX IF NOT EXISTS idx_route_milestones_yard_id
- ON freight.route_milestones(yard_id);
- `);
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`DROP TABLE IF EXISTS freight.route_milestones;`);
- await queryRunner.query(`DROP TABLE IF EXISTS freight.routes;`);
- await queryRunner.query(`
- ALTER TABLE freight.locomotives
- DROP COLUMN IF EXISTS max_speed_kmh,
- DROP COLUMN IF EXISTS traction_force_kn,
- DROP COLUMN IF EXISTS power_kw,
- DROP COLUMN IF EXISTS max_train_length_meters,
- DROP COLUMN IF EXISTS locomotive_type;
- `);
- await queryRunner.query(`
- UPDATE freight.locomotives
- SET status = 'INACTIVE'
- WHERE status = 'OUT_OF_SERVICE';
- `);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1750100000000-CreateFleetCrudTables.ts b/apps/edr-freight-api/src/migrations/1750100000000-CreateFleetCrudTables.ts
deleted file mode 100644
index 1763a9db0..000000000
--- a/apps/edr-freight-api/src/migrations/1750100000000-CreateFleetCrudTables.ts
+++ /dev/null
@@ -1,127 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-export class CreateFleetCrudTables1750100000000 implements MigrationInterface {
- name = 'CreateFleetCrudTables1750100000000';
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`CREATE SCHEMA IF NOT EXISTS freight`);
-
- await queryRunner.query(`
- CREATE TABLE IF NOT EXISTS freight.wagons (
- id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
- wagon_number VARCHAR NOT NULL UNIQUE,
- wagon_type_id UUID NOT NULL,
- train_id UUID,
- sequence_number INT,
- tare_weight NUMERIC(10, 2) NOT NULL,
- max_payload_weight NUMERIC(10, 2) NOT NULL,
- status VARCHAR NOT NULL DEFAULT 'AVAILABLE',
- notes TEXT,
- created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
- updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
- deleted_at TIMESTAMPTZ
- );
- `);
-
- await queryRunner.query(`
- CREATE TABLE IF NOT EXISTS freight.containers (
- id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
- container_number VARCHAR NOT NULL UNIQUE,
- container_type_id UUID NOT NULL,
- wagon_id UUID,
- position INT,
- tare_weight NUMERIC(10, 2) NOT NULL,
- max_gross_weight NUMERIC(10, 2) NOT NULL,
- seal_number VARCHAR,
- status VARCHAR NOT NULL DEFAULT 'AVAILABLE',
- created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
- updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
- deleted_at TIMESTAMPTZ
- );
- `);
-
- await queryRunner.query(`
- CREATE TABLE IF NOT EXISTS freight.cargoes (
- id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
- cargo_reference VARCHAR NOT NULL UNIQUE,
- shipment_id UUID NOT NULL,
- container_id UUID NOT NULL,
- cargo_type_id UUID,
- description TEXT,
- quantity NUMERIC(12, 3) NOT NULL,
- weight NUMERIC(10, 2) NOT NULL,
- volume NUMERIC(10, 2),
- status VARCHAR NOT NULL DEFAULT 'PENDING',
- loaded_at TIMESTAMP,
- unloaded_at TIMESTAMP,
- 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_wagons_train_id" ON freight.wagons (train_id)`);
- await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_containers_wagon_id" ON freight.containers (wagon_id)`);
- await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_cargoes_container_id" ON freight.cargoes (container_id)`);
-
- await queryRunner.query(`
- DO $$ BEGIN
- ALTER TABLE freight.wagons
- ADD CONSTRAINT "FK_wagons_train_id"
- FOREIGN KEY (train_id) REFERENCES freight.trains(id) ON DELETE SET NULL;
- EXCEPTION WHEN duplicate_object THEN NULL;
- END $$;
- `);
-
- await queryRunner.query(`
- DO $$ BEGIN
- ALTER TABLE freight.wagons
- ADD CONSTRAINT "FK_wagons_wagon_type_id"
- FOREIGN KEY (wagon_type_id) REFERENCES freight.wagon_types(id);
- EXCEPTION WHEN duplicate_object THEN NULL;
- END $$;
- `);
-
- await queryRunner.query(`
- DO $$ BEGIN
- ALTER TABLE freight.containers
- ADD CONSTRAINT "FK_containers_wagon_id"
- FOREIGN KEY (wagon_id) REFERENCES freight.wagons(id) ON DELETE SET NULL;
- EXCEPTION WHEN duplicate_object THEN NULL;
- END $$;
- `);
-
- await queryRunner.query(`
- DO $$ BEGIN
- ALTER TABLE freight.containers
- ADD CONSTRAINT "FK_containers_container_type_id"
- FOREIGN KEY (container_type_id) REFERENCES freight.container_types(id);
- EXCEPTION WHEN duplicate_object THEN NULL;
- END $$;
- `);
-
- await queryRunner.query(`
- DO $$ BEGIN
- ALTER TABLE freight.cargoes
- ADD CONSTRAINT "FK_cargoes_container_id"
- FOREIGN KEY (container_id) REFERENCES freight.containers(id) ON DELETE RESTRICT;
- EXCEPTION WHEN duplicate_object THEN NULL;
- END $$;
- `);
-
- await queryRunner.query(`
- DO $$ BEGIN
- ALTER TABLE freight.cargoes
- ADD CONSTRAINT "FK_cargoes_cargo_type_id"
- FOREIGN KEY (cargo_type_id) REFERENCES freight.cargo_types(id);
- EXCEPTION WHEN duplicate_object THEN NULL;
- END $$;
- `);
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`DROP TABLE IF EXISTS freight.cargoes CASCADE`);
- await queryRunner.query(`DROP TABLE IF EXISTS freight.containers CASCADE`);
- await queryRunner.query(`DROP TABLE IF EXISTS freight.wagons CASCADE`);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1750200000000-AddPhysicalWagonToTrainSetWagons.ts b/apps/edr-freight-api/src/migrations/1750200000000-AddPhysicalWagonToTrainSetWagons.ts
deleted file mode 100644
index 9ce1db6c1..000000000
--- a/apps/edr-freight-api/src/migrations/1750200000000-AddPhysicalWagonToTrainSetWagons.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-export class AddPhysicalWagonToTrainSetWagons1750200000000 implements MigrationInterface {
- name = 'AddPhysicalWagonToTrainSetWagons1750200000000';
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- ALTER TABLE freight.train_set_wagons
- ADD COLUMN IF NOT EXISTS physical_wagon_id UUID NULL;
- `);
-
- await queryRunner.query(`
- DO $$
- BEGIN
- IF NOT EXISTS (
- SELECT 1
- FROM information_schema.table_constraints
- WHERE constraint_schema = 'freight'
- AND table_name = 'train_set_wagons'
- AND constraint_name = 'fk_train_set_wagons_physical_wagon'
- ) THEN
- ALTER TABLE freight.train_set_wagons
- ADD CONSTRAINT fk_train_set_wagons_physical_wagon
- FOREIGN KEY (physical_wagon_id)
- REFERENCES freight.wagons(id)
- ON DELETE SET NULL;
- END IF;
- END $$;
- `);
-
- await queryRunner.query(`
- CREATE INDEX IF NOT EXISTS idx_train_set_wagons_physical_wagon
- ON freight.train_set_wagons(physical_wagon_id);
- `);
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`DROP INDEX IF EXISTS freight.idx_train_set_wagons_physical_wagon;`);
- await queryRunner.query(`
- ALTER TABLE freight.train_set_wagons
- DROP CONSTRAINT IF EXISTS fk_train_set_wagons_physical_wagon;
- `);
- await queryRunner.query(`
- ALTER TABLE freight.train_set_wagons
- DROP COLUMN IF EXISTS physical_wagon_id;
- `);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1750200000000-SeedDefaultWagonTypes.ts b/apps/edr-freight-api/src/migrations/1750200000000-SeedDefaultWagonTypes.ts
deleted file mode 100644
index 06dc0b917..000000000
--- a/apps/edr-freight-api/src/migrations/1750200000000-SeedDefaultWagonTypes.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-export class SeedDefaultWagonTypes1750200000000 implements MigrationInterface {
- name = 'SeedDefaultWagonTypes1750200000000';
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- INSERT INTO freight.wagon_types (
- code,
- name,
- capacity_tons,
- length_meters,
- max_wagons_per_train,
- supported_load_types,
- is_active
- )
- VALUES
- ('NW7', 'Double deck sedan wagon', 22, 26.066, NULL, ARRAY['vehicles', 'sedan'], true),
- ('NW5', 'Flat wagon (container)', 70, 14.000, 53, ARRAY['container', 'steel', 'machinery'], true),
- ('PW2', 'Box wagon', 70, 17.066, 18, ARRAY['general cargo', 'break bulk'], true),
- ('GW2', 'Tank wagon', 70, 12.228, 37, ARRAY['liquid', 'fuel'], true),
- ('CW4', 'Gondola covered wagon', 70, 13.976, 37, ARRAY['covered bulk cargo'], true),
- ('CW3', 'Gondola open wagon', 70, 13.976, NULL, ARRAY['open bulk cargo'], true),
- ('KW2', 'Hopper covered wagon', 69, 16.466, NULL, ARRAY['bulk grains'], true),
- ('KW3', 'Hopper open wagon', 70, 14.400, NULL, ARRAY['coal', 'bulk cargo'], true),
- ('NW6', 'Flat wagon (long cargo)', 70, 18.560, NULL, ARRAY['long cargo'], true),
- ('BW1', 'Refrigerated wagon', 38, 21.996, NULL, ARRAY['refrigerated cargo'], true)
- ON CONFLICT (code) DO UPDATE SET
- name = EXCLUDED.name,
- capacity_tons = EXCLUDED.capacity_tons,
- length_meters = EXCLUDED.length_meters,
- max_wagons_per_train = EXCLUDED.max_wagons_per_train,
- supported_load_types = EXCLUDED.supported_load_types,
- is_active = true,
- deleted_at = NULL,
- updated_at = now();
- `);
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- DELETE FROM freight.wagon_types
- WHERE code IN ('NW7', 'NW5', 'PW2', 'GW2', 'CW4', 'CW3', 'KW2', 'KW3', 'NW6', 'BW1');
- `);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1750300000000-AddCurrentLocationToWagons.ts b/apps/edr-freight-api/src/migrations/1750300000000-AddCurrentLocationToWagons.ts
deleted file mode 100644
index 454218fd7..000000000
--- a/apps/edr-freight-api/src/migrations/1750300000000-AddCurrentLocationToWagons.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-export class AddCurrentLocationToWagons1750300000000 implements MigrationInterface {
- name = 'AddCurrentLocationToWagons1750300000000';
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- ALTER TABLE freight.wagons
- ADD COLUMN IF NOT EXISTS current_location_yard_id UUID NULL;
- `);
-
- await queryRunner.query(`
- DO $$
- BEGIN
- IF NOT EXISTS (
- SELECT 1
- FROM information_schema.table_constraints
- WHERE constraint_schema = 'freight'
- AND table_name = 'wagons'
- AND constraint_name = 'FK_wagons_current_location_yard_id'
- ) THEN
- ALTER TABLE freight.wagons
- ADD CONSTRAINT "FK_wagons_current_location_yard_id"
- FOREIGN KEY (current_location_yard_id)
- REFERENCES freight.yards(id)
- ON DELETE SET NULL;
- END IF;
- END $$;
- `);
-
- await queryRunner.query(`
- CREATE INDEX IF NOT EXISTS "IDX_wagons_current_location_yard_id"
- ON freight.wagons(current_location_yard_id);
- `);
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`DROP INDEX IF EXISTS freight."IDX_wagons_current_location_yard_id";`);
- await queryRunner.query(`
- ALTER TABLE freight.wagons
- DROP CONSTRAINT IF EXISTS "FK_wagons_current_location_yard_id";
- `);
- await queryRunner.query(`
- ALTER TABLE freight.wagons
- DROP COLUMN IF EXISTS current_location_yard_id;
- `);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1750300000000-AddRouteToTrainSchedules.ts b/apps/edr-freight-api/src/migrations/1750300000000-AddRouteToTrainSchedules.ts
deleted file mode 100644
index 027ebfe98..000000000
--- a/apps/edr-freight-api/src/migrations/1750300000000-AddRouteToTrainSchedules.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-export class AddRouteToTrainSchedules1750300000000 implements MigrationInterface {
- name = 'AddRouteToTrainSchedules1750300000000';
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- ALTER TABLE freight.train_schedules
- ADD COLUMN IF NOT EXISTS route_id UUID NULL;
- `);
-
- await queryRunner.query(`
- DO $$
- BEGIN
- IF NOT EXISTS (
- SELECT 1
- FROM pg_constraint
- WHERE conname = 'fk_train_schedules_route'
- ) THEN
- ALTER TABLE freight.train_schedules
- ADD CONSTRAINT fk_train_schedules_route
- FOREIGN KEY (route_id) REFERENCES freight.routes(id);
- END IF;
- END $$;
- `);
-
- await queryRunner.query(`
- CREATE INDEX IF NOT EXISTS idx_train_schedules_route_id
- ON freight.train_schedules(route_id);
- `);
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`DROP INDEX IF EXISTS freight.idx_train_schedules_route_id;`);
- await queryRunner.query(`
- ALTER TABLE freight.train_schedules
- DROP CONSTRAINT IF EXISTS fk_train_schedules_route;
- `);
- await queryRunner.query(`
- ALTER TABLE freight.train_schedules
- DROP COLUMN IF EXISTS route_id;
- `);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1750400000000-AddSchedulingAllocationEnhancements.ts b/apps/edr-freight-api/src/migrations/1750400000000-AddSchedulingAllocationEnhancements.ts
deleted file mode 100644
index a0ca64303..000000000
--- a/apps/edr-freight-api/src/migrations/1750400000000-AddSchedulingAllocationEnhancements.ts
+++ /dev/null
@@ -1,321 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-export class AddSchedulingAllocationEnhancements1750400000000
- implements MigrationInterface
-{
- name = 'AddSchedulingAllocationEnhancements1750400000000';
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- ALTER TABLE freight.bookings
- ADD COLUMN IF NOT EXISTS wagons_required NUMERIC(6,2) NULL,
- ADD COLUMN IF NOT EXISTS scheduling_status VARCHAR(30) NOT NULL DEFAULT 'NOT_SCHEDULED',
- ADD COLUMN IF NOT EXISTS hold_started_at TIMESTAMPTZ NULL,
- ADD COLUMN IF NOT EXISTS hold_expires_at TIMESTAMPTZ NULL,
- ADD COLUMN IF NOT EXISTS scheduled_at TIMESTAMPTZ NULL;
- `);
-
- await queryRunner.query(`
- ALTER TABLE freight.train_schedules
- ADD COLUMN IF NOT EXISTS train_number VARCHAR(20) NULL,
- ADD COLUMN IF NOT EXISTS direction VARCHAR(10) NULL,
- ADD COLUMN IF NOT EXISTS actual_departure_at TIMESTAMPTZ NULL,
- ADD COLUMN IF NOT EXISTS actual_arrival_at TIMESTAMPTZ NULL,
- ADD COLUMN IF NOT EXISTS prepared_by_user_id UUID NULL,
- ADD COLUMN IF NOT EXISTS checked_by_user_id UUID NULL,
- ADD COLUMN IF NOT EXISTS max_wagons INT NOT NULL DEFAULT 53;
- `);
-
- await queryRunner.query(`
- ALTER TABLE freight.train_set_wagons
- ADD COLUMN IF NOT EXISTS physical_wagon_id UUID NULL,
- ADD COLUMN IF NOT EXISTS status VARCHAR(20) NOT NULL DEFAULT 'PLANNED';
- `);
-
- await queryRunner.query(`
- ALTER TABLE freight.wagon_booking_allocations
- ADD COLUMN IF NOT EXISTS load_type VARCHAR(20) NULL,
- ADD COLUMN IF NOT EXISTS status VARCHAR(20) NOT NULL DEFAULT 'PLANNED',
- ADD COLUMN IF NOT EXISTS confirmed_at TIMESTAMPTZ NULL,
- ADD COLUMN IF NOT EXISTS confirmed_by_user_id UUID NULL;
- `);
-
- await queryRunner.query(`
- ALTER TABLE freight.wagon_types
- ADD COLUMN IF NOT EXISTS equated_length_m NUMERIC(10,3) NULL,
- ADD COLUMN IF NOT EXISTS tare_weight_tons NUMERIC(10,3) NULL,
- ADD COLUMN IF NOT EXISTS supports_container BOOLEAN NOT NULL DEFAULT false,
- ADD COLUMN IF NOT EXISTS max_container_gross_t NUMERIC(10,3) NULL;
- `);
-
- await queryRunner.query(`
- ALTER TABLE freight.wagons
- ADD COLUMN IF NOT EXISTS train_set_wagon_id UUID NULL,
- ADD COLUMN IF NOT EXISTS current_train_schedule_id UUID NULL;
- `);
-
- await queryRunner.query(`
- ALTER TABLE freight.containers
- ADD COLUMN IF NOT EXISTS booking_id UUID NULL,
- ADD COLUMN IF NOT EXISTS wagon_booking_allocation_id UUID NULL,
- ADD COLUMN IF NOT EXISTS booking_container_id UUID NULL;
- `);
-
- await queryRunner.query(`
- ALTER TABLE freight.cargoes
- ADD COLUMN IF NOT EXISTS wagon_booking_allocation_id UUID NULL,
- ADD COLUMN IF NOT EXISTS booking_id UUID NULL,
- ADD COLUMN IF NOT EXISTS load_type VARCHAR(20) NULL;
- `);
-
- await queryRunner.query(`
- ALTER TABLE freight.cargoes
- ALTER COLUMN container_id DROP NOT NULL;
- `);
-
- await queryRunner.query(`
- CREATE TABLE IF NOT EXISTS freight.wagon_allocation_container_items (
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
- wagon_booking_allocation_id UUID NOT NULL,
- booking_container_id UUID NULL,
- container_id UUID NULL,
- container_number VARCHAR(64) NULL,
- container_type_id UUID NOT NULL,
- position_on_wagon SMALLINT NULL,
- seal_number VARCHAR(64) NULL,
- chassis_number VARCHAR(64) NULL,
- gross_weight_tons NUMERIC(10,3) NULL,
- created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
- updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
- deleted_at TIMESTAMPTZ NULL,
- CONSTRAINT fk_waci_allocation FOREIGN KEY (wagon_booking_allocation_id)
- REFERENCES freight.wagon_booking_allocations(id) ON DELETE CASCADE,
- CONSTRAINT fk_waci_booking_container FOREIGN KEY (booking_container_id)
- REFERENCES freight.booking_container(id) ON DELETE SET NULL,
- CONSTRAINT fk_waci_container FOREIGN KEY (container_id)
- REFERENCES freight.containers(id) ON DELETE SET NULL,
- CONSTRAINT fk_waci_container_type FOREIGN KEY (container_type_id)
- REFERENCES freight.container_types(id)
- );
- `);
-
- await queryRunner.query(`
- CREATE TABLE IF NOT EXISTS freight.wagon_allocation_bulk_loads (
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
- wagon_booking_allocation_id UUID NOT NULL UNIQUE,
- booking_id UUID NOT NULL,
- cargo_type_id UUID NULL,
- cargo_description TEXT NULL,
- pricing_unit VARCHAR(20) NOT NULL DEFAULT 'PER_TON',
- quantity NUMERIC(12,3) NOT NULL DEFAULT 0,
- weight_tons NUMERIC(10,3) NOT NULL DEFAULT 0,
- truck_plate_number VARCHAR(32) NULL,
- created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
- updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
- deleted_at TIMESTAMPTZ NULL,
- CONSTRAINT fk_wabl_allocation FOREIGN KEY (wagon_booking_allocation_id)
- REFERENCES freight.wagon_booking_allocations(id) ON DELETE CASCADE,
- CONSTRAINT fk_wabl_booking FOREIGN KEY (booking_id)
- REFERENCES freight.bookings(id),
- CONSTRAINT fk_wabl_cargo_type FOREIGN KEY (cargo_type_id)
- REFERENCES freight.cargo_types(id) ON DELETE SET NULL
- );
- `);
-
- await queryRunner.query(`
- CREATE INDEX IF NOT EXISTS idx_bookings_scheduling_status
- ON freight.bookings(scheduling_status);
- `);
- await queryRunner.query(`
- CREATE INDEX IF NOT EXISTS idx_train_schedules_train_number
- ON freight.train_schedules(train_number);
- `);
- await queryRunner.query(`
- CREATE INDEX IF NOT EXISTS idx_train_set_wagons_physical_wagon
- ON freight.train_set_wagons(physical_wagon_id);
- `);
- await queryRunner.query(`
- CREATE INDEX IF NOT EXISTS idx_wagons_train_set_wagon_id
- ON freight.wagons(train_set_wagon_id);
- `);
- await queryRunner.query(`
- CREATE INDEX IF NOT EXISTS idx_wagons_current_train_schedule_id
- ON freight.wagons(current_train_schedule_id);
- `);
- await queryRunner.query(`
- CREATE INDEX IF NOT EXISTS idx_waci_allocation
- ON freight.wagon_allocation_container_items(wagon_booking_allocation_id);
- `);
- await queryRunner.query(`
- CREATE INDEX IF NOT EXISTS idx_wabl_booking
- ON freight.wagon_allocation_bulk_loads(booking_id);
- `);
-
- await queryRunner.query(`
- DO $$ BEGIN
- ALTER TABLE freight.train_set_wagons
- ADD CONSTRAINT fk_train_set_wagons_physical_wagon
- FOREIGN KEY (physical_wagon_id) REFERENCES freight.wagons(id) ON DELETE SET NULL;
- EXCEPTION WHEN duplicate_object THEN NULL;
- END $$;
- `);
- await queryRunner.query(`
- DO $$ BEGIN
- ALTER TABLE freight.wagons
- ADD CONSTRAINT fk_wagons_train_set_wagon
- FOREIGN KEY (train_set_wagon_id) REFERENCES freight.train_set_wagons(id) ON DELETE SET NULL;
- EXCEPTION WHEN duplicate_object THEN NULL;
- END $$;
- `);
- await queryRunner.query(`
- DO $$ BEGIN
- ALTER TABLE freight.wagons
- ADD CONSTRAINT fk_wagons_current_train_schedule
- FOREIGN KEY (current_train_schedule_id) REFERENCES freight.train_schedules(id) ON DELETE SET NULL;
- EXCEPTION WHEN duplicate_object THEN NULL;
- END $$;
- `);
- await queryRunner.query(`
- DO $$ BEGIN
- ALTER TABLE freight.containers
- ADD CONSTRAINT fk_containers_booking
- FOREIGN KEY (booking_id) REFERENCES freight.bookings(id) ON DELETE SET NULL;
- EXCEPTION WHEN duplicate_object THEN NULL;
- END $$;
- `);
- await queryRunner.query(`
- DO $$ BEGIN
- ALTER TABLE freight.containers
- ADD CONSTRAINT fk_containers_wagon_allocation
- FOREIGN KEY (wagon_booking_allocation_id) REFERENCES freight.wagon_booking_allocations(id) ON DELETE SET NULL;
- EXCEPTION WHEN duplicate_object THEN NULL;
- END $$;
- `);
- await queryRunner.query(`
- DO $$ BEGIN
- ALTER TABLE freight.containers
- ADD CONSTRAINT fk_containers_booking_container
- FOREIGN KEY (booking_container_id) REFERENCES freight.booking_container(id) ON DELETE SET NULL;
- EXCEPTION WHEN duplicate_object THEN NULL;
- END $$;
- `);
- await queryRunner.query(`
- DO $$ BEGIN
- ALTER TABLE freight.cargoes
- ADD CONSTRAINT fk_cargoes_wagon_allocation
- FOREIGN KEY (wagon_booking_allocation_id) REFERENCES freight.wagon_booking_allocations(id) ON DELETE SET NULL;
- EXCEPTION WHEN duplicate_object THEN NULL;
- END $$;
- `);
- await queryRunner.query(`
- DO $$ BEGIN
- ALTER TABLE freight.cargoes
- ADD CONSTRAINT fk_cargoes_booking
- FOREIGN KEY (booking_id) REFERENCES freight.bookings(id) ON DELETE SET NULL;
- EXCEPTION WHEN duplicate_object THEN NULL;
- END $$;
- `);
-
- await queryRunner.query(`
- UPDATE freight.wagon_types SET
- equated_length_m = 1.3,
- tare_weight_tons = 22.4,
- supports_container = true,
- max_container_gross_t = 30.48
- WHERE code = 'NW5';
- `);
- await queryRunner.query(`
- UPDATE freight.wagon_types SET
- equated_length_m = 1.6,
- tare_weight_tons = 25.2,
- supports_container = false
- WHERE code = 'PW2';
- `);
- await queryRunner.query(`
- UPDATE freight.wagon_types SET
- equated_length_m = 1.5,
- tare_weight_tons = 25.2,
- supports_container = false
- WHERE code = 'KW2';
- `);
- await queryRunner.query(`
- UPDATE freight.wagon_types SET
- equated_length_m = 1.3,
- tare_weight_tons = 23.4,
- supports_container = false
- WHERE code = 'CW3';
- `);
- await queryRunner.query(`
- UPDATE freight.wagon_types SET
- equated_length_m = 1.3,
- tare_weight_tons = 24.8,
- supports_container = false
- WHERE code = 'CW4';
- `);
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`DROP TABLE IF EXISTS freight.wagon_allocation_bulk_loads;`);
- await queryRunner.query(`DROP TABLE IF EXISTS freight.wagon_allocation_container_items;`);
-
- await queryRunner.query(`
- ALTER TABLE freight.cargoes
- ALTER COLUMN container_id SET NOT NULL;
- `);
-
- await queryRunner.query(`
- ALTER TABLE freight.bookings
- DROP COLUMN IF EXISTS wagons_required,
- DROP COLUMN IF EXISTS scheduling_status,
- DROP COLUMN IF EXISTS hold_started_at,
- DROP COLUMN IF EXISTS hold_expires_at,
- DROP COLUMN IF EXISTS scheduled_at;
- `);
- await queryRunner.query(`
- ALTER TABLE freight.train_schedules
- DROP COLUMN IF EXISTS train_number,
- DROP COLUMN IF EXISTS direction,
- DROP COLUMN IF EXISTS actual_departure_at,
- DROP COLUMN IF EXISTS actual_arrival_at,
- DROP COLUMN IF EXISTS prepared_by_user_id,
- DROP COLUMN IF EXISTS checked_by_user_id,
- DROP COLUMN IF EXISTS max_wagons;
- `);
- await queryRunner.query(`
- ALTER TABLE freight.train_set_wagons
- DROP COLUMN IF EXISTS physical_wagon_id,
- DROP COLUMN IF EXISTS status;
- `);
- await queryRunner.query(`
- ALTER TABLE freight.wagon_booking_allocations
- DROP COLUMN IF EXISTS load_type,
- DROP COLUMN IF EXISTS status,
- DROP COLUMN IF EXISTS confirmed_at,
- DROP COLUMN IF EXISTS confirmed_by_user_id;
- `);
- await queryRunner.query(`
- ALTER TABLE freight.wagon_types
- DROP COLUMN IF EXISTS equated_length_m,
- DROP COLUMN IF EXISTS tare_weight_tons,
- DROP COLUMN IF EXISTS supports_container,
- DROP COLUMN IF EXISTS max_container_gross_t;
- `);
- await queryRunner.query(`
- ALTER TABLE freight.wagons
- DROP COLUMN IF EXISTS train_set_wagon_id,
- DROP COLUMN IF EXISTS current_train_schedule_id;
- `);
- await queryRunner.query(`
- ALTER TABLE freight.containers
- DROP COLUMN IF EXISTS booking_id,
- DROP COLUMN IF EXISTS wagon_booking_allocation_id,
- DROP COLUMN IF EXISTS booking_container_id;
- `);
- await queryRunner.query(`
- ALTER TABLE freight.cargoes
- DROP COLUMN IF EXISTS wagon_booking_allocation_id,
- DROP COLUMN IF EXISTS booking_id,
- DROP COLUMN IF EXISTS load_type;
- `);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1750400000000-SeedEdRWagonFleet.ts b/apps/edr-freight-api/src/migrations/1750400000000-SeedEdRWagonFleet.ts
deleted file mode 100644
index 0a4e96276..000000000
--- a/apps/edr-freight-api/src/migrations/1750400000000-SeedEdRWagonFleet.ts
+++ /dev/null
@@ -1,214 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-type FleetRow = {
- code: string;
- name: string;
- count: number;
- start: number;
- end: number;
- capacityTons: number;
- tareWeight: number;
- lengthMeters: number;
- supportedLoadTypes: string[];
-};
-
-const FLEET: FleetRow[] = [
- {
- code: 'PW2',
- name: 'Box wagon',
- count: 220,
- start: 1,
- end: 220,
- capacityTons: 70,
- tareWeight: 25.2,
- lengthMeters: 17.066,
- supportedLoadTypes: ['BULK', 'GENERAL_CARGO', 'BAGGED_CARGO', 'BOXED_CARGO'],
- },
- {
- code: 'CW4',
- name: 'Gondola wagon covered',
- count: 110,
- start: 221,
- end: 330,
- capacityTons: 70,
- tareWeight: 24.8,
- lengthMeters: 13.976,
- supportedLoadTypes: ['CONTAINER'],
- },
- {
- code: 'CW3',
- name: 'Gondola wagon',
- count: 20,
- start: 331,
- end: 350,
- capacityTons: 70,
- tareWeight: 23.4,
- lengthMeters: 13.976,
- supportedLoadTypes: ['BULK', 'COAL', 'ORE'],
- },
- {
- code: 'KW2',
- name: 'Hopper wagon covered',
- count: 20,
- start: 351,
- end: 370,
- capacityTons: 69,
- tareWeight: 25.2,
- lengthMeters: 16.466,
- supportedLoadTypes: ['BULK', 'GRAIN'],
- },
- {
- code: 'KW3',
- name: 'Hopper wagon',
- count: 20,
- start: 371,
- end: 390,
- capacityTons: 70,
- tareWeight: 24,
- lengthMeters: 14.4,
- supportedLoadTypes: ['BULK', 'COAL'],
- },
- {
- code: 'NW5',
- name: 'Flat wagon container',
- count: 550,
- start: 391,
- end: 940,
- capacityTons: 70,
- tareWeight: 0,
- lengthMeters: 14,
- supportedLoadTypes: ['CONTAINER'],
- },
-];
-
-const wagonNumber = (sequence: number) => `ER${String(sequence).padStart(4, '0')}`;
-
-export class SeedEdRWagonFleet1750400000000 implements MigrationInterface {
- name = 'SeedEdRWagonFleet1750400000000';
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- UPDATE freight.wagon_types
- SET name = 'Flat wagon container',
- capacity_tons = 70,
- length_meters = 14.000,
- supported_load_types = ARRAY['CONTAINER'],
- max_wagons_per_train = 53,
- is_active = true,
- deleted_at = NULL,
- updated_at = now()
- WHERE code = 'NW5';
- `);
-
- const [defaultLocation] = await queryRunner.query(`
- SELECT id
- FROM freight.yards
- WHERE code IN ('DJIBOUTI', 'DJIB_PORT', 'NAGAD')
- OR lower(label) LIKE '%djibouti%'
- ORDER BY
- CASE code
- WHEN 'DJIBOUTI' THEN 1
- WHEN 'DJIB_PORT' THEN 2
- WHEN 'NAGAD' THEN 3
- ELSE 4
- END,
- display_order ASC
- LIMIT 1;
- `);
- const defaultLocationYardId = defaultLocation?.id ?? null;
-
- for (const row of FLEET) {
- await queryRunner.query(
- `
- INSERT INTO freight.wagon_types (
- code,
- name,
- capacity_tons,
- length_meters,
- max_wagons_per_train,
- supported_load_types,
- is_active
- )
- VALUES ($1, $2, $3, $4, $5, $6::text[], true)
- ON CONFLICT (code) DO UPDATE SET
- name = EXCLUDED.name,
- capacity_tons = EXCLUDED.capacity_tons,
- length_meters = EXCLUDED.length_meters,
- max_wagons_per_train = EXCLUDED.max_wagons_per_train,
- supported_load_types = EXCLUDED.supported_load_types,
- is_active = true,
- deleted_at = NULL,
- updated_at = now();
- `,
- [
- row.code,
- row.name,
- row.capacityTons,
- row.lengthMeters,
- row.supportedLoadTypes.includes('CONTAINER') ? 53 : 37,
- row.supportedLoadTypes,
- ],
- );
-
- const [typeRecord] = await queryRunner.query(
- `SELECT id FROM freight.wagon_types WHERE code = $1 LIMIT 1;`,
- [row.code],
- );
-
- if (!typeRecord?.id) {
- throw new Error(`wagon_type_seed_failed:${row.code}`);
- }
-
- if (row.end - row.start + 1 !== row.count) {
- throw new Error(`wagon_range_mismatch:${row.code}`);
- }
-
- for (let sequence = row.start; sequence <= row.end; sequence += 1) {
- await queryRunner.query(
- `
- INSERT INTO freight.wagons (
- wagon_number,
- wagon_type_id,
- tare_weight,
- max_payload_weight,
- current_location_yard_id,
- status,
- notes
- )
- VALUES ($1, $2, $3, $4, $5, $6, $7)
- ON CONFLICT (wagon_number) DO UPDATE SET
- wagon_type_id = EXCLUDED.wagon_type_id,
- tare_weight = EXCLUDED.tare_weight,
- max_payload_weight = EXCLUDED.max_payload_weight,
- current_location_yard_id = CASE
- WHEN freight.wagons.train_id IS NULL THEN EXCLUDED.current_location_yard_id
- ELSE freight.wagons.current_location_yard_id
- END,
- status = CASE
- WHEN freight.wagons.train_id IS NULL THEN EXCLUDED.status
- ELSE freight.wagons.status
- END,
- notes = EXCLUDED.notes,
- updated_at = now();
- `,
- [
- wagonNumber(sequence),
- typeRecord.id,
- row.tareWeight,
- row.capacityTons,
- defaultLocationYardId,
- defaultLocationYardId ? 'IMPORT_READY' : 'AVAILABLE',
- `Seeded Ethio-Djibouti Railway ${row.code} fleet record.`,
- ],
- );
- }
- }
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- DELETE FROM freight.wagons
- WHERE wagon_number BETWEEN 'ER0001' AND 'ER0940';
- `);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1750500000000-AddWagonReadiness.ts b/apps/edr-freight-api/src/migrations/1750500000000-AddWagonReadiness.ts
deleted file mode 100644
index 17cc23f9a..000000000
--- a/apps/edr-freight-api/src/migrations/1750500000000-AddWagonReadiness.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-export class AddWagonReadiness1750500000000 implements MigrationInterface {
- name = 'AddWagonReadiness1750500000000';
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- ALTER TABLE freight.wagons
- ADD COLUMN IF NOT EXISTS readiness VARCHAR(20) NOT NULL DEFAULT 'IMPORT_READY'
- `);
- await queryRunner.query(`
- CREATE INDEX IF NOT EXISTS idx_wagons_readiness
- ON freight.wagons (readiness)
- WHERE deleted_at IS NULL
- `);
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`DROP INDEX IF EXISTS freight.idx_wagons_readiness`);
- await queryRunner.query(`
- ALTER TABLE freight.wagons
- DROP COLUMN IF EXISTS readiness
- `);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1750600000000-AddGovernmentBookingFields.ts b/apps/edr-freight-api/src/migrations/1750600000000-AddGovernmentBookingFields.ts
deleted file mode 100644
index ce833e4ac..000000000
--- a/apps/edr-freight-api/src/migrations/1750600000000-AddGovernmentBookingFields.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-export class AddGovernmentBookingFields1750600000000 implements MigrationInterface {
- name = 'AddGovernmentBookingFields1750600000000';
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- ALTER TABLE freight.bookings
- ADD COLUMN IF NOT EXISTS is_government BOOLEAN NOT NULL DEFAULT false
- `);
- await queryRunner.query(`
- ALTER TABLE freight.bookings
- ADD COLUMN IF NOT EXISTS government_institution VARCHAR(255) NULL
- `);
- await queryRunner.query(`
- ALTER TABLE freight.bookings
- ALTER COLUMN company_id DROP NOT NULL
- `);
- await queryRunner.query(`
- CREATE INDEX IF NOT EXISTS idx_bookings_is_government
- ON freight.bookings (is_government)
- WHERE is_government = true AND deleted_at IS NULL
- `);
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`DROP INDEX IF EXISTS freight.idx_bookings_is_government`);
- await queryRunner.query(`
- UPDATE freight.bookings
- SET company_id = '00000000-0000-0000-0000-000000000000'
- WHERE company_id IS NULL
- `);
- await queryRunner.query(`
- ALTER TABLE freight.bookings
- ALTER COLUMN company_id SET NOT NULL
- `);
- await queryRunner.query(`
- ALTER TABLE freight.bookings
- DROP COLUMN IF EXISTS government_institution
- `);
- await queryRunner.query(`
- ALTER TABLE freight.bookings
- DROP COLUMN IF EXISTS is_government
- `);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1750700000000-CreateSchedulingEvents.ts b/apps/edr-freight-api/src/migrations/1750700000000-CreateSchedulingEvents.ts
deleted file mode 100644
index 9f92f70b4..000000000
--- a/apps/edr-freight-api/src/migrations/1750700000000-CreateSchedulingEvents.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-export class CreateSchedulingEvents1750700000000 implements MigrationInterface {
- name = 'CreateSchedulingEvents1750700000000';
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- CREATE TABLE IF NOT EXISTS freight.scheduling_events (
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
- train_schedule_id UUID NOT NULL,
- trigger VARCHAR(40) NOT NULL,
- actor_user_id UUID NULL,
- reason TEXT NULL,
- plan_snapshot JSONB NOT NULL DEFAULT '{}',
- displaced_booking_ids JSONB NOT NULL DEFAULT '[]',
- 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_scheduling_events_train_schedule_id
- ON freight.scheduling_events (train_schedule_id)
- WHERE deleted_at IS NULL
- `);
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`DROP INDEX IF EXISTS freight.idx_scheduling_events_train_schedule_id`);
- await queryRunner.query(`DROP TABLE IF EXISTS freight.scheduling_events`);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1750800000000-FixContainerWagonsPerUnit.ts b/apps/edr-freight-api/src/migrations/1750800000000-FixContainerWagonsPerUnit.ts
deleted file mode 100644
index 1bb1a9518..000000000
--- a/apps/edr-freight-api/src/migrations/1750800000000-FixContainerWagonsPerUnit.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-/** 20ft = 0.5 wagon slots (2 per wagon); 40ft = 1.0 wagon slot (1 per wagon). */
-export class FixContainerWagonsPerUnit1750800000000 implements MigrationInterface {
- name = 'FixContainerWagonsPerUnit1750800000000';
-
- public async up(queryRunner: QueryRunner): Promise {
- const hasContainerTypes = await queryRunner.hasTable('freight.container_types');
- if (!hasContainerTypes) {
- return;
- }
-
- await queryRunner.query(`
- UPDATE freight.container_types
- SET wagons_per_unit = 0.50
- WHERE size_ft = 20 OR code LIKE '20%';
- `);
- await queryRunner.query(`
- UPDATE freight.container_types
- SET wagons_per_unit = 1.00
- WHERE size_ft = 40 OR code LIKE '40%';
- `);
-
- const hasBookingContainer = await queryRunner.hasTable('freight.booking_container');
- if (!hasBookingContainer) {
- return;
- }
-
- await queryRunner.query(`
- UPDATE freight.booking_container bc
- SET wagons_required = CEILING(bc.quantity * ct.wagons_per_unit)
- FROM freight.container_types ct
- WHERE ct.id = bc.container_type_id;
- `);
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- const hasContainerTypes = await queryRunner.hasTable('freight.container_types');
- if (!hasContainerTypes) {
- return;
- }
-
- await queryRunner.query(`
- UPDATE freight.container_types SET wagons_per_unit = 1.00;
- `);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1750900000000-AddContainerNumberToBookingContainer.ts b/apps/edr-freight-api/src/migrations/1750900000000-AddContainerNumberToBookingContainer.ts
deleted file mode 100644
index eb28da8f3..000000000
--- a/apps/edr-freight-api/src/migrations/1750900000000-AddContainerNumberToBookingContainer.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-import { MigrationInterface, QueryRunner } from "typeorm";
-
-export class AddContainerNumberToBookingContainer1750900000000 implements MigrationInterface {
- name = "AddContainerNumberToBookingContainer1750900000000";
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- ALTER TABLE freight.booking_container
- ALTER COLUMN container_type_id DROP NOT NULL;
- `);
-
- await queryRunner.query(`
- ALTER TABLE freight.booking_container
- ADD COLUMN container_number varchar(64);
- `);
-
- await queryRunner.query(`
- ALTER TABLE freight.wagon_allocation_container_items
- ALTER COLUMN container_type_id DROP NOT NULL;
- `);
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- ALTER TABLE freight.wagon_allocation_container_items
- ALTER COLUMN container_type_id SET NOT NULL;
- `);
-
- await queryRunner.query(`
- ALTER TABLE freight.booking_container
- DROP COLUMN container_number;
- `);
-
- await queryRunner.query(`
- ALTER TABLE freight.booking_container
- ALTER COLUMN container_type_id SET NOT NULL;
- `);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1751000000000-CreateTrainSchedulingGlobalRules.ts b/apps/edr-freight-api/src/migrations/1751000000000-CreateTrainSchedulingGlobalRules.ts
deleted file mode 100644
index ac9741c5a..000000000
--- a/apps/edr-freight-api/src/migrations/1751000000000-CreateTrainSchedulingGlobalRules.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-import { MigrationInterface, QueryRunner } from "typeorm";
-
-export class CreateTrainSchedulingGlobalRules1751000000000 implements MigrationInterface {
- name = "CreateTrainSchedulingGlobalRules1751000000000";
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- CREATE TABLE freight.train_scheduling_global_rules (
- id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
- max_train_length_meters numeric(10, 2) NOT NULL DEFAULT 760,
- max_train_weight_tons numeric(10, 3) NOT NULL DEFAULT 3500,
- max_wagons_per_train integer NOT NULL DEFAULT 53,
- max_20ft_container_weight_tons numeric(8, 3) NOT NULL DEFAULT 30,
- max_20ft_pair_weight_diff_tons numeric(8, 3) NOT NULL DEFAULT 10,
- created_at timestamptz NOT NULL DEFAULT now(),
- updated_at timestamptz NOT NULL DEFAULT now(),
- deleted_at timestamptz NULL
- );
- `);
-
- await queryRunner.query(`
- INSERT INTO freight.train_scheduling_global_rules (
- max_train_length_meters,
- max_train_weight_tons,
- max_wagons_per_train,
- max_20ft_container_weight_tons,
- max_20ft_pair_weight_diff_tons
- ) VALUES (760, 3500, 53, 30, 10);
- `);
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`DROP TABLE IF EXISTS freight.train_scheduling_global_rules;`);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1751000000001-AddDeletedAtToTrainSchedulingGlobalRules.ts b/apps/edr-freight-api/src/migrations/1751000000001-AddDeletedAtToTrainSchedulingGlobalRules.ts
deleted file mode 100644
index fd72f6c3b..000000000
--- a/apps/edr-freight-api/src/migrations/1751000000001-AddDeletedAtToTrainSchedulingGlobalRules.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { MigrationInterface, QueryRunner } from "typeorm";
-
-export class AddDeletedAtToTrainSchedulingGlobalRules1751000000001
- implements MigrationInterface
-{
- name = "AddDeletedAtToTrainSchedulingGlobalRules1751000000001";
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- ALTER TABLE freight.train_scheduling_global_rules
- ADD COLUMN IF NOT EXISTS deleted_at timestamptz NULL;
- `);
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- ALTER TABLE freight.train_scheduling_global_rules
- DROP COLUMN IF EXISTS deleted_at;
- `);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1752000000000-CreateCompanyProfiles.ts b/apps/edr-freight-api/src/migrations/1752000000000-CreateCompanyProfiles.ts
deleted file mode 100644
index a27cef3ad..000000000
--- a/apps/edr-freight-api/src/migrations/1752000000000-CreateCompanyProfiles.ts
+++ /dev/null
@@ -1,103 +0,0 @@
-import {
- MigrationInterface,
- QueryRunner,
- Table,
- TableIndex,
- TableForeignKey,
-} from "typeorm";
-
-export class CreateCompanyProfiles1752000000000 implements MigrationInterface {
- name = "CreateCompanyProfiles1752000000000";
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.createTable(
- new Table({
- schema: "freight",
- name: "company_profiles",
- columns: [
- {
- name: "id",
- type: "uuid",
- isPrimary: true,
- generationStrategy: "uuid",
- default: "gen_random_uuid()",
- },
- { name: "company_id", type: "uuid" },
- { name: "type", type: "varchar", length: "32" },
- { name: "reference", type: "varchar", length: "20", isUnique: true },
- {
- name: "status",
- type: "varchar",
- length: "32",
- default: "'active'",
- },
- {
- name: "business_license",
- type: "varchar",
- length: "100",
- isNullable: true,
- },
- { name: "attributes", type: "jsonb", isNullable: true },
- { name: "created_at", type: "timestamptz", default: "now()" },
- { name: "updated_at", type: "timestamptz", default: "now()" },
- { name: "deleted_at", type: "timestamptz", isNullable: true },
- ],
- }),
- true,
- );
-
- await queryRunner.createForeignKey(
- "freight.company_profiles",
- new TableForeignKey({
- columnNames: ["company_id"],
- referencedTableName: "companies",
- referencedSchema: "freight",
- referencedColumnNames: ["id"],
- }),
- );
-
- await queryRunner.createIndex(
- "freight.company_profiles",
- new TableIndex({ columnNames: ["company_id"] }),
- );
- await queryRunner.createIndex(
- "freight.company_profiles",
- new TableIndex({ columnNames: ["type"] }),
- );
-
- await queryRunner.query(
- `CREATE SEQUENCE IF NOT EXISTS freight.seq_company_profile_ex START WITH 1`,
- );
- await queryRunner.query(
- `CREATE SEQUENCE IF NOT EXISTS freight.seq_company_profile_im START WITH 1`,
- );
- await queryRunner.query(
- `CREATE SEQUENCE IF NOT EXISTS freight.seq_company_profile_ffe START WITH 1`,
- );
- await queryRunner.query(
- `CREATE SEQUENCE IF NOT EXISTS freight.seq_company_profile_fwj START WITH 1`,
- );
- await queryRunner.query(
- `CREATE SEQUENCE IF NOT EXISTS freight.seq_company_profile_tr START WITH 1`,
- );
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.dropTable("freight.company_profiles");
- await queryRunner.query(
- `DROP SEQUENCE IF EXISTS freight.seq_company_profile_ex`,
- );
- await queryRunner.query(
- `DROP SEQUENCE IF EXISTS freight.seq_company_profile_im`,
- );
- await queryRunner.query(
- `DROP SEQUENCE IF EXISTS freight.seq_company_profile_ffe`,
- );
- await queryRunner.query(
- `DROP SEQUENCE IF EXISTS freight.seq_company_profile_fwj`,
- );
- await queryRunner.query(
- `DROP SEQUENCE IF EXISTS freight.seq_company_profile_tr`,
- );
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1752000000001-MoveBusinessLicenseToProfile.ts b/apps/edr-freight-api/src/migrations/1752000000001-MoveBusinessLicenseToProfile.ts
deleted file mode 100644
index f15996773..000000000
--- a/apps/edr-freight-api/src/migrations/1752000000001-MoveBusinessLicenseToProfile.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-export class MoveBusinessLicenseToProfile1752000000001
- implements MigrationInterface
-{
- name = 'MoveBusinessLicenseToProfile1752000000001';
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- UPDATE freight.company_profiles cp
- SET business_license = c.business_license
- FROM freight.companies c
- WHERE cp.company_id = c.id AND c.business_license IS NOT NULL
- `);
-
- await queryRunner.query(
- `ALTER TABLE freight.companies DROP COLUMN IF EXISTS business_license`,
- );
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(
- `ALTER TABLE freight.companies ADD COLUMN business_license varchar(100) NULL`,
- );
-
- await queryRunner.query(`
- UPDATE freight.companies c
- SET business_license = cp.business_license
- FROM (
- SELECT DISTINCT ON (cp2.company_id)
- cp2.company_id, cp2.business_license
- FROM freight.company_profiles cp2
- WHERE cp2.business_license IS NOT NULL
- ORDER BY cp2.company_id, cp2.created_at
- ) cp
- WHERE cp.company_id = c.id
- `);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1770000000000-CreateVehiclesTable.ts b/apps/edr-freight-api/src/migrations/1770000000000-CreateVehiclesTable.ts
deleted file mode 100644
index 2cf09c9f5..000000000
--- a/apps/edr-freight-api/src/migrations/1770000000000-CreateVehiclesTable.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-export class CreateVehiclesTable1770000000000 implements MigrationInterface {
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- DO $$
- BEGIN
- IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'vehicles' AND table_schema = 'freight') THEN
- CREATE TABLE freight.vehicles (
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
- plate_number VARCHAR NOT NULL UNIQUE,
- registration_number VARCHAR NOT NULL UNIQUE,
- vehicle_type VARCHAR NOT NULL,
- manufacturer VARCHAR NOT NULL,
- model VARCHAR NOT NULL,
- year INTEGER NOT NULL,
- fuel_type VARCHAR NOT NULL,
- capacity NUMERIC NOT NULL,
- status VARCHAR DEFAULT 'ACTIVE' NOT NULL,
- description TEXT,
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
- deleted_at TIMESTAMP NULL
- );
-
- CREATE INDEX idx_vehicles_plate_number ON freight.vehicles(plate_number);
- CREATE INDEX idx_vehicles_registration_number ON freight.vehicles(registration_number);
- CREATE INDEX idx_vehicles_status ON freight.vehicles(status);
- CREATE INDEX idx_vehicles_vehicle_type ON freight.vehicles(vehicle_type);
- CREATE INDEX idx_vehicles_manufacturer ON freight.vehicles(manufacturer);
- END IF;
- END $$;
- `);
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`DROP TABLE IF EXISTS freight.vehicles CASCADE;`);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1775000000000-CreateDriversTable.ts b/apps/edr-freight-api/src/migrations/1775000000000-CreateDriversTable.ts
deleted file mode 100644
index 8f41587cf..000000000
--- a/apps/edr-freight-api/src/migrations/1775000000000-CreateDriversTable.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-export class CreateDriversTable1775000000000 implements MigrationInterface {
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- DO $$
- BEGIN
- IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'drivers' AND table_schema = 'freight') THEN
- CREATE TABLE freight.drivers (
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
- license_number VARCHAR NOT NULL UNIQUE,
- first_name VARCHAR NOT NULL,
- last_name VARCHAR NOT NULL,
- email VARCHAR NOT NULL UNIQUE,
- phone_number VARCHAR NOT NULL UNIQUE,
- date_of_birth DATE NOT NULL,
- license_expiry_date DATE NOT NULL,
- status VARCHAR DEFAULT 'ACTIVE' NOT NULL,
- vehicle_types_authorized VARCHAR[],
- address TEXT,
- emergency_contact VARCHAR,
- notes TEXT,
- total_trips INTEGER DEFAULT 0,
- rating NUMERIC(3, 2),
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
- deleted_at TIMESTAMP NULL
- );
-
- CREATE INDEX idx_drivers_license_number ON freight.drivers(license_number);
- CREATE INDEX idx_drivers_email ON freight.drivers(email);
- CREATE INDEX idx_drivers_phone_number ON freight.drivers(phone_number);
- CREATE INDEX idx_drivers_status ON freight.drivers(status);
- END IF;
- END $$;
- `);
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`DROP TABLE IF EXISTS freight.drivers CASCADE;`);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1780639311366-CreatePaymentTable.ts b/apps/edr-freight-api/src/migrations/1780639311366-CreatePaymentTable.ts
deleted file mode 100644
index 0382c834b..000000000
--- a/apps/edr-freight-api/src/migrations/1780639311366-CreatePaymentTable.ts
+++ /dev/null
@@ -1,98 +0,0 @@
-import { MigrationInterface, QueryRunner } from "typeorm";
-
-export class CreatePaymentTable1780639311366 implements MigrationInterface {
- name = "CreatePaymentTable1780639311366";
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- CREATE TYPE freight.payments_type_enum AS ENUM ('booking');
- `);
-
- await queryRunner.query(`
- CREATE TYPE freight.payments_method_enum AS ENUM ('telebirr', 'cbe-birr', 'ebirr');
- `);
-
- await queryRunner.query(`
- CREATE TYPE freight.payments_currency_enum AS ENUM ('ETB', 'USD');
- `);
-
- await queryRunner.query(`
- CREATE TYPE freight.payments_status_enum AS ENUM (
- 'action-required',
- 'processing',
- 'success',
- 'failed',
- 'canceled',
- 'refunded'
- );
- `);
-
- await queryRunner.query(`
- CREATE TABLE freight.payments (
- id uuid NOT NULL DEFAULT uuid_generate_v4(),
-
- ref_id varchar(255) NOT NULL,
-
- type freight.payments_type_enum NOT NULL,
-
- method freight.payments_method_enum NOT NULL,
-
- currency freight.payments_currency_enum NOT NULL,
-
- amount numeric NOT NULL,
-
- raw_initiation jsonb NOT NULL DEFAULT '{}'::jsonb,
-
- client_action json,
-
- merchant_order_id varchar(255) NOT NULL,
-
- transaction_id varchar(255),
-
- status freight.payments_status_enum NOT NULL DEFAULT 'action-required',
-
- paid_at date,
-
- refunded_at date,
-
- expires_at date,
-
- failer_code varchar(30),
-
- failer_message varchar(255),
-
- reason varchar(255),
-
- created_at TIMESTAMP NOT NULL DEFAULT now(),
-
- CONSTRAINT PK_payments PRIMARY KEY (id),
-
- CONSTRAINT UQ_payments_merchant_order_id UNIQUE (merchant_order_id),
-
- CONSTRAINT UQ_payments_transaction_id UNIQUE (transaction_id)
- );
-`);
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- DROP TABLE IF EXISTS freight.payments;
- `);
-
- await queryRunner.query(`
- DROP TYPE IF EXISTS freight.payments_status_enum;
- `);
-
- await queryRunner.query(`
- DROP TYPE IF EXISTS freight.payments_currency_enum;
- `);
-
- await queryRunner.query(`
- DROP TYPE IF EXISTS freight.payments_method_enum;
- `);
-
- await queryRunner.query(`
- DROP TYPE IF EXISTS freight.payments_type_enum;
- `);
- }
-}
\ No newline at end of file
diff --git a/apps/edr-freight-api/src/migrations/1780639978834-AlterClientActionToJsonb.ts b/apps/edr-freight-api/src/migrations/1780639978834-AlterClientActionToJsonb.ts
deleted file mode 100644
index 723331ab3..000000000
--- a/apps/edr-freight-api/src/migrations/1780639978834-AlterClientActionToJsonb.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import { MigrationInterface, QueryRunner } from "typeorm";
-
-export class AlterClientActionToJsonb1780639978834 implements MigrationInterface {
- name = "AlterClientActionToJsonb1780639978834";
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- ALTER TABLE freight.payments
- ALTER COLUMN client_action TYPE jsonb
- USING client_action::jsonb;
- `);
-
- await queryRunner.query(`
- ALTER TABLE freight.payments
- ALTER COLUMN client_action DROP DEFAULT;
- `);
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- ALTER TABLE freight.payments
- ALTER COLUMN client_action TYPE json
- USING client_action::json;
- `);
- }
-
-
-}
diff --git a/apps/edr-freight-api/src/migrations/1780644945086-UpdatePaymentTimestamp.ts b/apps/edr-freight-api/src/migrations/1780644945086-UpdatePaymentTimestamp.ts
deleted file mode 100644
index 6cfc7fc8f..000000000
--- a/apps/edr-freight-api/src/migrations/1780644945086-UpdatePaymentTimestamp.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import { MigrationInterface, QueryRunner } from "typeorm";
-
-export class UpdatePaymentTimestamp1780644945086 implements MigrationInterface {
- name = "UpdatePaymentTimestamp1780644945086";
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- ALTER TABLE freight.payments
- ALTER COLUMN refunded_at TYPE timestamp
- USING refunded_at::timestamp;
- `);
-
- await queryRunner.query(`
- ALTER TABLE freight.payments
- ALTER COLUMN expires_at TYPE timestamp
- USING expires_at::timestamp;
- `);
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- ALTER TABLE freight.payments
- ALTER COLUMN refunded_at TYPE timestamptz
- USING refunded_at::timestamptz;
- `);
-
- await queryRunner.query(`
- ALTER TABLE freight.payments
- ALTER COLUMN expires_at TYPE timestamptz
- USING expires_at::timestamptz;
- `);
- }
-}
\ No newline at end of file
diff --git a/apps/edr-freight-api/src/migrations/1781000000000-AddLocomotiveReadiness.ts b/apps/edr-freight-api/src/migrations/1781000000000-AddLocomotiveReadiness.ts
deleted file mode 100644
index f6d87f41d..000000000
--- a/apps/edr-freight-api/src/migrations/1781000000000-AddLocomotiveReadiness.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-export class AddLocomotiveReadiness1781000000000 implements MigrationInterface {
- name = 'AddLocomotiveReadiness1781000000000';
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- ALTER TABLE freight.locomotives
- ADD COLUMN IF NOT EXISTS readiness VARCHAR(20) NOT NULL DEFAULT 'IMPORT_READY'
- `);
- await queryRunner.query(`
- CREATE INDEX IF NOT EXISTS idx_locomotives_readiness
- ON freight.locomotives (readiness)
- WHERE deleted_at IS NULL
- `);
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`DROP INDEX IF EXISTS freight.idx_locomotives_readiness`);
- await queryRunner.query(`
- ALTER TABLE freight.locomotives
- DROP COLUMN IF EXISTS readiness
- `);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1781000000001-CreateTrainCheckpointEvents.ts b/apps/edr-freight-api/src/migrations/1781000000001-CreateTrainCheckpointEvents.ts
deleted file mode 100644
index 7d9ce745a..000000000
--- a/apps/edr-freight-api/src/migrations/1781000000001-CreateTrainCheckpointEvents.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-export class CreateTrainCheckpointEvents1781000000001 implements MigrationInterface {
- name = 'CreateTrainCheckpointEvents1781000000001';
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- CREATE TABLE IF NOT EXISTS freight.train_checkpoint_events (
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
- train_schedule_id UUID NOT NULL REFERENCES freight.train_schedules(id) ON DELETE CASCADE,
- yard_id UUID NOT NULL,
- sequence_no INT NOT NULL,
- kind VARCHAR(20) NOT NULL,
- occurred_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
- note TEXT NULL,
- recorded_by_user_id UUID 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_train_checkpoint_events_schedule
- ON freight.train_checkpoint_events (train_schedule_id, sequence_no)
- WHERE deleted_at IS NULL
- `);
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(
- `DROP INDEX IF EXISTS freight.idx_train_checkpoint_events_schedule`,
- );
- await queryRunner.query(`DROP TABLE IF EXISTS freight.train_checkpoint_events`);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1781000000002-AddBatchBookingFields.ts b/apps/edr-freight-api/src/migrations/1781000000002-AddBatchBookingFields.ts
deleted file mode 100644
index 0287b55be..000000000
--- a/apps/edr-freight-api/src/migrations/1781000000002-AddBatchBookingFields.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-export class AddBatchBookingFields1781000000002 implements MigrationInterface {
- name = 'AddBatchBookingFields1781000000002';
-
- public async up(queryRunner: QueryRunner): Promise {
- // Booking → target schedule (pool membership) + 1h pay-window deadline.
- await queryRunner.query(`
- ALTER TABLE freight.bookings
- ADD COLUMN IF NOT EXISTS train_schedule_id UUID NULL,
- ADD COLUMN IF NOT EXISTS payment_deadline TIMESTAMPTZ NULL
- `);
- await queryRunner.query(`
- CREATE INDEX IF NOT EXISTS idx_bookings_train_schedule_id
- ON freight.bookings (train_schedule_id)
- WHERE deleted_at IS NULL
- `);
-
- // TrainSchedule → booking-window status (OPEN/FULL/CLOSED).
- await queryRunner.query(`
- ALTER TABLE freight.train_schedules
- ADD COLUMN IF NOT EXISTS booking_window_status VARCHAR(10) NOT NULL DEFAULT 'OPEN'
- `);
- await queryRunner.query(`
- CREATE INDEX IF NOT EXISTS idx_train_schedules_booking_window_status
- ON freight.train_schedules (booking_window_status)
- WHERE deleted_at IS NULL
- `);
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(
- `DROP INDEX IF EXISTS freight.idx_train_schedules_booking_window_status`,
- );
- await queryRunner.query(
- `ALTER TABLE freight.train_schedules DROP COLUMN IF EXISTS booking_window_status`,
- );
- await queryRunner.query(`DROP INDEX IF EXISTS freight.idx_bookings_train_schedule_id`);
- await queryRunner.query(`
- ALTER TABLE freight.bookings
- DROP COLUMN IF EXISTS train_schedule_id,
- DROP COLUMN IF EXISTS payment_deadline
- `);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1781000000003-AddSelectedForBatchStatus.ts b/apps/edr-freight-api/src/migrations/1781000000003-AddSelectedForBatchStatus.ts
deleted file mode 100644
index 1ba9670b2..000000000
--- a/apps/edr-freight-api/src/migrations/1781000000003-AddSelectedForBatchStatus.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-export class AddSelectedForBatchStatus1781000000003 implements MigrationInterface {
- name = 'AddSelectedForBatchStatus1781000000003';
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- ALTER TABLE freight.bookings
- ADD COLUMN IF NOT EXISTS selected_for_batch_at TIMESTAMPTZ NULL
- `);
-
- await queryRunner.query(`
- UPDATE freight.bookings
- SET
- status = 'SELECTED_FOR_BATCH',
- selected_for_batch_at = COALESCE(
- payment_deadline - INTERVAL '5 minutes',
- updated_at
- )
- WHERE status = 'AWAITING_PAYMENT'
- `);
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- UPDATE freight.bookings
- SET status = 'AWAITING_PAYMENT'
- WHERE status = 'SELECTED_FOR_BATCH'
- `);
-
- await queryRunner.query(`
- ALTER TABLE freight.bookings
- DROP COLUMN IF EXISTS selected_for_batch_at
- `);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1781000000004-AddDomesticWeightLimitTradeDirection.ts b/apps/edr-freight-api/src/migrations/1781000000004-AddDomesticWeightLimitTradeDirection.ts
deleted file mode 100644
index 59b1de22e..000000000
--- a/apps/edr-freight-api/src/migrations/1781000000004-AddDomesticWeightLimitTradeDirection.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-/**
- * Allow DOMESTIC trade direction on weight_limit_rules (domestic corridor bookings).
- */
-export class AddDomesticWeightLimitTradeDirection1781000000004
- implements MigrationInterface
-{
- name = 'AddDomesticWeightLimitTradeDirection1781000000004';
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- DO $$ BEGIN
- ALTER TYPE freight.weight_limit_rules_trade_direction_enum ADD VALUE 'DOMESTIC';
- EXCEPTION
- WHEN duplicate_object THEN NULL;
- WHEN undefined_object THEN
- BEGIN
- ALTER TYPE weight_limit_rules_trade_direction_enum ADD VALUE 'DOMESTIC';
- EXCEPTION
- WHEN duplicate_object THEN NULL;
- END;
- END $$;
- `);
- }
-
- public async down(_queryRunner: QueryRunner): Promise {
- // PostgreSQL does not support removing enum values safely.
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1781000000005-CreateTrainCompositionRemovalLog.ts b/apps/edr-freight-api/src/migrations/1781000000005-CreateTrainCompositionRemovalLog.ts
deleted file mode 100644
index 475a52574..000000000
--- a/apps/edr-freight-api/src/migrations/1781000000005-CreateTrainCompositionRemovalLog.ts
+++ /dev/null
@@ -1,81 +0,0 @@
-import { MigrationInterface, QueryRunner, Table, TableIndex } from 'typeorm';
-
-export class CreateTrainCompositionRemovalLog1781000000005 implements MigrationInterface {
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.createTable(
- new Table({
- schema: 'freight',
- name: 'train_composition_removal_logs',
- columns: [
- {
- name: 'id',
- type: 'uuid',
- isPrimary: true,
- default: 'uuid_generate_v4()',
- },
- {
- name: 'schedule_id',
- type: 'uuid',
- isNullable: false,
- },
- {
- name: 'booking_id',
- type: 'uuid',
- isNullable: false,
- },
- {
- name: 'booking_reference',
- type: 'varchar',
- length: '64',
- isNullable: true,
- },
- {
- name: 'removed_by_user_id',
- type: 'uuid',
- isNullable: true,
- },
- {
- name: 'removed_at',
- type: 'timestamptz',
- default: 'NOW()',
- isNullable: false,
- },
- {
- name: 'notes',
- type: 'text',
- isNullable: true,
- },
- {
- name: 'created_at',
- type: 'timestamptz',
- default: 'NOW()',
- isNullable: false,
- },
- {
- name: 'updated_at',
- type: 'timestamptz',
- default: 'NOW()',
- isNullable: false,
- },
- {
- name: 'deleted_at',
- type: 'timestamptz',
- isNullable: true,
- },
- ],
- }),
- true,
- );
-
- await queryRunner.createIndex(
- 'freight.train_composition_removal_logs',
- new TableIndex({
- columnNames: ['schedule_id'],
- }),
- );
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.dropTable('freight.train_composition_removal_logs', true);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1782000000000-WagonLocomotiveYardLink.ts b/apps/edr-freight-api/src/migrations/1782000000000-WagonLocomotiveYardLink.ts
deleted file mode 100644
index 68100f367..000000000
--- a/apps/edr-freight-api/src/migrations/1782000000000-WagonLocomotiveYardLink.ts
+++ /dev/null
@@ -1,88 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-export class WagonLocomotiveYardLink1782000000000 implements MigrationInterface {
- name = 'WagonLocomotiveYardLink1782000000000';
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- ALTER TABLE freight.wagons
- ADD COLUMN IF NOT EXISTS "current_yard_id" UUID NULL;
- `);
- await queryRunner.query(`
- DO $$
- BEGIN
- IF NOT EXISTS (
- SELECT 1 FROM pg_constraint WHERE conname = 'FK_wagon_current_yard'
- ) THEN
- ALTER TABLE freight.wagons
- ADD CONSTRAINT "FK_wagon_current_yard"
- FOREIGN KEY ("current_yard_id") REFERENCES freight.yards(id) ON DELETE SET NULL;
- END IF;
- END $$;
- `);
- await queryRunner.query(`
- CREATE INDEX IF NOT EXISTS "IDX_wagon_current_yard_id"
- ON freight.wagons ("current_yard_id");
- `);
-
- await queryRunner.query(`DROP INDEX IF EXISTS freight.idx_wagons_readiness`);
- await queryRunner.query(`ALTER TABLE freight.wagons DROP COLUMN IF EXISTS readiness;`);
-
- await queryRunner.query(`
- ALTER TABLE freight.locomotives
- ADD COLUMN IF NOT EXISTS "current_yard_id" UUID NULL;
- `);
- await queryRunner.query(`
- DO $$
- BEGIN
- IF NOT EXISTS (
- SELECT 1 FROM pg_constraint WHERE conname = 'FK_locomotive_current_yard'
- ) THEN
- ALTER TABLE freight.locomotives
- ADD CONSTRAINT "FK_locomotive_current_yard"
- FOREIGN KEY ("current_yard_id") REFERENCES freight.yards(id) ON DELETE SET NULL;
- END IF;
- END $$;
- `);
- await queryRunner.query(`
- CREATE INDEX IF NOT EXISTS "IDX_locomotive_current_yard_id"
- ON freight.locomotives ("current_yard_id");
- `);
-
- await queryRunner.query(`DROP INDEX IF EXISTS freight.idx_locomotives_readiness`);
- await queryRunner.query(`ALTER TABLE freight.locomotives DROP COLUMN IF EXISTS readiness;`);
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- ALTER TABLE freight.wagons
- ADD COLUMN IF NOT EXISTS readiness VARCHAR(20) NOT NULL DEFAULT 'IMPORT_READY';
- `);
- await queryRunner.query(`
- ALTER TABLE freight.locomotives
- ADD COLUMN IF NOT EXISTS readiness VARCHAR(20) NOT NULL DEFAULT 'IMPORT_READY';
- `);
-
- await queryRunner.query(`
- CREATE INDEX IF NOT EXISTS idx_wagons_readiness
- ON freight.wagons (readiness)
- WHERE deleted_at IS NULL;
- `);
- await queryRunner.query(`
- CREATE INDEX IF NOT EXISTS idx_locomotives_readiness
- ON freight.locomotives (readiness)
- WHERE deleted_at IS NULL;
- `);
-
- await queryRunner.query(`DROP INDEX IF EXISTS freight."IDX_wagon_current_yard_id"`);
- await queryRunner.query(`DROP INDEX IF EXISTS freight."IDX_locomotive_current_yard_id"`);
- await queryRunner.query(`
- ALTER TABLE freight.wagons DROP CONSTRAINT IF EXISTS "FK_wagon_current_yard";
- `);
- await queryRunner.query(`
- ALTER TABLE freight.locomotives DROP CONSTRAINT IF EXISTS "FK_locomotive_current_yard";
- `);
- await queryRunner.query(`ALTER TABLE freight.wagons DROP COLUMN IF EXISTS "current_yard_id";`);
- await queryRunner.query(`ALTER TABLE freight.locomotives DROP COLUMN IF EXISTS "current_yard_id";`);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1782000000001-AddPaymentWebhookEventAndRefund.ts b/apps/edr-freight-api/src/migrations/1782000000001-AddPaymentWebhookEventAndRefund.ts
deleted file mode 100644
index e1cddc05b..000000000
--- a/apps/edr-freight-api/src/migrations/1782000000001-AddPaymentWebhookEventAndRefund.ts
+++ /dev/null
@@ -1,62 +0,0 @@
-import { MigrationInterface, QueryRunner } from "typeorm";
-
-export class AddPaymentWebhookEventAndRefund1782000000001 implements MigrationInterface {
- name = "AddPaymentWebhookEventAndRefund1782000000001";
-
- public async up(queryRunner: QueryRunner): Promise {
- // Enum for webhook provider — shares the same values as payments_method_enum
- // but is a separate type so both tables remain independently evolvable.
- await queryRunner.query(`
- CREATE TYPE freight.payment_webhook_method_enum AS ENUM ('telebirr', 'cbe-birr', 'ebirr');
- `);
-
- await queryRunner.query(`
- CREATE TABLE freight.payment_webhook_events (
- id uuid NOT NULL DEFAULT uuid_generate_v4(),
- provider freight.payment_webhook_method_enum NOT NULL,
- external_event_id varchar(255) NOT NULL,
- merchant_order_id varchar(255),
- provider_txn_id varchar(255),
- signature_valid boolean NOT NULL,
- status varchar(100) NOT NULL,
- payload jsonb NOT NULL,
- received_at TIMESTAMP NOT NULL DEFAULT now(),
- processed_at TIMESTAMP,
- processing_error text,
-
- CONSTRAINT PK_payment_webhook_events PRIMARY KEY (id),
- CONSTRAINT UQ_payment_webhook_events_provider_event UNIQUE (provider, external_event_id)
- );
- `);
-
- await queryRunner.query(`
- CREATE INDEX IDX_payment_webhook_events_merchant_order_id
- ON freight.payment_webhook_events (merchant_order_id);
- `);
-
- await queryRunner.query(`
- CREATE TABLE freight.payment_refunds (
- id uuid NOT NULL DEFAULT uuid_generate_v4(),
- payment_id uuid NOT NULL,
- amount_minor int NOT NULL,
- reason varchar(255),
- provider_refund_id varchar(255),
- status varchar(50) NOT NULL,
- created_at TIMESTAMP NOT NULL DEFAULT now(),
-
- CONSTRAINT PK_payment_refunds PRIMARY KEY (id),
- CONSTRAINT FK_payment_refunds_payment
- FOREIGN KEY (payment_id)
- REFERENCES freight.payments (id)
- ON DELETE RESTRICT
- );
- `);
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`DROP TABLE IF EXISTS freight.payment_refunds;`);
- await queryRunner.query(`DROP INDEX IF EXISTS freight.IDX_payment_webhook_events_merchant_order_id;`);
- await queryRunner.query(`DROP TABLE IF EXISTS freight.payment_webhook_events;`);
- await queryRunner.query(`DROP TYPE IF EXISTS freight.payment_webhook_method_enum;`);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1782000000002-ExtendPaymentMethodEnum.ts b/apps/edr-freight-api/src/migrations/1782000000002-ExtendPaymentMethodEnum.ts
deleted file mode 100644
index 607926e92..000000000
--- a/apps/edr-freight-api/src/migrations/1782000000002-ExtendPaymentMethodEnum.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { MigrationInterface, QueryRunner } from "typeorm";
-
-export class ExtendPaymentMethodEnum1782000000002 implements MigrationInterface {
- name = "ExtendPaymentMethodEnum1782000000002";
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`ALTER TYPE freight.payments_method_enum ADD VALUE IF NOT EXISTS 'waafi';`);
- await queryRunner.query(`ALTER TYPE freight.payments_method_enum ADD VALUE IF NOT EXISTS 'card';`);
- await queryRunner.query(`ALTER TYPE freight.payments_method_enum ADD VALUE IF NOT EXISTS 'dmoney';`);
- }
-
- public async down(_queryRunner: QueryRunner): Promise {
- // PostgreSQL does not support removing enum values directly.
- // To roll back, recreate the type without the added values and update the column.
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1783000000000-ReplacePriorityRulesWithPriorityConfigs.ts b/apps/edr-freight-api/src/migrations/1783000000000-ReplacePriorityRulesWithPriorityConfigs.ts
deleted file mode 100644
index b8eccbbaf..000000000
--- a/apps/edr-freight-api/src/migrations/1783000000000-ReplacePriorityRulesWithPriorityConfigs.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-export class ReplacePriorityRulesWithPriorityConfigs1783000000000 implements MigrationInterface {
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- CREATE TABLE freight.priority_configs (
- id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
- type VARCHAR(20) NOT NULL CHECK (type IN ('WAGON', 'CURRENCY')),
- label VARCHAR(100) NOT NULL,
- currency VARCHAR(5) NULL,
- min_wagon_count INT NOT NULL,
- max_wagon_count INT NOT NULL,
- score_points INT NOT NULL DEFAULT 0,
- is_active BOOLEAN NOT NULL DEFAULT false,
- display_order INT NOT NULL DEFAULT 1,
- created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
- updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
- deleted_at TIMESTAMPTZ NULL,
- CONSTRAINT chk_wagon_range CHECK (min_wagon_count <= max_wagon_count),
- CONSTRAINT chk_currency_for_type CHECK (
- (type = 'WAGON' AND currency IS NULL) OR
- (type = 'CURRENCY' AND currency IS NOT NULL)
- )
- );
- `);
-
- await queryRunner.query(`
- CREATE INDEX idx_priority_configs_type_active ON freight.priority_configs (type, is_active);
- `);
-
- await queryRunner.query(`
- CREATE INDEX idx_priority_configs_currency_type ON freight.priority_configs (currency, type);
- `);
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`DROP TABLE IF EXISTS freight.priority_configs;`);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1784000000000-CreateSavedSignatures.ts b/apps/edr-freight-api/src/migrations/1784000000000-CreateSavedSignatures.ts
deleted file mode 100644
index e0c36f67b..000000000
--- a/apps/edr-freight-api/src/migrations/1784000000000-CreateSavedSignatures.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-export class CreateSavedSignatures1784000000000 implements MigrationInterface {
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- CREATE TABLE freight.saved_signatures (
- id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
- user_id UUID NOT NULL,
- signer_display_name VARCHAR(200) NOT NULL,
- signature_file_id UUID NULL,
- created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
- updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
- deleted_at TIMESTAMPTZ NULL,
- CONSTRAINT uq_saved_signatures_user_id UNIQUE (user_id)
- );
- `);
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`DROP TABLE IF EXISTS freight.saved_signatures;`);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1784000000001-SeedWagonsWithYardAssignment.ts b/apps/edr-freight-api/src/migrations/1784000000001-SeedWagonsWithYardAssignment.ts
deleted file mode 100644
index a615fe9a4..000000000
--- a/apps/edr-freight-api/src/migrations/1784000000001-SeedWagonsWithYardAssignment.ts
+++ /dev/null
@@ -1,156 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-/**
- * Full wagon re-seed — runs in this order:
- *
- * 1. DELETE all existing wagons (hard delete, not soft).
- * 2. UPSERT all 10 standard wagon types so they are guaranteed to exist.
- * 3. INSERT 50 wagons per wagon type (500 total), distributed evenly across
- * the 5 main operational yards (10 wagons per yard per type):
- *
- * KALITY — Kality Rail Terminal
- * MOJO — Mojo Dry Port
- * DIRE_DAWA — Dire Dawa Yard
- * DJIB_PORT — Djibouti Port Terminal
- * NAGAD — Nagad Terminal, Djibouti
- *
- * Wagon numbers follow the pattern -NNNN (e.g. NW5-0001 … NW5-0050).
- * Yard IDs are fetched live from freight.yards so the migration is safe across
- * all environments regardless of UUID values.
- */
-export class SeedWagonsWithYardAssignment1784000000001
- implements MigrationInterface
-{
- name = 'SeedWagonsWithYardAssignment1784000000001';
-
- public async up(queryRunner: QueryRunner): Promise {
- // ── STEP 1: Remove all wagons ──────────────────────────────────────────
- await queryRunner.query(`DELETE FROM freight.wagons;`);
-
- // ── STEP 2: Ensure all 10 wagon types exist ────────────────────────────
- await queryRunner.query(`
- INSERT INTO freight.wagon_types (
- code,
- name,
- capacity_tons,
- length_meters,
- max_wagons_per_train,
- supported_load_types,
- is_active,
- tare_weight_tons
- )
- VALUES
- ('NW7', 'Double deck sedan wagon', 22, 26.066, NULL, ARRAY['vehicles', 'sedan'], true, 18.0),
- ('NW5', 'Flat wagon (container)', 70, 14.000, 53, ARRAY['container', 'steel', 'machinery'], true, 22.0),
- ('PW2', 'Box wagon', 70, 17.066, 18, ARRAY['general cargo', 'break bulk'], true, 20.0),
- ('GW2', 'Tank wagon', 70, 12.228, 37, ARRAY['liquid', 'fuel'], true, 25.0),
- ('CW4', 'Gondola covered wagon', 70, 13.976, 37, ARRAY['covered bulk cargo'], true, 22.0),
- ('CW3', 'Gondola open wagon', 70, 13.976, NULL, ARRAY['open bulk cargo'], true, 20.0),
- ('KW2', 'Hopper covered wagon', 69, 16.466, NULL, ARRAY['bulk grains'], true, 22.0),
- ('KW3', 'Hopper open wagon', 70, 14.400, NULL, ARRAY['coal', 'bulk cargo'], true, 20.0),
- ('NW6', 'Flat wagon (long cargo)', 70, 18.560, NULL, ARRAY['long cargo'], true, 22.0),
- ('BW1', 'Refrigerated wagon', 38, 21.996, NULL, ARRAY['refrigerated cargo'], true, 24.0)
- ON CONFLICT (code) DO UPDATE SET
- name = EXCLUDED.name,
- capacity_tons = EXCLUDED.capacity_tons,
- length_meters = EXCLUDED.length_meters,
- max_wagons_per_train = EXCLUDED.max_wagons_per_train,
- supported_load_types = EXCLUDED.supported_load_types,
- is_active = true,
- tare_weight_tons = EXCLUDED.tare_weight_tons,
- deleted_at = NULL,
- updated_at = now();
- `);
-
- // ── STEP 3: Seed 50 wagons per type across 5 yards ────────────────────
- await queryRunner.query(`
- DO $$
- DECLARE
- wt RECORD;
- yard_kality UUID;
- yard_mojo UUID;
- yard_dire_dawa UUID;
- yard_djib_port UUID;
- yard_nagad UUID;
- yards UUID[];
- i INT;
- yard_id UUID;
- wagon_num TEXT;
- v_tare NUMERIC;
- v_payload NUMERIC;
- BEGIN
- -- Fetch yard IDs by code (safe across envs — UUIDs differ per DB)
- SELECT id INTO yard_kality FROM freight.yards WHERE code = 'KALITY' LIMIT 1;
- SELECT id INTO yard_mojo FROM freight.yards WHERE code = 'MOJO' LIMIT 1;
- SELECT id INTO yard_dire_dawa FROM freight.yards WHERE code = 'DIRE_DAWA' LIMIT 1;
- SELECT id INTO yard_djib_port FROM freight.yards WHERE code = 'DJIB_PORT' LIMIT 1;
- SELECT id INTO yard_nagad FROM freight.yards WHERE code = 'NAGAD' LIMIT 1;
-
- IF yard_kality IS NULL OR yard_mojo IS NULL OR yard_dire_dawa IS NULL
- OR yard_djib_port IS NULL OR yard_nagad IS NULL
- THEN
- RAISE EXCEPTION 'One or more operational yards not found. Run the yards seed first.';
- END IF;
-
- yards := ARRAY[
- yard_kality,
- yard_mojo,
- yard_dire_dawa,
- yard_djib_port,
- yard_nagad
- ];
-
- FOR wt IN
- SELECT id, code, capacity_tons, tare_weight_tons
- FROM freight.wagon_types
- WHERE is_active = true
- ORDER BY code
- LOOP
- v_tare := COALESCE(wt.tare_weight_tons, 20.0);
- v_payload := COALESCE(wt.capacity_tons, 60.0);
-
- FOR i IN 1 .. 50 LOOP
- wagon_num := wt.code || '-' || LPAD(i::TEXT, 4, '0');
- yard_id := yards[ ((i - 1) % 5) + 1 ]; -- round-robin: 1→K, 2→M, 3→D, 4→J, 5→N, 6→K …
-
- INSERT INTO freight.wagons (
- id,
- wagon_number,
- wagon_type_id,
- tare_weight,
- max_payload_weight,
- status,
- current_yard_id,
- train_id,
- sequence_number,
- notes,
- train_set_wagon_id,
- current_train_schedule_id,
- created_at,
- updated_at
- )
- VALUES (
- uuid_generate_v4(),
- wagon_num,
- wt.id,
- v_tare,
- v_payload,
- 'Available',
- yard_id,
- NULL, NULL, NULL, NULL, NULL,
- now(), now()
- )
- ON CONFLICT (wagon_number) DO NOTHING;
- END LOOP;
-
- RAISE NOTICE 'Seeded 50 wagons for type %.', wt.code;
- END LOOP;
- END $$;
- `);
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- // Remove all seeded wagons (full wipe — mirrors what up() did)
- await queryRunner.query(`DELETE FROM freight.wagons;`);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1784100000000-AddBookingRouteDayIndex.ts b/apps/edr-freight-api/src/migrations/1784100000000-AddBookingRouteDayIndex.ts
deleted file mode 100644
index 544600bef..000000000
--- a/apps/edr-freight-api/src/migrations/1784100000000-AddBookingRouteDayIndex.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-/**
- * Day-level booking pool: customers select a DAY (route + day), not a specific
- * train. The batch engine's pool query filters bookings on
- * (origin_yard_id, destination_yard_id, scheduled_date, status); this partial
- * index backs that scan.
- */
-export class AddBookingRouteDayIndex1784100000000 implements MigrationInterface {
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- CREATE INDEX IF NOT EXISTS idx_bookings_route_day
- ON freight.bookings (origin_yard_id, destination_yard_id, scheduled_date, status)
- WHERE deleted_at IS NULL;
- `);
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`DROP INDEX IF EXISTS freight.idx_bookings_route_day;`);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1790000000000-CreateWarehouseModule.ts b/apps/edr-freight-api/src/migrations/1790000000000-CreateWarehouseModule.ts
deleted file mode 100644
index 0f9021ff2..000000000
--- a/apps/edr-freight-api/src/migrations/1790000000000-CreateWarehouseModule.ts
+++ /dev/null
@@ -1,125 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-export class CreateWarehouseModule1790000000000 implements MigrationInterface {
- name = 'CreateWarehouseModule1790000000000';
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- CREATE TABLE IF NOT EXISTS freight.warehouses (
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
- name VARCHAR(160) NOT NULL,
- code VARCHAR(40) NOT NULL UNIQUE,
- type VARCHAR(32) NOT NULL,
- station_id UUID NULL,
- location_name VARCHAR(200) NULL,
- capacity_weight NUMERIC(14,3) NULL,
- capacity_containers INT NULL,
- current_weight NUMERIC(14,3) NOT NULL DEFAULT 0,
- current_containers INT NOT NULL DEFAULT 0,
- status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE',
- is_active BOOLEAN NOT NULL DEFAULT true,
- created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
- updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
- deleted_at TIMESTAMPTZ NULL
- );
- `);
-
- await queryRunner.query(`
- CREATE TABLE IF NOT EXISTS freight.warehouse_yards (
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
- warehouse_id UUID NOT NULL REFERENCES freight.warehouses(id) ON DELETE CASCADE,
- name VARCHAR(160) NOT NULL,
- code VARCHAR(40) NOT NULL,
- type VARCHAR(32) NOT NULL,
- capacity_weight NUMERIC(14,3) NULL,
- capacity_containers INT NULL,
- current_weight NUMERIC(14,3) NOT NULL DEFAULT 0,
- current_containers INT NOT NULL DEFAULT 0,
- status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE',
- is_active BOOLEAN NOT NULL DEFAULT true,
- created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
- updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
- deleted_at TIMESTAMPTZ NULL,
- CONSTRAINT uq_warehouse_yards_code UNIQUE (warehouse_id, code)
- );
- `);
-
- await queryRunner.query(`
- CREATE TABLE IF NOT EXISTS freight.warehouse_zones (
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
- yard_id UUID NOT NULL REFERENCES freight.warehouse_yards(id) ON DELETE CASCADE,
- name VARCHAR(160) NOT NULL,
- code VARCHAR(40) NOT NULL,
- type VARCHAR(32) NOT NULL,
- capacity_weight NUMERIC(14,3) NULL,
- capacity_containers INT NULL,
- current_weight NUMERIC(14,3) NOT NULL DEFAULT 0,
- current_containers INT NOT NULL DEFAULT 0,
- status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE',
- is_active BOOLEAN NOT NULL DEFAULT true,
- created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
- updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
- deleted_at TIMESTAMPTZ NULL,
- CONSTRAINT uq_warehouse_zones_code UNIQUE (yard_id, code)
- );
- `);
-
- await queryRunner.query(`
- CREATE TABLE IF NOT EXISTS freight.warehouse_inventory (
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
- warehouse_id UUID NOT NULL REFERENCES freight.warehouses(id),
- yard_id UUID NOT NULL REFERENCES freight.warehouse_yards(id),
- zone_id UUID NOT NULL REFERENCES freight.warehouse_zones(id),
- booking_id UUID NOT NULL,
- cargo_id UUID NULL,
- container_id UUID NULL,
- goods_id UUID NULL,
- quantity NUMERIC(12,3) NOT NULL DEFAULT 0,
- weight NUMERIC(14,3) NOT NULL DEFAULT 0,
- volume NUMERIC(12,3) NULL,
- status VARCHAR(32) NOT NULL DEFAULT 'ARRIVED_AT_WAREHOUSE',
- inspection_status VARCHAR(20) NULL,
- arrived_at TIMESTAMPTZ NULL,
- inspected_at TIMESTAMPTZ NULL,
- ready_for_loading_at TIMESTAMPTZ NULL,
- notes TEXT NULL,
- created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
- updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
- deleted_at TIMESTAMPTZ NULL
- );
- `);
-
- const indexes: Array<[string, string, string]> = [
- ['idx_warehouses_type', 'warehouses', 'type'],
- ['idx_warehouses_status', 'warehouses', 'status'],
- ['idx_warehouses_station_id', 'warehouses', 'station_id'],
- ['idx_warehouse_yards_warehouse_id', 'warehouse_yards', 'warehouse_id'],
- ['idx_warehouse_yards_type', 'warehouse_yards', 'type'],
- ['idx_warehouse_yards_status', 'warehouse_yards', 'status'],
- ['idx_warehouse_zones_yard_id', 'warehouse_zones', 'yard_id'],
- ['idx_warehouse_zones_type', 'warehouse_zones', 'type'],
- ['idx_warehouse_zones_status', 'warehouse_zones', 'status'],
- ['idx_warehouse_inventory_warehouse_id', 'warehouse_inventory', 'warehouse_id'],
- ['idx_warehouse_inventory_yard_id', 'warehouse_inventory', 'yard_id'],
- ['idx_warehouse_inventory_zone_id', 'warehouse_inventory', 'zone_id'],
- ['idx_warehouse_inventory_booking_id', 'warehouse_inventory', 'booking_id'],
- ['idx_warehouse_inventory_cargo_id', 'warehouse_inventory', 'cargo_id'],
- ['idx_warehouse_inventory_container_id', 'warehouse_inventory', 'container_id'],
- ['idx_warehouse_inventory_goods_id', 'warehouse_inventory', 'goods_id'],
- ['idx_warehouse_inventory_status', 'warehouse_inventory', 'status'],
- ];
-
- for (const [indexName, table, column] of indexes) {
- await queryRunner.query(
- `CREATE INDEX IF NOT EXISTS ${indexName} ON freight.${table}(${column});`,
- );
- }
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`DROP TABLE IF EXISTS freight.warehouse_inventory;`);
- await queryRunner.query(`DROP TABLE IF EXISTS freight.warehouse_zones;`);
- await queryRunner.query(`DROP TABLE IF EXISTS freight.warehouse_yards;`);
- await queryRunner.query(`DROP TABLE IF EXISTS freight.warehouses;`);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1790000000001-WarehouseBatch2.ts b/apps/edr-freight-api/src/migrations/1790000000001-WarehouseBatch2.ts
deleted file mode 100644
index a1431d7bd..000000000
--- a/apps/edr-freight-api/src/migrations/1790000000001-WarehouseBatch2.ts
+++ /dev/null
@@ -1,123 +0,0 @@
-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;
- `);
- }
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1790000000002-WarehouseBatch3.ts b/apps/edr-freight-api/src/migrations/1790000000002-WarehouseBatch3.ts
deleted file mode 100644
index 4dcc9329c..000000000
--- a/apps/edr-freight-api/src/migrations/1790000000002-WarehouseBatch3.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-/**
- * Batch 3 — Warehouse → Loading → Train Departure visibility.
- * Adds the warehouse_loadings record (inventory ↔ wagon). Does NOT touch any
- * scheduling / wagon tables — the warehouse only reads from those.
- */
-export class WarehouseBatch31790000000002 implements MigrationInterface {
- name = 'WarehouseBatch31790000000002';
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- CREATE TABLE IF NOT EXISTS freight.warehouse_loadings (
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
- warehouse_inventory_id UUID NOT NULL REFERENCES freight.warehouse_inventory(id) ON DELETE CASCADE,
- booking_id UUID NULL,
- wagon_id UUID NOT NULL,
- loaded_at TIMESTAMPTZ NOT NULL DEFAULT now(),
- loaded_by VARCHAR(120) NULL,
- loaded_weight NUMERIC(14,3) NULL,
- notes 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_warehouse_loadings_inventory_id
- ON freight.warehouse_loadings(warehouse_inventory_id);
- `);
- await queryRunner.query(`
- CREATE INDEX IF NOT EXISTS idx_warehouse_loadings_booking_id
- ON freight.warehouse_loadings(booking_id);
- `);
- await queryRunner.query(`
- CREATE INDEX IF NOT EXISTS idx_warehouse_loadings_wagon_id
- ON freight.warehouse_loadings(wagon_id);
- `);
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`DROP TABLE IF EXISTS freight.warehouse_loadings;`);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1791000000000-AddActiveModeAndOnboardingToExternalProfiles.ts b/apps/edr-freight-api/src/migrations/1791000000000-AddActiveModeAndOnboardingToExternalProfiles.ts
deleted file mode 100644
index 8484ca0f9..000000000
--- a/apps/edr-freight-api/src/migrations/1791000000000-AddActiveModeAndOnboardingToExternalProfiles.ts
+++ /dev/null
@@ -1,67 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-export class AddActiveModeAndOnboardingToExternalProfiles1791000000000
- implements MigrationInterface
-{
- name = 'AddActiveModeAndOnboardingToExternalProfiles1791000000000';
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- ALTER TABLE freight.external_profiles
- ADD COLUMN IF NOT EXISTS active_profile_type varchar(32);
- `);
-
- await queryRunner.query(`
- ALTER TABLE freight.external_profiles
- ADD COLUMN IF NOT EXISTS onboarding_step varchar(40);
- `);
-
- await queryRunner.query(`
- ALTER TABLE freight.external_profiles
- ADD COLUMN IF NOT EXISTS onboarding_completed boolean NOT NULL DEFAULT false;
- `);
-
- // Existing users already use the portal — never re-gate them behind the
- // new onboarding wizard.
- await queryRunner.query(`
- UPDATE freight.external_profiles
- SET onboarding_completed = true
- WHERE onboarding_completed = false;
- `);
-
- // Backfill the active mode for existing users from their company's
- // operational profiles. Prefer importer, then exporter, then whichever
- // single profile the company has (forwarder/dj/transporter).
- await queryRunner.query(`
- UPDATE freight.external_profiles ep
- SET active_profile_type = cp.type
- FROM (
- SELECT DISTINCT ON (company_id) company_id, type
- FROM freight.company_profiles
- ORDER BY company_id,
- CASE type
- WHEN 'importer' THEN 0
- WHEN 'exporter' THEN 1
- ELSE 2
- END
- ) cp
- WHERE ep.company_id = cp.company_id
- AND ep.active_profile_type IS NULL;
- `);
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- ALTER TABLE freight.external_profiles
- DROP COLUMN IF EXISTS onboarding_completed;
- `);
- await queryRunner.query(`
- ALTER TABLE freight.external_profiles
- DROP COLUMN IF EXISTS onboarding_step;
- `);
- await queryRunner.query(`
- ALTER TABLE freight.external_profiles
- DROP COLUMN IF EXISTS active_profile_type;
- `);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1791000000000-AddWarehouseAllocationAndFeeRules.ts b/apps/edr-freight-api/src/migrations/1791000000000-AddWarehouseAllocationAndFeeRules.ts
deleted file mode 100644
index 1cad0fe66..000000000
--- a/apps/edr-freight-api/src/migrations/1791000000000-AddWarehouseAllocationAndFeeRules.ts
+++ /dev/null
@@ -1,126 +0,0 @@
-import { MigrationInterface, QueryRunner, Table, TableColumn } from 'typeorm';
-
-/**
- * Batch 5 — warehouse allocation rules, storage/demurrage fee rules,
- * and demurrage lifecycle timestamps on inventory. Idempotent.
- */
-export class AddWarehouseAllocationAndFeeRules1791000000000 implements MigrationInterface {
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.createTable(
- new Table({
- schema: 'freight',
- name: 'warehouse_allocation_rules',
- columns: [
- { name: 'id', type: 'uuid', isPrimary: true, default: 'uuid_generate_v4()' },
- { name: 'name', type: 'varchar', length: '160' },
- { name: 'priority', type: 'int', default: 100 },
- { name: 'freight_type', type: 'varchar', length: '16', isNullable: true },
- { name: 'trade_direction', type: 'varchar', length: '16', isNullable: true },
- { name: 'cargo_type_code', type: 'varchar', length: '50', isNullable: true },
- { name: 'container_status', type: 'varchar', length: '24', isNullable: true },
- { name: 'requires_inspection', type: 'boolean', isNullable: true },
- { name: 'target_facility_code', type: 'varchar', length: '40', isNullable: true },
- { name: 'target_yard_code', type: 'varchar', length: '40' },
- { name: 'target_warehouse_code', type: 'varchar', length: '40', isNullable: true },
- { name: 'target_zone_code', type: 'varchar', length: '40', isNullable: true },
- { name: 'storage_type', type: 'varchar', length: '80', isNullable: true },
- { name: 'is_active', type: 'boolean', default: true },
- { name: 'created_at', type: 'timestamptz', default: 'now()' },
- { name: 'updated_at', type: 'timestamptz', default: 'now()' },
- { name: 'deleted_at', type: 'timestamptz', isNullable: true },
- ],
- indices: [
- { name: 'idx_war_priority', columnNames: ['priority'] },
- { name: 'idx_war_active', columnNames: ['is_active'] },
- ],
- }),
- true,
- );
-
- await queryRunner.createTable(
- new Table({
- schema: 'freight',
- name: 'warehouse_fee_rules',
- columns: [
- { name: 'id', type: 'uuid', isPrimary: true, default: 'uuid_generate_v4()' },
- { name: 'name', type: 'varchar', length: '160' },
- { name: 'rule_type', type: 'varchar', length: '20' },
- { name: 'priority', type: 'int', default: 100 },
- { name: 'freight_type', type: 'varchar', length: '16', isNullable: true },
- { name: 'trade_direction', type: 'varchar', length: '16', isNullable: true },
- { name: 'cargo_type_code', type: 'varchar', length: '50', isNullable: true },
- { name: 'container_type', type: 'varchar', length: '40', isNullable: true },
- { name: 'facility_id', type: 'uuid', isNullable: true },
- { name: 'warehouse_id', type: 'uuid', isNullable: true },
- { name: 'yard_id', type: 'uuid', isNullable: true },
- { name: 'zone_id', type: 'uuid', isNullable: true },
- { name: 'free_days', type: 'int', default: 0 },
- { name: 'rate_per_day', type: 'numeric', precision: 14, scale: 2, default: 0 },
- { name: 'tiers', type: 'jsonb', default: "'[]'" },
- { name: 'currency', type: 'varchar', length: '8', default: "'USD'" },
- { name: 'is_active', type: 'boolean', default: true },
- { name: 'created_at', type: 'timestamptz', default: 'now()' },
- { name: 'updated_at', type: 'timestamptz', default: 'now()' },
- { name: 'deleted_at', type: 'timestamptz', isNullable: true },
- ],
- indices: [
- { name: 'idx_wfr_type', columnNames: ['rule_type'] },
- { name: 'idx_wfr_active', columnNames: ['is_active'] },
- ],
- }),
- true,
- );
-
- const inventoryTable = await queryRunner.getTable('freight.warehouse_inventory');
- if (inventoryTable) {
- const columnsToAdd = [
- { name: 'inspection_started_at', type: 'timestamptz', isNullable: true },
- { name: 'inspection_completed_at', type: 'timestamptz', isNullable: true },
- { name: 'ready_for_pickup_at', type: 'timestamptz', isNullable: true },
- { name: 'release_date', type: 'timestamptz', isNullable: true },
- { name: 'gate_cleared_at', type: 'timestamptz', isNullable: true },
- ];
-
- const columnsToCreate = columnsToAdd.filter(
- (col) => !inventoryTable.columns.some((c) => c.name === col.name),
- );
-
- if (columnsToCreate.length > 0) {
- await queryRunner.addColumns(
- 'freight.warehouse_inventory',
- columnsToCreate.map((col) => new TableColumn(col)),
- );
- }
- }
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- const inventoryTable = await queryRunner.getTable('freight.warehouse_inventory');
- if (inventoryTable) {
- const columnNames = [
- 'inspection_started_at',
- 'inspection_completed_at',
- 'ready_for_pickup_at',
- 'release_date',
- 'gate_cleared_at',
- ];
- const columnsToRemove = columnNames.filter((name) =>
- inventoryTable.columns.some((c) => c.name === name),
- );
-
- if (columnsToRemove.length > 0) {
- await queryRunner.dropColumns('freight.warehouse_inventory', columnsToRemove);
- }
- }
-
- const feeRulesTable = await queryRunner.getTable('freight.warehouse_fee_rules');
- if (feeRulesTable) {
- await queryRunner.dropTable('freight.warehouse_fee_rules', true);
- }
-
- const allocationRulesTable = await queryRunner.getTable('freight.warehouse_allocation_rules');
- if (allocationRulesTable) {
- await queryRunner.dropTable('freight.warehouse_allocation_rules', true);
- }
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1791000000001-AddCompanyProfileIdToBookings.ts b/apps/edr-freight-api/src/migrations/1791000000001-AddCompanyProfileIdToBookings.ts
deleted file mode 100644
index 0ca1a0d28..000000000
--- a/apps/edr-freight-api/src/migrations/1791000000001-AddCompanyProfileIdToBookings.ts
+++ /dev/null
@@ -1,94 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-export class AddCompanyProfileIdToBookings1791000000001
- implements MigrationInterface
-{
- name = 'AddCompanyProfileIdToBookings1791000000001';
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- ALTER TABLE freight.bookings
- ADD COLUMN IF NOT EXISTS company_profile_id UUID;
- `);
-
- await queryRunner.query(`
- CREATE INDEX IF NOT EXISTS idx_bookings_company_profile_id
- ON freight.bookings(company_profile_id);
- `);
-
- await queryRunner.query(`
- DO $$
- BEGIN
- IF NOT EXISTS (
- SELECT 1 FROM pg_constraint WHERE conname = 'FK_bookings_company_profile_id'
- ) THEN
- ALTER TABLE freight.bookings
- ADD CONSTRAINT "FK_bookings_company_profile_id"
- FOREIGN KEY (company_profile_id)
- REFERENCES freight.company_profiles(id);
- END IF;
- END $$;
- `);
-
- // Backfill by natural mapping: IMPORT → importer profile, EXPORT → exporter
- // profile, for each booking's own company.
- await queryRunner.query(`
- UPDATE freight.bookings b
- SET company_profile_id = cp.id
- FROM freight.company_profiles cp
- WHERE cp.company_id = b.company_id
- AND b.company_profile_id IS NULL
- AND (
- (b.trade_direction = 'IMPORT' AND cp.type = 'importer') OR
- (b.trade_direction = 'EXPORT' AND cp.type = 'exporter')
- );
- `);
-
- // Forwarder / single-profile companies: one profile per company, so the
- // mapping is unambiguous regardless of trade direction.
- await queryRunner.query(`
- UPDATE freight.bookings b
- SET company_profile_id = cp.id
- FROM freight.company_profiles cp
- JOIN freight.companies c ON c.id = cp.company_id
- WHERE cp.company_id = b.company_id
- AND c.type <> 'customer'
- AND b.company_profile_id IS NULL;
- `);
-
- // Remaining customer-owned rows (e.g. DOMESTIC, or a direction with no
- // matching profile): attribute to the company's importer profile, else its
- // exporter profile, so nothing disappears from the customer's list.
- await queryRunner.query(`
- UPDATE freight.bookings b
- SET company_profile_id = cp.id
- FROM (
- SELECT DISTINCT ON (company_id) company_id, id
- FROM freight.company_profiles
- ORDER BY company_id,
- CASE type
- WHEN 'importer' THEN 0
- WHEN 'exporter' THEN 1
- ELSE 2
- END
- ) cp
- WHERE cp.company_id = b.company_id
- AND b.company_id IS NOT NULL
- AND b.company_profile_id IS NULL;
- `);
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- ALTER TABLE freight.bookings
- DROP CONSTRAINT IF EXISTS "FK_bookings_company_profile_id";
- `);
- await queryRunner.query(`
- DROP INDEX IF EXISTS freight.idx_bookings_company_profile_id;
- `);
- await queryRunner.query(`
- ALTER TABLE freight.bookings
- DROP COLUMN IF EXISTS company_profile_id;
- `);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1791000000001-AddWarehouseFeeInvoices.ts b/apps/edr-freight-api/src/migrations/1791000000001-AddWarehouseFeeInvoices.ts
deleted file mode 100644
index 662a35739..000000000
--- a/apps/edr-freight-api/src/migrations/1791000000001-AddWarehouseFeeInvoices.ts
+++ /dev/null
@@ -1,88 +0,0 @@
-import { MigrationInterface, QueryRunner, Table } from 'typeorm';
-
-/** Batch 6 — warehouse fee invoices + invoice items. Idempotent (createTable ifNotExists). */
-export class AddWarehouseFeeInvoices1791000000001 implements MigrationInterface {
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.createTable(
- new Table({
- schema: 'freight',
- name: 'warehouse_fee_invoices',
- columns: [
- { name: 'id', type: 'uuid', isPrimary: true, default: 'uuid_generate_v4()' },
- { name: 'invoice_number', type: 'varchar', length: '40', isUnique: true },
- { name: 'booking_id', type: 'uuid', isNullable: true },
- { name: 'customer_id', type: 'uuid', isNullable: true },
- { name: 'inventory_id', type: 'uuid' },
- { name: 'facility_id', type: 'uuid', isNullable: true },
- { name: 'warehouse_id', type: 'uuid', isNullable: true },
- { name: 'yard_id', type: 'uuid', isNullable: true },
- { name: 'zone_id', type: 'uuid', isNullable: true },
- { name: 'invoice_type', type: 'varchar', length: '32', default: "'MIXED_WAREHOUSE_FEES'" },
- { name: 'status', type: 'varchar', length: '20', default: "'DRAFT'" },
- { name: 'subtotal_amount', type: 'numeric', precision: 14, scale: 2, default: 0 },
- { name: 'tax_amount', type: 'numeric', precision: 14, scale: 2, default: 0 },
- { name: 'total_amount', type: 'numeric', precision: 14, scale: 2, default: 0 },
- { name: 'paid_amount', type: 'numeric', precision: 14, scale: 2, default: 0 },
- { name: 'balance_amount', type: 'numeric', precision: 14, scale: 2, default: 0 },
- { name: 'currency', type: 'varchar', length: '8', default: "'USD'" },
- { name: 'period_start', type: 'timestamptz', isNullable: true },
- { name: 'period_end', type: 'timestamptz', isNullable: true },
- { name: 'issued_at', type: 'timestamptz', isNullable: true },
- { name: 'due_date', type: 'timestamptz', isNullable: true },
- { name: 'paid_at', type: 'timestamptz', isNullable: true },
- { name: 'cancelled_at', type: 'timestamptz', isNullable: true },
- { name: 'payments', type: 'jsonb', default: "'[]'" },
- { name: 'notes', type: 'text', isNullable: true },
- { name: 'created_at', type: 'timestamptz', default: 'now()' },
- { name: 'updated_at', type: 'timestamptz', default: 'now()' },
- { name: 'deleted_at', type: 'timestamptz', isNullable: true },
- ],
- indices: [
- { name: 'idx_wfi_booking', columnNames: ['booking_id'] },
- { name: 'idx_wfi_inventory', columnNames: ['inventory_id'] },
- { name: 'idx_wfi_status', columnNames: ['status'] },
- ],
- }),
- true,
- );
-
- await queryRunner.createTable(
- new Table({
- schema: 'freight',
- name: 'warehouse_fee_invoice_items',
- columns: [
- { name: 'id', type: 'uuid', isPrimary: true, default: 'uuid_generate_v4()' },
- { name: 'invoice_id', type: 'uuid' },
- { name: 'fee_rule_id', type: 'uuid', isNullable: true },
- { name: 'fee_type', type: 'varchar', length: '32' },
- { name: 'description', type: 'varchar', length: '255' },
- { name: 'quantity', type: 'numeric', precision: 12, scale: 2, default: 1 },
- { name: 'unit_rate', type: 'numeric', precision: 14, scale: 2, default: 0 },
- { name: 'amount', type: 'numeric', precision: 14, scale: 2, default: 0 },
- { name: 'currency', type: 'varchar', length: '8', default: "'USD'" },
- { name: 'chargeable_days', type: 'int', isNullable: true },
- { name: 'free_days', type: 'int', isNullable: true },
- { name: 'created_at', type: 'timestamptz', default: 'now()' },
- { name: 'updated_at', type: 'timestamptz', default: 'now()' },
- { name: 'deleted_at', type: 'timestamptz', isNullable: true },
- ],
- foreignKeys: [
- {
- columnNames: ['invoice_id'],
- referencedSchema: 'freight',
- referencedTableName: 'warehouse_fee_invoices',
- referencedColumnNames: ['id'],
- onDelete: 'CASCADE',
- },
- ],
- indices: [{ name: 'idx_wfii_invoice', columnNames: ['invoice_id'] }],
- }),
- true,
- );
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.dropTable('freight.warehouse_fee_invoice_items', true);
- await queryRunner.dropTable('freight.warehouse_fee_invoices', true);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1791000000002-AddImportPickupDeliveryColumns.ts b/apps/edr-freight-api/src/migrations/1791000000002-AddImportPickupDeliveryColumns.ts
deleted file mode 100644
index 3e272c859..000000000
--- a/apps/edr-freight-api/src/migrations/1791000000002-AddImportPickupDeliveryColumns.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-import { MigrationInterface, QueryRunner, TableColumn } from 'typeorm';
-
-/**
- * Import pickup branch on warehouse_inventory:
- * - release_order_reference: DO / release order number sent to the customer
- * - delivered_at: when the goods were handed over (proof of delivery)
- *
- * Idempotent: the shared dev DB may already carry some of these columns
- * (added by another checkout), so only add what is missing.
- */
-export class AddImportPickupDeliveryColumns1791000000002 implements MigrationInterface {
- private readonly table = 'freight.warehouse_inventory';
-
- public async up(queryRunner: QueryRunner): Promise {
- if (!(await queryRunner.hasColumn(this.table, 'release_order_reference'))) {
- await queryRunner.addColumn(
- this.table,
- new TableColumn({ name: 'release_order_reference', type: 'varchar', length: '100', isNullable: true }),
- );
- }
- if (!(await queryRunner.hasColumn(this.table, 'delivered_at'))) {
- await queryRunner.addColumn(
- this.table,
- new TableColumn({ name: 'delivered_at', type: 'timestamptz', isNullable: true }),
- );
- }
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- if (await queryRunner.hasColumn(this.table, 'release_order_reference')) {
- await queryRunner.dropColumn(this.table, 'release_order_reference');
- }
- if (await queryRunner.hasColumn(this.table, 'delivered_at')) {
- await queryRunner.dropColumn(this.table, 'delivered_at');
- }
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1791000000002-AddNationalityToCompanies.ts b/apps/edr-freight-api/src/migrations/1791000000002-AddNationalityToCompanies.ts
deleted file mode 100644
index 1a05a9e45..000000000
--- a/apps/edr-freight-api/src/migrations/1791000000002-AddNationalityToCompanies.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import { MigrationInterface, QueryRunner } from "typeorm";
-
-export class AddNationalityToCompanies1791000000002
- implements MigrationInterface
-{
- name = "AddNationalityToCompanies1791000000002";
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- ALTER TABLE freight.companies
- ADD COLUMN IF NOT EXISTS nationality varchar(32);
- `);
-
- // Existing companies default to Ethiopian (country defaults to Ethiopia).
- await queryRunner.query(`
- UPDATE freight.companies
- SET nationality = 'ethiopian'
- WHERE nationality IS NULL;
- `);
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- ALTER TABLE freight.companies
- DROP COLUMN IF EXISTS nationality;
- `);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1791000000003-AddBusinessLicenseFilesToCompanyProfiles.ts b/apps/edr-freight-api/src/migrations/1791000000003-AddBusinessLicenseFilesToCompanyProfiles.ts
deleted file mode 100644
index d1e0412f0..000000000
--- a/apps/edr-freight-api/src/migrations/1791000000003-AddBusinessLicenseFilesToCompanyProfiles.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { MigrationInterface, QueryRunner } from "typeorm";
-
-export class AddBusinessLicenseFilesToCompanyProfiles1791000000003
- implements MigrationInterface
-{
- name = "AddBusinessLicenseFilesToCompanyProfiles1791000000003";
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- ALTER TABLE freight.company_profiles
- ADD COLUMN IF NOT EXISTS business_license_files jsonb;
- `);
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- ALTER TABLE freight.company_profiles
- DROP COLUMN IF EXISTS business_license_files;
- `);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1791000000003-AddETradeFieldsToCompanies.ts b/apps/edr-freight-api/src/migrations/1791000000003-AddETradeFieldsToCompanies.ts
deleted file mode 100644
index 07f0d2555..000000000
--- a/apps/edr-freight-api/src/migrations/1791000000003-AddETradeFieldsToCompanies.ts
+++ /dev/null
@@ -1,109 +0,0 @@
-import { MigrationInterface, QueryRunner } from "typeorm";
-
-export class AddETradeFieldsToCompanies1791000000003
- implements MigrationInterface
-{
- name = "AddETradeFieldsToCompanies1791000000003";
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- ALTER TABLE freight.companies
- ADD COLUMN IF NOT EXISTS licence_number varchar(100);
- `);
- await queryRunner.query(`
- ALTER TABLE freight.companies
- ADD COLUMN IF NOT EXISTS status_description text;
- `);
- await queryRunner.query(`
- ALTER TABLE freight.companies
- ADD COLUMN IF NOT EXISTS date_registered varchar(50);
- `);
- await queryRunner.query(`
- ALTER TABLE freight.companies
- ADD COLUMN IF NOT EXISTS renewed_from varchar(50);
- `);
- await queryRunner.query(`
- ALTER TABLE freight.companies
- ADD COLUMN IF NOT EXISTS renewal_date varchar(50);
- `);
- await queryRunner.query(`
- ALTER TABLE freight.companies
- ADD COLUMN IF NOT EXISTS renewed_to varchar(50);
- `);
- await queryRunner.query(`
- ALTER TABLE freight.companies
- ADD COLUMN IF NOT EXISTS region varchar(100);
- `);
- await queryRunner.query(`
- ALTER TABLE freight.companies
- ADD COLUMN IF NOT EXISTS zone varchar(100);
- `);
- await queryRunner.query(`
- ALTER TABLE freight.companies
- ADD COLUMN IF NOT EXISTS woreda varchar(100);
- `);
- await queryRunner.query(`
- ALTER TABLE freight.companies
- ADD COLUMN IF NOT EXISTS kebele varchar(100);
- `);
- await queryRunner.query(`
- ALTER TABLE freight.companies
- ADD COLUMN IF NOT EXISTS house_no varchar(100);
- `);
- await queryRunner.query(`
- ALTER TABLE freight.companies
- ADD COLUMN IF NOT EXISTS etrade_phone varchar(20);
- `);
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- ALTER TABLE freight.companies
- DROP COLUMN IF EXISTS licence_number;
- `);
- await queryRunner.query(`
- ALTER TABLE freight.companies
- DROP COLUMN IF EXISTS status_description;
- `);
- await queryRunner.query(`
- ALTER TABLE freight.companies
- DROP COLUMN IF EXISTS date_registered;
- `);
- await queryRunner.query(`
- ALTER TABLE freight.companies
- DROP COLUMN IF EXISTS renewed_from;
- `);
- await queryRunner.query(`
- ALTER TABLE freight.companies
- DROP COLUMN IF EXISTS renewal_date;
- `);
- await queryRunner.query(`
- ALTER TABLE freight.companies
- DROP COLUMN IF EXISTS renewed_to;
- `);
- await queryRunner.query(`
- ALTER TABLE freight.companies
- DROP COLUMN IF EXISTS region;
- `);
- await queryRunner.query(`
- ALTER TABLE freight.companies
- DROP COLUMN IF EXISTS zone;
- `);
- await queryRunner.query(`
- ALTER TABLE freight.companies
- DROP COLUMN IF EXISTS woreda;
- `);
- await queryRunner.query(`
- ALTER TABLE freight.companies
- DROP COLUMN IF EXISTS kebele;
- `);
- await queryRunner.query(`
- ALTER TABLE freight.companies
- DROP COLUMN IF EXISTS house_no;
- `);
- await queryRunner.query(`
- ALTER TABLE freight.companies
- DROP COLUMN IF EXISTS etrade_phone;
- `);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1791000000003-AddInventoryUnloadedAt.ts b/apps/edr-freight-api/src/migrations/1791000000003-AddInventoryUnloadedAt.ts
deleted file mode 100644
index 6009d9ea4..000000000
--- a/apps/edr-freight-api/src/migrations/1791000000003-AddInventoryUnloadedAt.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-import { MigrationInterface, QueryRunner, TableColumn } from 'typeorm';
-
-/**
- * Batch 8 — train-arrival unload landing state on warehouse_inventory:
- * - unloaded_at: when the goods were unloaded off the arrived train (before storage/inspection)
- *
- * The `status` column is a free varchar, so the new 'UNLOADED' value needs no schema change.
- * Idempotent: the shared dev DB may already carry this column (added by another checkout).
- */
-export class AddInventoryUnloadedAt1791000000003 implements MigrationInterface {
- private readonly table = 'freight.warehouse_inventory';
-
- public async up(queryRunner: QueryRunner): Promise {
- if (!(await queryRunner.hasColumn(this.table, 'unloaded_at'))) {
- await queryRunner.addColumn(
- this.table,
- new TableColumn({ name: 'unloaded_at', type: 'timestamptz', isNullable: true }),
- );
- }
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- if (await queryRunner.hasColumn(this.table, 'unloaded_at')) {
- await queryRunner.dropColumn(this.table, 'unloaded_at');
- }
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1791000000004-AddFacilityIdToWarehousesFix.ts b/apps/edr-freight-api/src/migrations/1791000000004-AddFacilityIdToWarehousesFix.ts
deleted file mode 100644
index df239a375..000000000
--- a/apps/edr-freight-api/src/migrations/1791000000004-AddFacilityIdToWarehousesFix.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-import { MigrationInterface, QueryRunner, TableColumn, TableForeignKey } from 'typeorm';
-
-/**
- * Fix: migration 1750000000001 (AddFacilityIdToWarehouses) silently skipped because
- * the freight.warehouses table didn't exist yet at that timestamp. The column was
- * never added. Add it now with idempotent guards.
- */
-export class AddFacilityIdToWarehousesFix1791000000004 implements MigrationInterface {
- private readonly table = 'freight.warehouses';
-
- public async up(queryRunner: QueryRunner): Promise {
- if (!(await queryRunner.hasColumn(this.table, 'facility_id'))) {
- await queryRunner.addColumn(
- this.table,
- new TableColumn({
- name: 'facility_id',
- type: 'uuid',
- isNullable: true,
- }),
- );
- }
-
- const table = await queryRunner.getTable(this.table);
- const hasFk = table?.foreignKeys.some((fk) => fk.columnNames.includes('facility_id'));
- if (!hasFk) {
- await queryRunner.createForeignKey(
- this.table,
- new TableForeignKey({
- columnNames: ['facility_id'],
- referencedColumnNames: ['id'],
- referencedTableName: 'facilities',
- referencedSchema: 'freight',
- onDelete: 'SET NULL',
- }),
- );
- }
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- const table = await queryRunner.getTable(this.table);
- if (!table) return;
-
- const foreignKey = table.foreignKeys.find((fk) => fk.columnNames.includes('facility_id'));
- if (foreignKey) {
- await queryRunner.dropForeignKey(this.table, foreignKey);
- }
-
- if (await queryRunner.hasColumn(this.table, 'facility_id')) {
- await queryRunner.dropColumn(this.table, 'facility_id');
- }
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1791000000005-AddWarehouseInventoryInspectionStatusFix.ts b/apps/edr-freight-api/src/migrations/1791000000005-AddWarehouseInventoryInspectionStatusFix.ts
deleted file mode 100644
index 4a59e33b1..000000000
--- a/apps/edr-freight-api/src/migrations/1791000000005-AddWarehouseInventoryInspectionStatusFix.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import { MigrationInterface, QueryRunner, TableColumn } from 'typeorm';
-
-/**
- * Fix for fresh deployments: AddWarehouseInspection1750000000003 runs before
- * the warehouse_inventory table exists, so it cannot add inspection_status.
- */
-export class AddWarehouseInventoryInspectionStatusFix1791000000005 implements MigrationInterface {
- private readonly table = 'freight.warehouse_inventory';
-
- public async up(queryRunner: QueryRunner): Promise {
- if ((await queryRunner.hasTable(this.table)) && !(await queryRunner.hasColumn(this.table, 'inspection_status'))) {
- await queryRunner.addColumn(
- this.table,
- new TableColumn({ name: 'inspection_status', type: 'varchar', length: '20', isNullable: true }),
- );
- }
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- if ((await queryRunner.hasTable(this.table)) && (await queryRunner.hasColumn(this.table, 'inspection_status'))) {
- await queryRunner.dropColumn(this.table, 'inspection_status');
- }
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1791999999999-CreateDropdownSettings.ts b/apps/edr-freight-api/src/migrations/1791999999999-CreateDropdownSettings.ts
deleted file mode 100644
index d86efa239..000000000
--- a/apps/edr-freight-api/src/migrations/1791999999999-CreateDropdownSettings.ts
+++ /dev/null
@@ -1,70 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-/**
- * Creates the generic dropdown settings tables (freight.dropdown_settings +
- * freight.dropdown_options) backing the DropdownSetting / DropdownOption
- * entities. These tables previously only existed via `synchronize` on some
- * databases; this migration makes them part of the migration history so the
- * SeedGeneralContractPeriod migration (which inserts into them) can run on a
- * fresh database. Idempotent so it is safe on DBs where the tables already exist.
- */
-export class CreateDropdownSettings1791999999999
- implements MigrationInterface
-{
- name = 'CreateDropdownSettings1791999999999';
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- CREATE TABLE IF NOT EXISTS "freight"."dropdown_settings" (
- "id" uuid NOT NULL DEFAULT gen_random_uuid(),
- "code" varchar(128) NOT NULL,
- "label" varchar(256) NOT NULL,
- "description" text,
- "multiple" boolean NOT NULL DEFAULT false,
- "meta" jsonb,
- "created_at" timestamptz NOT NULL DEFAULT now(),
- "updated_at" timestamptz NOT NULL DEFAULT now(),
- "deleted_at" timestamptz,
- CONSTRAINT "PK_dropdown_settings" PRIMARY KEY ("id")
- );
- `);
-
- await queryRunner.query(`
- CREATE UNIQUE INDEX IF NOT EXISTS "UQ_dropdown_settings_code"
- ON "freight"."dropdown_settings" ("code");
- `);
-
- await queryRunner.query(`
- CREATE TABLE IF NOT EXISTS "freight"."dropdown_options" (
- "id" uuid NOT NULL DEFAULT gen_random_uuid(),
- "setting_id" uuid NOT NULL,
- "value" varchar(256) NOT NULL,
- "label" varchar(256) NOT NULL,
- "note" text,
- "is_disabled" boolean NOT NULL DEFAULT false,
- "display_order" integer NOT NULL DEFAULT 0,
- "meta" jsonb,
- "created_at" timestamptz NOT NULL DEFAULT now(),
- "updated_at" timestamptz NOT NULL DEFAULT now(),
- "deleted_at" timestamptz,
- CONSTRAINT "PK_dropdown_options" PRIMARY KEY ("id"),
- CONSTRAINT "FK_dropdown_options_setting" FOREIGN KEY ("setting_id")
- REFERENCES "freight"."dropdown_settings" ("id") ON DELETE CASCADE
- );
- `);
-
- await queryRunner.query(`
- CREATE UNIQUE INDEX IF NOT EXISTS "UQ_dropdown_options_setting_value"
- ON "freight"."dropdown_options" ("setting_id", "value");
- `);
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(
- `DROP TABLE IF EXISTS "freight"."dropdown_options";`,
- );
- await queryRunner.query(
- `DROP TABLE IF EXISTS "freight"."dropdown_settings";`,
- );
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1792000000000-AddUnitOfMeasureToCargoTypes.ts b/apps/edr-freight-api/src/migrations/1792000000000-AddUnitOfMeasureToCargoTypes.ts
deleted file mode 100644
index 6ec9d60cd..000000000
--- a/apps/edr-freight-api/src/migrations/1792000000000-AddUnitOfMeasureToCargoTypes.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-export class AddUnitOfMeasureToCargoTypes1792000000000
- implements MigrationInterface
-{
- name = 'AddUnitOfMeasureToCargoTypes1792000000000';
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(
- `ALTER TABLE freight.cargo_types ADD COLUMN IF NOT EXISTS unit_of_measure VARCHAR(16);`,
- );
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(
- `ALTER TABLE freight.cargo_types DROP COLUMN IF EXISTS unit_of_measure;`,
- );
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1792000000001-AddBookingTypeAndContractFields.ts b/apps/edr-freight-api/src/migrations/1792000000001-AddBookingTypeAndContractFields.ts
deleted file mode 100644
index c55186a38..000000000
--- a/apps/edr-freight-api/src/migrations/1792000000001-AddBookingTypeAndContractFields.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-export class AddBookingTypeAndContractFields1792000000001
- implements MigrationInterface
-{
- name = 'AddBookingTypeAndContractFields1792000000001';
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(
- `ALTER TABLE freight.bookings ADD COLUMN IF NOT EXISTS booking_type VARCHAR(20) NOT NULL DEFAULT 'ONE_TIME';`,
- );
- await queryRunner.query(
- `ALTER TABLE freight.bookings ADD COLUMN IF NOT EXISTS expires_at TIMESTAMPTZ;`,
- );
- // General contracts have no shipment date at creation — relax the NOT NULL.
- await queryRunner.query(
- `ALTER TABLE freight.bookings ALTER COLUMN scheduled_date DROP NOT NULL;`,
- );
- await queryRunner.query(
- `CREATE INDEX IF NOT EXISTS idx_bookings_booking_type ON freight.bookings (booking_type);`,
- );
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(
- `DROP INDEX IF EXISTS freight.idx_bookings_booking_type;`,
- );
- // Reinstate NOT NULL only if no null rows exist (general contracts would block it).
- await queryRunner.query(
- `ALTER TABLE freight.bookings ALTER COLUMN scheduled_date SET NOT NULL;`,
- );
- await queryRunner.query(
- `ALTER TABLE freight.bookings DROP COLUMN IF EXISTS expires_at;`,
- );
- await queryRunner.query(
- `ALTER TABLE freight.bookings DROP COLUMN IF EXISTS booking_type;`,
- );
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1792000000002-CreateBookingOrders.ts b/apps/edr-freight-api/src/migrations/1792000000002-CreateBookingOrders.ts
deleted file mode 100644
index ceb98b5d1..000000000
--- a/apps/edr-freight-api/src/migrations/1792000000002-CreateBookingOrders.ts
+++ /dev/null
@@ -1,74 +0,0 @@
-import { MigrationInterface, QueryRunner, Table, TableIndex } from 'typeorm';
-
-export class CreateBookingOrders1792000000002 implements MigrationInterface {
- name = 'CreateBookingOrders1792000000002';
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.createTable(
- new Table({
- schema: 'freight',
- name: 'booking_orders',
- columns: [
- { name: 'id', type: 'uuid', isPrimary: true, generationStrategy: 'uuid', default: 'gen_random_uuid()' },
- { name: 'reference', type: 'varchar', length: '64', isUnique: true },
- { name: 'contract_booking_id', type: 'uuid' },
- { name: 'booking_id', type: 'uuid', isNullable: true },
- { name: 'company_id', type: 'uuid', isNullable: true },
- { name: 'scheduled_date', type: 'timestamptz' },
- { name: 'status', type: 'varchar', length: '40', default: "'PAID'" },
- { name: 'scheduling_status', type: 'varchar', length: '30', default: "'NOT_SCHEDULED'" },
- { name: 'train_schedule_id', type: 'uuid', isNullable: true },
- { name: 'created_at', type: 'timestamptz', default: 'now()' },
- { name: 'updated_at', type: 'timestamptz', default: 'now()' },
- { name: 'deleted_at', type: 'timestamptz', isNullable: true },
- ],
- }),
- true,
- );
-
- await queryRunner.createIndex(
- 'freight.booking_orders',
- new TableIndex({ name: 'idx_booking_orders_contract', columnNames: ['contract_booking_id'] }),
- );
- await queryRunner.createIndex(
- 'freight.booking_orders',
- new TableIndex({ name: 'idx_booking_orders_company', columnNames: ['company_id'] }),
- );
-
- await queryRunner.createTable(
- new Table({
- schema: 'freight',
- name: 'booking_order_lines',
- columns: [
- { name: 'id', type: 'uuid', isPrimary: true, generationStrategy: 'uuid', default: 'gen_random_uuid()' },
- { name: 'order_id', type: 'uuid' },
- { name: 'container_type_id', type: 'uuid', isNullable: true },
- { name: 'quantity', type: 'numeric', precision: 12, scale: 3 },
- { name: 'created_at', type: 'timestamptz', default: 'now()' },
- { name: 'updated_at', type: 'timestamptz', default: 'now()' },
- { name: 'deleted_at', type: 'timestamptz', isNullable: true },
- ],
- foreignKeys: [
- {
- columnNames: ['order_id'],
- referencedSchema: 'freight',
- referencedTableName: 'booking_orders',
- referencedColumnNames: ['id'],
- onDelete: 'CASCADE',
- },
- ],
- }),
- true,
- );
-
- await queryRunner.createIndex(
- 'freight.booking_order_lines',
- new TableIndex({ name: 'idx_booking_order_lines_order', columnNames: ['order_id'] }),
- );
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.dropTable('freight.booking_order_lines', true);
- await queryRunner.dropTable('freight.booking_orders', true);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1792000000003-SeedGeneralContractPeriod.ts b/apps/edr-freight-api/src/migrations/1792000000003-SeedGeneralContractPeriod.ts
deleted file mode 100644
index e6c0708d4..000000000
--- a/apps/edr-freight-api/src/migrations/1792000000003-SeedGeneralContractPeriod.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-/**
- * Seeds the global "general contract period" setting (months). Stored as a
- * dropdown_settings row with a single option whose `value` holds the month count
- * so backoffice can manage it through the existing settings UI later.
- */
-export class SeedGeneralContractPeriod1792000000003
- implements MigrationInterface
-{
- name = 'SeedGeneralContractPeriod1792000000003';
- private readonly code = 'general_contract_period';
-
- public async up(queryRunner: QueryRunner): Promise {
- const existing = await queryRunner.query(
- `SELECT id FROM freight.dropdown_settings WHERE code = $1 LIMIT 1;`,
- [this.code],
- );
- if (existing.length > 0) return;
-
- const inserted = await queryRunner.query(
- `INSERT INTO freight.dropdown_settings (code, label, description, multiple)
- VALUES ($1, $2, $3, false)
- RETURNING id;`,
- [
- this.code,
- 'General Contract Period (months)',
- 'How many months a general contract stays open for ordering after activation.',
- ],
- );
- const settingId = inserted[0].id;
-
- await queryRunner.query(
- `INSERT INTO freight.dropdown_options (setting_id, value, label, display_order)
- VALUES ($1, $2, $3, 0);`,
- [settingId, '3', '3 months'],
- );
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(
- `DELETE FROM freight.dropdown_settings WHERE code = $1;`,
- [this.code],
- );
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1792000000004-SeedContractValidityPeriods.ts b/apps/edr-freight-api/src/migrations/1792000000004-SeedContractValidityPeriods.ts
deleted file mode 100644
index e635d19c1..000000000
--- a/apps/edr-freight-api/src/migrations/1792000000004-SeedContractValidityPeriods.ts
+++ /dev/null
@@ -1,55 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-/**
- * Seeds the admin-configurable "contract validity periods" setting (days). Stored
- * as a dropdown_settings row whose options each hold a day count in `value`, so
- * backoffice manages them through the existing Dropdown Settings UI and the
- * contract staff-accept dialog only offers the configured durations.
- */
-export class SeedContractValidityPeriods1792000000004
- implements MigrationInterface
-{
- name = 'SeedContractValidityPeriods1792000000004';
- private readonly code = 'contract_validity_periods';
- private readonly options: Array<{ value: string; label: string }> = [
- { value: '180', label: '6 months' },
- { value: '365', label: '1 year' },
- { value: '730', label: '2 years' },
- ];
-
- public async up(queryRunner: QueryRunner): Promise {
- const existing = await queryRunner.query(
- `SELECT id FROM freight.dropdown_settings WHERE code = $1 LIMIT 1;`,
- [this.code],
- );
- if (existing.length > 0) return;
-
- const inserted = await queryRunner.query(
- `INSERT INTO freight.dropdown_settings (code, label, description, multiple)
- VALUES ($1, $2, $3, false)
- RETURNING id;`,
- [
- this.code,
- 'Contract Validity Periods (days)',
- 'Validity durations (in days) a staff can choose when accepting a submitted contract.',
- ],
- );
- const settingId = inserted[0].id;
-
- for (let i = 0; i < this.options.length; i++) {
- const opt = this.options[i];
- await queryRunner.query(
- `INSERT INTO freight.dropdown_options (setting_id, value, label, display_order)
- VALUES ($1, $2, $3, $4);`,
- [settingId, opt.value, opt.label, i],
- );
- }
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(
- `DELETE FROM freight.dropdown_settings WHERE code = $1;`,
- [this.code],
- );
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1800000000001-AddVehicleDriverAssignment.ts b/apps/edr-freight-api/src/migrations/1800000000001-AddVehicleDriverAssignment.ts
deleted file mode 100644
index 7a45a881b..000000000
--- a/apps/edr-freight-api/src/migrations/1800000000001-AddVehicleDriverAssignment.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-import { MigrationInterface, QueryRunner, TableColumn } from 'typeorm';
-
-/**
- * Add driver assignment fields to vehicles table
- */
-export class AddVehicleDriverAssignment1800000000001 implements MigrationInterface {
- public async up(queryRunner: QueryRunner): Promise {
- const vehiclesTable = await queryRunner.getTable('freight.vehicles');
- if (vehiclesTable) {
- const hasAssignedDriverId = vehiclesTable.columns.some((col) => col.name === 'assigned_driver_id');
- if (!hasAssignedDriverId) {
- await queryRunner.addColumn(
- 'freight.vehicles',
- new TableColumn({
- name: 'assigned_driver_id',
- type: 'uuid',
- isNullable: true,
- }),
- );
- }
-
- const hasAssignedDriverName = vehiclesTable.columns.some((col) => col.name === 'assigned_driver_name');
- if (!hasAssignedDriverName) {
- await queryRunner.addColumn(
- 'freight.vehicles',
- new TableColumn({
- name: 'assigned_driver_name',
- type: 'varchar',
- isNullable: true,
- }),
- );
- }
- }
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- const vehiclesTable = await queryRunner.getTable('freight.vehicles');
- if (vehiclesTable) {
- const hasAssignedDriverId = vehiclesTable.columns.some((col) => col.name === 'assigned_driver_id');
- if (hasAssignedDriverId) {
- await queryRunner.dropColumn('freight.vehicles', 'assigned_driver_id');
- }
-
- const hasAssignedDriverName = vehiclesTable.columns.some((col) => col.name === 'assigned_driver_name');
- if (hasAssignedDriverName) {
- await queryRunner.dropColumn('freight.vehicles', 'assigned_driver_name');
- }
- }
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1810000000000-CreateFirstMile.ts b/apps/edr-freight-api/src/migrations/1810000000000-CreateFirstMile.ts
deleted file mode 100644
index 10c4f3a44..000000000
--- a/apps/edr-freight-api/src/migrations/1810000000000-CreateFirstMile.ts
+++ /dev/null
@@ -1,106 +0,0 @@
-import { MigrationInterface, QueryRunner, Table, TableForeignKey } from 'typeorm';
-
-/**
- * Create the freight.first_mile table — one row per booking's first-mile
- * (door → terminal) leg, with payment split and an optional assigned vehicle.
- */
-export class CreateFirstMile1810000000000 implements MigrationInterface {
- public async up(queryRunner: QueryRunner): Promise {
- const exists = await queryRunner.hasTable('freight.first_mile');
- if (exists) return;
-
- await queryRunner.createTable(
- new Table({
- name: 'freight.first_mile',
- columns: [
- {
- name: 'id',
- type: 'uuid',
- isPrimary: true,
- default: 'gen_random_uuid()',
- },
- { name: 'booking_id', type: 'uuid', isNullable: false },
- {
- name: 'status',
- type: 'varchar',
- length: '30',
- default: `'PAYMENT_PENDING'`,
- isNullable: false,
- },
- {
- name: 'advanced_payment',
- type: 'numeric',
- precision: 14,
- scale: 2,
- default: 0,
- isNullable: false,
- },
- {
- name: 'remaining_payment',
- type: 'numeric',
- precision: 14,
- scale: 2,
- default: 0,
- isNullable: false,
- },
- {
- name: 'estimated_km',
- type: 'numeric',
- precision: 10,
- scale: 2,
- isNullable: true,
- },
- {
- name: 'exact_km',
- type: 'numeric',
- precision: 10,
- scale: 2,
- isNullable: true,
- },
- { name: 'vehicle_id', type: 'uuid', isNullable: true },
- { name: 'created_at', type: 'timestamptz', default: 'now()' },
- { name: 'updated_at', type: 'timestamptz', default: 'now()' },
- { name: 'deleted_at', type: 'timestamptz', isNullable: true },
- ],
- }),
- true,
- );
-
- await queryRunner.createForeignKey(
- 'freight.first_mile',
- new TableForeignKey({
- columnNames: ['booking_id'],
- referencedTableName: 'freight.bookings',
- referencedColumnNames: ['id'],
- onDelete: 'CASCADE',
- }),
- );
-
- await queryRunner.createForeignKey(
- 'freight.first_mile',
- new TableForeignKey({
- columnNames: ['vehicle_id'],
- referencedTableName: 'freight.vehicles',
- referencedColumnNames: ['id'],
- onDelete: 'SET NULL',
- }),
- );
-
- await queryRunner.query(
- `CREATE INDEX "IDX_first_mile_booking_id" ON "freight"."first_mile" ("booking_id")`,
- );
- await queryRunner.query(
- `CREATE INDEX "IDX_first_mile_status" ON "freight"."first_mile" ("status")`,
- );
- await queryRunner.query(
- `CREATE INDEX "IDX_first_mile_vehicle_id" ON "freight"."first_mile" ("vehicle_id")`,
- );
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- const exists = await queryRunner.hasTable('freight.first_mile');
- if (exists) {
- await queryRunner.dropTable('freight.first_mile');
- }
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1810000000001-CreateLastMile.ts b/apps/edr-freight-api/src/migrations/1810000000001-CreateLastMile.ts
deleted file mode 100644
index 3c0ee8dfc..000000000
--- a/apps/edr-freight-api/src/migrations/1810000000001-CreateLastMile.ts
+++ /dev/null
@@ -1,106 +0,0 @@
-import { MigrationInterface, QueryRunner, Table, TableForeignKey } from 'typeorm';
-
-/**
- * Create the freight.last_mile table — one row per booking's last-mile
- * (terminal → door) leg, with payment split and an optional assigned vehicle.
- */
-export class CreateLastMile1810000000001 implements MigrationInterface {
- public async up(queryRunner: QueryRunner): Promise {
- const exists = await queryRunner.hasTable('freight.last_mile');
- if (exists) return;
-
- await queryRunner.createTable(
- new Table({
- name: 'freight.last_mile',
- columns: [
- {
- name: 'id',
- type: 'uuid',
- isPrimary: true,
- default: 'gen_random_uuid()',
- },
- { name: 'booking_id', type: 'uuid', isNullable: false },
- {
- name: 'status',
- type: 'varchar',
- length: '30',
- default: `'PAYMENT_PENDING'`,
- isNullable: false,
- },
- {
- name: 'advanced_payment',
- type: 'numeric',
- precision: 14,
- scale: 2,
- default: 0,
- isNullable: false,
- },
- {
- name: 'remaining_payment',
- type: 'numeric',
- precision: 14,
- scale: 2,
- default: 0,
- isNullable: false,
- },
- {
- name: 'estimated_km',
- type: 'numeric',
- precision: 10,
- scale: 2,
- isNullable: true,
- },
- {
- name: 'exact_km',
- type: 'numeric',
- precision: 10,
- scale: 2,
- isNullable: true,
- },
- { name: 'vehicle_id', type: 'uuid', isNullable: true },
- { name: 'created_at', type: 'timestamptz', default: 'now()' },
- { name: 'updated_at', type: 'timestamptz', default: 'now()' },
- { name: 'deleted_at', type: 'timestamptz', isNullable: true },
- ],
- }),
- true,
- );
-
- await queryRunner.createForeignKey(
- 'freight.last_mile',
- new TableForeignKey({
- columnNames: ['booking_id'],
- referencedTableName: 'freight.bookings',
- referencedColumnNames: ['id'],
- onDelete: 'CASCADE',
- }),
- );
-
- await queryRunner.createForeignKey(
- 'freight.last_mile',
- new TableForeignKey({
- columnNames: ['vehicle_id'],
- referencedTableName: 'freight.vehicles',
- referencedColumnNames: ['id'],
- onDelete: 'SET NULL',
- }),
- );
-
- await queryRunner.query(
- `CREATE INDEX "IDX_last_mile_booking_id" ON "freight"."last_mile" ("booking_id")`,
- );
- await queryRunner.query(
- `CREATE INDEX "IDX_last_mile_status" ON "freight"."last_mile" ("status")`,
- );
- await queryRunner.query(
- `CREATE INDEX "IDX_last_mile_vehicle_id" ON "freight"."last_mile" ("vehicle_id")`,
- );
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- const exists = await queryRunner.hasTable('freight.last_mile');
- if (exists) {
- await queryRunner.dropTable('freight.last_mile');
- }
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1810000000002-AddVehicleCodeAndPlates.ts b/apps/edr-freight-api/src/migrations/1810000000002-AddVehicleCodeAndPlates.ts
deleted file mode 100644
index a2a1aae17..000000000
--- a/apps/edr-freight-api/src/migrations/1810000000002-AddVehicleCodeAndPlates.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-import { MigrationInterface, QueryRunner, TableColumn } from 'typeorm';
-
-export class AddVehicleCodeAndPlates1810000000002 implements MigrationInterface {
- public async up(queryRunner: QueryRunner): Promise {
- const hasCode = await queryRunner.hasColumn('freight.vehicles', 'code');
- if (!hasCode) {
- await queryRunner.addColumn(
- 'freight.vehicles',
- new TableColumn({ name: 'code', type: 'varchar', isNullable: true }),
- );
- }
-
- const hasPower = await queryRunner.hasColumn('freight.vehicles', 'power_plate_no');
- if (!hasPower) {
- await queryRunner.addColumn(
- 'freight.vehicles',
- new TableColumn({ name: 'power_plate_no', type: 'varchar', isNullable: true }),
- );
- }
-
- const hasTrailer = await queryRunner.hasColumn('freight.vehicles', 'trailer_plate_no');
- if (!hasTrailer) {
- await queryRunner.addColumn(
- 'freight.vehicles',
- new TableColumn({ name: 'trailer_plate_no', type: 'varchar', isNullable: true }),
- );
- }
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.dropColumn('freight.vehicles', 'trailer_plate_no');
- await queryRunner.dropColumn('freight.vehicles', 'power_plate_no');
- await queryRunner.dropColumn('freight.vehicles', 'code');
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1810000000002-CreateLastMileContainerAllocations.ts b/apps/edr-freight-api/src/migrations/1810000000002-CreateLastMileContainerAllocations.ts
deleted file mode 100644
index b2026b753..000000000
--- a/apps/edr-freight-api/src/migrations/1810000000002-CreateLastMileContainerAllocations.ts
+++ /dev/null
@@ -1,78 +0,0 @@
-import { MigrationInterface, QueryRunner, Table, TableForeignKey } from 'typeorm';
-
-/**
- * Create the freight.last_mile_container_allocations table — container allocation
- * records linking last-mile deliveries with containers and vehicles.
- */
-export class CreateLastMileContainerAllocations1810000000002 implements MigrationInterface {
- public async up(queryRunner: QueryRunner): Promise {
- const exists = await queryRunner.hasTable('freight.last_mile_container_allocations');
- if (exists) return;
-
- await queryRunner.createTable(
- new Table({
- name: 'freight.last_mile_container_allocations',
- columns: [
- {
- name: 'id',
- type: 'uuid',
- isPrimary: true,
- default: 'gen_random_uuid()',
- },
- { name: 'last_mile_id', type: 'uuid', isNullable: false },
- { name: 'container_id', type: 'uuid', isNullable: false },
- { name: 'vehicle_id', type: 'uuid', isNullable: true },
- {
- name: 'container_type',
- type: 'text',
- isNullable: false,
- },
- {
- name: 'quantity',
- type: 'integer',
- default: 1,
- isNullable: false,
- },
- { name: 'created_at', type: 'timestamptz', default: 'now()' },
- { name: 'updated_at', type: 'timestamptz', default: 'now()' },
- { name: 'deleted_at', type: 'timestamptz', isNullable: true },
- ],
- }),
- true,
- );
-
- await queryRunner.createForeignKey(
- 'freight.last_mile_container_allocations',
- new TableForeignKey({
- columnNames: ['last_mile_id'],
- referencedTableName: 'freight.last_mile',
- referencedColumnNames: ['id'],
- onDelete: 'CASCADE',
- }),
- );
-
- await queryRunner.createForeignKey(
- 'freight.last_mile_container_allocations',
- new TableForeignKey({
- columnNames: ['vehicle_id'],
- referencedTableName: 'freight.vehicles',
- referencedColumnNames: ['id'],
- onDelete: 'SET NULL',
- }),
- );
-
- await queryRunner.query(
- `CREATE INDEX "IDX_last_mile_container_allocations_last_mile_id" ON "freight"."last_mile_container_allocations" ("last_mile_id")`,
- );
- await queryRunner.query(
- `CREATE INDEX "IDX_last_mile_container_allocations_vehicle_id" ON "freight"."last_mile_container_allocations" ("vehicle_id")`,
- );
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- const exists = await queryRunner.hasTable('freight.last_mile_container_allocations');
- if (exists) {
- await queryRunner.dropTable('freight.last_mile_container_allocations');
- }
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1810000000002-MakeCompanyProfileReferenceNullable.ts b/apps/edr-freight-api/src/migrations/1810000000002-MakeCompanyProfileReferenceNullable.ts
deleted file mode 100644
index bc1765cfe..000000000
--- a/apps/edr-freight-api/src/migrations/1810000000002-MakeCompanyProfileReferenceNullable.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-import { MigrationInterface, QueryRunner } from "typeorm";
-
-/**
- * Company-profile references are now minted only when a profile is approved
- * (status → Active); pending profiles carry NULL. Drop the NOT NULL constraint
- * on freight.company_profiles.reference. The existing unique index is kept —
- * Postgres treats NULLs as distinct, so multiple pending (NULL) profiles don't
- * collide.
- */
-export class MakeCompanyProfileReferenceNullable1810000000002
- implements MigrationInterface
-{
- name = "MakeCompanyProfileReferenceNullable1810000000002";
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(
- `ALTER TABLE "freight"."company_profiles" ALTER COLUMN "reference" DROP NOT NULL`,
- );
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- // Reinstating NOT NULL requires every row to have a reference; any pending
- // (NULL) profiles get a placeholder so the constraint can be re-applied.
- await queryRunner.query(
- `UPDATE "freight"."company_profiles" SET "reference" = 'PENDING-' || left(replace("id"::text, '-', ''), 12) WHERE "reference" IS NULL`,
- );
- await queryRunner.query(
- `ALTER TABLE "freight"."company_profiles" ALTER COLUMN "reference" SET NOT NULL`,
- );
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1810000000003-CreateOtpVerifications.ts b/apps/edr-freight-api/src/migrations/1810000000003-CreateOtpVerifications.ts
deleted file mode 100644
index ec0610f52..000000000
--- a/apps/edr-freight-api/src/migrations/1810000000003-CreateOtpVerifications.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-import { MigrationInterface, QueryRunner, Table } from "typeorm";
-
-/**
- * Create the public.otp_verifications table backing the OTP module
- * (OtpVerification entity). One row per phone, holding the latest server-issued
- * code and whether that phone has been verified.
- */
-export class CreateOtpVerifications1810000000003
- implements MigrationInterface
-{
- name = "CreateOtpVerifications1810000000003";
-
- public async up(queryRunner: QueryRunner): Promise {
- const exists = await queryRunner.hasTable("otp_verifications");
- if (exists) return;
-
- await queryRunner.createTable(
- new Table({
- name: "otp_verifications",
- columns: [
- {
- name: "id",
- type: "uuid",
- isPrimary: true,
- default: "gen_random_uuid()",
- },
- { name: "phone", type: "varchar", isUnique: true },
- { name: "otp", type: "varchar" },
- { name: "verified", type: "boolean", default: false },
- { name: "created_at", type: "timestamptz", default: "now()" },
- { name: "updated_at", type: "timestamptz", default: "now()" },
- { name: "deleted_at", type: "timestamptz", isNullable: true },
- ],
- }),
- true,
- );
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.dropTable("otp_verifications", true);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1810000000004-AddPostPaymentCompletedColumn.ts b/apps/edr-freight-api/src/migrations/1810000000004-AddPostPaymentCompletedColumn.ts
deleted file mode 100644
index aeedae2b1..000000000
--- a/apps/edr-freight-api/src/migrations/1810000000004-AddPostPaymentCompletedColumn.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-import { MigrationInterface, QueryRunner, TableColumn } from 'typeorm';
-
-export class AddPostPaymentCompletedColumn1810000000004 implements MigrationInterface {
- name = 'AddPostPaymentCompletedColumn1810000000004';
-
- public async up(queryRunner: QueryRunner): Promise {
- const firstMileTable = await queryRunner.hasTable('freight.first_mile_deliveries');
- if (firstMileTable) {
- const hasColumn = await queryRunner.hasColumn('freight.first_mile_deliveries', 'is_post_payment_completed');
- if (!hasColumn) {
- await queryRunner.addColumn(
- 'freight.first_mile_deliveries',
- new TableColumn({
- name: 'is_post_payment_completed',
- type: 'boolean',
- default: false,
- isNullable: false,
- })
- );
- }
- }
-
- const lastMileTable = await queryRunner.hasTable('freight.last_mile_deliveries');
- if (lastMileTable) {
- const hasColumn = await queryRunner.hasColumn('freight.last_mile_deliveries', 'is_post_payment_completed');
- if (!hasColumn) {
- await queryRunner.addColumn(
- 'freight.last_mile_deliveries',
- new TableColumn({
- name: 'is_post_payment_completed',
- type: 'boolean',
- default: false,
- isNullable: false,
- })
- );
- }
- }
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- const lastMileTable = await queryRunner.hasTable('freight.last_mile_deliveries');
- if (lastMileTable) {
- await queryRunner.dropColumn('freight.last_mile_deliveries', 'is_post_payment_completed');
- }
-
- const firstMileTable = await queryRunner.hasTable('freight.first_mile_deliveries');
- if (firstMileTable) {
- await queryRunner.dropColumn('freight.first_mile_deliveries', 'is_post_payment_completed');
- }
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1820000000000-DropAllowConsolidation.ts b/apps/edr-freight-api/src/migrations/1820000000000-DropAllowConsolidation.ts
deleted file mode 100644
index dd232b28a..000000000
--- a/apps/edr-freight-api/src/migrations/1820000000000-DropAllowConsolidation.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-/**
- * Consolidation is now system-managed: the backend consolidates partial-wagon
- * container bookings automatically, derived from the container quantities. The
- * `allow_consolidation` opt-in flag is therefore redundant and is dropped.
- * `consolidation_partner_id` (the actual pairing link) is unaffected.
- */
-export class DropAllowConsolidation1820000000000 implements MigrationInterface {
- name = 'DropAllowConsolidation1820000000000';
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(
- `ALTER TABLE freight.bookings DROP COLUMN IF EXISTS allow_consolidation;`,
- );
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(
- `ALTER TABLE freight.bookings ADD COLUMN IF NOT EXISTS allow_consolidation BOOLEAN NOT NULL DEFAULT false;`,
- );
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1820000000001-CreateContractRouteLines.ts b/apps/edr-freight-api/src/migrations/1820000000001-CreateContractRouteLines.ts
deleted file mode 100644
index 23eda1730..000000000
--- a/apps/edr-freight-api/src/migrations/1820000000001-CreateContractRouteLines.ts
+++ /dev/null
@@ -1,61 +0,0 @@
-import { MigrationInterface, QueryRunner, Table, TableIndex } from 'typeorm';
-
-/**
- * Multi-route general contracts: a contract may reserve quantity across several
- * routes. Each (contract, route, container type) is a row here; drawdown orders
- * reference the route line they drew from via booking_orders.route_line_id.
- */
-export class CreateContractRouteLines1820000000001
- implements MigrationInterface
-{
- name = 'CreateContractRouteLines1820000000001';
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.createTable(
- new Table({
- schema: 'freight',
- name: 'contract_route_lines',
- columns: [
- { name: 'id', type: 'uuid', isPrimary: true, generationStrategy: 'uuid', default: 'gen_random_uuid()' },
- { name: 'contract_booking_id', type: 'uuid' },
- { name: 'origin_yard_id', type: 'uuid' },
- { name: 'destination_yard_id', type: 'uuid' },
- { name: 'container_type_id', type: 'uuid', isNullable: true },
- { name: 'quantity', type: 'numeric', precision: 12, scale: 3 },
- { name: 'created_at', type: 'timestamptz', default: 'now()' },
- { name: 'updated_at', type: 'timestamptz', default: 'now()' },
- { name: 'deleted_at', type: 'timestamptz', isNullable: true },
- ],
- foreignKeys: [
- {
- columnNames: ['contract_booking_id'],
- referencedSchema: 'freight',
- referencedTableName: 'bookings',
- referencedColumnNames: ['id'],
- onDelete: 'CASCADE',
- },
- ],
- }),
- true,
- );
-
- await queryRunner.createIndex(
- 'freight.contract_route_lines',
- new TableIndex({
- name: 'idx_contract_route_lines_contract',
- columnNames: ['contract_booking_id'],
- }),
- );
-
- await queryRunner.query(
- `ALTER TABLE freight.booking_orders ADD COLUMN IF NOT EXISTS route_line_id uuid;`,
- );
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(
- `ALTER TABLE freight.booking_orders DROP COLUMN IF EXISTS route_line_id;`,
- );
- await queryRunner.dropTable('freight.contract_route_lines', true);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1820000000002-CreateBookingDocumentReview.ts b/apps/edr-freight-api/src/migrations/1820000000002-CreateBookingDocumentReview.ts
deleted file mode 100644
index 0237648f7..000000000
--- a/apps/edr-freight-api/src/migrations/1820000000002-CreateBookingDocumentReview.ts
+++ /dev/null
@@ -1,66 +0,0 @@
-import { MigrationInterface, QueryRunner, Table, TableIndex } from 'typeorm';
-
-/**
- * Per-document GL review for the post-counter-sign clearance gate. One row per
- * required clearance document; GL marks each APPROVED or QUERIED before the
- * booking can proceed to operations.
- */
-export class CreateBookingDocumentReview1820000000002
- implements MigrationInterface
-{
- name = 'CreateBookingDocumentReview1820000000002';
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.createTable(
- new Table({
- schema: 'freight',
- name: 'booking_document_review',
- columns: [
- { name: 'id', type: 'uuid', isPrimary: true, generationStrategy: 'uuid', default: 'gen_random_uuid()' },
- { name: 'booking_id', type: 'uuid' },
- { name: 'setting_code', type: 'varchar', length: '128' },
- { name: 'file_key', type: 'varchar', length: '128' },
- { name: 'file_record_id', type: 'uuid', isNullable: true },
- { name: 'status', type: 'varchar', length: '20', default: "'PENDING'" },
- { name: 'note', type: 'text', isNullable: true },
- { name: 'reviewed_by_staff_id', type: 'uuid', isNullable: true },
- { name: 'reviewed_at', type: 'timestamptz', isNullable: true },
- { name: 'created_at', type: 'timestamptz', default: 'now()' },
- { name: 'updated_at', type: 'timestamptz', default: 'now()' },
- { name: 'deleted_at', type: 'timestamptz', isNullable: true },
- ],
- foreignKeys: [
- {
- columnNames: ['booking_id'],
- referencedSchema: 'freight',
- referencedTableName: 'bookings',
- referencedColumnNames: ['id'],
- onDelete: 'CASCADE',
- },
- ],
- }),
- true,
- );
-
- await queryRunner.createIndex(
- 'freight.booking_document_review',
- new TableIndex({ name: 'idx_booking_document_review_booking', columnNames: ['booking_id'] }),
- );
- await queryRunner.createIndex(
- 'freight.booking_document_review',
- new TableIndex({ name: 'idx_booking_document_review_status', columnNames: ['status'] }),
- );
- await queryRunner.createIndex(
- 'freight.booking_document_review',
- new TableIndex({
- name: 'uq_booking_document_review_doc',
- columnNames: ['booking_id', 'setting_code', 'file_key'],
- isUnique: true,
- }),
- );
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.dropTable('freight.booking_document_review', true);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1820000000003-AddPriceAdjustment.ts b/apps/edr-freight-api/src/migrations/1820000000003-AddPriceAdjustment.ts
deleted file mode 100644
index 7ad97e7bf..000000000
--- a/apps/edr-freight-api/src/migrations/1820000000003-AddPriceAdjustment.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-/**
- * Staff price adjustment: an optional override of a booking's computed total,
- * with who/when/why. When set, the customer sees the adjusted total + a badge.
- */
-export class AddPriceAdjustment1820000000003 implements MigrationInterface {
- name = 'AddPriceAdjustment1820000000003';
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(
- `ALTER TABLE freight.bookings ADD COLUMN IF NOT EXISTS adjusted_total_amount numeric(14,2);`,
- );
- await queryRunner.query(
- `ALTER TABLE freight.bookings ADD COLUMN IF NOT EXISTS adjusted_by_staff_id uuid;`,
- );
- await queryRunner.query(
- `ALTER TABLE freight.bookings ADD COLUMN IF NOT EXISTS adjusted_at timestamptz;`,
- );
- await queryRunner.query(
- `ALTER TABLE freight.bookings ADD COLUMN IF NOT EXISTS adjustment_reason text;`,
- );
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(
- `ALTER TABLE freight.bookings DROP COLUMN IF EXISTS adjustment_reason;`,
- );
- await queryRunner.query(
- `ALTER TABLE freight.bookings DROP COLUMN IF EXISTS adjusted_at;`,
- );
- await queryRunner.query(
- `ALTER TABLE freight.bookings DROP COLUMN IF EXISTS adjusted_by_staff_id;`,
- );
- await queryRunner.query(
- `ALTER TABLE freight.bookings DROP COLUMN IF EXISTS adjusted_total_amount;`,
- );
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1820000000004-FoldSurchargeTypesIntoRates.ts b/apps/edr-freight-api/src/migrations/1820000000004-FoldSurchargeTypesIntoRates.ts
deleted file mode 100644
index d4eadf34e..000000000
--- a/apps/edr-freight-api/src/migrations/1820000000004-FoldSurchargeTypesIntoRates.ts
+++ /dev/null
@@ -1,187 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-/**
- * Fold the `surcharge_types` table into self-describing rates.
- *
- * Previously a surcharge was a separate row {trigger_condition, rate_id}. Now
- * each rate carries its own `applies_to` (friendly category) and `trigger`
- * (ALWAYS = base freight, otherwise a surcharge condition), plus an optional
- * `cargo_type_id` for bulk leaf commodities. The rule engine reads triggers
- * directly off LIVE rates, so the join table is no longer needed.
- *
- * This migration:
- * 1. adds applies_to / trigger / cargo_type_id to rates and backfills them
- * from the existing rate_type matrix,
- * 2. repoints booking_cargo_modifier from surcharge_type_id → rate_id
- * (backfilled via surcharge_types.rate_id),
- * 3. drops surcharge_types and its FK.
- */
-export class FoldSurchargeTypesIntoRates1820000000004 implements MigrationInterface {
- name = 'FoldSurchargeTypesIntoRates1820000000004';
-
- public async up(queryRunner: QueryRunner): Promise {
- // ── 1. New rate columns ────────────────────────────────────────────────
- await queryRunner.query(`
- ALTER TABLE freight.rates
- ADD COLUMN IF NOT EXISTS applies_to varchar(20) NOT NULL DEFAULT 'OTHER',
- ADD COLUMN IF NOT EXISTS "trigger" varchar(20) NOT NULL DEFAULT 'ALWAYS',
- ADD COLUMN IF NOT EXISTS cargo_type_id uuid NULL;
- `);
-
- await queryRunner.query(`
- ALTER TABLE freight.rates
- ADD CONSTRAINT "FK_rates_cargo_type_id"
- FOREIGN KEY (cargo_type_id) REFERENCES freight.cargo_types(id)
- ON DELETE SET NULL;
- `);
-
- await queryRunner.query(
- `CREATE INDEX IF NOT EXISTS "IDX_rates_trigger" ON freight.rates ("trigger");`,
- );
-
- // ── 1a. Backfill applies_to from the legacy rate_type matrix ────────────
- await queryRunner.query(`
- UPDATE freight.rates SET applies_to = CASE
- WHEN rate_type IN ('CONTAINER_IMPORT','CONTAINER_EXPORT','CONTAINER_WITH_RETURN') THEN 'CONTAINER'
- WHEN rate_type IN ('BULK_IMPORT','BULK_EXPORT') THEN 'BULK'
- WHEN rate_type IN ('INTERCITY_CONTAINER','INTERCITY_BULK') THEN 'INTERCITY'
- WHEN rate_type = 'FIRST_MILE' THEN 'FIRST_MILE'
- WHEN rate_type = 'LAST_MILE' THEN 'LAST_MILE'
- ELSE 'OTHER'
- END;
- `);
-
- // ── 1b. Backfill trigger from the legacy rate_type matrix ───────────────
- await queryRunner.query(`
- UPDATE freight.rates SET "trigger" = CASE
- WHEN rate_type = 'HAZARD_SURCHARGE' THEN 'HAZARDOUS'
- WHEN rate_type = 'REEFER_SURCHARGE' THEN 'REEFER'
- WHEN rate_type = 'OVERWEIGHT_PER_TON' THEN 'OVERWEIGHT'
- WHEN rate_type = 'DOUBLE_HANDLING' THEN 'SHIPPING_LINE'
- WHEN rate_type = 'LASHING' THEN 'CONSOLIDATION'
- WHEN rate_type = 'CANCELLATION_FEE' THEN 'CANCELLATION'
- WHEN rate_type = 'DEMURRAGE' THEN 'DEMURRAGE'
- WHEN rate_type = 'PIL_EXTRA_FEE' THEN 'PIL_EXTRA_FEE'
- ELSE 'ALWAYS'
- END;
- `);
-
- // Align the trigger to the actual surcharge_types mapping where one exists
- // (covers any rate wired as a surcharge with a non-obvious rate_type).
- await queryRunner.query(`
- UPDATE freight.rates r SET "trigger" = m.trig
- FROM (
- SELECT st.rate_id, CASE st.trigger_condition
- WHEN 'CARGO_FLAG_HAZARDOUS' THEN 'HAZARDOUS'
- WHEN 'CARGO_FLAG_REEFER' THEN 'REEFER'
- WHEN 'VGM_EXCEEDS_LIMIT' THEN 'OVERWEIGHT'
- WHEN 'SHIPPING_LINE_MAPPED' THEN 'SHIPPING_LINE'
- WHEN 'CONSOLIDATION_ENABLED' THEN 'CONSOLIDATION'
- ELSE 'ALWAYS'
- END AS trig
- FROM freight.surcharge_types st
- WHERE st.rate_id IS NOT NULL AND st.deleted_at IS NULL
- ) m
- WHERE r.id = m.rate_id AND m.trig <> 'ALWAYS';
- `);
-
- // ── 2. Repoint booking_cargo_modifier to rate_id ────────────────────────
- await queryRunner.query(`
- ALTER TABLE freight.booking_cargo_modifier
- ADD COLUMN IF NOT EXISTS rate_id uuid NULL;
- `);
-
- await queryRunner.query(`
- UPDATE freight.booking_cargo_modifier bcm
- SET rate_id = st.rate_id
- FROM freight.surcharge_types st
- WHERE bcm.surcharge_type_id = st.id AND st.rate_id IS NOT NULL;
- `);
-
- // Rows whose surcharge lost its rate can't be repointed — they reference a
- // now-defunct surcharge. Remove them so the NOT NULL + FK can be enforced.
- await queryRunner.query(`
- DELETE FROM freight.booking_cargo_modifier WHERE rate_id IS NULL;
- `);
-
- await queryRunner.query(`
- ALTER TABLE freight.booking_cargo_modifier
- ALTER COLUMN rate_id SET NOT NULL;
- `);
-
- // Drop the old FK + column + index for surcharge_type_id.
- await queryRunner.query(`
- ALTER TABLE freight.booking_cargo_modifier
- DROP CONSTRAINT IF EXISTS "FK_booking_cargo_modifier_surcharge_type_id";
- `);
- await queryRunner.query(
- `DROP INDEX IF EXISTS freight."IDX_booking_cargo_modifier_surcharge_type_id";`,
- );
- await queryRunner.query(`
- ALTER TABLE freight.booking_cargo_modifier
- DROP COLUMN IF EXISTS surcharge_type_id;
- `);
-
- await queryRunner.query(`
- ALTER TABLE freight.booking_cargo_modifier
- ADD CONSTRAINT "FK_booking_cargo_modifier_rate_id"
- FOREIGN KEY (rate_id) REFERENCES freight.rates(id);
- `);
- await queryRunner.query(
- `CREATE INDEX IF NOT EXISTS "IDX_booking_cargo_modifier_rate_id" ON freight.booking_cargo_modifier (rate_id);`,
- );
-
- // ── 3. Drop the surcharge_types table ───────────────────────────────────
- await queryRunner.query(`DROP TABLE IF EXISTS freight.surcharge_types;`);
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- // Recreate surcharge_types (structure only — data is not restored).
- await queryRunner.query(`
- CREATE TABLE IF NOT EXISTS freight.surcharge_types (
- id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
- code varchar(40) NOT NULL,
- label varchar(100),
- trigger_condition varchar(50),
- rate_id uuid,
- is_active boolean NOT NULL DEFAULT true,
- created_at timestamptz NOT NULL DEFAULT now(),
- updated_at timestamptz NOT NULL DEFAULT now(),
- deleted_at timestamptz
- );
- `);
- await queryRunner.query(
- `CREATE UNIQUE INDEX IF NOT EXISTS "IDX_surcharge_types_code" ON freight.surcharge_types (code);`,
- );
-
- // Restore booking_cargo_modifier.surcharge_type_id (nullable; not backfilled).
- await queryRunner.query(`
- ALTER TABLE freight.booking_cargo_modifier
- DROP CONSTRAINT IF EXISTS "FK_booking_cargo_modifier_rate_id";
- `);
- await queryRunner.query(
- `DROP INDEX IF EXISTS freight."IDX_booking_cargo_modifier_rate_id";`,
- );
- await queryRunner.query(`
- ALTER TABLE freight.booking_cargo_modifier
- ADD COLUMN IF NOT EXISTS surcharge_type_id uuid NULL;
- `);
- await queryRunner.query(`
- ALTER TABLE freight.booking_cargo_modifier DROP COLUMN IF EXISTS rate_id;
- `);
-
- // Drop the new rate columns.
- await queryRunner.query(
- `DROP INDEX IF EXISTS freight."IDX_rates_trigger";`,
- );
- await queryRunner.query(`
- ALTER TABLE freight.rates DROP CONSTRAINT IF EXISTS "FK_rates_cargo_type_id";
- `);
- await queryRunner.query(`
- ALTER TABLE freight.rates
- DROP COLUMN IF EXISTS cargo_type_id,
- DROP COLUMN IF EXISTS "trigger",
- DROP COLUMN IF EXISTS applies_to;
- `);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1820000000005-AddContractValidityWindow.ts b/apps/edr-freight-api/src/migrations/1820000000005-AddContractValidityWindow.ts
deleted file mode 100644
index 56d923ff7..000000000
--- a/apps/edr-freight-api/src/migrations/1820000000005-AddContractValidityWindow.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-/**
- * Contract validity window. When the backoffice accepts a price-confirmed
- * booking, staff define how many days the contract stays valid. The window runs
- * from the accept moment (valid_from) through valid_from + N days (valid_until).
- * Outside that window the contract is considered expired.
- */
-export class AddContractValidityWindow1820000000005
- implements MigrationInterface
-{
- name = 'AddContractValidityWindow1820000000005';
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(
- `ALTER TABLE freight.bookings ADD COLUMN IF NOT EXISTS contract_validity_days integer;`,
- );
- await queryRunner.query(
- `ALTER TABLE freight.bookings ADD COLUMN IF NOT EXISTS contract_valid_from timestamptz;`,
- );
- await queryRunner.query(
- `ALTER TABLE freight.bookings ADD COLUMN IF NOT EXISTS contract_valid_until timestamptz;`,
- );
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(
- `ALTER TABLE freight.bookings DROP COLUMN IF EXISTS contract_valid_until;`,
- );
- await queryRunner.query(
- `ALTER TABLE freight.bookings DROP COLUMN IF EXISTS contract_valid_from;`,
- );
- await queryRunner.query(
- `ALTER TABLE freight.bookings DROP COLUMN IF EXISTS contract_validity_days;`,
- );
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1820000000006-AddCustomsAgentAndMileCoordinates.ts b/apps/edr-freight-api/src/migrations/1820000000006-AddCustomsAgentAndMileCoordinates.ts
deleted file mode 100644
index 11d1b617a..000000000
--- a/apps/edr-freight-api/src/migrations/1820000000006-AddCustomsAgentAndMileCoordinates.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-/**
- * Booking now captures:
- * - customs clearing as an explicit flag + the customs clearing agent name
- * (shown when the service includes customs), and
- * - first/last-mile pickup & delivery coordinates (lat/lng) alongside the
- * existing address text, so the map picker can store and restore the pin.
- *
- * Shipping line is no longer collected from the booking form; the column stays
- * for historical data and the (now dormant) shipping-line pricing trigger.
- */
-export class AddCustomsAgentAndMileCoordinates1820000000006
- implements MigrationInterface
-{
- name = 'AddCustomsAgentAndMileCoordinates1820000000006';
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- ALTER TABLE freight.bookings
- ADD COLUMN IF NOT EXISTS first_mile_pickup_lat numeric(10,7) NULL,
- ADD COLUMN IF NOT EXISTS first_mile_pickup_lng numeric(10,7) NULL,
- ADD COLUMN IF NOT EXISTS last_mile_delivery_lat numeric(10,7) NULL,
- ADD COLUMN IF NOT EXISTS last_mile_delivery_lng numeric(10,7) NULL,
- ADD COLUMN IF NOT EXISTS customs_clearing_enabled boolean NOT NULL DEFAULT false,
- ADD COLUMN IF NOT EXISTS customs_clearing_agent varchar(200) NULL;
- `);
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- ALTER TABLE freight.bookings
- DROP COLUMN IF EXISTS customs_clearing_agent,
- DROP COLUMN IF EXISTS customs_clearing_enabled,
- DROP COLUMN IF EXISTS last_mile_delivery_lng,
- DROP COLUMN IF EXISTS last_mile_delivery_lat,
- DROP COLUMN IF EXISTS first_mile_pickup_lng,
- DROP COLUMN IF EXISTS first_mile_pickup_lat;
- `);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1820000000010-AddGeneralContractOrderFields.ts b/apps/edr-freight-api/src/migrations/1820000000010-AddGeneralContractOrderFields.ts
deleted file mode 100644
index a23c152b5..000000000
--- a/apps/edr-freight-api/src/migrations/1820000000010-AddGeneralContractOrderFields.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-/**
- * General-contract drawdown order fields:
- * - booking_order_lines.hazardous_quantity / reefer_quantity — per-order counts
- * the customer enters when toggling hazardous/reefer; drive the surcharge
- * rates on the spawned child booking.
- * - bookings.is_reefer — booking-level refrigerated flag so REEFER_SURCHARGE
- * applies to a contract order even when the container type is not a reefer.
- * - contract_route_lines.km — road distance configured with the route; road
- * orders bill KM × the PER_KM rate.
- *
- * NOTE: the shared dev DB has no applied migration history, so these columns
- * are also hand-applied there. ADD COLUMN IF NOT EXISTS keeps that idempotent.
- */
-export class AddGeneralContractOrderFields1820000000010
- implements MigrationInterface
-{
- name = 'AddGeneralContractOrderFields1820000000010';
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(
- `ALTER TABLE freight.booking_order_lines ADD COLUMN IF NOT EXISTS hazardous_quantity numeric(12,3) NOT NULL DEFAULT 0;`,
- );
- await queryRunner.query(
- `ALTER TABLE freight.booking_order_lines ADD COLUMN IF NOT EXISTS reefer_quantity numeric(12,3) NOT NULL DEFAULT 0;`,
- );
- await queryRunner.query(
- `ALTER TABLE freight.bookings ADD COLUMN IF NOT EXISTS is_reefer boolean NOT NULL DEFAULT false;`,
- );
- await queryRunner.query(
- `ALTER TABLE freight.contract_route_lines ADD COLUMN IF NOT EXISTS km numeric(10,2);`,
- );
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(
- `ALTER TABLE freight.contract_route_lines DROP COLUMN IF EXISTS km;`,
- );
- await queryRunner.query(
- `ALTER TABLE freight.bookings DROP COLUMN IF EXISTS is_reefer;`,
- );
- await queryRunner.query(
- `ALTER TABLE freight.booking_order_lines DROP COLUMN IF EXISTS reefer_quantity;`,
- );
- await queryRunner.query(
- `ALTER TABLE freight.booking_order_lines DROP COLUMN IF EXISTS hazardous_quantity;`,
- );
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1820000000011-AddTrainSetLocomotives.ts b/apps/edr-freight-api/src/migrations/1820000000011-AddTrainSetLocomotives.ts
deleted file mode 100644
index b014e71a2..000000000
--- a/apps/edr-freight-api/src/migrations/1820000000011-AddTrainSetLocomotives.ts
+++ /dev/null
@@ -1,59 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-/**
- * Multi-locomotive train sets: a train set is now pulled by 2+ locomotives.
- *
- * Adds the `freight.train_set_locomotives` link table (train set ⇄ locomotive,
- * with an order index) and backfills one row per existing train set from its
- * current `locomotive_id`, so existing read paths keep resolving locomotives.
- * The `train_sets.locomotive_id` column is retained as the "primary" locomotive.
- *
- * NOTE: the shared dev DB has no applied migration history, so this is also
- * hand-applied there. IF NOT EXISTS keeps that idempotent.
- */
-export class AddTrainSetLocomotives1820000000011 implements MigrationInterface {
- name = 'AddTrainSetLocomotives1820000000011';
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- CREATE TABLE IF NOT EXISTS freight.train_set_locomotives (
- id uuid NOT NULL DEFAULT uuid_generate_v4(),
- train_set_id uuid NOT NULL,
- locomotive_id uuid NOT NULL,
- sequence_no int NOT NULL DEFAULT 0,
- created_at timestamptz NOT NULL DEFAULT now(),
- updated_at timestamptz NOT NULL DEFAULT now(),
- deleted_at timestamptz,
- CONSTRAINT "PK_train_set_locomotives" PRIMARY KEY (id),
- CONSTRAINT "FK_train_set_locomotives_train_set" FOREIGN KEY (train_set_id)
- REFERENCES freight.train_sets (id) ON DELETE CASCADE,
- CONSTRAINT "FK_train_set_locomotives_locomotive" FOREIGN KEY (locomotive_id)
- REFERENCES freight.locomotives (id)
- );
- `);
-
- await queryRunner.query(`
- CREATE UNIQUE INDEX IF NOT EXISTS "UQ_train_set_locomotives_set_loco"
- ON freight.train_set_locomotives (train_set_id, locomotive_id);
- `);
-
- // Backfill: one link row per existing train set, from its current primary loco.
- await queryRunner.query(`
- INSERT INTO freight.train_set_locomotives (train_set_id, locomotive_id, sequence_no)
- SELECT ts.id, ts.locomotive_id, 0
- FROM freight.train_sets ts
- WHERE ts.locomotive_id IS NOT NULL
- AND NOT EXISTS (
- SELECT 1 FROM freight.train_set_locomotives tsl
- WHERE tsl.train_set_id = ts.id AND tsl.locomotive_id = ts.locomotive_id
- );
- `);
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(
- `DROP INDEX IF EXISTS freight."UQ_train_set_locomotives_set_loco";`,
- );
- await queryRunner.query(`DROP TABLE IF EXISTS freight.train_set_locomotives;`);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1820000000011-DropEmailPhoneFromExternalProfiles.ts b/apps/edr-freight-api/src/migrations/1820000000011-DropEmailPhoneFromExternalProfiles.ts
deleted file mode 100644
index 757c20720..000000000
--- a/apps/edr-freight-api/src/migrations/1820000000011-DropEmailPhoneFromExternalProfiles.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-/**
- * Contact email/phone for an external profile is sourced from IAM (the user's
- * identity) and from the company record, so the duplicated `email`/`phone`
- * columns on external_profiles are redundant and are dropped. Dropping `email`
- * also removes its UNIQUE constraint.
- */
-export class DropEmailPhoneFromExternalProfiles1820000000011
- implements MigrationInterface
-{
- name = 'DropEmailPhoneFromExternalProfiles1820000000011';
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(
- `ALTER TABLE freight.external_profiles DROP COLUMN IF EXISTS email;`,
- );
- await queryRunner.query(
- `ALTER TABLE freight.external_profiles DROP COLUMN IF EXISTS phone;`,
- );
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- // Re-added as nullable (the original email was UNIQUE NOT NULL) since the
- // dropped values cannot be recovered to satisfy those constraints.
- await queryRunner.query(
- `ALTER TABLE freight.external_profiles ADD COLUMN IF NOT EXISTS email varchar(150);`,
- );
- await queryRunner.query(
- `ALTER TABLE freight.external_profiles ADD COLUMN IF NOT EXISTS phone varchar(20);`,
- );
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1820000000012-AddEstimatedShipmentDate.ts b/apps/edr-freight-api/src/migrations/1820000000012-AddEstimatedShipmentDate.ts
deleted file mode 100644
index 6b77f53a3..000000000
--- a/apps/edr-freight-api/src/migrations/1820000000012-AddEstimatedShipmentDate.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-/**
- * The booking wizard now captures a NON-BINDING estimated shipment date instead
- * of the binding scheduledDate. The binding scheduledDate (validated against
- * open train departures) is set later, at the operation-request step.
- */
-export class AddEstimatedShipmentDate1820000000012
- implements MigrationInterface
-{
- name = 'AddEstimatedShipmentDate1820000000012';
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- ALTER TABLE freight.bookings
- ADD COLUMN IF NOT EXISTS estimated_shipment_date timestamptz NULL;
- `);
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- ALTER TABLE freight.bookings
- DROP COLUMN IF EXISTS estimated_shipment_date;
- `);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1821000000000-CreateInterchangeDocuments.ts b/apps/edr-freight-api/src/migrations/1821000000000-CreateInterchangeDocuments.ts
deleted file mode 100644
index 386eabd4e..000000000
--- a/apps/edr-freight-api/src/migrations/1821000000000-CreateInterchangeDocuments.ts
+++ /dev/null
@@ -1,109 +0,0 @@
-import { MigrationInterface, QueryRunner, Table } from 'typeorm';
-
-export class CreateInterchangeDocuments1821000000000 implements MigrationInterface {
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.createTable(
- new Table({
- schema: 'freight',
- name: 'interchange_documents',
- columns: [
- { name: 'id', type: 'uuid', isPrimary: true, default: 'uuid_generate_v4()' },
- { name: 'document_no', type: 'varchar', length: '40', isUnique: true },
- { name: 'direction', type: 'varchar', length: '10' },
- { name: 'schedule_id', type: 'uuid', isNullable: true },
- { name: 'train_no', type: 'varchar', length: '40', isNullable: true },
- { name: 'route_id', type: 'uuid', isNullable: true },
- { name: 'origin_facility_id', type: 'uuid', isNullable: true },
- { name: 'destination_facility_id', type: 'uuid', isNullable: true },
- { name: 'handover_location', type: 'varchar', length: '255' },
- { name: 'handover_from', type: 'varchar', length: '255' },
- { name: 'handover_to', type: 'varchar', length: '255' },
- { name: 'operator_name', type: 'varchar', length: '255', isNullable: true },
- { name: 'port_operator_name', type: 'varchar', length: '255', isNullable: true },
- { name: 'shipping_line_name', type: 'varchar', length: '255', isNullable: true },
- { name: 'customs_reference', type: 'varchar', length: '120', isNullable: true },
- { name: 'manifest_reference', type: 'varchar', length: '120', isNullable: true },
- { name: 'status', type: 'varchar', length: '20', default: "'DRAFT'" },
- { name: 'generated_at', type: 'timestamptz', isNullable: true },
- { name: 'acknowledged_at', type: 'timestamptz', isNullable: true },
- { name: 'generated_by', type: 'varchar', length: '120', isNullable: true },
- { name: 'acknowledged_by', type: 'varchar', length: '120', isNullable: true },
- { name: 'remarks', type: 'text', isNullable: true },
- { name: 'created_at', type: 'timestamptz', default: 'now()' },
- { name: 'updated_at', type: 'timestamptz', default: 'now()' },
- { name: 'deleted_at', type: 'timestamptz', isNullable: true },
- ],
- indices: [
- { name: 'idx_interchange_documents_direction', columnNames: ['direction'] },
- { name: 'idx_interchange_documents_status', columnNames: ['status'] },
- { name: 'idx_interchange_documents_schedule', columnNames: ['schedule_id'] },
- ],
- }),
- true,
- );
-
- await queryRunner.createTable(
- new Table({
- schema: 'freight',
- name: 'interchange_document_items',
- columns: [
- { name: 'id', type: 'uuid', isPrimary: true, default: 'uuid_generate_v4()' },
- { name: 'interchange_document_id', type: 'uuid' },
- { name: 'booking_id', type: 'uuid', isNullable: true },
- { name: 'booking_reference', type: 'varchar', length: '64', isNullable: true },
- { name: 'item_type', type: 'varchar', length: '20' },
- { name: 'booking_container_id', type: 'uuid', isNullable: true },
- { name: 'booking_cargo_id', type: 'uuid', isNullable: true },
- { name: 'container_number', type: 'varchar', length: '64', isNullable: true },
- { name: 'seal_number', type: 'varchar', length: '100', isNullable: true },
- { name: 'cargo_id', type: 'uuid', isNullable: true },
- { name: 'cargo_type', type: 'varchar', length: '255', isNullable: true },
- { name: 'cargo_description', type: 'text', isNullable: true },
- { name: 'weight', type: 'numeric', precision: 14, scale: 3, isNullable: true },
- { name: 'quantity', type: 'numeric', precision: 12, scale: 3, isNullable: true },
- { name: 'package_count', type: 'int', isNullable: true },
- { name: 'wagon_number', type: 'varchar', length: '80', isNullable: true },
- { name: 'condition_status', type: 'varchar', length: '20', default: "'GOOD'" },
- { name: 'damage_description', type: 'text', isNullable: true },
- { name: 'remarks', type: 'text', isNullable: true },
- { name: 'created_at', type: 'timestamptz', default: 'now()' },
- { name: 'updated_at', type: 'timestamptz', default: 'now()' },
- { name: 'deleted_at', type: 'timestamptz', isNullable: true },
- ],
- foreignKeys: [
- {
- columnNames: ['interchange_document_id'],
- referencedSchema: 'freight',
- referencedTableName: 'interchange_documents',
- referencedColumnNames: ['id'],
- onDelete: 'CASCADE',
- },
- {
- columnNames: ['booking_id'],
- referencedSchema: 'freight',
- referencedTableName: 'bookings',
- referencedColumnNames: ['id'],
- onDelete: 'SET NULL',
- },
- ],
- indices: [
- { name: 'idx_interchange_items_document', columnNames: ['interchange_document_id'] },
- { name: 'idx_interchange_items_booking', columnNames: ['booking_id'] },
- ],
- }),
- true,
- );
-
- await queryRunner.query(`
- CREATE UNIQUE INDEX IF NOT EXISTS uq_interchange_active_schedule_direction
- ON freight.interchange_documents(schedule_id, direction)
- WHERE schedule_id IS NOT NULL AND status <> 'CANCELLED' AND deleted_at IS NULL;
- `);
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query('DROP INDEX IF EXISTS freight.uq_interchange_active_schedule_direction;');
- await queryRunner.dropTable('freight.interchange_document_items', true);
- await queryRunner.dropTable('freight.interchange_documents', true);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1821000000001-EnsureWarehouseInventoryInspectionStatus.ts b/apps/edr-freight-api/src/migrations/1821000000001-EnsureWarehouseInventoryInspectionStatus.ts
deleted file mode 100644
index 523772b39..000000000
--- a/apps/edr-freight-api/src/migrations/1821000000001-EnsureWarehouseInventoryInspectionStatus.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-import { MigrationInterface, QueryRunner, TableColumn } from 'typeorm';
-
-/**
- * Catch-up for environments where AddWarehouseInspection ran before the
- * warehouse module table existed. Production needs this column for unload and
- * inspection flows because the WarehouseInventory entity maps inspectionStatus.
- */
-export class EnsureWarehouseInventoryInspectionStatus1821000000001 implements MigrationInterface {
- private readonly table = 'freight.warehouse_inventory';
-
- public async up(queryRunner: QueryRunner): Promise {
- if (!(await queryRunner.hasColumn(this.table, 'inspection_status'))) {
- await queryRunner.addColumn(
- this.table,
- new TableColumn({
- name: 'inspection_status',
- type: 'varchar',
- length: '20',
- isNullable: true,
- }),
- );
- }
-
- await queryRunner.query(`
- CREATE INDEX IF NOT EXISTS idx_warehouse_inventory_inspection_status
- ON freight.warehouse_inventory(inspection_status)
- `);
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- DROP INDEX IF EXISTS freight.idx_warehouse_inventory_inspection_status
- `);
-
- if (await queryRunner.hasColumn(this.table, 'inspection_status')) {
- await queryRunner.dropColumn(this.table, 'inspection_status');
- }
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1821000000002-AddDistanceColumnsToVehicles.ts b/apps/edr-freight-api/src/migrations/1821000000002-AddDistanceColumnsToVehicles.ts
deleted file mode 100644
index 98fa8b228..000000000
--- a/apps/edr-freight-api/src/migrations/1821000000002-AddDistanceColumnsToVehicles.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-export class AddDistanceColumnsToVehicles1821000000002 implements MigrationInterface {
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- ALTER TABLE freight.vehicles
- ADD COLUMN IF NOT EXISTS estimated_distance_km NUMERIC,
- ADD COLUMN IF NOT EXISTS actual_distance_km NUMERIC;
- `);
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- ALTER TABLE freight.vehicles
- DROP COLUMN IF EXISTS estimated_distance_km,
- DROP COLUMN IF EXISTS actual_distance_km;
- `);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1821000000002-CreateInvoices.ts b/apps/edr-freight-api/src/migrations/1821000000002-CreateInvoices.ts
deleted file mode 100644
index 4c2fb5d95..000000000
--- a/apps/edr-freight-api/src/migrations/1821000000002-CreateInvoices.ts
+++ /dev/null
@@ -1,185 +0,0 @@
-import { MigrationInterface, QueryRunner } from "typeorm";
-
-/**
- * Freight billing — `invoices` + `invoice_lines` tables.
- *
- * Matches:
- * - billing/entities/invoice.entity.ts
- * - billing/entities/invoice-line.entity.ts
- *
- * The status enum mirrors `Freight.InvoiceStatus` and uses TypeORM's default
- * enum-type name (`__enum`) so the entity's `type: "enum"`
- * column resolves to it without an explicit `enumName`.
- */
-export class CreateInvoices1821000000002 implements MigrationInterface {
- name = "CreateInvoices1821000000002";
-
- public async up(queryRunner: QueryRunner): Promise {
- const typeExists = await queryRunner.query(
- `SELECT 1 FROM pg_type WHERE typname = 'invoices_status_enum' AND typnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'freight');`,
- );
-
- if (!typeExists.length) {
- await queryRunner.query(`
- CREATE TYPE freight.invoices_status_enum AS ENUM (
- 'DRAFT',
- 'PENDING',
- 'PAID',
- 'OVERDUE',
- 'CANCELLED',
- 'REFUNDED'
- );
- `);
- }
-
- await queryRunner.query(`
- CREATE TABLE IF NOT EXISTS freight.invoices (
- id uuid NOT NULL DEFAULT uuid_generate_v4(),
- invoice_number varchar(64) NOT NULL,
- company_id uuid NOT NULL,
- company_profile_id uuid NOT NULL,
- total_amount numeric(14, 2) NOT NULL,
- currency varchar(8) NOT NULL DEFAULT 'ETB',
- status freight.invoices_status_enum NOT NULL DEFAULT 'DRAFT',
- source varchar(255) NOT NULL,
- source_id varchar(255) NOT NULL,
- type varchar(255) NOT NULL,
- issued_at timestamptz,
- payment_id uuid,
- due_at timestamptz NOT NULL,
- created_at timestamptz NOT NULL DEFAULT now(),
- updated_at timestamptz NOT NULL DEFAULT now(),
- deleted_at timestamptz,
- CONSTRAINT pk_invoices PRIMARY KEY (id),
- CONSTRAINT uq_invoices_invoice_number UNIQUE (invoice_number),
- CONSTRAINT fk_invoices_company FOREIGN KEY (company_id)
- REFERENCES freight.companies (id) ON DELETE RESTRICT,
- CONSTRAINT fk_invoices_company_profile FOREIGN KEY (company_profile_id)
- REFERENCES freight.company_profiles (id) ON DELETE RESTRICT,
- CONSTRAINT fk_invoices_payment FOREIGN KEY (payment_id)
- REFERENCES freight.payments (id) ON DELETE SET NULL
- );
- `);
-
- await queryRunner.query(
- `
- ALTER TABLE freight.invoices
- ADD COLUMN IF NOT EXISTS id uuid DEFAULT uuid_generate_v4(),
- ADD COLUMN IF NOT EXISTS invoice_number varchar(64),
- ADD COLUMN IF NOT EXISTS company_id uuid,
- ADD COLUMN IF NOT EXISTS company_profile_id uuid,
- ADD COLUMN IF NOT EXISTS total_amount numeric(14, 2) NOT NULL DEFAULT 0,
- ADD COLUMN IF NOT EXISTS currency varchar(8) NOT NULL DEFAULT 'ETB',
- ADD COLUMN IF NOT EXISTS status freight.invoices_status_enum NOT NULL DEFAULT 'DRAFT',
- ADD COLUMN IF NOT EXISTS source varchar(255),
- ADD COLUMN IF NOT EXISTS source_id varchar(255),
- ADD COLUMN IF NOT EXISTS type varchar(255),
- ADD COLUMN IF NOT EXISTS issued_at timestamptz,
- ADD COLUMN IF NOT EXISTS payment_id uuid,
- ADD COLUMN IF NOT EXISTS due_at timestamptz,
- ADD COLUMN IF NOT EXISTS created_at timestamptz NOT NULL DEFAULT now(),
- ADD COLUMN IF NOT EXISTS updated_at timestamptz NOT NULL DEFAULT now(),
- ADD COLUMN IF NOT EXISTS deleted_at timestamptz;
- `,
- );
- await queryRunner.query(`
- UPDATE freight.invoices
- SET due_at = COALESCE(due_at, issued_at, created_at, now())
- WHERE due_at IS NULL;
- `);
- await queryRunner.query(`ALTER TABLE freight.invoices ALTER COLUMN due_at SET NOT NULL;`);
-
- await queryRunner.query(`
- DO $$
- BEGIN
- IF NOT EXISTS (
- SELECT 1 FROM pg_constraint
- WHERE contype = 'p'
- AND conrelid = 'freight.invoices'::regclass
- ) THEN
- ALTER TABLE freight.invoices ADD CONSTRAINT pk_invoices PRIMARY KEY (id);
- END IF;
-
- IF NOT EXISTS (
- SELECT 1 FROM pg_constraint
- WHERE conname = 'uq_invoices_invoice_number'
- AND conrelid = 'freight.invoices'::regclass
- ) THEN
- ALTER TABLE freight.invoices ADD CONSTRAINT uq_invoices_invoice_number UNIQUE (invoice_number);
- END IF;
-
- IF NOT EXISTS (
- SELECT 1 FROM pg_constraint
- WHERE conname = 'fk_invoices_company'
- AND conrelid = 'freight.invoices'::regclass
- ) THEN
- ALTER TABLE freight.invoices ADD CONSTRAINT fk_invoices_company
- FOREIGN KEY (company_id) REFERENCES freight.companies (id) ON DELETE RESTRICT;
- END IF;
-
- IF NOT EXISTS (
- SELECT 1 FROM pg_constraint
- WHERE conname = 'fk_invoices_company_profile'
- AND conrelid = 'freight.invoices'::regclass
- ) THEN
- ALTER TABLE freight.invoices ADD CONSTRAINT fk_invoices_company_profile
- FOREIGN KEY (company_profile_id) REFERENCES freight.company_profiles (id) ON DELETE RESTRICT;
- END IF;
-
- IF NOT EXISTS (
- SELECT 1 FROM pg_constraint
- WHERE conname = 'fk_invoices_payment'
- AND conrelid = 'freight.invoices'::regclass
- ) THEN
- ALTER TABLE freight.invoices ADD CONSTRAINT fk_invoices_payment
- FOREIGN KEY (payment_id) REFERENCES freight.payments (id) ON DELETE SET NULL;
- END IF;
- END $$;
- `);
-
- await queryRunner.query(
- `CREATE INDEX IF NOT EXISTS idx_invoices_company ON freight.invoices (company_id);`,
- );
- await queryRunner.query(
- `CREATE INDEX IF NOT EXISTS idx_invoices_company_profile ON freight.invoices (company_profile_id);`,
- );
- await queryRunner.query(
- `CREATE INDEX IF NOT EXISTS idx_invoices_source ON freight.invoices (source, source_id);`,
- );
- await queryRunner.query(
- `CREATE INDEX IF NOT EXISTS idx_invoices_status ON freight.invoices (status);`,
- );
-
- await queryRunner.query(`
- CREATE TABLE IF NOT EXISTS freight.invoice_lines (
- id uuid NOT NULL DEFAULT uuid_generate_v4(),
- invoice_id uuid NOT NULL,
- charge_type varchar NOT NULL,
- description varchar(255),
- quantity numeric(12, 2) NOT NULL DEFAULT 1,
- unit_rate numeric(14, 2) NOT NULL DEFAULT 0,
- amount numeric(14, 2) NOT NULL,
- currency varchar(8) NOT NULL DEFAULT 'ETB',
- metadata jsonb,
- created_at timestamptz NOT NULL DEFAULT now(),
- updated_at timestamptz NOT NULL DEFAULT now(),
- deleted_at timestamptz,
- CONSTRAINT pk_invoice_lines PRIMARY KEY (id),
- CONSTRAINT fk_invoice_lines_invoice FOREIGN KEY (invoice_id)
- REFERENCES freight.invoices (id) ON DELETE CASCADE
- );
- `);
-
- await queryRunner.query(
- `CREATE INDEX IF NOT EXISTS idx_invoice_lines_invoice ON freight.invoice_lines (invoice_id);`,
- );
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`DROP TABLE IF EXISTS freight.invoice_lines;`);
- await queryRunner.query(`DROP TABLE IF EXISTS freight.invoices;`);
- await queryRunner.query(
- `DROP TYPE IF EXISTS freight.invoices_status_enum;`,
- );
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1821000000003-AddCompanyKindAndGovBookingLinks.ts b/apps/edr-freight-api/src/migrations/1821000000003-AddCompanyKindAndGovBookingLinks.ts
deleted file mode 100644
index 95ea3db1b..000000000
--- a/apps/edr-freight-api/src/migrations/1821000000003-AddCompanyKindAndGovBookingLinks.ts
+++ /dev/null
@@ -1,129 +0,0 @@
-import { MigrationInterface, QueryRunner } from "typeorm";
-
-/**
- * Government bookings now bill to a real seeded government company + an explicit
- * importer/exporter profile, instead of carrying a null company + free-text
- * institution. This migration:
- *
- * 1. Adds `companies.kind` (commercial | government).
- * 2. Seeds the Ethiopian government entities + their importer/exporter
- * profiles (mirrors src/seed/data/gov-companies.data.ts — keep in sync).
- * 3. Backfills every booking with a NULL company_id / company_profile_id so
- * the NOT NULL constraints below can be applied:
- * - NULL company_id → the default government company.
- * - NULL company_profile_id → the company's profile matching the booking
- * trade direction; else any profile of the company; else the default
- * government importer profile.
- * 4. Enforces NOT NULL on bookings.company_id and bookings.company_profile_id.
- */
-export class AddCompanyKindAndGovBookingLinks1821000000003
- implements MigrationInterface
-{
- name = "AddCompanyKindAndGovBookingLinks1821000000003";
-
- // Mirrors src/seed/data/gov-companies.data.ts
- private readonly govCompanies = [
- { id: "0a1b0001-0000-4000-8000-000000000001", name: "Federal Government of Ethiopia", tin: "0000000001", email: "procurement@gov.et", phone: "+251111000001", im: "0b1c0001-0000-4000-8000-000000000001", ex: "0b1c0001-0000-4000-8000-000000000002", imRef: "IM-90001", exRef: "EX-90001" },
- { id: "0a1b0002-0000-4000-8000-000000000002", name: "Ministry of National Defense", tin: "0000000002", email: "logistics@mod.gov.et", phone: "+251111000002", im: "0b1c0002-0000-4000-8000-000000000001", ex: "0b1c0002-0000-4000-8000-000000000002", imRef: "IM-90002", exRef: "EX-90002" },
- { id: "0a1b0003-0000-4000-8000-000000000003", name: "Ethiopian Roads Administration", tin: "0000000003", email: "supply@era.gov.et", phone: "+251111000003", im: "0b1c0003-0000-4000-8000-000000000001", ex: "0b1c0003-0000-4000-8000-000000000002", imRef: "IM-90003", exRef: "EX-90003" },
- { id: "0a1b0004-0000-4000-8000-000000000004", name: "Ministry of Agriculture", tin: "0000000004", email: "imports@moa.gov.et", phone: "+251111000004", im: "0b1c0004-0000-4000-8000-000000000001", ex: "0b1c0004-0000-4000-8000-000000000002", imRef: "IM-90004", exRef: "EX-90004" },
- { id: "0a1b0005-0000-4000-8000-000000000005", name: "Ministry of Trade and Regional Integration", tin: "0000000005", email: "trade@motri.gov.et", phone: "+251111000005", im: "0b1c0005-0000-4000-8000-000000000001", ex: "0b1c0005-0000-4000-8000-000000000002", imRef: "IM-90005", exRef: "EX-90005" },
- { id: "0a1b0006-0000-4000-8000-000000000006", name: "Ethiopian Disaster Risk Management Commission", tin: "0000000006", email: "relief@edrmc.gov.et", phone: "+251111000006", im: "0b1c0006-0000-4000-8000-000000000001", ex: "0b1c0006-0000-4000-8000-000000000002", imRef: "IM-90006", exRef: "EX-90006" },
- ];
-
- private get defaultCompanyId(): string {
- return this.govCompanies[0].id;
- }
- private get defaultImporterProfileId(): string {
- return this.govCompanies[0].im;
- }
-
- public async up(queryRunner: QueryRunner): Promise {
- // 1. kind column
- await queryRunner.query(
- `ALTER TABLE "freight"."companies" ADD COLUMN IF NOT EXISTS "kind" varchar(20) NOT NULL DEFAULT 'commercial'`,
- );
- await queryRunner.query(
- `CREATE INDEX IF NOT EXISTS "IDX_companies_kind" ON "freight"."companies" ("kind")`,
- );
-
- // 2. seed government companies + importer/exporter profiles (idempotent)
- for (const g of this.govCompanies) {
- await queryRunner.query(
- `INSERT INTO "freight"."companies" ("id", "name", "type", "kind", "status", "tin", "country", "email", "phone")
- VALUES ($1, $2, 'customer', 'government', 'active', $3, 'Ethiopia', $4, $5)
- ON CONFLICT ("id") DO NOTHING`,
- [g.id, g.name, g.tin, g.email, g.phone],
- );
- await queryRunner.query(
- `INSERT INTO "freight"."company_profiles" ("id", "company_id", "type", "reference", "status")
- VALUES ($1, $2, 'importer', $3, 'active'), ($4, $2, 'exporter', $5, 'active')
- ON CONFLICT ("id") DO NOTHING`,
- [g.im, g.id, g.imRef, g.ex, g.exRef],
- );
- }
-
- // 3a. backfill NULL company_id → default government company
- await queryRunner.query(
- `UPDATE "freight"."bookings" SET "company_id" = $1 WHERE "company_id" IS NULL`,
- [this.defaultCompanyId],
- );
-
- // 3b. backfill NULL company_profile_id → profile matching trade direction
- await queryRunner.query(
- `UPDATE "freight"."bookings" b
- SET "company_profile_id" = cp."id"
- FROM "freight"."company_profiles" cp
- WHERE b."company_profile_id" IS NULL
- AND cp."company_id" = b."company_id"
- AND cp."deleted_at" IS NULL
- AND cp."type" = CASE b."trade_direction"
- WHEN 'IMPORT' THEN 'importer'
- WHEN 'EXPORT' THEN 'exporter'
- ELSE NULL END`,
- );
-
- // 3c. fallback → any profile of the booking's company
- await queryRunner.query(
- `UPDATE "freight"."bookings" b
- SET "company_profile_id" = (
- SELECT cp."id" FROM "freight"."company_profiles" cp
- WHERE cp."company_id" = b."company_id" AND cp."deleted_at" IS NULL
- ORDER BY cp."created_at" ASC LIMIT 1)
- WHERE b."company_profile_id" IS NULL
- AND EXISTS (
- SELECT 1 FROM "freight"."company_profiles" cp
- WHERE cp."company_id" = b."company_id" AND cp."deleted_at" IS NULL)`,
- );
-
- // 3d. final fallback → default government importer profile
- await queryRunner.query(
- `UPDATE "freight"."bookings" SET "company_profile_id" = $1 WHERE "company_profile_id" IS NULL`,
- [this.defaultImporterProfileId],
- );
-
- // 4. enforce NOT NULL
- await queryRunner.query(
- `ALTER TABLE "freight"."bookings" ALTER COLUMN "company_id" SET NOT NULL`,
- );
- await queryRunner.query(
- `ALTER TABLE "freight"."bookings" ALTER COLUMN "company_profile_id" SET NOT NULL`,
- );
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(
- `ALTER TABLE "freight"."bookings" ALTER COLUMN "company_profile_id" DROP NOT NULL`,
- );
- await queryRunner.query(
- `ALTER TABLE "freight"."bookings" ALTER COLUMN "company_id" DROP NOT NULL`,
- );
- await queryRunner.query(
- `DROP INDEX IF EXISTS "freight"."IDX_companies_kind"`,
- );
- await queryRunner.query(
- `ALTER TABLE "freight"."companies" DROP COLUMN IF EXISTS "kind"`,
- );
- // Seeded government rows are intentionally left in place.
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1821000000004-MakePaymentsTypeGeneric.ts b/apps/edr-freight-api/src/migrations/1821000000004-MakePaymentsTypeGeneric.ts
deleted file mode 100644
index 9f0e8e7bd..000000000
--- a/apps/edr-freight-api/src/migrations/1821000000004-MakePaymentsTypeGeneric.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-import { MigrationInterface, QueryRunner } from "typeorm";
-
-/**
- * Make the payment projection source-agnostic so any domain (not just bookings)
- * can own a payment intent.
- *
- * - `payments.type` enum `('booking')` → `varchar(50)`. It now stores the
- * invoice SOURCE (e.g. 'booking', 'demurrage'), supplied by the caller, so a
- * new domain no longer needs an enum migration to write its intents.
- * - adds `payments.reference_type varchar(40)` — the gateway reference type
- * (`PaymentReferenceType`) the intent was opened with, so the reconcile/poll
- * path can query the provider without hardcoding it.
- *
- * Matches payment/entities/payment.entity.ts.
- */
-export class MakePaymentsTypeGeneric1821000000004 implements MigrationInterface {
- name = "MakePaymentsTypeGeneric1821000000004";
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(
- `ALTER TABLE freight.payments ALTER COLUMN type TYPE varchar(50) USING type::text;`,
- );
- await queryRunner.query(`DROP TYPE IF EXISTS freight.payments_type_enum;`);
-
- await queryRunner.query(
- `ALTER TABLE freight.payments ADD COLUMN reference_type varchar(40);`,
- );
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(
- `ALTER TABLE freight.payments DROP COLUMN IF EXISTS reference_type;`,
- );
-
- // Restore the single-value enum. Any non-'booking' rows would block the cast;
- // collapse them first so the down migration is safe.
- await queryRunner.query(
- `UPDATE freight.payments SET type = 'booking' WHERE type <> 'booking';`,
- );
- await queryRunner.query(
- `CREATE TYPE freight.payments_type_enum AS ENUM ('booking');`,
- );
- await queryRunner.query(
- `ALTER TABLE freight.payments ALTER COLUMN type TYPE freight.payments_type_enum USING type::freight.payments_type_enum;`,
- );
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1822000000000-CreateContracts.ts b/apps/edr-freight-api/src/migrations/1822000000000-CreateContracts.ts
deleted file mode 100644
index 7995e5023..000000000
--- a/apps/edr-freight-api/src/migrations/1822000000000-CreateContracts.ts
+++ /dev/null
@@ -1,357 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-/**
- * Contract–Booking separation (additive phase). Introduces a first-class
- * `freight.contracts` aggregate that owns the legal/commercial agreement (scope
- * + unit rates, no quantities) and spawns shipment `bookings` via `contract_id`.
- *
- * Purely additive: no legacy columns are dropped here. The data backfill and
- * legacy-column removal happen in a later cutover migration.
- *
- * See docs/new-doc.md §5.
- */
-export class CreateContracts1822000000000 implements MigrationInterface {
- name = 'CreateContracts1822000000000';
-
- public async up(queryRunner: QueryRunner): Promise {
- // ── contracts ───────────────────────────────────────────────────────────
- await queryRunner.query(`
- CREATE TABLE IF NOT EXISTS freight.contracts (
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
- reference VARCHAR(64) NOT NULL UNIQUE,
-
- company_id UUID,
- company_profile_id UUID,
- is_government BOOLEAN NOT NULL DEFAULT FALSE,
- government_institution VARCHAR(255),
-
- contract_kind VARCHAR(20) NOT NULL,
- renewal_of_id UUID REFERENCES freight.contracts(id),
- trade_direction VARCHAR(10) NOT NULL,
- freight_type VARCHAR(20) NOT NULL,
-
- service_type_id UUID NOT NULL,
- payment_currency VARCHAR(5) NOT NULL,
- customs_clearing_enabled BOOLEAN NOT NULL DEFAULT FALSE,
- customs_clearing_agent VARCHAR(200),
- equipment_return VARCHAR(20),
-
- first_mile_pickup_address TEXT,
- first_mile_pickup_lat NUMERIC(10,7),
- first_mile_pickup_lng NUMERIC(10,7),
- last_mile_delivery_address TEXT,
- last_mile_delivery_lat NUMERIC(10,7),
- last_mile_delivery_lng NUMERIC(10,7),
-
- is_hazardous BOOLEAN NOT NULL DEFAULT FALSE,
- is_reefer BOOLEAN NOT NULL DEFAULT FALSE,
-
- estimated_shipment_date TIMESTAMPTZ,
- contract_validity_days INT,
- contract_valid_from TIMESTAMPTZ,
- contract_valid_until TIMESTAMPTZ,
- expires_at TIMESTAMPTZ,
-
- status VARCHAR(40) NOT NULL DEFAULT 'DRAFT',
- clearance_status VARCHAR(40) NOT NULL DEFAULT 'NOT_APPLICABLE',
- clearance_cycle_number INT NOT NULL DEFAULT 0,
-
- pricing_breakdown JSONB,
- pricing_display_mode VARCHAR(20) DEFAULT 'UNIT_RATES',
-
- contract_type VARCHAR(20),
- contract_template_key VARCHAR(128),
- contract_generated_at TIMESTAMPTZ,
- contract_summary TEXT,
- version_number INT NOT NULL DEFAULT 1,
- financial_terms JSONB,
-
- approved_by_staff_id UUID,
- approved_by_staff_at TIMESTAMPTZ,
- signed_by_director_id UUID,
- signed_by_director_at TIMESTAMPTZ,
- signed_by_ceo_id UUID,
- signed_by_ceo_at TIMESTAMPTZ,
- customer_signed_at TIMESTAMPTZ,
- fully_executed_at TIMESTAMPTZ,
- locked_at TIMESTAMPTZ,
-
- 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_contracts_company ON freight.contracts(company_id);`);
- await queryRunner.query(`CREATE INDEX IF NOT EXISTS idx_contracts_status ON freight.contracts(status);`);
- await queryRunner.query(`CREATE INDEX IF NOT EXISTS idx_contracts_kind ON freight.contracts(contract_kind);`);
- await queryRunner.query(`CREATE INDEX IF NOT EXISTS idx_contracts_valid_until ON freight.contracts(contract_valid_until);`);
-
- // ── contract_routes ──────────────────────────────────────────────────────
- await queryRunner.query(`
- CREATE TABLE IF NOT EXISTS freight.contract_routes (
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
- contract_id UUID NOT NULL REFERENCES freight.contracts(id) ON DELETE CASCADE,
- origin_yard_id UUID NOT NULL,
- destination_yard_id UUID NOT NULL,
- km NUMERIC(10,2),
- sort_order SMALLINT NOT NULL DEFAULT 0,
- created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
- updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
- deleted_at TIMESTAMPTZ,
- CONSTRAINT uq_contract_route UNIQUE (contract_id, origin_yard_id, destination_yard_id)
- );
- `);
- await queryRunner.query(`CREATE INDEX IF NOT EXISTS idx_contract_routes_contract ON freight.contract_routes(contract_id);`);
-
- // ── contract_cargo_scope ─────────────────────────────────────────────────
- await queryRunner.query(`
- CREATE TABLE IF NOT EXISTS freight.contract_cargo_scope (
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
- contract_id UUID NOT NULL REFERENCES freight.contracts(id) ON DELETE CASCADE,
- container_size VARCHAR(10),
- cargo_type_id UUID,
- cargo_free_text VARCHAR(200),
- created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
- updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
- deleted_at TIMESTAMPTZ,
- CONSTRAINT uq_contract_container_size UNIQUE NULLS NOT DISTINCT (contract_id, container_size)
- );
- `);
- await queryRunner.query(`CREATE INDEX IF NOT EXISTS idx_contract_cargo_scope_contract ON freight.contract_cargo_scope(contract_id);`);
-
- // ── contract_signatures ──────────────────────────────────────────────────
- await queryRunner.query(`
- CREATE TABLE IF NOT EXISTS freight.contract_signatures (
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
- contract_id UUID NOT NULL REFERENCES freight.contracts(id) ON DELETE CASCADE,
- role VARCHAR(20) NOT NULL,
- signer_display_name VARCHAR(255) NOT NULL,
- signature_file_id UUID,
- consent_text TEXT,
- signed_at TIMESTAMPTZ NOT NULL DEFAULT now(),
- 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_contract_signatures_contract ON freight.contract_signatures(contract_id);`);
-
- // ── contract_approval_steps ──────────────────────────────────────────────
- await queryRunner.query(`
- CREATE TABLE IF NOT EXISTS freight.contract_approval_steps (
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
- contract_id UUID NOT NULL REFERENCES freight.contracts(id) ON DELETE CASCADE,
- step_order SMALLINT NOT NULL DEFAULT 0,
- required_role VARCHAR(40) NOT NULL,
- blocks_role VARCHAR(40),
- status VARCHAR(20) NOT NULL DEFAULT 'PENDING',
- acted_by_staff_id UUID,
- acted_at TIMESTAMPTZ,
- note TEXT,
- 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_contract_approval_steps_contract ON freight.contract_approval_steps(contract_id);`);
-
- // ── contract_rate_snapshots ──────────────────────────────────────────────
- await queryRunner.query(`
- CREATE TABLE IF NOT EXISTS freight.contract_rate_snapshots (
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
- contract_id UUID NOT NULL REFERENCES freight.contracts(id) ON DELETE CASCADE,
- rate_id UUID,
- rate_code VARCHAR(64) NOT NULL,
- description VARCHAR(255),
- unit_price NUMERIC(14,2) NOT NULL,
- unit_of_measure VARCHAR(32) NOT NULL,
- currency VARCHAR(5) NOT NULL,
- container_size VARCHAR(10),
- is_surcharge BOOLEAN DEFAULT FALSE,
- conditional_on VARCHAR(32),
- 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_contract_rate_snapshots_contract ON freight.contract_rate_snapshots(contract_id);`);
-
- // ── contract_review_notes ────────────────────────────────────────────────
- await queryRunner.query(`
- CREATE TABLE IF NOT EXISTS freight.contract_review_notes (
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
- contract_id UUID NOT NULL REFERENCES freight.contracts(id) ON DELETE CASCADE,
- note_type VARCHAR(40) NOT NULL,
- body TEXT NOT NULL,
- author_role VARCHAR(20),
- author_user_id UUID,
- 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_contract_review_notes_contract ON freight.contract_review_notes(contract_id);`);
-
- // ── contract_clearance_cycles ────────────────────────────────────────────
- await queryRunner.query(`
- CREATE TABLE IF NOT EXISTS freight.contract_clearance_cycles (
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
- contract_id UUID NOT NULL REFERENCES freight.contracts(id) ON DELETE CASCADE,
- cycle_number INT NOT NULL,
- status VARCHAR(40) NOT NULL DEFAULT 'AWAITING_DOCUMENTS',
- booking_id UUID,
- started_at TIMESTAMPTZ NOT NULL DEFAULT now(),
- clearance_ready_at TIMESTAMPTZ,
- completed_at TIMESTAMPTZ,
- created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
- updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
- deleted_at TIMESTAMPTZ,
- CONSTRAINT uq_contract_clearance_cycle UNIQUE (contract_id, cycle_number)
- );
- `);
- await queryRunner.query(`CREATE INDEX IF NOT EXISTS idx_contract_clearance_cycles_contract ON freight.contract_clearance_cycles(contract_id);`);
-
- // ── contract_document_review (pre-booking clearance, Path B) ─────────────
- await queryRunner.query(`
- CREATE TABLE IF NOT EXISTS freight.contract_document_review (
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
- contract_id UUID NOT NULL REFERENCES freight.contracts(id) ON DELETE CASCADE,
- clearance_cycle_id UUID REFERENCES freight.contract_clearance_cycles(id),
- setting_code VARCHAR(128) NOT NULL,
- file_key VARCHAR(128) NOT NULL,
- file_record_id UUID,
- status VARCHAR(20) NOT NULL DEFAULT 'PENDING',
- note TEXT,
- uploaded_by_role VARCHAR(20) NOT NULL DEFAULT 'CUSTOMER',
- reviewed_by_staff_id UUID,
- reviewed_at TIMESTAMPTZ,
- created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
- updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
- deleted_at TIMESTAMPTZ,
- CONSTRAINT uq_contract_document_review_doc
- UNIQUE NULLS NOT DISTINCT (contract_id, clearance_cycle_id, setting_code, file_key)
- );
- `);
- await queryRunner.query(`CREATE INDEX IF NOT EXISTS idx_contract_doc_review_contract ON freight.contract_document_review(contract_id);`);
- await queryRunner.query(`CREATE INDEX IF NOT EXISTS idx_contract_doc_review_status ON freight.contract_document_review(status);`);
-
- // ── clearance_milestones (GL tracking) ───────────────────────────────────
- await queryRunner.query(`
- CREATE TABLE IF NOT EXISTS freight.clearance_milestones (
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
- booking_id UUID REFERENCES freight.bookings(id) ON DELETE CASCADE,
- contract_id UUID REFERENCES freight.contracts(id) ON DELETE CASCADE,
- clearance_cycle_id UUID REFERENCES freight.contract_clearance_cycles(id),
- milestone_code VARCHAR(64) NOT NULL,
- milestone_label VARCHAR(255) NOT NULL,
- status VARCHAR(20) NOT NULL DEFAULT 'PENDING',
- owner_region VARCHAR(5),
- triggered_by_doc BOOLEAN DEFAULT FALSE,
- triggered_at TIMESTAMPTZ,
- triggered_by_user_id UUID,
- note TEXT,
- sort_order SMALLINT NOT NULL DEFAULT 0,
- 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_clearance_milestones_booking ON freight.clearance_milestones(booking_id);`);
- await queryRunner.query(`CREATE INDEX IF NOT EXISTS idx_clearance_milestones_contract ON freight.clearance_milestones(contract_id);`);
- await queryRunner.query(`CREATE INDEX IF NOT EXISTS idx_clearance_milestones_region ON freight.clearance_milestones(owner_region, status);`);
- // booking-scoped and contract-cycle-scoped uniqueness for milestone codes
- await queryRunner.query(`
- CREATE UNIQUE INDEX IF NOT EXISTS uq_clearance_milestone_booking
- ON freight.clearance_milestones(booking_id, milestone_code) WHERE booking_id IS NOT NULL;
- `);
- await queryRunner.query(`
- CREATE UNIQUE INDEX IF NOT EXISTS uq_clearance_milestone_cycle
- ON freight.clearance_milestones(clearance_cycle_id, milestone_code) WHERE clearance_cycle_id IS NOT NULL;
- `);
-
- // ── booking_container_units (per-unit container detail) ──────────────────
- await queryRunner.query(`
- CREATE TABLE IF NOT EXISTS freight.booking_container_units (
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
- booking_container_id UUID NOT NULL REFERENCES freight.booking_container(id) ON DELETE CASCADE,
- container_number VARCHAR(64) NOT NULL,
- seal_number VARCHAR(64),
- vgm_tons NUMERIC(10,3) NOT NULL,
- is_hazardous BOOLEAN DEFAULT FALSE,
- is_reefer BOOLEAN DEFAULT FALSE,
- sort_order SMALLINT NOT NULL DEFAULT 0,
- created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
- updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
- deleted_at TIMESTAMPTZ,
- CONSTRAINT uq_booking_container_unit_number UNIQUE (booking_container_id, container_number)
- );
- `);
-
- // ── ALTER bookings ───────────────────────────────────────────────────────
- await queryRunner.query(`ALTER TABLE freight.bookings ADD COLUMN IF NOT EXISTS contract_id UUID REFERENCES freight.contracts(id);`);
- await queryRunner.query(`ALTER TABLE freight.bookings ADD COLUMN IF NOT EXISTS contract_route_id UUID REFERENCES freight.contract_routes(id);`);
- await queryRunner.query(`ALTER TABLE freight.bookings ADD COLUMN IF NOT EXISTS created_by_role VARCHAR(20) DEFAULT 'CUSTOMER';`);
- await queryRunner.query(`ALTER TABLE freight.bookings ADD COLUMN IF NOT EXISTS created_by_user_id UUID;`);
- await queryRunner.query(`CREATE INDEX IF NOT EXISTS idx_bookings_contract ON freight.bookings(contract_id);`);
- // One active booking per ONE_TIME contract. Postgres forbids a subquery in an
- // index predicate, so we denormalize the contract kind onto the booking and
- // predicate on that. The column is stamped at booking creation from the
- // contract; the app layer (ContractBookingService) is the primary guard and
- // this index is the backstop.
- await queryRunner.query(`ALTER TABLE freight.bookings ADD COLUMN IF NOT EXISTS contract_kind VARCHAR(20);`);
- await queryRunner.query(`
- CREATE UNIQUE INDEX IF NOT EXISTS uq_one_active_booking_per_one_time_contract
- ON freight.bookings (contract_id)
- WHERE status NOT IN ('EXPIRED', 'CANCELLED', 'COMPLETED', 'REJECTED')
- AND contract_id IS NOT NULL
- AND contract_kind = 'ONE_TIME';
- `);
-
- // ── ALTER booking_container ──────────────────────────────────────────────
- await queryRunner.query(`ALTER TABLE freight.booking_container ADD COLUMN IF NOT EXISTS container_size VARCHAR(10);`);
- await queryRunner.query(`ALTER TABLE freight.booking_container ADD COLUMN IF NOT EXISTS hazardous_quantity SMALLINT DEFAULT 0;`);
- await queryRunner.query(`ALTER TABLE freight.booking_container ADD COLUMN IF NOT EXISTS reefer_quantity SMALLINT DEFAULT 0;`);
-
- // ── ALTER booking_document_review (denormalized contract link) ───────────
- await queryRunner.query(`ALTER TABLE freight.booking_document_review ADD COLUMN IF NOT EXISTS contract_id UUID REFERENCES freight.contracts(id);`);
-
- // ── Extend file_upload_fields with phased GL metadata ────────────────────
- await queryRunner.query(`ALTER TABLE freight.file_upload_fields ADD COLUMN IF NOT EXISTS phase VARCHAR(40);`);
- await queryRunner.query(`ALTER TABLE freight.file_upload_fields ADD COLUMN IF NOT EXISTS owner_region VARCHAR(5);`);
- await queryRunner.query(`ALTER TABLE freight.file_upload_fields ADD COLUMN IF NOT EXISTS trade_direction VARCHAR(10);`);
- await queryRunner.query(`ALTER TABLE freight.file_upload_fields ADD COLUMN IF NOT EXISTS triggers_milestone_code VARCHAR(64);`);
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`ALTER TABLE freight.file_upload_fields DROP COLUMN IF EXISTS triggers_milestone_code;`);
- await queryRunner.query(`ALTER TABLE freight.file_upload_fields DROP COLUMN IF EXISTS trade_direction;`);
- await queryRunner.query(`ALTER TABLE freight.file_upload_fields DROP COLUMN IF EXISTS owner_region;`);
- await queryRunner.query(`ALTER TABLE freight.file_upload_fields DROP COLUMN IF EXISTS phase;`);
-
- await queryRunner.query(`ALTER TABLE freight.booking_document_review DROP COLUMN IF EXISTS contract_id;`);
-
- await queryRunner.query(`ALTER TABLE freight.booking_container DROP COLUMN IF EXISTS reefer_quantity;`);
- await queryRunner.query(`ALTER TABLE freight.booking_container DROP COLUMN IF EXISTS hazardous_quantity;`);
- await queryRunner.query(`ALTER TABLE freight.booking_container DROP COLUMN IF EXISTS container_size;`);
-
- await queryRunner.query(`DROP INDEX IF EXISTS freight.uq_one_active_booking_per_one_time_contract;`);
- await queryRunner.query(`DROP INDEX IF EXISTS freight.idx_bookings_contract;`);
- await queryRunner.query(`ALTER TABLE freight.bookings DROP COLUMN IF EXISTS contract_kind;`);
- await queryRunner.query(`ALTER TABLE freight.bookings DROP COLUMN IF EXISTS created_by_user_id;`);
- await queryRunner.query(`ALTER TABLE freight.bookings DROP COLUMN IF EXISTS created_by_role;`);
- await queryRunner.query(`ALTER TABLE freight.bookings DROP COLUMN IF EXISTS contract_route_id;`);
- await queryRunner.query(`ALTER TABLE freight.bookings DROP COLUMN IF EXISTS contract_id;`);
-
- await queryRunner.query(`DROP TABLE IF EXISTS freight.booking_container_units;`);
- await queryRunner.query(`DROP TABLE IF EXISTS freight.clearance_milestones;`);
- await queryRunner.query(`DROP TABLE IF EXISTS freight.contract_document_review;`);
- await queryRunner.query(`DROP TABLE IF EXISTS freight.contract_clearance_cycles;`);
- await queryRunner.query(`DROP TABLE IF EXISTS freight.contract_review_notes;`);
- await queryRunner.query(`DROP TABLE IF EXISTS freight.contract_rate_snapshots;`);
- await queryRunner.query(`DROP TABLE IF EXISTS freight.contract_approval_steps;`);
- await queryRunner.query(`DROP TABLE IF EXISTS freight.contract_signatures;`);
- await queryRunner.query(`DROP TABLE IF EXISTS freight.contract_cargo_scope;`);
- await queryRunner.query(`DROP TABLE IF EXISTS freight.contract_routes;`);
- await queryRunner.query(`DROP TABLE IF EXISTS freight.contracts;`);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1822000000000-CreateImportDjiboutiOperations.ts b/apps/edr-freight-api/src/migrations/1822000000000-CreateImportDjiboutiOperations.ts
deleted file mode 100644
index 981918f25..000000000
--- a/apps/edr-freight-api/src/migrations/1822000000000-CreateImportDjiboutiOperations.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-import { MigrationInterface, QueryRunner, Table, TableForeignKey, TableIndex } from 'typeorm';
-
-export class CreateImportDjiboutiOperations1822000000000 implements MigrationInterface {
- name = 'CreateImportDjiboutiOperations1822000000000';
-
- async up(queryRunner: QueryRunner): Promise {
- await queryRunner.createTable(
- new Table({
- schema: 'freight',
- name: 'import_djibouti_operations',
- columns: [
- { name: 'id', type: 'uuid', isPrimary: true, generationStrategy: 'uuid', default: 'uuid_generate_v4()' },
- { name: 'train_schedule_id', type: 'uuid', isUnique: true },
- { name: 'documents', type: 'jsonb', default: "'{}'::jsonb" },
- { name: 'gatepass_granted_at', type: 'timestamptz', isNullable: true },
- { name: 'ready_for_loading_at', type: 'timestamptz', isNullable: true },
- { name: 'loaded_on_train_at', type: 'timestamptz', isNullable: true },
- { name: 'departed_from_djibouti_at', type: 'timestamptz', isNullable: true },
- { name: 'load_list_generated_at', type: 'timestamptz', isNullable: true },
- { name: 'performed_by', type: 'varchar', length: '120', isNullable: true },
- { name: 'notes', type: 'text', isNullable: true },
- { name: 'created_at', type: 'timestamptz', default: 'now()' },
- { name: 'updated_at', type: 'timestamptz', default: 'now()' },
- { name: 'deleted_at', type: 'timestamptz', isNullable: true },
- ],
- }),
- true,
- );
-
- await queryRunner.createIndex(
- 'freight.import_djibouti_operations',
- new TableIndex({
- name: 'idx_import_djibouti_operations_schedule',
- columnNames: ['train_schedule_id'],
- }),
- );
-
- await queryRunner.createForeignKey(
- 'freight.import_djibouti_operations',
- new TableForeignKey({
- columnNames: ['train_schedule_id'],
- referencedTableName: 'train_schedules',
- referencedSchema: 'freight',
- referencedColumnNames: ['id'],
- onDelete: 'CASCADE',
- }),
- );
- }
-
- async down(queryRunner: QueryRunner): Promise {
- await queryRunner.dropTable('freight.import_djibouti_operations', true);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1823000000000-BackfillContractsFromBookings.ts b/apps/edr-freight-api/src/migrations/1823000000000-BackfillContractsFromBookings.ts
deleted file mode 100644
index 552e4affd..000000000
--- a/apps/edr-freight-api/src/migrations/1823000000000-BackfillContractsFromBookings.ts
+++ /dev/null
@@ -1,175 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-/**
- * Data backfill for the contract–booking separation (docs/new-doc.md §17).
- *
- * For every legacy `booking_type = 'GENERAL_CONTRACT'` booking we synthesise a
- * `freight.contracts` row from its contract-phase columns, copy its routes
- * (contract_route_lines → contract_routes, dropping quantity), and point the
- * contract + every child shipment booking (linked via booking_orders) at it.
- *
- * Per §19 item 1, historical ONE_TIME bookings that went through the full
- * contract flow get a contract parent inserted and `contract_id` set on the same
- * booking row (no row split).
- *
- * Idempotent: skips bookings that already have `contract_id` set, and matches a
- * synthesised contract by a deterministic `CTR-` reference.
- */
-export class BackfillContractsFromBookings1823000000000
- implements MigrationInterface
-{
- name = 'BackfillContractsFromBookings1823000000000';
-
- public async up(queryRunner: QueryRunner): Promise {
- // 1. One contract per GENERAL_CONTRACT booking, carrying the contract-phase
- // columns. Reference is derived from the source booking id so re-runs are
- // idempotent (ON CONFLICT DO NOTHING on the unique reference).
- await queryRunner.query(`
- INSERT INTO freight.contracts (
- reference, company_id, company_profile_id, is_government, government_institution,
- contract_kind, trade_direction, freight_type, service_type_id, payment_currency,
- customs_clearing_enabled, customs_clearing_agent, equipment_return,
- first_mile_pickup_address, first_mile_pickup_lat, first_mile_pickup_lng,
- last_mile_delivery_address, last_mile_delivery_lat, last_mile_delivery_lng,
- is_hazardous, is_reefer, estimated_shipment_date,
- contract_validity_days, contract_valid_from, contract_valid_until, expires_at,
- status, clearance_status, clearance_cycle_number,
- pricing_breakdown, contract_type, contract_template_key, contract_generated_at,
- contract_summary, version_number,
- approved_by_staff_id, approved_by_staff_at,
- signed_by_director_id, signed_by_director_at,
- signed_by_ceo_id, signed_by_ceo_at, customer_signed_at, fully_executed_at,
- created_at, updated_at
- )
- SELECT
- 'CTR-' || b.id::text, b.company_id, b.company_profile_id, b.is_government, b.government_institution,
- 'GENERAL', b.trade_direction, b.freight_type, b.service_type_id, b.payment_currency,
- b.customs_clearing_enabled, b.customs_clearing_agent, b.equipment_return,
- b.first_mile_pickup_address, b.first_mile_pickup_lat, b.first_mile_pickup_lng,
- b.last_mile_delivery_address, b.last_mile_delivery_lat, b.last_mile_delivery_lng,
- b.is_hazardous, b.is_reefer, b.estimated_shipment_date,
- b.contract_validity_days, b.contract_valid_from, b.contract_valid_until, b.expires_at,
- CASE
- WHEN b.status IN ('CONTRACT_ACTIVE') THEN 'CONTRACT_ACTIVE'
- WHEN b.status IN ('CONTRACT_CLOSED') THEN 'CONTRACT_CLOSED'
- WHEN b.status IN ('EXPIRED') THEN 'EXPIRED'
- WHEN b.status IN ('CANCELLED') THEN 'CANCELLED'
- WHEN b.status IN ('REJECTED') THEN 'REJECTED'
- ELSE 'CONTRACT_ACTIVE'
- END,
- CASE WHEN b.customs_clearing_enabled THEN 'NOT_APPLICABLE' ELSE 'NOT_APPLICABLE' END,
- 0,
- b.pricing_breakdown,
- b.contract_type, b.contract_template_key, b.contract_generated_at,
- b.contract_summary, COALESCE(b.version_number, 1),
- b.approved_by_staff_id, b.approved_by_staff_at,
- b.signed_by_director_id, b.signed_by_director_at,
- b.signed_by_ceo_id, b.signed_by_ceo_at, b.customer_signed_at, b.fully_executed_at,
- b.created_at, b.updated_at
- FROM freight.bookings b
- WHERE b.booking_type = 'GENERAL_CONTRACT'
- ON CONFLICT (reference) DO NOTHING;
- `);
-
- // 2. Copy each general contract's route lines into contract_routes (no qty).
- await queryRunner.query(`
- INSERT INTO freight.contract_routes (contract_id, origin_yard_id, destination_yard_id, km, sort_order, created_at, updated_at)
- SELECT c.id, crl.origin_yard_id, crl.destination_yard_id, crl.km, 0, now(), now()
- FROM freight.contract_route_lines crl
- JOIN freight.contracts c ON c.reference = 'CTR-' || crl.contract_booking_id::text
- ON CONFLICT (contract_id, origin_yard_id, destination_yard_id) DO NOTHING;
- `);
-
- // 3. Point the general-contract booking itself at its new contract, and stamp
- // the denormalized contract_kind for the active-booking index.
- await queryRunner.query(`
- UPDATE freight.bookings b
- SET contract_id = c.id, contract_kind = 'GENERAL', created_by_role = 'CUSTOMER'
- FROM freight.contracts c
- WHERE c.reference = 'CTR-' || b.id::text
- AND b.booking_type = 'GENERAL_CONTRACT'
- AND b.contract_id IS NULL;
- `);
-
- // 4. Point each child shipment booking (spawned via booking_orders) at the
- // same contract as its parent general contract.
- await queryRunner.query(`
- UPDATE freight.bookings child
- SET contract_id = c.id, contract_kind = 'GENERAL', created_by_role = 'CUSTOMER'
- FROM freight.booking_orders bo
- JOIN freight.contracts c ON c.reference = 'CTR-' || bo.contract_booking_id::text
- WHERE child.id = bo.booking_id
- AND child.contract_id IS NULL;
- `);
-
- // 5. Historical ONE_TIME bookings that completed the contract flow: synthesise
- // a contract parent and point the same booking row at it (no row split).
- await queryRunner.query(`
- INSERT INTO freight.contracts (
- reference, company_id, company_profile_id, is_government, government_institution,
- contract_kind, trade_direction, freight_type, service_type_id, payment_currency,
- customs_clearing_enabled, customs_clearing_agent, equipment_return,
- first_mile_pickup_address, first_mile_pickup_lat, first_mile_pickup_lng,
- last_mile_delivery_address, last_mile_delivery_lat, last_mile_delivery_lng,
- is_hazardous, is_reefer, estimated_shipment_date,
- contract_validity_days, contract_valid_from, contract_valid_until,
- status, clearance_status, clearance_cycle_number,
- pricing_breakdown, contract_type, contract_template_key, contract_generated_at,
- contract_summary, version_number,
- approved_by_staff_id, approved_by_staff_at,
- signed_by_director_id, signed_by_director_at,
- signed_by_ceo_id, signed_by_ceo_at, customer_signed_at, fully_executed_at,
- created_at, updated_at
- )
- SELECT
- 'CTR-' || b.id::text, b.company_id, b.company_profile_id, b.is_government, b.government_institution,
- 'ONE_TIME', b.trade_direction, b.freight_type, b.service_type_id, b.payment_currency,
- b.customs_clearing_enabled, b.customs_clearing_agent, b.equipment_return,
- b.first_mile_pickup_address, b.first_mile_pickup_lat, b.first_mile_pickup_lng,
- b.last_mile_delivery_address, b.last_mile_delivery_lat, b.last_mile_delivery_lng,
- b.is_hazardous, b.is_reefer, b.estimated_shipment_date,
- b.contract_validity_days, b.contract_valid_from, b.contract_valid_until,
- 'FULLY_EXECUTED', 'NOT_APPLICABLE', 0,
- b.pricing_breakdown, b.contract_type, b.contract_template_key, b.contract_generated_at,
- b.contract_summary, COALESCE(b.version_number, 1),
- b.approved_by_staff_id, b.approved_by_staff_at,
- b.signed_by_director_id, b.signed_by_director_at,
- b.signed_by_ceo_id, b.signed_by_ceo_at, b.customer_signed_at, b.fully_executed_at,
- b.created_at, b.updated_at
- FROM freight.bookings b
- WHERE COALESCE(b.booking_type, 'ONE_TIME') = 'ONE_TIME'
- AND b.contract_id IS NULL
- AND b.contract_generated_at IS NOT NULL
- ON CONFLICT (reference) DO NOTHING;
- `);
-
- await queryRunner.query(`
- UPDATE freight.bookings b
- SET contract_id = c.id, contract_kind = 'ONE_TIME', created_by_role = 'CUSTOMER'
- FROM freight.contracts c
- WHERE c.reference = 'CTR-' || b.id::text
- AND COALESCE(b.booking_type, 'ONE_TIME') = 'ONE_TIME'
- AND b.contract_id IS NULL;
- `);
-
- // 6. Build a single route per ONE_TIME contract from the booking's own
- // origin/destination (general contracts already got their routes in step 2).
- await queryRunner.query(`
- INSERT INTO freight.contract_routes (contract_id, origin_yard_id, destination_yard_id, sort_order, created_at, updated_at)
- SELECT c.id, b.origin_yard_id, b.destination_yard_id, 0, now(), now()
- FROM freight.bookings b
- JOIN freight.contracts c ON c.id = b.contract_id AND c.contract_kind = 'ONE_TIME'
- WHERE b.origin_yard_id IS NOT NULL AND b.destination_yard_id IS NOT NULL
- ON CONFLICT (contract_id, origin_yard_id, destination_yard_id) DO NOTHING;
- `);
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- // Unlink bookings and drop the synthesised contracts (and their cascaded routes).
- await queryRunner.query(`
- UPDATE freight.bookings SET contract_id = NULL, contract_route_id = NULL
- WHERE contract_id IN (SELECT id FROM freight.contracts WHERE reference LIKE 'CTR-%');
- `);
- await queryRunner.query(`DELETE FROM freight.contracts WHERE reference LIKE 'CTR-%';`);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1823000000000-CreateImportOperationsTables.ts b/apps/edr-freight-api/src/migrations/1823000000000-CreateImportOperationsTables.ts
deleted file mode 100644
index 1a198e983..000000000
--- a/apps/edr-freight-api/src/migrations/1823000000000-CreateImportOperationsTables.ts
+++ /dev/null
@@ -1,95 +0,0 @@
-import { MigrationInterface, QueryRunner, Table, TableIndex } from 'typeorm';
-
-export class CreateImportOperationsTables1823000000000 implements MigrationInterface {
- name = 'CreateImportOperationsTables1823000000000';
-
- async up(queryRunner: QueryRunner): Promise {
- await queryRunner.createTable(
- new Table({
- schema: 'freight',
- name: 'djibouti_import_incidents',
- columns: [
- { name: 'id', type: 'uuid', isPrimary: true, generationStrategy: 'uuid', default: 'uuid_generate_v4()' },
- { name: 'booking_id', type: 'uuid' },
- { name: 'container_number', type: 'varchar', length: '80', isNullable: true },
- { name: 'cargo_id', type: 'uuid', isNullable: true },
- { name: 'facility', type: 'varchar', length: '120', isNullable: true },
- { name: 'station', type: 'varchar', length: '120', isNullable: true },
- { name: 'incident_type', type: 'varchar', length: '40' },
- { name: 'description', type: 'text' },
- { name: 'photos', type: 'jsonb', default: "'[]'::jsonb" },
- { name: 'reported_by', type: 'varchar', length: '120', isNullable: true },
- { name: 'reported_at', type: 'timestamptz' },
- { name: 'created_at', type: 'timestamptz', default: 'now()' },
- { name: 'updated_at', type: 'timestamptz', default: 'now()' },
- { name: 'deleted_at', type: 'timestamptz', isNullable: true },
- ],
- }),
- true,
- );
- await queryRunner.createIndex('freight.djibouti_import_incidents', new TableIndex({ name: 'idx_djibouti_incidents_booking', columnNames: ['booking_id'] }));
- await queryRunner.createIndex('freight.djibouti_import_incidents', new TableIndex({ name: 'idx_djibouti_incidents_container', columnNames: ['container_number'] }));
- await queryRunner.createIndex('freight.djibouti_import_incidents', new TableIndex({ name: 'idx_djibouti_incidents_type', columnNames: ['incident_type'] }));
-
- await queryRunner.createTable(
- new Table({
- schema: 'freight',
- name: 'import_customs_finalizations',
- columns: [
- { name: 'id', type: 'uuid', isPrimary: true, generationStrategy: 'uuid', default: 'uuid_generate_v4()' },
- { name: 'booking_id', type: 'uuid', isUnique: true },
- { name: 'documents', type: 'jsonb', default: "'{}'::jsonb" },
- { name: 'declaration_serial_number', type: 'varchar', length: '120', isNullable: true },
- { name: 'duties_taxes_notified_at', type: 'timestamptz', isNullable: true },
- { name: 'duties_taxes_paid_at', type: 'timestamptz', isNullable: true },
- { name: 'customs_risk', type: 'varchar', length: '12', isNullable: true },
- { name: 'import_release_permitted_at', type: 'timestamptz', isNullable: true },
- { name: 'completed_at', type: 'timestamptz', isNullable: true },
- { name: 'performed_by', type: 'varchar', length: '120', isNullable: true },
- { name: 'notes', type: 'text', isNullable: true },
- { name: 'created_at', type: 'timestamptz', default: 'now()' },
- { name: 'updated_at', type: 'timestamptz', default: 'now()' },
- { name: 'deleted_at', type: 'timestamptz', isNullable: true },
- ],
- }),
- true,
- );
- await queryRunner.createIndex('freight.import_customs_finalizations', new TableIndex({ name: 'idx_import_customs_booking', columnNames: ['booking_id'] }));
- await queryRunner.createIndex('freight.import_customs_finalizations', new TableIndex({ name: 'idx_import_customs_risk', columnNames: ['customs_risk'] }));
-
- await queryRunner.createTable(
- new Table({
- schema: 'freight',
- name: 'empty_container_returns',
- columns: [
- { name: 'id', type: 'uuid', isPrimary: true, generationStrategy: 'uuid', default: 'uuid_generate_v4()' },
- { name: 'container_number', type: 'varchar', length: '80' },
- { name: 'booking_id', type: 'uuid', isNullable: true },
- { name: 'customer_id', type: 'uuid', isNullable: true },
- { name: 'return_date', type: 'timestamptz' },
- { name: 'facility', type: 'varchar', length: '120', isNullable: true },
- { name: 'yard', type: 'varchar', length: '120', isNullable: true },
- { name: 'zone', type: 'varchar', length: '120', isNullable: true },
- { name: 'condition', type: 'text', isNullable: true },
- { name: 'handover_note', type: 'text', isNullable: true },
- { name: 'status', type: 'varchar', length: '40', default: "'RETURNED'" },
- { name: 'wagon_allocation_reference', type: 'varchar', length: '120', isNullable: true },
- { name: 'performed_by', type: 'varchar', length: '120', isNullable: true },
- { name: 'created_at', type: 'timestamptz', default: 'now()' },
- { name: 'updated_at', type: 'timestamptz', default: 'now()' },
- { name: 'deleted_at', type: 'timestamptz', isNullable: true },
- ],
- }),
- true,
- );
- await queryRunner.createIndex('freight.empty_container_returns', new TableIndex({ name: 'idx_empty_returns_container', columnNames: ['container_number'] }));
- await queryRunner.createIndex('freight.empty_container_returns', new TableIndex({ name: 'idx_empty_returns_booking', columnNames: ['booking_id'] }));
- await queryRunner.createIndex('freight.empty_container_returns', new TableIndex({ name: 'idx_empty_returns_status', columnNames: ['status'] }));
- }
-
- async down(queryRunner: QueryRunner): Promise {
- await queryRunner.dropTable('freight.empty_container_returns', true);
- await queryRunner.dropTable('freight.import_customs_finalizations', true);
- await queryRunner.dropTable('freight.djibouti_import_incidents', true);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1824000000000-DropLegacyContractTables.ts b/apps/edr-freight-api/src/migrations/1824000000000-DropLegacyContractTables.ts
deleted file mode 100644
index 06bcff14a..000000000
--- a/apps/edr-freight-api/src/migrations/1824000000000-DropLegacyContractTables.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-/**
- * Cutover cleanup (docs/new-doc.md §17 Phase 4). Runs AFTER the backfill
- * (1823…) so every legacy general contract + drawdown already lives in the
- * `contracts` aggregate.
- *
- * Drops the now-unused booking-as-contract artifacts:
- * - `bookings.booking_type` (every booking is a real shipment now)
- * - `bookings.previous_contract_id` (renewal lives on `contracts.renewal_of_id`)
- * - the `booking_orders` / `booking_order_lines` drawdown ledger
- * - `contract_route_lines` (superseded by `contract_routes`)
- *
- * The shipment/payment/scheduling/allocation columns on `bookings` are kept —
- * the operational pipeline is unchanged.
- */
-export class DropLegacyContractTables1824000000000 implements MigrationInterface {
- name = 'DropLegacyContractTables1824000000000';
-
- public async up(queryRunner: QueryRunner): Promise {
- // booking_order_lines references booking_orders → drop child first.
- await queryRunner.query(`DROP TABLE IF EXISTS freight.booking_order_lines CASCADE;`);
- await queryRunner.query(`DROP TABLE IF EXISTS freight.booking_orders CASCADE;`);
- await queryRunner.query(`DROP TABLE IF EXISTS freight.contract_route_lines CASCADE;`);
-
- await queryRunner.query(`ALTER TABLE freight.bookings DROP COLUMN IF EXISTS booking_type;`);
- await queryRunner.query(`ALTER TABLE freight.bookings DROP COLUMN IF EXISTS previous_contract_id;`);
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- // Re-add the dropped columns (data is not restored — this is a one-way cutover).
- await queryRunner.query(
- `ALTER TABLE freight.bookings ADD COLUMN IF NOT EXISTS booking_type VARCHAR(20) DEFAULT 'ONE_TIME';`,
- );
- await queryRunner.query(
- `ALTER TABLE freight.bookings ADD COLUMN IF NOT EXISTS previous_contract_id UUID;`,
- );
- // The legacy ledger/route tables are intentionally NOT recreated here; restore
- // from a backup if a rollback past the cutover is ever required.
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1825000000000-AddGlOperations.ts b/apps/edr-freight-api/src/migrations/1825000000000-AddGlOperations.ts
deleted file mode 100644
index cf0cc51f4..000000000
--- a/apps/edr-freight-api/src/migrations/1825000000000-AddGlOperations.ts
+++ /dev/null
@@ -1,65 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-/**
- * Global Logistics Phase-2 operational features (docs/new-doc.md §11–§13, gap
- * matrix #14/#16/#17/#18):
- * - `clearance_milestones.metadata` — structured payload for RISK_ASSIGNED
- * (risk level) and DUTY_TAXES_ADVISED (amount, currency, declaration serial)
- * - `bookings.gl_station_yard_id` / `gl_assigned_staff_id` / `gl_assigned_at`
- * — station routing + staff binding (GL US-02)
- * - `freight.clearance_incidents` — cargo exception/damage reports with photos
- * (GL Import US-07)
- */
-export class AddGlOperations1825000000000 implements MigrationInterface {
- name = 'AddGlOperations1825000000000';
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(
- `ALTER TABLE freight.clearance_milestones ADD COLUMN IF NOT EXISTS metadata JSONB;`,
- );
-
- await queryRunner.query(
- `ALTER TABLE freight.bookings ADD COLUMN IF NOT EXISTS gl_station_yard_id UUID;`,
- );
- await queryRunner.query(
- `ALTER TABLE freight.bookings ADD COLUMN IF NOT EXISTS gl_assigned_staff_id UUID;`,
- );
- await queryRunner.query(
- `ALTER TABLE freight.bookings ADD COLUMN IF NOT EXISTS gl_assigned_at TIMESTAMPTZ;`,
- );
-
- await queryRunner.query(`
- CREATE TABLE IF NOT EXISTS freight.clearance_incidents (
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
- booking_id UUID NOT NULL REFERENCES freight.bookings(id) ON DELETE CASCADE,
- incident_type VARCHAR(32) NOT NULL,
- description TEXT NOT NULL,
- photo_file_ids JSONB NOT NULL DEFAULT '[]',
- reported_by_user_id UUID,
- reported_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
- 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_clearance_incidents_booking ON freight.clearance_incidents(booking_id);`,
- );
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`DROP TABLE IF EXISTS freight.clearance_incidents CASCADE;`);
- await queryRunner.query(
- `ALTER TABLE freight.bookings DROP COLUMN IF EXISTS gl_assigned_at;`,
- );
- await queryRunner.query(
- `ALTER TABLE freight.bookings DROP COLUMN IF EXISTS gl_assigned_staff_id;`,
- );
- await queryRunner.query(
- `ALTER TABLE freight.bookings DROP COLUMN IF EXISTS gl_station_yard_id;`,
- );
- await queryRunner.query(
- `ALTER TABLE freight.clearance_milestones DROP COLUMN IF EXISTS metadata;`,
- );
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1825000000000-CreateBookingContainerAllocations.ts b/apps/edr-freight-api/src/migrations/1825000000000-CreateBookingContainerAllocations.ts
deleted file mode 100644
index 70b0832f5..000000000
--- a/apps/edr-freight-api/src/migrations/1825000000000-CreateBookingContainerAllocations.ts
+++ /dev/null
@@ -1,80 +0,0 @@
-import { MigrationInterface, QueryRunner, Table, TableForeignKey } from 'typeorm';
-
-/**
- * Create the freight.booking_container_allocations table — container-to-vehicle
- * allocation mapping for flexible routing of containers across available vehicles.
- */
-export class CreateBookingContainerAllocations1825000000000 implements MigrationInterface {
- name = 'CreateBookingContainerAllocations1825000000000';
-
- public async up(queryRunner: QueryRunner): Promise {
- const exists = await queryRunner.hasTable('freight.booking_container_allocations');
- if (exists) return;
-
- await queryRunner.createTable(
- new Table({
- name: 'freight.booking_container_allocations',
- columns: [
- {
- name: 'id',
- type: 'uuid',
- isPrimary: true,
- default: 'gen_random_uuid()',
- },
- { name: 'booking_id', type: 'uuid', isNullable: false },
- { name: 'container_id', type: 'uuid', isNullable: false },
- { name: 'vehicle_id', type: 'uuid', isNullable: true },
- {
- name: 'container_type',
- type: 'text',
- isNullable: false,
- },
- {
- name: 'quantity',
- type: 'integer',
- default: 1,
- isNullable: false,
- },
- { name: 'created_at', type: 'timestamptz', default: 'now()' },
- { name: 'updated_at', type: 'timestamptz', default: 'now()' },
- { name: 'deleted_at', type: 'timestamptz', isNullable: true },
- ],
- }),
- true,
- );
-
- await queryRunner.createForeignKey(
- 'freight.booking_container_allocations',
- new TableForeignKey({
- columnNames: ['booking_id'],
- referencedTableName: 'freight.bookings',
- referencedColumnNames: ['id'],
- onDelete: 'CASCADE',
- }),
- );
-
- await queryRunner.createForeignKey(
- 'freight.booking_container_allocations',
- new TableForeignKey({
- columnNames: ['vehicle_id'],
- referencedTableName: 'freight.vehicles',
- referencedColumnNames: ['id'],
- onDelete: 'SET NULL',
- }),
- );
-
- await queryRunner.query(
- `CREATE INDEX "IDX_booking_container_allocations_booking_id" ON "freight"."booking_container_allocations" ("booking_id")`,
- );
- await queryRunner.query(
- `CREATE INDEX "IDX_booking_container_allocations_vehicle_id" ON "freight"."booking_container_allocations" ("vehicle_id")`,
- );
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- const exists = await queryRunner.hasTable('freight.booking_container_allocations');
- if (exists) {
- await queryRunner.dropTable('freight.booking_container_allocations');
- }
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1826000000000-AddCargoScopeQuantityCap.ts b/apps/edr-freight-api/src/migrations/1826000000000-AddCargoScopeQuantityCap.ts
deleted file mode 100644
index 97428c11b..000000000
--- a/apps/edr-freight-api/src/migrations/1826000000000-AddCargoScopeQuantityCap.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-/**
- * GENERAL contracts can be booked repeatedly until a total cargo quantity cap is
- * reached (e.g. 100 containers across many shipments). `quantity_cap` on each
- * cargo-scope line holds that ceiling (containers per size, or tons/items for
- * bulk). NULL = uncapped; always NULL for ONE_TIME (single booking).
- */
-export class AddCargoScopeQuantityCap1826000000000 implements MigrationInterface {
- name = 'AddCargoScopeQuantityCap1826000000000';
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(
- `ALTER TABLE freight.contract_cargo_scope ADD COLUMN IF NOT EXISTS quantity_cap NUMERIC(12,2);`,
- );
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(
- `ALTER TABLE freight.contract_cargo_scope DROP COLUMN IF EXISTS quantity_cap;`,
- );
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1827000000000-CreateBookingRequests.ts b/apps/edr-freight-api/src/migrations/1827000000000-CreateBookingRequests.ts
deleted file mode 100644
index e88824e28..000000000
--- a/apps/edr-freight-api/src/migrations/1827000000000-CreateBookingRequests.ts
+++ /dev/null
@@ -1,75 +0,0 @@
-import { MigrationInterface, QueryRunner, Table, TableIndex } from 'typeorm';
-
-/**
- * Customer shipment requests for GENERAL customs (Path B) contracts. The customer
- * submits date + quantities; Global Logistics reviews, then creates the booking
- * on their behalf and per-booking clearance begins. Additive — no change to
- * existing tables; ONE_TIME contracts are unaffected.
- */
-export class CreateBookingRequests1827000000000 implements MigrationInterface {
- name = 'CreateBookingRequests1827000000000';
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.createTable(
- new Table({
- schema: 'freight',
- name: 'booking_requests',
- columns: [
- { name: 'id', type: 'uuid', isPrimary: true, generationStrategy: 'uuid', default: 'gen_random_uuid()' },
- { name: 'reference', type: 'varchar', length: '40', default: "''" },
- { name: 'contract_id', type: 'uuid' },
- { name: 'requested_by_user_id', type: 'uuid', isNullable: true },
- { name: 'contract_route_id', type: 'uuid', isNullable: true },
- { name: 'scheduled_date', type: 'timestamptz', isNullable: true },
- { name: 'status', type: 'varchar', length: '16', default: "'PENDING'" },
- { name: 'requested_lines', type: 'jsonb', default: "'{}'::jsonb" },
- { name: 'notes', type: 'text', isNullable: true },
- { name: 'created_booking_id', type: 'uuid', isNullable: true },
- { name: 'reviewed_by_staff_id', type: 'uuid', isNullable: true },
- { name: 'reviewed_at', type: 'timestamptz', isNullable: true },
- { name: 'review_note', type: 'text', isNullable: true },
- { name: 'created_at', type: 'timestamptz', default: 'now()' },
- { name: 'updated_at', type: 'timestamptz', default: 'now()' },
- { name: 'deleted_at', type: 'timestamptz', isNullable: true },
- ],
- foreignKeys: [
- {
- columnNames: ['contract_id'],
- referencedSchema: 'freight',
- referencedTableName: 'contracts',
- referencedColumnNames: ['id'],
- onDelete: 'CASCADE',
- },
- {
- columnNames: ['created_booking_id'],
- referencedSchema: 'freight',
- referencedTableName: 'bookings',
- referencedColumnNames: ['id'],
- onDelete: 'SET NULL',
- },
- ],
- }),
- true,
- );
-
- await queryRunner.createIndex(
- 'freight.booking_requests',
- new TableIndex({ name: 'idx_booking_requests_contract', columnNames: ['contract_id'] }),
- );
- await queryRunner.createIndex(
- 'freight.booking_requests',
- new TableIndex({ name: 'idx_booking_requests_status', columnNames: ['status'] }),
- );
- await queryRunner.createIndex(
- 'freight.booking_requests',
- new TableIndex({
- name: 'idx_booking_requests_contract_status',
- columnNames: ['contract_id', 'status'],
- }),
- );
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.dropTable('freight.booking_requests', true);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1828000000000-AddBulkHazmatReeferQuantity.ts b/apps/edr-freight-api/src/migrations/1828000000000-AddBulkHazmatReeferQuantity.ts
deleted file mode 100644
index cc9437eaf..000000000
--- a/apps/edr-freight-api/src/migrations/1828000000000-AddBulkHazmatReeferQuantity.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-/**
- * Bulk / break-bulk freight can now declare HOW MUCH of the cargo is hazardous
- * or refrigerated, in the cargo's own unit of measure (tons for PER_TON, item
- * count for PER_ITEM). These two columns hold that amount on the booking; they
- * stay 0 for container freight (which tracks it per line on booking_container)
- * and for bulk cargo with no hazardous/reefer portion. The existing
- * is_hazardous / is_reefer booleans remain the surcharge trigger.
- */
-export class AddBulkHazmatReeferQuantity1828000000000 implements MigrationInterface {
- name = 'AddBulkHazmatReeferQuantity1828000000000';
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(
- `ALTER TABLE freight.bookings ADD COLUMN IF NOT EXISTS bulk_hazardous_quantity NUMERIC(12,3) NOT NULL DEFAULT 0;`,
- );
- await queryRunner.query(
- `ALTER TABLE freight.bookings ADD COLUMN IF NOT EXISTS bulk_reefer_quantity NUMERIC(12,3) NOT NULL DEFAULT 0;`,
- );
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(
- `ALTER TABLE freight.bookings DROP COLUMN IF EXISTS bulk_reefer_quantity;`,
- );
- await queryRunner.query(
- `ALTER TABLE freight.bookings DROP COLUMN IF EXISTS bulk_hazardous_quantity;`,
- );
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1828000000000-AddGrnNumberToWarehouseInventory.ts b/apps/edr-freight-api/src/migrations/1828000000000-AddGrnNumberToWarehouseInventory.ts
deleted file mode 100644
index c57a43aaa..000000000
--- a/apps/edr-freight-api/src/migrations/1828000000000-AddGrnNumberToWarehouseInventory.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-export class AddGrnNumberToWarehouseInventory1828000000000 implements MigrationInterface {
- name = 'AddGrnNumberToWarehouseInventory1828000000000';
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- ALTER TABLE freight.warehouse_inventory
- ADD COLUMN IF NOT EXISTS grn_number VARCHAR(100) NULL
- `);
-
- await queryRunner.query(`
- UPDATE freight.warehouse_inventory
- SET grn_number = substring(notes FROM 'GRN Number: ([^\\n\\r]+)')
- WHERE grn_number IS NULL
- AND notes IS NOT NULL
- AND notes ~ 'GRN Number: '
- `);
-
- await queryRunner.query(`
- CREATE INDEX IF NOT EXISTS idx_warehouse_inventory_grn_number
- ON freight.warehouse_inventory(grn_number)
- WHERE grn_number IS NOT NULL
- `);
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`DROP INDEX IF EXISTS freight.idx_warehouse_inventory_grn_number`);
- await queryRunner.query(`
- ALTER TABLE freight.warehouse_inventory
- DROP COLUMN IF EXISTS grn_number
- `);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1828000000000-ExtendInvoicesForPartialPayment.ts b/apps/edr-freight-api/src/migrations/1828000000000-ExtendInvoicesForPartialPayment.ts
deleted file mode 100644
index 55239c13f..000000000
--- a/apps/edr-freight-api/src/migrations/1828000000000-ExtendInvoicesForPartialPayment.ts
+++ /dev/null
@@ -1,71 +0,0 @@
-import { MigrationInterface, QueryRunner } from "typeorm";
-
-/**
- * Extend `freight.invoices` into the billing record of record for every source
- * (booking, demurrage, warehouse fees, …) so warehouse fee invoices can be
- * centralized onto it instead of the parallel `warehouse_fee_invoices` table.
- *
- * Adds money tracking that supports partial payment (`subtotal/tax/paid/balance`),
- * a `paid_at` stamp, a `payments` jsonb ledger, and the `ISSUED` / `PARTIALLY_PAID`
- * statuses the warehouse flow uses.
- *
- * Matches billing/entities/invoice.entity.ts. All columns are additive with
- * defaults, so existing booking/demurrage rows are unaffected.
- */
-export class ExtendInvoicesForPartialPayment1828000000000
- implements MigrationInterface
-{
- name = "ExtendInvoicesForPartialPayment1828000000000";
-
- public async up(queryRunner: QueryRunner): Promise {
- // New statuses. ADD VALUE is non-transactional-value-safe on PG 12+ as long
- // as the value is not referenced in the same transaction (it is not here).
- await queryRunner.query(
- `ALTER TYPE freight.invoices_status_enum ADD VALUE IF NOT EXISTS 'ISSUED' BEFORE 'PENDING';`,
- );
- await queryRunner.query(
- `ALTER TYPE freight.invoices_status_enum ADD VALUE IF NOT EXISTS 'PARTIALLY_PAID' BEFORE 'PAID';`,
- );
-
- await queryRunner.query(`
- ALTER TABLE freight.invoices
- ADD COLUMN IF NOT EXISTS subtotal_amount numeric(14, 2) NOT NULL DEFAULT 0,
- ADD COLUMN IF NOT EXISTS tax_amount numeric(14, 2) NOT NULL DEFAULT 0,
- ADD COLUMN IF NOT EXISTS paid_amount numeric(14, 2) NOT NULL DEFAULT 0,
- ADD COLUMN IF NOT EXISTS balance_amount numeric(14, 2) NOT NULL DEFAULT 0,
- ADD COLUMN IF NOT EXISTS paid_at timestamptz,
- ADD COLUMN IF NOT EXISTS payments jsonb NOT NULL DEFAULT '[]';
- `);
-
- // Backfill existing rows: subtotal mirrors the total (no tax was modeled),
- // the outstanding balance is the full total for unpaid invoices.
- await queryRunner.query(`
- UPDATE freight.invoices
- SET subtotal_amount = total_amount,
- balance_amount = total_amount;
- `);
-
- // Already-settled invoices: fully paid, zero balance, stamped from updated_at.
- await queryRunner.query(`
- UPDATE freight.invoices
- SET paid_amount = total_amount,
- balance_amount = 0,
- paid_at = updated_at
- WHERE status = 'PAID';
- `);
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- ALTER TABLE freight.invoices
- DROP COLUMN IF EXISTS payments,
- DROP COLUMN IF EXISTS paid_at,
- DROP COLUMN IF EXISTS balance_amount,
- DROP COLUMN IF EXISTS paid_amount,
- DROP COLUMN IF EXISTS tax_amount,
- DROP COLUMN IF EXISTS subtotal_amount;
- `);
- // Postgres cannot drop individual enum values; ISSUED / PARTIALLY_PAID are
- // left on freight.invoices_status_enum (harmless, unused after down).
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1829000000000-CentralizeWarehouseInvoices.ts b/apps/edr-freight-api/src/migrations/1829000000000-CentralizeWarehouseInvoices.ts
deleted file mode 100644
index 75082c5c4..000000000
--- a/apps/edr-freight-api/src/migrations/1829000000000-CentralizeWarehouseInvoices.ts
+++ /dev/null
@@ -1,247 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-/**
- * Fold warehouse fee invoices into the central billing system.
- *
- * Warehouse fee invoices are no longer a standalone aggregate: each becomes a
- * global `freight.invoices` row (`source = 'warehouse'`, `source_id =
- * inventory_id`) with its items as `freight.invoice_lines`. The warehouse
- * service is now a thin layer over `BillingService`. This migration backfills the
- * existing rows (preserving ids, numbers, status, amounts and payment history),
- * then drops the two legacy tables.
- *
- * Rows that cannot be billed centrally — no company to bill (`company_id` /
- * `company_profile_id` underivable from the customer or the booking) — are not
- * migrated; they could never have been charged through the gateway and are
- * dropped with the table.
- */
-export class CentralizeWarehouseInvoices1829000000000 implements MigrationInterface {
- name = 'CentralizeWarehouseInvoices1829000000000';
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`
- DO $$
- BEGIN
- IF EXISTS (
- SELECT 1
- FROM information_schema.columns
- WHERE table_schema = 'freight'
- AND table_name = 'invoices'
- AND column_name = 'booking_id'
- ) THEN
- ALTER TABLE freight.invoices ALTER COLUMN booking_id DROP NOT NULL;
- END IF;
-
- IF EXISTS (
- SELECT 1
- FROM information_schema.columns
- WHERE table_schema = 'freight'
- AND table_name = 'invoices'
- AND column_name = 'amount'
- ) THEN
- ALTER TABLE freight.invoices ALTER COLUMN amount DROP NOT NULL;
- END IF;
- END $$;
- `);
-
- // 1. Invoice headers. Keep the same id so items still link, and so any
- // external reference to the invoice id stays valid.
- await queryRunner.query(`
- INSERT INTO freight.invoices (
- id, invoice_number, company_id, company_profile_id,
- subtotal_amount, tax_amount, total_amount, paid_amount, balance_amount,
- currency, status, source, source_id, type,
- issued_at, paid_at, payments, payment_id, due_at,
- created_at, updated_at, deleted_at
- )
- SELECT
- fee.id,
- fee.invoice_number,
- COALESCE(fee.customer_id, b.company_id),
- COALESCE(
- b.company_profile_id,
- (SELECT cp.id
- FROM freight.company_profiles cp
- WHERE cp.company_id = COALESCE(fee.customer_id, b.company_id)
- AND cp.deleted_at IS NULL
- ORDER BY cp.created_at ASC
- LIMIT 1)
- ),
- fee.subtotal_amount, fee.tax_amount, fee.total_amount, fee.paid_amount, fee.balance_amount,
- fee.currency,
- fee.status::freight.invoices_status_enum,
- 'warehouse',
- fee.inventory_id,
- fee.invoice_type,
- fee.issued_at,
- fee.paid_at,
- COALESCE(fee.payments, '[]'::jsonb),
- NULL,
- COALESCE(fee.due_date, fee.issued_at, fee.created_at),
- fee.created_at, fee.updated_at, fee.deleted_at
- FROM freight.warehouse_fee_invoices fee
- LEFT JOIN freight.bookings b ON b.id = fee.booking_id
- WHERE COALESCE(fee.customer_id, b.company_id) IS NOT NULL
- AND COALESCE(
- b.company_profile_id,
- (SELECT cp.id
- FROM freight.company_profiles cp
- WHERE cp.company_id = COALESCE(fee.customer_id, b.company_id)
- AND cp.deleted_at IS NULL
- ORDER BY cp.created_at ASC
- LIMIT 1)
- ) IS NOT NULL
- ON CONFLICT (id) DO NOTHING;
- `);
-
- // 2. Invoice lines — only for items whose parent invoice migrated. Warehouse
- // fee fields (fee_rule_id / chargeable_days / free_days) move into the
- // line's jsonb metadata.
- await queryRunner.query(`
- INSERT INTO freight.invoice_lines (
- id, invoice_id, charge_type, description, quantity, unit_rate, amount,
- currency, metadata, created_at, updated_at, deleted_at
- )
- SELECT
- item.id,
- item.invoice_id,
- item.fee_type,
- item.description,
- item.quantity,
- item.unit_rate,
- item.amount,
- item.currency,
- jsonb_build_object(
- 'feeRuleId', item.fee_rule_id,
- 'chargeableDays', item.chargeable_days,
- 'freeDays', item.free_days
- ),
- item.created_at, item.updated_at, item.deleted_at
- FROM freight.warehouse_fee_invoice_items item
- JOIN freight.invoices i ON i.id = item.invoice_id AND i.source = 'warehouse'
- ON CONFLICT (id) DO NOTHING;
- `);
-
- // 3. Drop the legacy tables (items first — FK to invoices).
- await queryRunner.query(`DROP TABLE IF EXISTS freight.warehouse_fee_invoice_items;`);
- await queryRunner.query(`DROP TABLE IF EXISTS freight.warehouse_fee_invoices;`);
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- // Recreate the legacy tables …
- await queryRunner.query(`
- CREATE TABLE IF NOT EXISTS freight.warehouse_fee_invoices (
- id uuid NOT NULL DEFAULT uuid_generate_v4(),
- created_at timestamptz NOT NULL DEFAULT now(),
- updated_at timestamptz NOT NULL DEFAULT now(),
- deleted_at timestamptz,
- invoice_number varchar(40) NOT NULL,
- booking_id uuid,
- customer_id uuid,
- inventory_id uuid NOT NULL,
- facility_id uuid,
- warehouse_id uuid,
- yard_id uuid,
- zone_id uuid,
- invoice_type varchar(32) NOT NULL DEFAULT 'MIXED_WAREHOUSE_FEES',
- status varchar(20) NOT NULL DEFAULT 'DRAFT',
- subtotal_amount numeric(14,2) NOT NULL DEFAULT 0,
- tax_amount numeric(14,2) NOT NULL DEFAULT 0,
- total_amount numeric(14,2) NOT NULL DEFAULT 0,
- paid_amount numeric(14,2) NOT NULL DEFAULT 0,
- balance_amount numeric(14,2) NOT NULL DEFAULT 0,
- currency varchar(8) NOT NULL DEFAULT 'USD',
- period_start timestamptz,
- period_end timestamptz,
- issued_at timestamptz,
- due_date timestamptz,
- paid_at timestamptz,
- cancelled_at timestamptz,
- payments jsonb NOT NULL DEFAULT '[]',
- notes text,
- CONSTRAINT "PK_warehouse_fee_invoices" PRIMARY KEY (id),
- CONSTRAINT "UQ_warehouse_fee_invoices_invoice_number" UNIQUE (invoice_number)
- );
- `);
- await queryRunner.query(
- `CREATE INDEX IF NOT EXISTS "IDX_warehouse_fee_invoices_booking_id" ON freight.warehouse_fee_invoices (booking_id);`,
- );
- await queryRunner.query(
- `CREATE INDEX IF NOT EXISTS "IDX_warehouse_fee_invoices_inventory_id" ON freight.warehouse_fee_invoices (inventory_id);`,
- );
- await queryRunner.query(
- `CREATE INDEX IF NOT EXISTS "IDX_warehouse_fee_invoices_status" ON freight.warehouse_fee_invoices (status);`,
- );
- await queryRunner.query(`
- CREATE TABLE IF NOT EXISTS freight.warehouse_fee_invoice_items (
- id uuid NOT NULL DEFAULT uuid_generate_v4(),
- created_at timestamptz NOT NULL DEFAULT now(),
- updated_at timestamptz NOT NULL DEFAULT now(),
- deleted_at timestamptz,
- invoice_id uuid NOT NULL,
- fee_rule_id uuid,
- fee_type varchar(32) NOT NULL,
- description varchar(255) NOT NULL,
- quantity numeric(12,2) NOT NULL DEFAULT 1,
- unit_rate numeric(14,2) NOT NULL DEFAULT 0,
- amount numeric(14,2) NOT NULL DEFAULT 0,
- currency varchar(8) NOT NULL DEFAULT 'USD',
- chargeable_days int,
- free_days int,
- CONSTRAINT "PK_warehouse_fee_invoice_items" PRIMARY KEY (id),
- CONSTRAINT "FK_warehouse_fee_invoice_items_invoice"
- FOREIGN KEY (invoice_id) REFERENCES freight.warehouse_fee_invoices (id) ON DELETE CASCADE
- );
- `);
- await queryRunner.query(
- `CREATE INDEX IF NOT EXISTS "IDX_warehouse_fee_invoice_items_invoice_id" ON freight.warehouse_fee_invoice_items (invoice_id);`,
- );
-
- // … then copy the warehouse-source invoices back, deriving the typed FKs and
- // period from the linked inventory item.
- await queryRunner.query(`
- INSERT INTO freight.warehouse_fee_invoices (
- id, created_at, updated_at, deleted_at, invoice_number,
- booking_id, customer_id, inventory_id, facility_id, warehouse_id, yard_id, zone_id,
- invoice_type, status, subtotal_amount, tax_amount, total_amount, paid_amount, balance_amount,
- currency, period_start, period_end, issued_at, due_date, paid_at, cancelled_at, payments, notes
- )
- SELECT
- i.id, i.created_at, i.updated_at, i.deleted_at, i.invoice_number,
- inv.booking_id, i.company_id, i.source_id, w.facility_id, inv.warehouse_id, inv.yard_id, inv.zone_id,
- i.type, i.status::text, i.subtotal_amount, i.tax_amount, i.total_amount, i.paid_amount, i.balance_amount,
- i.currency, inv.arrived_at, i.issued_at, i.issued_at, i.due_at, i.paid_at,
- CASE WHEN i.status::text = 'CANCELLED' THEN i.updated_at ELSE NULL END,
- i.payments, NULL
- FROM freight.invoices i
- LEFT JOIN freight.warehouse_inventory inv ON inv.id = i.source_id
- LEFT JOIN freight.warehouses w ON w.id = inv.warehouse_id
- WHERE i.source = 'warehouse'
- ON CONFLICT (id) DO NOTHING;
- `);
- await queryRunner.query(`
- INSERT INTO freight.warehouse_fee_invoice_items (
- id, created_at, updated_at, deleted_at, invoice_id, fee_rule_id, fee_type,
- description, quantity, unit_rate, amount, currency, chargeable_days, free_days
- )
- SELECT
- l.id, l.created_at, l.updated_at, l.deleted_at, l.invoice_id,
- NULLIF(l.metadata->>'feeRuleId', '')::uuid,
- l.charge_type,
- COALESCE(l.description, ''),
- l.quantity, l.unit_rate, l.amount, l.currency,
- NULLIF(l.metadata->>'chargeableDays', '')::int,
- NULLIF(l.metadata->>'freeDays', '')::int
- FROM freight.invoice_lines l
- JOIN freight.invoices i ON i.id = l.invoice_id AND i.source = 'warehouse'
- ON CONFLICT (id) DO NOTHING;
- `);
-
- // Remove the migrated rows from the central tables.
- await queryRunner.query(`
- DELETE FROM freight.invoice_lines
- WHERE invoice_id IN (SELECT id FROM freight.invoices WHERE source = 'warehouse');
- `);
- await queryRunner.query(`DELETE FROM freight.invoices WHERE source = 'warehouse';`);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1829000000000-PhasedClearanceCycleMeta.ts b/apps/edr-freight-api/src/migrations/1829000000000-PhasedClearanceCycleMeta.ts
deleted file mode 100644
index 75288e94c..000000000
--- a/apps/edr-freight-api/src/migrations/1829000000000-PhasedClearanceCycleMeta.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-export class PhasedClearanceCycleMeta1829000000000 implements MigrationInterface {
- name = 'PhasedClearanceCycleMeta1829000000000';
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(
- `ALTER TABLE freight.contract_clearance_cycles ADD COLUMN IF NOT EXISTS duty_required BOOLEAN;`,
- );
- await queryRunner.query(
- `ALTER TABLE freight.contract_clearance_cycles ADD COLUMN IF NOT EXISTS vessel_departure_date DATE;`,
- );
- await queryRunner.query(
- `ALTER TABLE freight.contract_clearance_cycles ADD COLUMN IF NOT EXISTS ro_amendment_requested_at TIMESTAMPTZ;`,
- );
- await queryRunner.query(
- `ALTER TABLE freight.contract_clearance_cycles ADD COLUMN IF NOT EXISTS ro_hold_reason TEXT;`,
- );
- await queryRunner.query(
- `ALTER TABLE freight.contract_clearance_cycles ADD COLUMN IF NOT EXISTS current_phase VARCHAR(40);`,
- );
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(
- `ALTER TABLE freight.contract_clearance_cycles DROP COLUMN IF EXISTS duty_required;`,
- );
- await queryRunner.query(
- `ALTER TABLE freight.contract_clearance_cycles DROP COLUMN IF EXISTS vessel_departure_date;`,
- );
- await queryRunner.query(
- `ALTER TABLE freight.contract_clearance_cycles DROP COLUMN IF EXISTS ro_amendment_requested_at;`,
- );
- await queryRunner.query(
- `ALTER TABLE freight.contract_clearance_cycles DROP COLUMN IF EXISTS ro_hold_reason;`,
- );
- await queryRunner.query(
- `ALTER TABLE freight.contract_clearance_cycles DROP COLUMN IF EXISTS current_phase;`,
- );
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1829000000001-SeedRoVesselMinDays.ts b/apps/edr-freight-api/src/migrations/1829000000001-SeedRoVesselMinDays.ts
deleted file mode 100644
index 7165ea7a8..000000000
--- a/apps/edr-freight-api/src/migrations/1829000000001-SeedRoVesselMinDays.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-/** Admin-configurable minimum days between today and export RO vessel departure. */
-export class SeedRoVesselMinDays1829000000001 implements MigrationInterface {
- name = 'SeedRoVesselMinDays1829000000001';
- private readonly code = 'ro_vessel_min_days';
- private readonly options: Array<{ value: string; label: string }> = [
- { value: '2', label: '2 days' },
- { value: '3', label: '3 days' },
- ];
-
- public async up(queryRunner: QueryRunner): Promise {
- const existing = await queryRunner.query(
- `SELECT id FROM freight.dropdown_settings WHERE code = $1 LIMIT 1;`,
- [this.code],
- );
- if (existing.length > 0) return;
-
- const inserted = await queryRunner.query(
- `INSERT INTO freight.dropdown_settings (code, label, description, multiple)
- VALUES ($1, $2, $3, false)
- RETURNING id;`,
- [
- this.code,
- 'RO vessel minimum lead time (days)',
- 'Minimum days between today and the vessel departure date on an export Release Order.',
- ],
- );
- const settingId = inserted[0].id;
-
- for (let i = 0; i < this.options.length; i++) {
- const opt = this.options[i];
- await queryRunner.query(
- `INSERT INTO freight.dropdown_options (setting_id, value, label, display_order)
- VALUES ($1, $2, $3, $4);`,
- [settingId, opt.value, opt.label, i],
- );
- }
- }
-
- public async down(queryRunner: QueryRunner): Promise {
- await queryRunner.query(`DELETE FROM freight.dropdown_settings WHERE code = $1;`, [
- this.code,
- ]);
- }
-}
diff --git a/apps/edr-freight-api/src/migrations/1829000000002-BookingClearanceMeta.ts b/apps/edr-freight-api/src/migrations/1829000000002-BookingClearanceMeta.ts
deleted file mode 100644
index c108b7baa..000000000
--- a/apps/edr-freight-api/src/migrations/1829000000002-BookingClearanceMeta.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-export class BookingClearanceMeta1829000000002 implements MigrationInterface {
- name = 'BookingClearanceMeta1829000000002';
-
- public async up(queryRunner: QueryRunner): Promise {
- await queryRunner.query(
- `ALTER TABLE freight.bookings ADD COLUMN IF NOT EXISTS clearance_current_phase VARCHAR(40);`,
- );
- await queryRunner.query(
- `ALTER TABLE freight.bookings ADD COLUMN IF NOT EXISTS duty_required BOOLEAN;`,
- );
- await queryRunner.query(
- `ALTER TABLE freight.bookings ADD COLUMN IF NOT EXISTS vessel_departure_date DATE;`,
- );
- await queryRunner.query(
- `ALTER TABLE freight.bookings ADD COLUMN IF NOT EXISTS ro_amendment_requested_at TIMESTAMPTZ;`,
- );
- await queryRunner.query(
- `ALTER TABLE freight.bookings ADD COLUMN IF NOT EXISTS ro_hold_reason TEXT;`,
- );
- }
-
- public async down(queryRunner: QueryRunner): Promise