Customer Truck Assignment and Portal Delivary Approval

This commit is contained in:
hagiye
2026-07-02 15:54:16 +03:00
319 changed files with 24989 additions and 5041 deletions

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

@@ -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,27 @@
import { MigrationInterface, QueryRunner } from "typeorm";
/**
* Add the `EXPIRED` invoice status. An invoice expires when its source's pay
* window closes before settlement (e.g. a booking whose `paymentDeadline`
* lapses) — driven event-style from the domain via `BillingService.expirePayable`,
* which emits `${source}.invoice.expired`. Terminal and not settle-able (kept out
* of `OPEN_STATUSES`), so it is distinct from `CANCELLED` (manual void) and
* `OVERDUE` (still payable).
*
* Matches Freight.InvoiceStatus in packages/types. ADD VALUE only — additive and
* not referenced in this same transaction, so it is PG 12+ safe.
*/
export class AddExpiredInvoiceStatus1830000000000 implements MigrationInterface {
name = "AddExpiredInvoiceStatus1830000000000";
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TYPE freight.invoices_status_enum ADD VALUE IF NOT EXISTS 'EXPIRED' AFTER 'REFUNDED';`,
);
}
public async down(): Promise<void> {
// Postgres cannot drop individual enum values; EXPIRED is left on
// freight.invoices_status_enum (harmless, unused after down).
}
}

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,79 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class CreateFuelTables1840000000000 implements MigrationInterface {
name = "CreateFuelTables1840000000000";
public async up(queryRunner: QueryRunner): Promise<void> {
const fuelPurchasesExists = await queryRunner.query(
`SELECT 1 FROM information_schema.tables WHERE table_schema = 'freight' AND table_name = 'fuel_purchases';`,
);
if (!fuelPurchasesExists.length) {
await queryRunner.query(`
CREATE TABLE freight.fuel_purchases (
id uuid NOT NULL DEFAULT gen_random_uuid(),
vehicle_id uuid NOT NULL,
purchase_date timestamptz NOT NULL,
liters numeric(10, 2) NOT NULL,
cost_per_liter numeric(10, 2) NOT NULL,
total_cost numeric(14, 2) NOT NULL,
fuel_station varchar(255) NULL,
payment_method varchar(50) DEFAULT 'CASH',
odometer_reading numeric(10, 2) NULL,
driver_id uuid NULL,
receipt_number varchar(255) NULL,
notes text NULL,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
deleted_at timestamptz NULL,
CONSTRAINT pk_fuel_purchases PRIMARY KEY (id),
CONSTRAINT fk_fuel_purchases_vehicle FOREIGN KEY (vehicle_id)
REFERENCES freight.vehicles (id) ON DELETE CASCADE
);
`);
await queryRunner.query(
`CREATE INDEX idx_fuel_purchases_vehicle ON freight.fuel_purchases (vehicle_id);`,
);
await queryRunner.query(
`CREATE INDEX idx_fuel_purchases_date ON freight.fuel_purchases (purchase_date);`,
);
}
const fuelConsumptionExists = await queryRunner.query(
`SELECT 1 FROM information_schema.tables WHERE table_schema = 'freight' AND table_name = 'fuel_consumption';`,
);
if (!fuelConsumptionExists.length) {
await queryRunner.query(`
CREATE TABLE freight.fuel_consumption (
id uuid NOT NULL DEFAULT gen_random_uuid(),
vehicle_id uuid NOT NULL,
month date NOT NULL,
total_liters numeric(10, 2) NOT NULL,
total_cost numeric(14, 2) NOT NULL,
total_distance_km numeric(10, 2) NOT NULL,
fuel_efficiency_km_per_l numeric(10, 2) NULL,
number_of_purchases integer DEFAULT 0,
average_cost_per_liter numeric(10, 2) NULL,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
deleted_at timestamptz NULL,
CONSTRAINT pk_fuel_consumption PRIMARY KEY (id),
CONSTRAINT fk_fuel_consumption_vehicle FOREIGN KEY (vehicle_id)
REFERENCES freight.vehicles (id) ON DELETE CASCADE,
CONSTRAINT uq_fuel_consumption_vehicle_month UNIQUE (vehicle_id, month)
);
`);
await queryRunner.query(
`CREATE INDEX idx_fuel_consumption_vehicle_month ON freight.fuel_consumption (vehicle_id, month);`,
);
}
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE IF EXISTS freight.fuel_consumption;`);
await queryRunner.query(`DROP TABLE IF EXISTS freight.fuel_purchases;`);
}
}

View File

@@ -0,0 +1,82 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class CreateMaintenanceTables1850000000000 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
// Create maintenance_schedules table
const scheduleTableExists = await queryRunner.query(`
SELECT EXISTS(
SELECT 1 FROM information_schema.tables
WHERE table_schema = 'freight' AND table_name = 'maintenance_schedules'
)
`);
if (!scheduleTableExists[0].exists) {
await queryRunner.query(`
CREATE TABLE "freight"."maintenance_schedules" (
"id" uuid NOT NULL DEFAULT gen_random_uuid(),
"vehicle_id" uuid NOT NULL,
"maintenance_type" varchar NOT NULL,
"description" varchar NOT NULL,
"scheduled_date" timestamptz NOT NULL,
"completed_date" timestamptz,
"estimated_cost" numeric(14,2),
"actual_cost" numeric(14,2),
"status" varchar NOT NULL DEFAULT 'SCHEDULED',
"odometer_reading" numeric,
"service_provider" varchar,
"notes" text,
"next_due_km" numeric,
"next_due_date" timestamptz,
"created_at" timestamptz NOT NULL DEFAULT now(),
"updated_at" timestamptz NOT NULL DEFAULT now(),
"deleted_at" timestamptz,
PRIMARY KEY ("id")
)
`);
await queryRunner.query(
`CREATE INDEX "idx_maintenance_schedules_vehicle_date" ON "freight"."maintenance_schedules" ("vehicle_id", "scheduled_date")`
);
}
// Create maintenance_costs table
const costsTableExists = await queryRunner.query(`
SELECT EXISTS(
SELECT 1 FROM information_schema.tables
WHERE table_schema = 'freight' AND table_name = 'maintenance_costs'
)
`);
if (!costsTableExists[0].exists) {
await queryRunner.query(`
CREATE TABLE "freight"."maintenance_costs" (
"id" uuid NOT NULL DEFAULT gen_random_uuid(),
"vehicle_id" uuid NOT NULL,
"maintenance_schedule_id" uuid,
"incurred_date" timestamptz NOT NULL,
"cost_amount" numeric(14,2) NOT NULL,
"cost_type" varchar NOT NULL,
"description" varchar NOT NULL,
"service_provider" varchar,
"invoice_number" varchar,
"notes" text,
"created_at" timestamptz NOT NULL DEFAULT now(),
"updated_at" timestamptz NOT NULL DEFAULT now(),
"deleted_at" timestamptz,
PRIMARY KEY ("id"),
CONSTRAINT "fk_maintenance_schedule" FOREIGN KEY ("maintenance_schedule_id")
REFERENCES "freight"."maintenance_schedules" ("id") ON DELETE SET NULL
)
`);
await queryRunner.query(
`CREATE INDEX "idx_maintenance_costs_vehicle_date" ON "freight"."maintenance_costs" ("vehicle_id", "incurred_date")`
);
}
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE IF EXISTS "freight"."maintenance_costs"`);
await queryRunner.query(`DROP TABLE IF EXISTS "freight"."maintenance_schedules"`);
}
}

View File

@@ -0,0 +1,34 @@
import { MigrationInterface, QueryRunner } from "typeorm";
/**
* Add paid column to first_mile and last_mile tables to track invoice payment status.
*/
export class AddPaidToFirstAndLastMile1860000000000
implements MigrationInterface
{
name = "AddPaidToFirstAndLastMile1860000000000";
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE freight.first_mile
ADD COLUMN IF NOT EXISTS paid boolean NOT NULL DEFAULT false;
`);
await queryRunner.query(`
ALTER TABLE freight.last_mile
ADD COLUMN IF NOT EXISTS paid boolean NOT NULL DEFAULT false;
`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE freight.first_mile
DROP COLUMN IF EXISTS paid;
`);
await queryRunner.query(`
ALTER TABLE freight.last_mile
DROP COLUMN IF EXISTS paid;
`);
}
}