Merge pull request #764 from Tria-plc/freight/feature/user_management_UI

Freight/feature/user management UI
This commit is contained in:
yaschalew10
2026-07-17 14:05:10 +03:00
committed by GitHub
2 changed files with 87 additions and 4 deletions

View File

@@ -0,0 +1,68 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
/**
* Stand the whole wagon fleet in Doraleh.
*
* Runs AFTER SeedEdrWagonFleetErNumbering2260000000000, which recreates every
* wagon with a NULL yard — so this must stay later in timestamp order.
*
* A wagon with no yard cannot be coupled to a train (the train builder only
* offers AVAILABLE wagons standing in the train's own yard), which left the
* seeded fleet unusable. Doraleh is the Djibouti-side port yard the import runs
* originate from.
*
* The yard is created when absent: environments disagree about which yards
* exist, so this cannot assume one is there.
*/
const YARD_CODE = 'DORALEH';
export class SeedWagonYardDoraleh2290000000000 implements MigrationInterface {
name = 'SeedWagonYardDoraleh2290000000000';
public async up(queryRunner: QueryRunner): Promise<void> {
// Ensure the yard exists and is usable. Deliberately does NOT overwrite an
// existing label/country — a deployment that already calls this yard
// something else keeps its own naming.
await queryRunner.query(
`
INSERT INTO freight.yards (code, label, country, is_active, display_order)
VALUES ($1, 'Doraleh', 'Djibouti', true, 12)
ON CONFLICT (code) DO UPDATE SET
is_active = true,
deleted_at = NULL,
updated_at = now();
`,
[YARD_CODE],
);
const [yard] = await queryRunner.query(
`SELECT id FROM freight.yards WHERE code = $1 AND deleted_at IS NULL LIMIT 1;`,
[YARD_CODE],
);
if (!yard?.id) {
throw new Error(`yard_missing:${YARD_CODE}`);
}
// Whole fleet — a wagon already coupled to a built train follows the train,
// so leave those where they stand.
await queryRunner.query(
`
UPDATE freight.wagons
SET current_yard_id = $1::uuid,
updated_at = now()
WHERE train_id IS NULL;
`,
[yard.id],
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
// Back to the state SeedEdrWagonFleetErNumbering leaves them in.
await queryRunner.query(`
UPDATE freight.wagons
SET current_yard_id = NULL
WHERE train_id IS NULL;
`);
}
}

View File

@@ -2,6 +2,7 @@ import { AppDataSource } from '../data-source';
import { SeedEdrWagonFleetErNumbering2260000000000 } from '../migrations/2260000000000-SeedEdrWagonFleetErNumbering';
import { AddWagonTrainNumbers2270000000000 } from '../migrations/2270000000000-AddWagonTrainNumbers';
import { SeedWagonRunNumbers2280000000000 } from '../migrations/2280000000000-SeedWagonRunNumbers';
import { SeedWagonYardDoraleh2290000000000 } from '../migrations/2290000000000-SeedWagonYardDoraleh';
async function seedEdRWagons() {
await AppDataSource.initialize();
@@ -12,12 +13,14 @@ async function seedEdRWagons() {
await queryRunner.connect();
await queryRunner.startTransaction();
// Fleet first (recreates every wagon with NULL runs), then the columns are
// ensured to exist, then the run roster is applied on top. Same order the
// migrations run in, so the script and a fresh migrate agree.
// Fleet first (recreates every wagon with NULL yard + NULL runs), then the
// columns are ensured to exist, then the run roster and the yard are applied
// on top. Same order the migrations run in, so the script and a fresh
// migrate agree.
await new SeedEdrWagonFleetErNumbering2260000000000().up(queryRunner);
await new AddWagonTrainNumbers2270000000000().up(queryRunner);
await new SeedWagonRunNumbers2280000000000().up(queryRunner);
await new SeedWagonYardDoraleh2290000000000().up(queryRunner);
const summary = await queryRunner.query(`
SELECT
@@ -27,7 +30,7 @@ async function seedEdRWagons() {
MIN(w.wagon_number) AS first_wagon,
MAX(w.wagon_number) AS last_wagon,
COUNT(*) FILTER (WHERE w.status = 'AVAILABLE')::int AS available,
COUNT(*) FILTER (WHERE w.current_yard_id IS NULL)::int AS unassigned_yard,
COUNT(*) FILTER (WHERE w.current_yard_id IS NULL)::int AS no_yard,
COUNT(*) FILTER (WHERE w.export_train_number IS NOT NULL)::int AS on_a_run
FROM freight.wagons w
JOIN freight.wagon_types wt ON wt.id = w.wagon_type_id
@@ -43,6 +46,16 @@ async function seedEdRWagons() {
FROM freight.wagons;
`);
const yards = await queryRunner.query(`
SELECT
COALESCE(y.label, '(no yard)') AS yard,
COUNT(*)::int AS wagons
FROM freight.wagons w
LEFT JOIN freight.yards y ON y.id = w.current_yard_id
GROUP BY y.label
ORDER BY 2 DESC;
`);
const runs = await queryRunner.query(`
SELECT
export_train_number AS export_run,
@@ -60,6 +73,8 @@ async function seedEdRWagons() {
console.table(summary);
console.log('Run roster (export/import pairs):');
console.table(runs);
console.log('Fleet by yard:');
console.table(yards);
console.log(
`Seeded EDR wagon fleet — ${totals.total} wagons total (expected 1100), ` +
`${totals.on_a_run} on a run (expected 533).`,