mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-28 18:20:57 +00:00
Merge freight/develop into Warehouse_updates
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
import {
|
||||
MigrationInterface,
|
||||
QueryRunner,
|
||||
Table,
|
||||
TableIndex,
|
||||
TableForeignKey,
|
||||
} from "typeorm";
|
||||
|
||||
export class CreateCompanyProfiles1752000000000 implements MigrationInterface {
|
||||
name = "CreateCompanyProfiles1752000000000";
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
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<void> {
|
||||
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`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class MoveBusinessLicenseToProfile1752000000001
|
||||
implements MigrationInterface
|
||||
{
|
||||
name = 'MoveBusinessLicenseToProfile1752000000001';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
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<void> {
|
||||
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
|
||||
`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class CreateVehiclesTable1770000000000 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
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<void> {
|
||||
await queryRunner.query(`DROP TABLE IF EXISTS freight.vehicles CASCADE;`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
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 <TYPE_CODE>-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<void> {
|
||||
// ── 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<void> {
|
||||
// Remove all seeded wagons (full wipe — mirrors what up() did)
|
||||
await queryRunner.query(`DELETE FROM freight.wagons;`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
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<void> {
|
||||
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<void> {
|
||||
await queryRunner.query(`DROP INDEX IF EXISTS freight.idx_bookings_route_day;`);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user