mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-09 07:08:18 +00:00
75 lines
2.9 KiB
TypeScript
75 lines
2.9 KiB
TypeScript
import { WagonEventCategory, WagonEventType } from '@edr/types';
|
|
import { Column, CreateDateColumn, Entity, Index, PrimaryGeneratedColumn } from 'typeorm';
|
|
|
|
/**
|
|
* Append-only history of everything that happens to a wagon — one row per
|
|
* wagon per transition, written inside the same transaction as the change.
|
|
* Plain id columns, no foreign keys and no soft delete on purpose: the history
|
|
* must outlive the wagon, train, schedule or booking it refers to, exactly like
|
|
* `audit_logs` and `schedule_wagon_adjustment_logs`. Rows are never updated.
|
|
*
|
|
* Read path: `(wagon_id, occurred_at DESC, id DESC)` keyset pagination — one
|
|
* index range scan per page regardless of how long the wagon has been in
|
|
* service. Labels (yard, train, schedule, booking, actor) are joined at read
|
|
* time on primary keys, so the write path stays a single INSERT.
|
|
*/
|
|
@Entity({ schema: 'freight', name: 'wagon_events' })
|
|
@Index('idx_wagon_events_wagon_time', ['wagonId', 'occurredAt', 'id'])
|
|
@Index('idx_wagon_events_wagon_cat_time', ['wagonId', 'category', 'occurredAt', 'id'])
|
|
export class WagonEvent {
|
|
@PrimaryGeneratedColumn('uuid')
|
|
id!: string;
|
|
|
|
@Column({ name: 'wagon_id', type: 'uuid' })
|
|
wagonId!: string;
|
|
|
|
/** Snapshot so the row still reads after the wagon is purged or renumbered. */
|
|
@Column({ name: 'wagon_number', type: 'varchar', nullable: true })
|
|
wagonNumber?: string | null;
|
|
|
|
@Column({ name: 'event_type', type: 'varchar', length: 40 })
|
|
type!: WagonEventType;
|
|
|
|
/** Derived from `type` at write time; stored so the category filter hits the index. */
|
|
@Column({ name: 'category', type: 'varchar', length: 20 })
|
|
category!: WagonEventCategory;
|
|
|
|
@Column({ name: 'occurred_at', type: 'timestamptz' })
|
|
occurredAt!: Date;
|
|
|
|
@Column({ name: 'actor_user_id', type: 'uuid', nullable: true })
|
|
actorUserId?: string | null;
|
|
|
|
@Column({ name: 'from_yard_id', type: 'uuid', nullable: true })
|
|
fromYardId?: string | null;
|
|
|
|
@Column({ name: 'to_yard_id', type: 'uuid', nullable: true })
|
|
toYardId?: string | null;
|
|
|
|
@Column({ name: 'train_id', type: 'uuid', nullable: true })
|
|
trainId?: string | null;
|
|
|
|
@Column({ name: 'train_schedule_id', type: 'uuid', nullable: true })
|
|
trainScheduleId?: string | null;
|
|
|
|
@Column({ name: 'booking_id', type: 'uuid', nullable: true })
|
|
bookingId?: string | null;
|
|
|
|
/** Previous value of whatever the event changed (status, sequence, train code…). */
|
|
@Column({ name: 'from_value', type: 'varchar', length: 120, nullable: true })
|
|
fromValue?: string | null;
|
|
|
|
@Column({ name: 'to_value', type: 'varchar', length: 120, nullable: true })
|
|
toValue?: string | null;
|
|
|
|
/** Staff-entered reason / note, when the action carried one. */
|
|
@Column({ name: 'reason', type: 'text', nullable: true })
|
|
reason?: string | null;
|
|
|
|
@Column({ name: 'metadata', type: 'jsonb', nullable: true })
|
|
metadata?: Record<string, unknown> | null;
|
|
|
|
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
|
|
createdAt!: Date;
|
|
}
|