Files
edr-platform/apps/edr-freight-api/src/modules/warehouses/entities/warehouse-allocation-rule.entity.ts
Hagernesh 01aec12ee9 feat(warehouse): Batch 5 — config-driven allocation + storage/demurrage fee rules
- Allocation rules engine: cargo/container/trade criteria -> deterministic yard/warehouse/zone by code; wired into auto-unload (fallback to default)
- Storage/demurrage fee rules: configurable freeDays + ratePerDay; most-specific match; fee preview per inventory item
- Inventory demurrage timestamps: inspectionStartedAt, inspectionCompletedAt, readyForPickupAt, releaseDate, gateClearedAt
- Migration 1790000000000 (allocation_rules + fee_rules tables + inventory date columns)
- Frontend: Allocation & Fees config page, automatic Fee Preview modal, plumbing/hooks
- No invoice/payment (Batch 6)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 23:53:04 +00:00

55 lines
2.4 KiB
TypeScript

import { BaseEntity } from '@edr/api-common';
import { Column, Entity, Index } from 'typeorm';
/**
* Batch 5 — deterministic warehouse/yard allocation.
* A booking's (freightType, tradeDirection, cargoType, containerStatus, inspection)
* is matched against active rules in ascending `priority`; the first match wins and
* resolves the target Yard (and optional Warehouse/Zone) by code.
*/
@Entity({ schema: 'freight', name: 'warehouse_allocation_rules' })
@Index(['priority'])
@Index(['isActive'])
export class WarehouseAllocationRule extends BaseEntity {
@Column({ name: 'name', type: 'varchar', length: 160 })
name!: string;
@Column({ name: 'priority', type: 'int', default: 100 })
priority!: number;
// ── Match criteria (null = wildcard) ──────────────────────────────────────
@Column({ name: 'freight_type', type: 'varchar', length: 16, nullable: true })
freightType?: string | null; // CONTAINER | BULK
@Column({ name: 'trade_direction', type: 'varchar', length: 16, nullable: true })
tradeDirection?: string | null; // IMPORT | EXPORT | DOMESTIC | BOTH
@Column({ name: 'cargo_type_code', type: 'varchar', length: 50, nullable: true })
cargoTypeCode?: string | null;
@Column({ name: 'container_status', type: 'varchar', length: 24, nullable: true })
containerStatus?: string | null; // e.g. EMPTY | MAINTENANCE
@Column({ name: 'requires_inspection', type: 'boolean', nullable: true })
requiresInspection?: boolean | null;
// ── Resolved target (by code) ─────────────────────────────────────────────
@Column({ name: 'target_facility_code', type: 'varchar', length: 40, nullable: true })
targetFacilityCode?: string | null;
@Column({ name: 'target_yard_code', type: 'varchar', length: 40 })
targetYardCode!: string;
@Column({ name: 'target_warehouse_code', type: 'varchar', length: 40, nullable: true })
targetWarehouseCode?: string | null;
@Column({ name: 'target_zone_code', type: 'varchar', length: 40, nullable: true })
targetZoneCode?: string | null;
@Column({ name: 'storage_type', type: 'varchar', length: 80, nullable: true })
storageType?: string | null; // descriptive: "Container terminal import / stack area"
@Column({ name: 'is_active', type: 'boolean', default: true })
isActive!: boolean;
}