mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 18:48:11 +00:00
177 lines
6.5 KiB
TypeScript
177 lines
6.5 KiB
TypeScript
import { BaseEntity } from '@edr/api-common';
|
|
import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm';
|
|
|
|
import { Booking } from '../../bookings/entities/booking.entity';
|
|
import { Cargo } from '../../cargoes/entities/cargoes.entity';
|
|
import { Container } from '../../container-management/entities/container.entity';
|
|
import { Warehouse } from './warehouse.entity';
|
|
import { WarehouseYard } from './warehouse-yard.entity';
|
|
import { WarehouseZone } from './warehouse-zone.entity';
|
|
|
|
// Lifecycle. Supersedes the Batch 1 set
|
|
// (ARRIVED_AT_WAREHOUSE / UNDER_INSPECTION / READY_FOR_LOADING) — migrated in place.
|
|
// After RECEIVED + inspection (PASSED), the flow branches by booking trade direction:
|
|
// EXPORT/DOMESTIC: STORED → RESERVED → READY_FOR_LOADING → LOADED → DISPATCHED
|
|
// IMPORT: READY_FOR_PICKUP → DELIVERED (release order + proof of delivery)
|
|
export const WAREHOUSE_INVENTORY_STATUSES = [
|
|
'UNLOADED',
|
|
'UNLOADED_AT_DJIBOUTI_PORT',
|
|
'RECEIVED',
|
|
'STORED',
|
|
'RESERVED',
|
|
'READY_FOR_LOADING',
|
|
'LOADED',
|
|
'DISPATCHED',
|
|
'READY_FOR_PICKUP',
|
|
'DELIVERED',
|
|
] as const;
|
|
export type WarehouseInventoryStatus = (typeof WAREHOUSE_INVENTORY_STATUSES)[number];
|
|
|
|
/** Allowed forward transitions for the inventory lifecycle. */
|
|
export const WAREHOUSE_INVENTORY_TRANSITIONS: Record<WarehouseInventoryStatus, WarehouseInventoryStatus[]> = {
|
|
// UNLOADED = train-arrival landing state (Batch 8). Not yet stored/inspected.
|
|
// Mirrors RECEIVED so the import flow can store or go straight to pickup after inspection.
|
|
UNLOADED: ['STORED', 'READY_FOR_PICKUP'],
|
|
UNLOADED_AT_DJIBOUTI_PORT: [],
|
|
RECEIVED: ['STORED', 'READY_FOR_PICKUP'],
|
|
// Reserve is retired from the operator flow — a stored export item advances
|
|
// straight to loading prep. RESERVED kept for any in-flight/legacy items.
|
|
STORED: ['RESERVED', 'READY_FOR_LOADING'],
|
|
RESERVED: ['READY_FOR_LOADING'],
|
|
READY_FOR_LOADING: ['LOADED'],
|
|
LOADED: ['DISPATCHED'],
|
|
DISPATCHED: ['UNLOADED_AT_DJIBOUTI_PORT'],
|
|
// Batch 10: an inspected import item can leave by customer pickup (DELIVERED) or be dispatched
|
|
// out by EDR (DISPATCHED) — kept separate — or be put into storage (STORED) if no one collects
|
|
// it / customs or inspection hold / operator chooses to store.
|
|
READY_FOR_PICKUP: ['DELIVERED', 'STORED', 'DISPATCHED'],
|
|
DELIVERED: [],
|
|
};
|
|
|
|
@Entity({ schema: 'freight', name: 'warehouse_inventory' })
|
|
@Index(['warehouseId'])
|
|
@Index(['yardId'])
|
|
@Index(['zoneId'])
|
|
@Index(['bookingId'])
|
|
@Index(['cargoId'])
|
|
@Index(['containerId'])
|
|
@Index(['goodsId'])
|
|
@Index(['status'])
|
|
export class WarehouseInventory extends BaseEntity {
|
|
@Column({ name: 'warehouse_id', type: 'uuid' })
|
|
warehouseId!: string;
|
|
|
|
@ManyToOne(() => Warehouse)
|
|
@JoinColumn({ name: 'warehouse_id' })
|
|
warehouse?: Warehouse;
|
|
|
|
@Column({ name: 'yard_id', type: 'uuid' })
|
|
yardId!: string;
|
|
|
|
@ManyToOne(() => WarehouseYard)
|
|
@JoinColumn({ name: 'yard_id' })
|
|
yard?: WarehouseYard;
|
|
|
|
@Column({ name: 'zone_id', type: 'uuid' })
|
|
zoneId!: string;
|
|
|
|
@ManyToOne(() => WarehouseZone)
|
|
@JoinColumn({ name: 'zone_id' })
|
|
zone?: WarehouseZone;
|
|
|
|
@Column({ name: 'booking_id', type: 'uuid', nullable: true })
|
|
bookingId?: string | null;
|
|
|
|
@ManyToOne(() => Booking, { nullable: true })
|
|
@JoinColumn({ name: 'booking_id' })
|
|
booking?: Booking | null;
|
|
|
|
@Column({ name: 'cargo_id', type: 'uuid', nullable: true })
|
|
cargoId?: string | null;
|
|
|
|
@ManyToOne(() => Cargo, { nullable: true })
|
|
@JoinColumn({ name: 'cargo_id' })
|
|
cargo?: Cargo | null;
|
|
|
|
@Column({ name: 'container_id', type: 'uuid', nullable: true })
|
|
containerId?: string | null;
|
|
|
|
@ManyToOne(() => Container, { nullable: true })
|
|
@JoinColumn({ name: 'container_id' })
|
|
container?: Container | null;
|
|
|
|
@Column({ name: 'goods_id', type: 'uuid', nullable: true })
|
|
goodsId?: string | null;
|
|
|
|
@Column({ name: 'quantity', type: 'numeric', precision: 12, scale: 3, default: 0 })
|
|
quantity!: number;
|
|
|
|
@Column({ name: 'weight', type: 'numeric', precision: 14, scale: 3, default: 0 })
|
|
weight!: number;
|
|
|
|
@Column({ name: 'volume', type: 'numeric', precision: 12, scale: 3, nullable: true })
|
|
volume?: number | null;
|
|
|
|
@Column({ name: 'grn_number', type: 'varchar', length: 100, nullable: true })
|
|
grnNumber?: string | null;
|
|
|
|
@Column({ name: 'status', type: 'varchar', length: 32, default: 'RECEIVED' })
|
|
status!: WarehouseInventoryStatus;
|
|
|
|
// Batch 4.5: latest inspection outcome (PASSED | FAILED | NEEDS_REVIEW). Null = not yet inspected.
|
|
@Column({ name: 'inspection_status', type: 'varchar', length: 20, nullable: true })
|
|
inspectionStatus?: string | null;
|
|
|
|
@Column({ name: 'arrived_at', type: 'timestamptz', nullable: true })
|
|
arrivedAt?: Date | null;
|
|
|
|
// Batch 8: when the goods were unloaded off the arrived train (before storage/inspection).
|
|
@Column({ name: 'unloaded_at', type: 'timestamptz', nullable: true })
|
|
unloadedAt?: Date | null;
|
|
|
|
@Column({ name: 'stored_at', type: 'timestamptz', nullable: true })
|
|
storedAt?: Date | null;
|
|
|
|
@Column({ name: 'reserved_at', type: 'timestamptz', nullable: true })
|
|
reservedAt?: Date | null;
|
|
|
|
@Column({ name: 'inspected_at', type: 'timestamptz', nullable: true })
|
|
inspectedAt?: Date | null;
|
|
|
|
@Column({ name: 'ready_for_loading_at', type: 'timestamptz', nullable: true })
|
|
readyForLoadingAt?: Date | null;
|
|
|
|
@Column({ name: 'loaded_at', type: 'timestamptz', nullable: true })
|
|
loadedAt?: Date | null;
|
|
|
|
@Column({ name: 'dispatched_at', type: 'timestamptz', nullable: true })
|
|
dispatchedAt?: Date | null;
|
|
|
|
// Batch 5 — demurrage / storage lifecycle timestamps.
|
|
@Column({ name: 'inspection_started_at', type: 'timestamptz', nullable: true })
|
|
inspectionStartedAt?: Date | null;
|
|
|
|
@Column({ name: 'inspection_completed_at', type: 'timestamptz', nullable: true })
|
|
inspectionCompletedAt?: Date | null;
|
|
|
|
@Column({ name: 'ready_for_pickup_at', type: 'timestamptz', nullable: true })
|
|
readyForPickupAt?: Date | null;
|
|
|
|
@Column({ name: 'release_date', type: 'timestamptz', nullable: true })
|
|
releaseDate?: Date | null;
|
|
|
|
// Import branch: reference of the DO / release order sent to the customer.
|
|
@Column({ name: 'release_order_reference', type: 'varchar', length: 100, nullable: true })
|
|
releaseOrderReference?: string | null;
|
|
|
|
// Import branch: when the goods were handed over to the customer (proof of delivery).
|
|
@Column({ name: 'delivered_at', type: 'timestamptz', nullable: true })
|
|
deliveredAt?: Date | null;
|
|
|
|
@Column({ name: 'gate_cleared_at', type: 'timestamptz', nullable: true })
|
|
gateClearedAt?: Date | null;
|
|
|
|
@Column({ name: 'notes', type: 'text', nullable: true })
|
|
notes?: string | null;
|
|
}
|