mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +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>
68 lines
2.7 KiB
TypeScript
68 lines
2.7 KiB
TypeScript
import { Injectable, Logger } from '@nestjs/common';
|
|
import { DataSource } from 'typeorm';
|
|
|
|
/**
|
|
* EDR's load/unload facilities, mapped onto the yards that already represent them.
|
|
*
|
|
* The codes are historical and don't read like the facility names, so map by code
|
|
* and never by label: Indode is `KALITY` ("Gelan Multi Purpose Port (Indode)") and
|
|
* Sebeta is `LEGACY_DEST` ("Sebeta"). Creating fresh INDODE/SEBETA yards would
|
|
* split data that existing routes and bookings already point at.
|
|
*
|
|
* Only Indode stores cargo, so it is the only facility with a warehouse — the rest
|
|
* move cargo on and off the train, which is why they accrue no storage/demurrage.
|
|
*
|
|
* Negad is deliberately absent: there are two candidates (`NAGAD` "DCT/SGDT" in
|
|
* Djibouti and `NEGAD_FY_BCC` in Ethiopia, currently inactive) and it is not yet
|
|
* settled which is the intercity facility.
|
|
*/
|
|
const FACILITY_YARDS: Array<{ code: string; facility: string; hasWarehouse: boolean }> = [
|
|
{ code: 'KALITY', facility: 'Indode', hasWarehouse: true },
|
|
{ code: 'LEGACY_DEST', facility: 'Sebeta', hasWarehouse: false },
|
|
{ code: 'MOJO', facility: 'Modjo', hasWarehouse: false },
|
|
{ code: 'ADAMA', facility: 'Adama', hasWarehouse: false },
|
|
{ code: 'DIRE_DAWA', facility: 'Dire Dawa', hasWarehouse: false },
|
|
];
|
|
|
|
@Injectable()
|
|
export class YardFacilitiesSeeder {
|
|
private readonly logger = new Logger(YardFacilitiesSeeder.name);
|
|
|
|
constructor(private readonly dataSource: DataSource) {}
|
|
|
|
/**
|
|
* Idempotent: flags existing yards and upserts their facility record. Creates no
|
|
* yards — a missing code is logged and skipped rather than invented.
|
|
*/
|
|
async run(): Promise<void> {
|
|
for (const { code, facility, hasWarehouse } of FACILITY_YARDS) {
|
|
const [yard]: Array<{ id: string }> = await this.dataSource.query(
|
|
`SELECT id FROM freight.yards WHERE code = $1 AND deleted_at IS NULL`,
|
|
[code],
|
|
);
|
|
if (!yard) {
|
|
this.logger.warn(`Yard ${code} (${facility}) not found — skipping facility flag`);
|
|
continue;
|
|
}
|
|
|
|
await this.dataSource.query(
|
|
`UPDATE freight.yards
|
|
SET has_facility = true, updated_at = NOW()
|
|
WHERE id = $1 AND has_facility = false`,
|
|
[yard.id],
|
|
);
|
|
|
|
await this.dataSource.query(
|
|
`INSERT INTO freight.yard_facilities (yard_id, has_warehouse, equipment_notes)
|
|
VALUES ($1, $2, $3)
|
|
ON CONFLICT (yard_id) WHERE deleted_at IS NULL
|
|
DO UPDATE SET has_warehouse = EXCLUDED.has_warehouse, updated_at = NOW()`,
|
|
[yard.id, hasWarehouse, `${facility} load/unload facility`],
|
|
);
|
|
}
|
|
this.logger.log(
|
|
`Yard facilities seeded: ${FACILITY_YARDS.map((f) => f.facility).join(', ')}`,
|
|
);
|
|
}
|
|
}
|