mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 23:28:11 +00:00
feat: add wagon usage computation and maintenance logging features
- Implemented utility to calculate wagon usage metrics for train schedules. - Created for sending wagons to maintenance with optional notes. - Added unit tests for train builder maintenance functionalities, including formatting train run labels and building maintenance notes. - Developed component for merging train schedules with detailed previews and reasons for merging. - Introduced component for selecting wagons with search functionality and selection limits. - Created for displaying and filtering audit logs, including detailed views of individual log entries. - Added for handling API interactions related to audit logs, including fetching logs and entity types.
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
/**
|
||||
* Backoffice audit trail for every state-changing freight endpoint.
|
||||
*
|
||||
* No `updated_at` / `deleted_at` columns, unlike every other table here: audit
|
||||
* rows are insert-only evidence. A soft-delete column would let an actor erase
|
||||
* their own trail and TypeORM would then hide those rows from default queries
|
||||
* silently — see the entity comment.
|
||||
*
|
||||
* `user_id` intentionally carries NO foreign key to the `iam` schema.
|
||||
* Cross-schema FKs are forbidden platform-wide, and one here would let user
|
||||
* deletion cascade away the record of what that user did.
|
||||
*
|
||||
* DDL is idempotent (`IF NOT EXISTS`) because watch-mode API instances race
|
||||
* `migrationsRun` against each other on the shared dev database.
|
||||
*/
|
||||
export class CreateAuditLogs3390000000000 implements MigrationInterface {
|
||||
name = "CreateAuditLogs3390000000000";
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
CREATE TABLE IF NOT EXISTS freight.audit_logs (
|
||||
id uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
title varchar(255) NOT NULL,
|
||||
method varchar(10) NOT NULL,
|
||||
url text NOT NULL,
|
||||
route_path varchar(255),
|
||||
type varchar(50) NOT NULL,
|
||||
is_success boolean NOT NULL,
|
||||
user_id uuid,
|
||||
resource_id varchar(64),
|
||||
request jsonb,
|
||||
status_code smallint,
|
||||
error_message text,
|
||||
user_name varchar(150),
|
||||
user_role varchar(100),
|
||||
ip_address inet,
|
||||
user_agent text,
|
||||
request_id varchar(64),
|
||||
duration_ms integer,
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
CONSTRAINT "PK_audit_logs" PRIMARY KEY (id)
|
||||
)
|
||||
`);
|
||||
|
||||
// Every audit query is time-bounded, so created_at leads each index.
|
||||
// DESC matches the "newest first" read path the controller exposes.
|
||||
await queryRunner.query(`
|
||||
CREATE INDEX IF NOT EXISTS "IDX_audit_logs_created_at"
|
||||
ON freight.audit_logs (created_at DESC)
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
CREATE INDEX IF NOT EXISTS "IDX_audit_logs_user_id_created_at"
|
||||
ON freight.audit_logs (user_id, created_at DESC)
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
CREATE INDEX IF NOT EXISTS "IDX_audit_logs_type_created_at"
|
||||
ON freight.audit_logs (type, created_at DESC)
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
CREATE INDEX IF NOT EXISTS "IDX_audit_logs_type_resource_id"
|
||||
ON freight.audit_logs (type, resource_id)
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
CREATE INDEX IF NOT EXISTS "IDX_audit_logs_route_path_created_at"
|
||||
ON freight.audit_logs (route_path, created_at DESC)
|
||||
`);
|
||||
// Failures are a small slice of the table but carry the security signal
|
||||
// (403s especially), so they get their own partial index.
|
||||
await queryRunner.query(`
|
||||
CREATE INDEX IF NOT EXISTS "IDX_audit_logs_failures"
|
||||
ON freight.audit_logs (created_at DESC)
|
||||
WHERE is_success = false
|
||||
`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`DROP TABLE IF EXISTS freight.audit_logs`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
/**
|
||||
* Wagons the requester specifically asked for. A transfer request stays
|
||||
* count-driven (`quantity` is what must be delivered), but a requester who
|
||||
* picked wagons off the yard desk now records WHICH ones — OCC sees the numbers
|
||||
* on the queue and the fulfil picker pre-selects them.
|
||||
*
|
||||
* Stored as a uuid[] column rather than a join table: the list is read and
|
||||
* written whole, never queried by wagon, and a preference carries no lifecycle
|
||||
* of its own (no FK — a purged wagon simply drops out of the display).
|
||||
*/
|
||||
export class TransferRequestPreferredWagons3400000000000
|
||||
implements MigrationInterface
|
||||
{
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE freight.wagon_transfer_requests
|
||||
ADD COLUMN IF NOT EXISTS preferred_wagon_ids uuid[]
|
||||
`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE freight.wagon_transfer_requests
|
||||
DROP COLUMN IF EXISTS preferred_wagon_ids
|
||||
`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
/**
|
||||
* Voyage number for one departure.
|
||||
*
|
||||
* `train_number` already exists on a schedule (the run number), but operations
|
||||
* also quote a VOYAGE number — the sailing/run identifier yards and customs use
|
||||
* for a specific departure. It belongs on the schedule, not the built train: one
|
||||
* train serves many departures and each carries its own voyage.
|
||||
*
|
||||
* Nullable and un-indexed: it is display/reference data typed by staff, not a
|
||||
* lookup key, and older schedules simply have none.
|
||||
*/
|
||||
export class ScheduleVoyageNumber3410000000000 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE freight.train_schedules
|
||||
ADD COLUMN IF NOT EXISTS voyage_number varchar(20)
|
||||
`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE freight.train_schedules
|
||||
DROP COLUMN IF EXISTS voyage_number
|
||||
`);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user