Files
edr-platform/apps/edr-freight-api/src/modules/warehouses/entities/warehouse-inventory.entity.ts

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;
}