This commit is contained in:
yaschalew
2026-07-02 17:56:17 +03:00
273 changed files with 22748 additions and 5431 deletions

View File

@@ -56,6 +56,7 @@ export class AddWarehouseAllocationAndFeeRules1791000000000 implements Migration
{ 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()' },

View File

@@ -62,20 +62,96 @@ export class CreateInvoices1821000000002 implements MigrationInterface {
`);
await queryRunner.query(
`CREATE INDEX idx_invoices_company ON freight.invoices (company_id);`,
`
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 idx_invoices_company_profile ON freight.invoices (company_profile_id);`,
`CREATE INDEX IF NOT EXISTS idx_invoices_company_profile ON freight.invoices (company_profile_id);`,
);
await queryRunner.query(
`CREATE INDEX idx_invoices_source ON freight.invoices (source, source_id);`,
`CREATE INDEX IF NOT EXISTS idx_invoices_source ON freight.invoices (source, source_id);`,
);
await queryRunner.query(
`CREATE INDEX idx_invoices_status ON freight.invoices (status);`,
`CREATE INDEX IF NOT EXISTS idx_invoices_status ON freight.invoices (status);`,
);
await queryRunner.query(`
CREATE TABLE freight.invoice_lines (
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,
@@ -95,7 +171,7 @@ export class CreateInvoices1821000000002 implements MigrationInterface {
`);
await queryRunner.query(
`CREATE INDEX idx_invoice_lines_invoice ON freight.invoice_lines (invoice_id);`,
`CREATE INDEX IF NOT EXISTS idx_invoice_lines_invoice ON freight.invoice_lines (invoice_id);`,
);
}

View File

@@ -0,0 +1,31 @@
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<void> {
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<void> {
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;`,
);
}
}

View File

@@ -19,6 +19,31 @@ export class CentralizeWarehouseInvoices1829000000000 implements MigrationInterf
name = 'CentralizeWarehouseInvoices1829000000000';
public async up(queryRunner: QueryRunner): Promise<void> {
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(`

View File

@@ -0,0 +1,41 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class PhasedClearanceCycleMeta1829000000000 implements MigrationInterface {
name = 'PhasedClearanceCycleMeta1829000000000';
public async up(queryRunner: QueryRunner): Promise<void> {
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<void> {
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;`,
);
}
}

View File

@@ -0,0 +1,46 @@
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<void> {
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<void> {
await queryRunner.query(`DELETE FROM freight.dropdown_settings WHERE code = $1;`, [
this.code,
]);
}
}

View File

@@ -0,0 +1,41 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class BookingClearanceMeta1829000000002 implements MigrationInterface {
name = 'BookingClearanceMeta1829000000002';
public async up(queryRunner: QueryRunner): Promise<void> {
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<void> {
await queryRunner.query(
`ALTER TABLE freight.bookings DROP COLUMN IF EXISTS clearance_current_phase;`,
);
await queryRunner.query(
`ALTER TABLE freight.bookings DROP COLUMN IF EXISTS duty_required;`,
);
await queryRunner.query(
`ALTER TABLE freight.bookings DROP COLUMN IF EXISTS vessel_departure_date;`,
);
await queryRunner.query(
`ALTER TABLE freight.bookings DROP COLUMN IF EXISTS ro_amendment_requested_at;`,
);
await queryRunner.query(
`ALTER TABLE freight.bookings DROP COLUMN IF EXISTS ro_hold_reason;`,
);
}
}

View File

@@ -0,0 +1,19 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class DropCargoTypeShowFreeTextBox1830000000000 implements MigrationInterface {
name = 'DropCargoTypeShowFreeTextBox1830000000000';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE freight.cargo_types
DROP COLUMN IF EXISTS show_free_text_box
`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE freight.cargo_types
ADD COLUMN IF NOT EXISTS show_free_text_box boolean NOT NULL DEFAULT false
`);
}
}

View File

@@ -0,0 +1,72 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class RouteStatusAndSegmentKm1830000000001 implements MigrationInterface {
name = 'RouteStatusAndSegmentKm1830000000001';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE freight.routes
ADD COLUMN IF NOT EXISTS status varchar(32) NOT NULL DEFAULT 'AVAILABLE'
`);
await queryRunner.query(`
UPDATE freight.routes
SET status = CASE WHEN is_active = true THEN 'AVAILABLE' ELSE 'STOP_WORKING' END
`);
await queryRunner.query(`
DROP INDEX IF EXISTS freight."IDX_routes_name"
`);
await queryRunner.query(`
ALTER TABLE freight.routes DROP COLUMN IF EXISTS name
`);
await queryRunner.query(`
ALTER TABLE freight.routes DROP COLUMN IF EXISTS is_active
`);
await queryRunner.query(`
CREATE INDEX IF NOT EXISTS "IDX_routes_status" ON freight.routes (status)
`);
await queryRunner.query(`
ALTER TABLE freight.route_milestones
ADD COLUMN IF NOT EXISTS distance_km numeric(10,2)
`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE freight.route_milestones DROP COLUMN IF EXISTS distance_km
`);
await queryRunner.query(`
ALTER TABLE freight.routes
ADD COLUMN IF NOT EXISTS name varchar(120)
`);
await queryRunner.query(`
UPDATE freight.routes SET name = id::text WHERE name IS NULL
`);
await queryRunner.query(`
ALTER TABLE freight.routes ALTER COLUMN name SET NOT NULL
`);
await queryRunner.query(`
ALTER TABLE freight.routes
ADD COLUMN IF NOT EXISTS is_active boolean NOT NULL DEFAULT true
`);
await queryRunner.query(`
UPDATE freight.routes
SET is_active = CASE WHEN status = 'AVAILABLE' THEN true ELSE false END
`);
await queryRunner.query(`
ALTER TABLE freight.routes DROP COLUMN IF EXISTS status
`);
await queryRunner.query(`
DROP INDEX IF EXISTS freight."IDX_routes_status"
`);
await queryRunner.query(`
CREATE UNIQUE INDEX IF NOT EXISTS "IDX_routes_name" ON freight.routes (name)
`);
}
}

View File

@@ -0,0 +1,23 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class PreClearanceFinalizedAt1830000000002 implements MigrationInterface {
name = 'PreClearanceFinalizedAt1830000000002';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE freight.contract_clearance_cycles ADD COLUMN IF NOT EXISTS pre_clearance_finalized_at TIMESTAMPTZ;`,
);
await queryRunner.query(
`ALTER TABLE freight.bookings ADD COLUMN IF NOT EXISTS pre_clearance_finalized_at TIMESTAMPTZ;`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE freight.contract_clearance_cycles DROP COLUMN IF EXISTS pre_clearance_finalized_at;`,
);
await queryRunner.query(
`ALTER TABLE freight.bookings DROP COLUMN IF EXISTS pre_clearance_finalized_at;`,
);
}
}

View File

@@ -0,0 +1,19 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class AddWarehouseFeeRuleTiers1831000000000 implements MigrationInterface {
name = 'AddWarehouseFeeRuleTiers1831000000000';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE freight.warehouse_fee_rules
ADD COLUMN IF NOT EXISTS tiers jsonb NOT NULL DEFAULT '[]';
`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE freight.warehouse_fee_rules
DROP COLUMN IF EXISTS tiers;
`);
}
}

View File

@@ -0,0 +1,29 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class AddCustomerTruckAssignmentToBookings1832000000000 implements MigrationInterface {
name = 'AddCustomerTruckAssignmentToBookings1832000000000';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE freight.bookings
ADD COLUMN IF NOT EXISTS customer_truck_plate_number varchar(32),
ADD COLUMN IF NOT EXISTS customer_truck_driver_name varchar(120),
ADD COLUMN IF NOT EXISTS customer_truck_type varchar(60),
ADD COLUMN IF NOT EXISTS customer_truck_container_number varchar(16),
ADD COLUMN IF NOT EXISTS customer_truck_assigned_at timestamptz,
ADD COLUMN IF NOT EXISTS customer_truck_arrived_at timestamptz
`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE freight.bookings
DROP COLUMN IF EXISTS customer_truck_arrived_at,
DROP COLUMN IF EXISTS customer_truck_assigned_at,
DROP COLUMN IF EXISTS customer_truck_container_number,
DROP COLUMN IF EXISTS customer_truck_type,
DROP COLUMN IF EXISTS customer_truck_driver_name,
DROP COLUMN IF EXISTS customer_truck_plate_number
`);
}
}