mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 04:50:54 +00:00
Intercity cargo is loaded at its origin yard and unloaded at its destination, but
only some yards have the equipment. EDR's facilities are Indode, Sebeta, Modjo,
Adama and Dire Dawa — and the set grows, so it has to be data.
- yards.has_facility marks a yard as a load/unload point; the new yard_facilities
record says what it can do. Only Indode stores cargo (has_warehouse), so only it
accrues storage/demurrage — the rest just move cargo on and off the train.
- facility_handling_events records each load/unload and carries its GRN.
warehouse_inventory cannot: its warehouse/yard/zone are NOT NULL, so a facility
without a warehouse could never have a row. inventory_id links to the storage
record when there is one.
- YardFacilitiesService.facilityForYard is the single resolver the handling flows
share, so they cannot drift on what a facility is.
- The seeder flags EXISTING yards and creates none. The codes are historical and
do not read like the facility names — Indode is KALITY ("Gelan Multi Purpose
Port (Indode)") and Sebeta is LEGACY_DEST — so it maps by code. Creating fresh
INDODE/SEBETA yards would have split data that routes and bookings already
reference.
Negad is deliberately absent: NAGAD ("DCT/SGDT") is in Djibouti while
NEGAD_FY_BCC is in Ethiopia and inactive, and which one is the intercity facility
is unsettled.
No behaviour change yet — nothing reads has_facility until the gate lands.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
86 lines
3.8 KiB
TypeScript
86 lines
3.8 KiB
TypeScript
import { MigrationInterface, QueryRunner } from 'typeorm';
|
|
|
|
/**
|
|
* Intercity (DOMESTIC) cargo is loaded at its origin yard and unloaded at its
|
|
* destination yard, but only some yards have the equipment to do it. EDR's
|
|
* load/unload facilities are Indode, Sebeta, Modjo, Adama, Dire Dawa and Negad —
|
|
* and the set grows, so it must be data, not a constant.
|
|
*
|
|
* `yards.has_facility` marks a yard as a load/unload point; `yard_facilities`
|
|
* holds what that facility can do. Only a facility with `has_warehouse` (Indode
|
|
* today) stores cargo, and therefore accrues storage/demurrage — the rest just
|
|
* move it on and off the train.
|
|
*
|
|
* `facility_handling_events` records each load/unload and carries its GRN.
|
|
* warehouse_inventory can't do that job: its warehouse/yard/zone are NOT NULL, so
|
|
* a facility with no warehouse could never have a row. `inventory_id` links to the
|
|
* storage record when the facility does have a warehouse.
|
|
*/
|
|
export class YardFacilities2290000000000 implements MigrationInterface {
|
|
name = 'YardFacilities2290000000000';
|
|
|
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
await queryRunner.query(`
|
|
ALTER TABLE freight.yards
|
|
ADD COLUMN IF NOT EXISTS has_facility boolean NOT NULL DEFAULT false
|
|
`);
|
|
|
|
await queryRunner.query(`
|
|
CREATE TABLE IF NOT EXISTS freight.yard_facilities (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
yard_id uuid NOT NULL REFERENCES freight.yards(id) ON DELETE CASCADE,
|
|
has_warehouse boolean NOT NULL DEFAULT false,
|
|
equipment_notes text NULL,
|
|
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
|
|
)
|
|
`);
|
|
// One facility record per yard.
|
|
await queryRunner.query(`
|
|
CREATE UNIQUE INDEX IF NOT EXISTS "UQ_yard_facility_yard"
|
|
ON freight.yard_facilities (yard_id) WHERE deleted_at IS NULL
|
|
`);
|
|
|
|
await queryRunner.query(`
|
|
CREATE TABLE IF NOT EXISTS freight.facility_handling_events (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
booking_id uuid NOT NULL REFERENCES freight.bookings(id),
|
|
yard_id uuid NOT NULL REFERENCES freight.yards(id),
|
|
train_schedule_id uuid NULL REFERENCES freight.train_schedules(id),
|
|
event_type varchar(10) NOT NULL,
|
|
grn_number varchar(60) NULL,
|
|
quantity numeric(14, 3) NULL,
|
|
weight_tons numeric(14, 3) NULL,
|
|
inventory_id uuid NULL REFERENCES freight.warehouse_inventory(id),
|
|
performed_by varchar(120) NULL,
|
|
occurred_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_facility_handling_events_booking"
|
|
ON freight.facility_handling_events (booking_id)
|
|
`);
|
|
await queryRunner.query(`
|
|
CREATE INDEX IF NOT EXISTS "IDX_facility_handling_events_yard"
|
|
ON freight.facility_handling_events (yard_id)
|
|
`);
|
|
await queryRunner.query(`
|
|
CREATE INDEX IF NOT EXISTS "IDX_facility_handling_events_grn"
|
|
ON freight.facility_handling_events (grn_number) WHERE grn_number IS NOT NULL
|
|
`);
|
|
}
|
|
|
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
|
await queryRunner.query(`DROP TABLE IF EXISTS freight.facility_handling_events`);
|
|
await queryRunner.query(`DROP TABLE IF EXISTS freight.yard_facilities`);
|
|
await queryRunner.query(`
|
|
ALTER TABLE freight.yards DROP COLUMN IF EXISTS has_facility
|
|
`);
|
|
}
|
|
}
|