mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
- Added StationWorkControls to manage loading/unloading phases in TrainScheduleV2DetailPage. - Implemented API endpoints for recording station work and managing wagon detach requests. - Updated contract templates to include Ethiopian customs handling options. - Enhanced shipment forms to collect customs clearing agent details for without-customs bookings. - Introduced NUMBER_OF_WAGONS as a unit of measure for bulk cargo, allowing customers to specify wagon counts. - Improved validation for customs clearing agent information in shipment forms. - Updated various components and services to accommodate new features and ensure data integrity.
75 lines
3.1 KiB
TypeScript
75 lines
3.1 KiB
TypeScript
import { MigrationInterface, QueryRunner } from 'typeorm';
|
|
|
|
/**
|
|
* Approval gate for detaching a wagon (or sending it to maintenance) from a
|
|
* train whose run is already SCHEDULED.
|
|
*
|
|
* Before scheduling, the consist is the builder's to edit. After scheduling,
|
|
* pulling a wagon changes a departure customers booked against, so it becomes
|
|
* a two-person action: one staffer files a request with a reason, another
|
|
* staffer (with trains:approve_wagon_detach) approves it — approval executes
|
|
* the detach on the spot. Rows are never deleted; decided rows are the audit
|
|
* trail of who asked, who decided, and why.
|
|
*
|
|
* One PENDING row per (train, wagon) at a time — a second request while one is
|
|
* undecided is a coordination failure, not a workflow (partial unique index).
|
|
*/
|
|
export class WagonDetachRequests3730000000000 implements MigrationInterface {
|
|
name = 'WagonDetachRequests3730000000000';
|
|
|
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
await queryRunner.query(`
|
|
DO $$ BEGIN
|
|
CREATE TYPE freight.wagon_detach_requests_status_enum
|
|
AS ENUM ('PENDING', 'APPROVED', 'REJECTED');
|
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$
|
|
`);
|
|
|
|
await queryRunner.query(`
|
|
CREATE TABLE IF NOT EXISTS freight.wagon_detach_requests (
|
|
id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
train_id uuid NOT NULL REFERENCES freight.trains (id),
|
|
wagon_id uuid NOT NULL REFERENCES freight.wagons (id),
|
|
-- Snapshot: the audit trail must still read correctly after the wagon
|
|
-- is renumbered or deleted.
|
|
wagon_number varchar(50) NOT NULL,
|
|
action varchar(20) NOT NULL,
|
|
reason varchar(500) NOT NULL,
|
|
status freight.wagon_detach_requests_status_enum NOT NULL DEFAULT 'PENDING',
|
|
-- Who asked and who decided. Both recorded: the point of the gate is
|
|
-- that they are different people.
|
|
requested_by uuid,
|
|
decided_by uuid,
|
|
decided_at timestamptz,
|
|
decision_note varchar(500),
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
deleted_at timestamptz
|
|
)
|
|
`);
|
|
|
|
await queryRunner.query(`
|
|
CREATE INDEX IF NOT EXISTS idx_wagon_detach_requests_train
|
|
ON freight.wagon_detach_requests (train_id)
|
|
`);
|
|
|
|
await queryRunner.query(`
|
|
CREATE INDEX IF NOT EXISTS idx_wagon_detach_requests_train_status
|
|
ON freight.wagon_detach_requests (train_id, status)
|
|
`);
|
|
|
|
// The workflow invariant, enforced where it cannot race: at most one
|
|
// undecided request per wagon per train.
|
|
await queryRunner.query(`
|
|
CREATE UNIQUE INDEX IF NOT EXISTS uq_wagon_detach_requests_one_pending
|
|
ON freight.wagon_detach_requests (train_id, wagon_id)
|
|
WHERE status = 'PENDING' AND deleted_at IS NULL
|
|
`);
|
|
}
|
|
|
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
|
await queryRunner.query(`DROP TABLE IF EXISTS freight.wagon_detach_requests`);
|
|
await queryRunner.query(`DROP TYPE IF EXISTS freight.wagon_detach_requests_status_enum`);
|
|
}
|
|
}
|