Merge branch 'dev' of github.com:Tria-plc/edr-platform into origin/freight_feature/transit

This commit is contained in:
marshal
2026-09-02 22:38:15 +00:00
425 changed files with 30919 additions and 3238 deletions

View File

@@ -76,9 +76,14 @@ export interface ETradeBusinessOption {
export interface CompanyRegistrationData {
/**
* The registered organization name — `ETradeCompanyInfo.BusinessName`, falling
* back to the licence's `TradeName`. Never the manager/owner's personal name;
* that is {@link managerName}.
* The selected licence's trade name — `ETradeBusinessInfo.TradeName`, falling
* back to the registered organization name (`ETradeCompanyInfo.BusinessName`)
* when eTrade leaves the licence's trade name blank. Never the manager/owner's
* personal name; that is {@link managerName}.
*
* NOT the legal entity name: a TIN often trades under a different name, and
* some hold several licences with different trade names. Anything that needs
* the registered name (tax/EIMS) must read `BusinessName` directly.
*/
companyName: string;
licenceNumber: string;

View File

@@ -439,6 +439,123 @@ export interface IWagonMovement extends BaseEntity {
note?: string | null;
}
/** Which slice of a wagon's life an event belongs to — the history filter axis. */
export enum WagonEventCategory {
Lifecycle = "LIFECYCLE",
Yard = "YARD",
Train = "TRAIN",
Schedule = "SCHEDULE",
Status = "STATUS",
Cargo = "CARGO",
}
/**
* Every recorded transition in a wagon's history (`freight.wagon_events`).
* One row per wagon per transition, append-only, written inside the same
* transaction as the change itself.
*/
export enum WagonEventType {
// Lifecycle
Registered = "REGISTERED",
DetailsUpdated = "DETAILS_UPDATED",
Deleted = "DELETED",
Purged = "PURGED",
// Yard (where the wagon physically is)
MovedManually = "MOVED_MANUALLY",
MovedWithTrain = "MOVED_WITH_TRAIN",
PassedCheckpoint = "PASSED_CHECKPOINT",
CutAtYard = "CUT_AT_YARD",
SettledOnArrival = "SETTLED_ON_ARRIVAL",
ReleasedAtUnload = "RELEASED_AT_UNLOAD",
ReturnedOnCancel = "RETURNED_ON_CANCEL",
// Built train / consist
CoupledToTrain = "COUPLED_TO_TRAIN",
UncoupledFromTrain = "UNCOUPLED_FROM_TRAIN",
SequenceChanged = "SEQUENCE_CHANGED",
TrainMerged = "TRAIN_MERGED",
TrainDisbanded = "TRAIN_DISBANDED",
// Schedule slot
PinnedToSchedule = "PINNED_TO_SCHEDULE",
UnpinnedFromSchedule = "UNPINNED_FROM_SCHEDULE",
Dispatched = "DISPATCHED",
ReleasedFromSchedule = "RELEASED_FROM_SCHEDULE",
// Status
StatusChanged = "STATUS_CHANGED",
// Cargo
CargoLoaded = "CARGO_LOADED",
CargoUnloaded = "CARGO_UNLOADED",
BookingUnassigned = "BOOKING_UNASSIGNED",
BookingCancelled = "BOOKING_CANCELLED",
LoadMovedIn = "LOAD_MOVED_IN",
LoadMovedOut = "LOAD_MOVED_OUT",
ContainerPlaced = "CONTAINER_PLACED",
ContainerRemoved = "CONTAINER_REMOVED",
}
export const WAGON_EVENT_CATEGORY: Record<WagonEventType, WagonEventCategory> = {
[WagonEventType.Registered]: WagonEventCategory.Lifecycle,
[WagonEventType.DetailsUpdated]: WagonEventCategory.Lifecycle,
[WagonEventType.Deleted]: WagonEventCategory.Lifecycle,
[WagonEventType.Purged]: WagonEventCategory.Lifecycle,
[WagonEventType.MovedManually]: WagonEventCategory.Yard,
[WagonEventType.MovedWithTrain]: WagonEventCategory.Yard,
[WagonEventType.PassedCheckpoint]: WagonEventCategory.Yard,
[WagonEventType.CutAtYard]: WagonEventCategory.Yard,
[WagonEventType.SettledOnArrival]: WagonEventCategory.Yard,
[WagonEventType.ReleasedAtUnload]: WagonEventCategory.Yard,
[WagonEventType.ReturnedOnCancel]: WagonEventCategory.Yard,
[WagonEventType.CoupledToTrain]: WagonEventCategory.Train,
[WagonEventType.UncoupledFromTrain]: WagonEventCategory.Train,
[WagonEventType.SequenceChanged]: WagonEventCategory.Train,
[WagonEventType.TrainMerged]: WagonEventCategory.Train,
[WagonEventType.TrainDisbanded]: WagonEventCategory.Train,
[WagonEventType.PinnedToSchedule]: WagonEventCategory.Schedule,
[WagonEventType.UnpinnedFromSchedule]: WagonEventCategory.Schedule,
[WagonEventType.Dispatched]: WagonEventCategory.Schedule,
[WagonEventType.ReleasedFromSchedule]: WagonEventCategory.Schedule,
[WagonEventType.StatusChanged]: WagonEventCategory.Status,
[WagonEventType.CargoLoaded]: WagonEventCategory.Cargo,
[WagonEventType.CargoUnloaded]: WagonEventCategory.Cargo,
[WagonEventType.BookingUnassigned]: WagonEventCategory.Cargo,
[WagonEventType.BookingCancelled]: WagonEventCategory.Cargo,
[WagonEventType.LoadMovedIn]: WagonEventCategory.Cargo,
[WagonEventType.LoadMovedOut]: WagonEventCategory.Cargo,
[WagonEventType.ContainerPlaced]: WagonEventCategory.Cargo,
[WagonEventType.ContainerRemoved]: WagonEventCategory.Cargo,
};
/** One row of a wagon's history as served by `GET /wagons/:id/history` (labels resolved). */
export interface WagonHistoryEvent {
id: string;
wagonId: string;
wagonNumber: string | null;
type: WagonEventType;
category: WagonEventCategory;
occurredAt: string;
actorUserId: string | null;
actorName: string | null;
fromYardId: string | null;
fromYardLabel: string | null;
toYardId: string | null;
toYardLabel: string | null;
trainId: string | null;
trainCode: string | null;
trainScheduleId: string | null;
scheduleLabel: string | null;
bookingId: string | null;
bookingReference: string | null;
fromValue: string | null;
toValue: string | null;
reason: string | null;
metadata: Record<string, unknown> | null;
}
/** Keyset page of a wagon's history, newest first. `nextCursor` is null on the last page. */
export interface WagonHistoryPage {
items: WagonHistoryEvent[];
nextCursor: string | null;
}
/**
* Lifecycle of a two-person wagon-transfer request. A requester asks for N
* wagons of a type to move from one yard to another (count only, no specific
@@ -648,6 +765,25 @@ export interface ICustomerTruckContainer {
containerNumber: string;
}
/**
* The truck-type vocabulary a customer picks from when self-hauling. The API
* validates against this exact list (`@IsIn`), the portal's dropdown renders it,
* and the bulk-upload template documents it — all three read this constant so a
* value the customer can type can never be one the API rejects.
*/
export const CUSTOMER_TRUCK_TYPES = [
"Flatbed",
"Container Chassis",
"Lowboy",
"Box Truck",
"Tipper",
] as const;
export type CustomerTruckType = (typeof CUSTOMER_TRUCK_TYPES)[number];
/** ISO 6346 container number: four letters then seven digits, e.g. ABCD1234567. */
export const ISO_CONTAINER_NUMBER = /^[A-Z]{4}\d{7}$/;
/** A customer self-haul truck on a booking, carrying 12 containers. */
export interface ICustomerTruck {
id: string;
@@ -659,6 +795,10 @@ export interface ICustomerTruck {
arrivedAt?: string | null;
departedAt?: string | null;
containers?: ICustomerTruckContainer[];
/** Bulk: planned tonnage this truck hauls. `numeric` — serialises as a string. */
plannedTons?: number | string | null;
/** Bulk PER_ITEM: planned item/piece count on this truck. */
plannedQuantity?: number | null;
}
/** Payload to add a customer self-haul truck (12 container numbers). */
@@ -666,7 +806,12 @@ export interface AddCustomerTruckPayload {
truckPlateNumber: string;
driverName: string;
truckType: string;
containerNumbers: string[];
/** Container bookings only — bulk trucks haul loose tonnage instead. */
containerNumbers?: string[];
/** Bulk: planned tonnage, drawn down against the booking's declared VGM. */
plannedTons?: number;
/** Bulk PER_ITEM: planned item/piece count. */
plannedQuantity?: number;
}
export interface IBooking extends BaseEntity {

View File

@@ -3,6 +3,7 @@ import type { BaseEntity } from "../common";
export * from "./support-chat";
export * from "./blocked-seat-revenue-loss";
export enum TicketStatus {
Reserved = "RESERVED",
Confirmed = "CONFIRMED",
@@ -38,9 +39,9 @@ export enum ScheduleStatus {
/**
* Why a /search leg (outbound or inbound) came back with zero bookable schedules.
* Priority order applied by the API when classifying: NoRoute > NoScheduleOnDate >
* Cancelled > PackageOnly > CheckinClosed > FullyBooked (see search.service.ts
* classifyEmptySearch). The frontend uses this to show a specific empty-state
* message instead of a generic "no trains available".
* Cancelled > PackageOnly > GroupBookingOnly > CheckinClosed > FullyBooked (see
* search.service.ts classifyEmptySearch). The frontend uses this to show a specific
* empty-state message instead of a generic "no trains available".
*/
export enum SearchEmptyReasonCode {
/** No route (in either direction) ever connects these two stations. */
@@ -51,6 +52,8 @@ export enum SearchEmptyReasonCode {
Cancelled = "CANCELLED",
/** Every schedule for this pair on this date is package-only (excluded from ticket search). */
PackageOnly = "PACKAGE_ONLY",
/** Every schedule for this pair on this date is reserved for staff group bookings (excluded from normal ticket search). */
GroupBookingOnly = "GROUP_BOOKING_ONLY",
/** A bookable schedule exists, but its check-in cutoff has already passed for every option. */
CheckinClosed = "CHECKIN_CLOSED",
/** A bookable, still-open schedule exists but has no seats left for the requested party. */