feat(train-scheduling): load returned empties onto an export train

Empties had no way onto a departure: the return record could name a train
but nothing seated it on a wagon. Export schedules now expose a loading
action that packs selected returns onto free wagons at one 40ft or two 20ft
each, enforced both in the picker and in the API (existing empties on the
schedule count against their wagon).

Adds container_size, train_schedule_id and wagon_sequence_no to
freight.empty_container_returns.
This commit is contained in:
Hagernesh
2026-08-14 09:57:20 +00:00
parent 30c3567048
commit b4f2ec1c75
15 changed files with 630 additions and 2 deletions

View File

@@ -0,0 +1,35 @@
import { MigrationInterface, QueryRunner } from "typeorm";
/**
* Empty containers ride an export departure back to Djibouti, so a return now
* records which train schedule carries it and on which wagon slot. Size is
* captured too: the wagon rule is one 40ft OR two 20ft per wagon, which cannot
* be enforced without knowing the box size.
*/
export class EmptyReturnTrainLoad3540000000000 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE freight.empty_container_returns
ADD COLUMN IF NOT EXISTS container_size character varying(10),
ADD COLUMN IF NOT EXISTS train_schedule_id uuid,
ADD COLUMN IF NOT EXISTS wagon_sequence_no integer
`);
await queryRunner.query(`
CREATE INDEX IF NOT EXISTS idx_empty_container_returns_train_schedule_id
ON freight.empty_container_returns (train_schedule_id)
WHERE train_schedule_id IS NOT NULL
`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
DROP INDEX IF EXISTS freight.idx_empty_container_returns_train_schedule_id
`);
await queryRunner.query(`
ALTER TABLE freight.empty_container_returns
DROP COLUMN IF EXISTS container_size,
DROP COLUMN IF EXISTS train_schedule_id,
DROP COLUMN IF EXISTS wagon_sequence_no
`);
}
}