resolve conflict

This commit is contained in:
Marshal
2026-07-07 21:46:25 +00:00
213 changed files with 15163 additions and 1998 deletions

View File

@@ -0,0 +1,59 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
/**
* Vehicle Compliance & Expiry Alerts.
* - Adds expiry-tracking columns to freight.vehicles.
* - Creates freight.compliance_records for per-document compliance tracking.
*/
export class AddVehicleCompliance1950000000000 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
// Vehicle expiry / compliance columns.
await queryRunner.query(`
ALTER TABLE freight.vehicles
ADD COLUMN IF NOT EXISTS vin VARCHAR,
ADD COLUMN IF NOT EXISTS ownership VARCHAR,
ADD COLUMN IF NOT EXISTS insurance_expiry DATE,
ADD COLUMN IF NOT EXISTS registration_expiry DATE,
ADD COLUMN IF NOT EXISTS next_inspection_date DATE;
`);
// Compliance records table.
await queryRunner.query(`
CREATE TABLE IF NOT EXISTS freight.compliance_records (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
vehicle_id UUID NOT NULL REFERENCES freight.vehicles(id),
type VARCHAR NOT NULL,
document_number VARCHAR,
issued_date DATE,
expiry_date DATE NOT NULL,
status VARCHAR NOT NULL DEFAULT 'VALID',
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_compliance_records_vehicle_id ON freight.compliance_records(vehicle_id);`,
);
await queryRunner.query(
`CREATE INDEX IF NOT EXISTS idx_compliance_records_expiry_date ON freight.compliance_records(expiry_date);`,
);
await queryRunner.query(
`CREATE INDEX IF NOT EXISTS idx_compliance_records_type ON freight.compliance_records(type);`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE IF EXISTS freight.compliance_records CASCADE;`);
await queryRunner.query(`
ALTER TABLE freight.vehicles
DROP COLUMN IF EXISTS vin,
DROP COLUMN IF EXISTS ownership,
DROP COLUMN IF EXISTS insurance_expiry,
DROP COLUMN IF EXISTS registration_expiry,
DROP COLUMN IF EXISTS next_inspection_date;
`);
}
}

View File

@@ -0,0 +1,46 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
/**
* Accident & Incident register for the fleet. Tracks accidents, breakdowns,
* traffic violations, thefts and other incidents against a vehicle, driver
* and/or booking, with severity, damage estimate, insurance claim tracking and
* a lifecycle status. Queried by driver_id for per-driver incident history.
*/
export class AddIncidents1960000000000 implements MigrationInterface {
name = 'AddIncidents1960000000000';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
CREATE TABLE IF NOT EXISTS freight.incidents (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
deleted_at timestamptz,
vehicle_id uuid,
driver_id uuid,
booking_id uuid,
type varchar NOT NULL,
severity varchar NOT NULL,
occurred_at timestamptz NOT NULL,
location varchar,
description text NOT NULL,
damage_estimate numeric(14,2),
status varchar NOT NULL DEFAULT 'REPORTED',
insurance_claim_number varchar,
reported_by varchar
)
`);
await queryRunner.query(`
CREATE INDEX IF NOT EXISTS "IDX_INCIDENTS_DRIVER"
ON freight.incidents (driver_id, occurred_at)
`);
await queryRunner.query(`
CREATE INDEX IF NOT EXISTS "IDX_INCIDENTS_VEHICLE"
ON freight.incidents (vehicle_id, occurred_at)
`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE IF EXISTS freight.incidents`);
}
}

View File

@@ -0,0 +1,93 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class AddMaintenanceDepth1970000000000 implements MigrationInterface {
name = 'AddMaintenanceDepth1970000000000';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`CREATE SCHEMA IF NOT EXISTS freight`);
await queryRunner.query(`
CREATE TABLE IF NOT EXISTS freight.work_orders (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
vehicle_id UUID NOT NULL,
title VARCHAR NOT NULL,
description TEXT,
status VARCHAR NOT NULL DEFAULT 'OPEN',
priority VARCHAR NOT NULL DEFAULT 'MEDIUM',
assigned_to VARCHAR,
opened_at TIMESTAMPTZ NOT NULL DEFAULT now(),
closed_at TIMESTAMPTZ,
labor_cost NUMERIC(14, 2),
parts_cost NUMERIC(14, 2),
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.parts (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR NOT NULL,
sku VARCHAR,
category VARCHAR,
quantity_in_stock INT NOT NULL DEFAULT 0,
reorder_level INT NOT NULL DEFAULT 0,
unit_cost NUMERIC(14, 2),
location VARCHAR,
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.warranties (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
vehicle_id UUID NOT NULL,
component VARCHAR NOT NULL,
provider VARCHAR,
start_date DATE,
expiry_date DATE NOT NULL,
coverage_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_work_orders_vehicle_id_status" ON freight.work_orders (vehicle_id, status)`,
);
await queryRunner.query(
`CREATE INDEX IF NOT EXISTS "IDX_parts_category" ON freight.parts (category)`,
);
await queryRunner.query(
`CREATE INDEX IF NOT EXISTS "IDX_warranties_vehicle_id_expiry_date" ON freight.warranties (vehicle_id, expiry_date)`,
);
await queryRunner.query(`
DO $$ BEGIN
ALTER TABLE freight.work_orders
ADD CONSTRAINT "FK_work_orders_vehicle_id"
FOREIGN KEY (vehicle_id) REFERENCES freight.vehicles(id) ON DELETE CASCADE;
EXCEPTION WHEN duplicate_object THEN NULL;
END $$;
`);
await queryRunner.query(`
DO $$ BEGIN
ALTER TABLE freight.warranties
ADD CONSTRAINT "FK_warranties_vehicle_id"
FOREIGN KEY (vehicle_id) REFERENCES freight.vehicles(id) ON DELETE CASCADE;
EXCEPTION WHEN duplicate_object THEN NULL;
END $$;
`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE IF EXISTS freight.warranties CASCADE`);
await queryRunner.query(`DROP TABLE IF EXISTS freight.parts CASCADE`);
await queryRunner.query(`DROP TABLE IF EXISTS freight.work_orders CASCADE`);
}
}

View File

@@ -0,0 +1,73 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class AddProcurement1980000000000 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
CREATE TABLE IF NOT EXISTS freight.vendors (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
deleted_at timestamptz,
name varchar NOT NULL,
type varchar,
contact_person varchar,
phone varchar,
email varchar,
address varchar,
is_active boolean NOT NULL DEFAULT true
);
`);
await queryRunner.query(`
CREATE TABLE IF NOT EXISTS freight.asset_acquisitions (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
deleted_at timestamptz,
vehicle_id uuid,
vendor_id uuid,
acquisition_type varchar NOT NULL,
acquisition_date date NOT NULL,
cost numeric(14,2),
useful_life_months integer,
salvage_value numeric(14,2),
lease_start date,
lease_end date,
monthly_payment numeric(14,2),
status varchar NOT NULL DEFAULT 'ACTIVE',
notes text
);
`);
await queryRunner.query(`
CREATE INDEX IF NOT EXISTS idx_asset_acquisitions_vehicle_date
ON freight.asset_acquisitions(vehicle_id, acquisition_date);
`);
await queryRunner.query(`
CREATE TABLE IF NOT EXISTS freight.asset_disposals (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
deleted_at timestamptz,
vehicle_id uuid NOT NULL,
disposal_date date NOT NULL,
method varchar NOT NULL,
sale_price numeric(14,2),
buyer varchar,
notes text
);
`);
await queryRunner.query(`
CREATE INDEX IF NOT EXISTS idx_asset_disposals_vehicle_date
ON freight.asset_disposals(vehicle_id, disposal_date);
`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE IF EXISTS freight.asset_disposals CASCADE;`);
await queryRunner.query(`DROP TABLE IF EXISTS freight.asset_acquisitions CASCADE;`);
await queryRunner.query(`DROP TABLE IF EXISTS freight.vendors CASCADE;`);
}
}

View File

@@ -0,0 +1,25 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
/**
* Double-handling fee support. warehouse_fee_rules.basis: how a
* DOUBLE_HANDLING_FEE rule is charged — PER_CONTAINER | PER_TON | PER_ITEM
* (null for the day-based fee types). The PER_TON / PER_ITEM quantity comes from
* the booking's cargo total (cargo_total_weight_vgm, expressed in the cargo's
* unit of measure), so no new booking column is needed.
*/
export class AddDoubleHandlingBasisAndMachinery1990000000000 implements MigrationInterface {
name = 'AddDoubleHandlingBasisAndMachinery1990000000000';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE freight.warehouse_fee_rules ADD COLUMN IF NOT EXISTS basis varchar(20)`,
);
// machinery_units is not used (PER_ITEM reads cargo_total_weight_vgm); drop it
// if a prior version of this migration added it.
await queryRunner.query(`ALTER TABLE freight.bookings DROP COLUMN IF EXISTS machinery_units`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE freight.warehouse_fee_rules DROP COLUMN IF EXISTS basis`);
}
}

View File

@@ -0,0 +1,25 @@
import { MigrationInterface, QueryRunner } from "typeorm";
/**
* Per-km haulage rate on a vehicle (mainly trucks) plus the currency it's
* quoted in (ETB | USD, default ETB).
*/
export class AddVehiclePricePerKm1990000000000 implements MigrationInterface {
name = "AddVehiclePricePerKm1990000000000";
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE freight.vehicles
ADD COLUMN IF NOT EXISTS price_per_km numeric(14,2),
ADD COLUMN IF NOT EXISTS currency varchar(8) NOT NULL DEFAULT 'ETB'
`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE freight.vehicles
DROP COLUMN IF EXISTS price_per_km,
DROP COLUMN IF EXISTS currency
`);
}
}

View File

@@ -0,0 +1,69 @@
import { MigrationInterface, QueryRunner } from "typeorm";
/**
* GPS tracking: physical trackers (gps_devices, one denormalized latest fix per
* device for the live map) + append-only fix history (gps_positions).
*/
export class AddGpsTracking2000000000000 implements MigrationInterface {
name = "AddGpsTracking2000000000000";
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
CREATE TABLE IF NOT EXISTS freight.gps_devices (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
imei varchar(20) NOT NULL UNIQUE,
name varchar,
vehicle_id uuid REFERENCES freight.vehicles(id),
status varchar(16) NOT NULL DEFAULT 'REGISTERED',
last_seen_at timestamptz,
last_lat numeric(10,6),
last_lng numeric(10,6),
last_speed numeric(6,2),
last_course int,
last_fix_at timestamptz,
voltage_level int,
gsm_level int,
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_GPS_DEVICES_VEHICLE"
ON freight.gps_devices (vehicle_id)
`);
await queryRunner.query(`
CREATE TABLE IF NOT EXISTS freight.gps_positions (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
device_id uuid NOT NULL,
imei varchar(20) NOT NULL,
vehicle_id uuid,
lat numeric(10,6) NOT NULL,
lng numeric(10,6) NOT NULL,
speed numeric(6,2) NOT NULL DEFAULT 0,
course int NOT NULL DEFAULT 0,
satellites int NOT NULL DEFAULT 0,
positioned boolean NOT NULL DEFAULT false,
gps_time timestamptz NOT NULL,
alarm int 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_GPS_POSITIONS_DEVICE_TIME"
ON freight.gps_positions (device_id, gps_time)
`);
await queryRunner.query(`
CREATE INDEX IF NOT EXISTS "IDX_GPS_POSITIONS_VEHICLE_TIME"
ON freight.gps_positions (vehicle_id, gps_time)
`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE IF EXISTS freight.gps_positions`);
await queryRunner.query(`DROP TABLE IF EXISTS freight.gps_devices`);
}
}

View File

@@ -0,0 +1,32 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
/**
* Truck detention support.
* - last_mile.arrived_at / delivered_at: the detention window for an EDR
* last-mile vehicle. The clock runs from arrival at destination; the customer
* has a grace period (default 3h) to clear/return, after which detention
* accrues per truck per day until delivered_at (or now, if still out).
* - warehouse_fee_rules.free_hours: configurable grace window (hours) for a
* TRUCK_DETENTION_FEE rule; null/0 falls back to the 3-hour default.
*/
export class AddTruckDetentionTiming2000000000000 implements MigrationInterface {
name = 'AddTruckDetentionTiming2000000000000';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE freight.last_mile ADD COLUMN IF NOT EXISTS arrived_at timestamptz`,
);
await queryRunner.query(
`ALTER TABLE freight.last_mile ADD COLUMN IF NOT EXISTS delivered_at timestamptz`,
);
await queryRunner.query(
`ALTER TABLE freight.warehouse_fee_rules ADD COLUMN IF NOT EXISTS free_hours int`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE freight.warehouse_fee_rules DROP COLUMN IF EXISTS free_hours`);
await queryRunner.query(`ALTER TABLE freight.last_mile DROP COLUMN IF EXISTS delivered_at`);
await queryRunner.query(`ALTER TABLE freight.last_mile DROP COLUMN IF EXISTS arrived_at`);
}
}

View File

@@ -0,0 +1,20 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
/**
* Truck-detention rules can be scoped by vehicle type (TRUCK / VAN / TRAILER /
* TANKER / FLATBED / …), so different truck types carry different detention
* rates. Null = applies to any truck type.
*/
export class AddFeeRuleVehicleType2010000000000 implements MigrationInterface {
name = 'AddFeeRuleVehicleType2010000000000';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE freight.warehouse_fee_rules ADD COLUMN IF NOT EXISTS vehicle_type varchar(20)`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE freight.warehouse_fee_rules DROP COLUMN IF EXISTS vehicle_type`);
}
}