import { IMPORT_MILESTONES, EXPORT_MILESTONES } from '@edr/types'; import type { MilestoneOwnerRegion } from './entities/clearance-milestone.entity'; /** * Static catalog of clearance milestones per trade direction (doc §11.3, §12.2). * Drives the rows seeded onto a contract clearance cycle (pre-booking) and * booking (post-booking). `phaseBoundaryAfter` marks the last pre-booking * milestone — everything after it tracks on the booking. */ export interface MilestoneDef { code: string; label: string; ownerRegion: MilestoneOwnerRegion; triggeredByDoc: boolean; } const IMPORT_DEFS: Record> = { IMPORT_DOCS_UPLOADED: { label: 'Import Documents Uploaded', ownerRegion: 'CUST', triggeredByDoc: false }, PENDING_DOCUMENT_REVIEW: { label: 'Pending Document Review', ownerRegion: 'ET', triggeredByDoc: true }, DOCUMENTS_APPROVED: { label: 'Documents Approved', ownerRegion: 'ET', triggeredByDoc: false }, UNDER_CUSTOMS_CLEARANCE: { label: 'Under Customs Clearance', ownerRegion: 'ET', triggeredByDoc: false }, DECLARED: { label: 'Declared', ownerRegion: 'ET', triggeredByDoc: true }, DUTY_TAXES_ADVISED: { label: 'Duty and Taxes Advised', ownerRegion: 'ET', triggeredByDoc: false }, DUTY_TAX_PAID: { label: 'Duty and Tax Paid', ownerRegion: 'CUST', triggeredByDoc: true }, TRANSIT_PERMIT_UPLOADED: { label: 'Transit Permit Uploaded', ownerRegion: 'ET', triggeredByDoc: true, }, DO_COLLECTED: { label: 'DO Collected', ownerRegion: 'DJ', triggeredByDoc: true }, WAGON_REQUESTED: { label: 'Wagon Allocation Requested', ownerRegion: 'ET', triggeredByDoc: false }, FREIGHT_PAYMENT_SETTLED: { label: 'Payment Settled (freight)', ownerRegion: 'CUST', triggeredByDoc: true }, WAGON_ALLOCATED: { label: 'Wagon Allocated', ownerRegion: 'OPS', triggeredByDoc: false }, GATEPASS_GRANTED: { label: 'Gatepass Granted', ownerRegion: 'DJ', triggeredByDoc: false }, READY_FOR_LOADING: { label: 'Ready for Loading', ownerRegion: 'DJ', triggeredByDoc: false }, LOADED: { label: 'Loaded', ownerRegion: 'DJ', triggeredByDoc: false }, DEPARTED_FROM_DJIBOUTI: { label: 'Departed from Djibouti', ownerRegion: 'DJ', triggeredByDoc: false }, ARRIVED_ETHIOPIA: { label: 'Arrived at Port in Ethiopia', ownerRegion: 'OPS', triggeredByDoc: false }, OFFLOADED: { label: 'Offloaded', ownerRegion: 'OPS', triggeredByDoc: false }, T1_CLOSED: { label: 'T1 Closed', ownerRegion: 'ET', triggeredByDoc: false }, RISK_ASSIGNED: { label: 'Risk Assigned', ownerRegion: 'ET', triggeredByDoc: false }, SECOND_DUTY_ADVISED: { label: 'Additional Duty and Taxes Advised', ownerRegion: 'ET', triggeredByDoc: false, }, SECOND_DUTY_PAID: { label: 'Additional Duty and Tax Paid', ownerRegion: 'CUST', triggeredByDoc: true, }, IMPORT_RELEASE_GRANTED: { label: 'Import Release Granted', ownerRegion: 'ET', triggeredByDoc: true }, IMPORT_PROCESS_COMPLETED: { label: 'Import Process Completed', ownerRegion: 'ET', triggeredByDoc: true }, STORAGE_INVOICE_RAISED: { label: 'Storage Invoice Raised', ownerRegion: 'OPS', triggeredByDoc: false }, EXIT_NOTE_GENERATED: { label: 'Exit Note Generated', ownerRegion: 'OPS', triggeredByDoc: true }, }; const EXPORT_DEFS: Record> = { EXPORT_DOCS_UPLOADED: { label: 'Export Documents Uploaded', ownerRegion: 'CUST', triggeredByDoc: false }, PENDING_DOCUMENT_REVIEW: { label: 'Pending Document Review', ownerRegion: 'ET', triggeredByDoc: true }, DOCUMENTS_APPROVED: { label: 'Documents Approved', ownerRegion: 'ET', triggeredByDoc: false }, RELEASE_ORDER_SECURED: { label: 'Release Order Secured', ownerRegion: 'DJ', triggeredByDoc: true }, UNDER_CUSTOMS_CLEARANCE: { label: 'Under Customs Clearance', ownerRegion: 'ET', triggeredByDoc: false }, DECLARED: { label: 'Declared', ownerRegion: 'ET', triggeredByDoc: true }, EXPORT_RELEASED: { label: 'Export Released', ownerRegion: 'ET', triggeredByDoc: false }, WAGON_REQUESTED: { label: 'Wagon Requested', ownerRegion: 'ET', triggeredByDoc: false }, FREIGHT_PAYMENT_PENDING: { label: 'Pending Payment', ownerRegion: 'CUST', triggeredByDoc: false }, FREIGHT_PAYMENT_SETTLED: { label: 'Payment Settled', ownerRegion: 'CUST', triggeredByDoc: true }, WAGON_ALLOCATED: { label: 'Wagon Allocated', ownerRegion: 'OPS', triggeredByDoc: false }, EXPORT_TRANSPORT_ISSUED: { label: 'Export Transport Document Issued', ownerRegion: 'ET', triggeredByDoc: true, }, CARGO_ARRIVED: { label: 'Cargo Arrived', ownerRegion: 'OPS', triggeredByDoc: false }, READY_FOR_LOADING: { label: 'Ready for Loading', ownerRegion: 'OPS', triggeredByDoc: false }, LOADED: { label: 'Loaded', ownerRegion: 'OPS', triggeredByDoc: false }, DEPARTED_TO_DJIBOUTI: { label: 'Departed to Djibouti', ownerRegion: 'OPS', triggeredByDoc: false }, ARRIVED_AT_DJIBOUTI: { label: 'Arrived at Djibouti', ownerRegion: 'DJ', triggeredByDoc: false }, GATEPASS_GRANTED: { label: 'Gatepass Granted', ownerRegion: 'DJ', triggeredByDoc: false }, T1_CLOSED: { label: 'T1 Closed', ownerRegion: 'DJ', triggeredByDoc: false }, OFFLOADED: { label: 'Offloaded', ownerRegion: 'DJ', triggeredByDoc: true }, }; /** * Codes BEFORE (and including) this one are pre-booking — they attach to the * contract clearance cycle. From the next code onward, milestones attach to the * booking GL creates. Per doc §11.3 the booking is created right after DO_COLLECTED * (import) / EXPORT_RELEASED (export), i.e. before WAGON_REQUESTED. */ const IMPORT_PRE_BOOKING_LAST = 'DO_COLLECTED'; const EXPORT_PRE_BOOKING_LAST = 'EXPORT_RELEASED'; function buildDefs( codes: readonly string[], defs: Record>, ): MilestoneDef[] { return codes.map((code) => ({ code, ...defs[code] })); } export function milestonesForDirection(tradeDirection: string): MilestoneDef[] { if (tradeDirection === 'IMPORT') return buildDefs(IMPORT_MILESTONES, IMPORT_DEFS); if (tradeDirection === 'EXPORT') return buildDefs(EXPORT_MILESTONES, EXPORT_DEFS); return []; } /** Split the milestone list into pre-booking (contract) and post-booking (booking). */ export function splitMilestones(tradeDirection: string): { preBooking: MilestoneDef[]; postBooking: MilestoneDef[]; } { const all = milestonesForDirection(tradeDirection); const boundary = tradeDirection === 'IMPORT' ? IMPORT_PRE_BOOKING_LAST : EXPORT_PRE_BOOKING_LAST; const idx = all.findIndex((m) => m.code === boundary); if (idx < 0) return { preBooking: all, postBooking: [] }; return { preBooking: all.slice(0, idx + 1), postBooking: all.slice(idx + 1) }; } /** The handoff milestone that flips primary ownership ET ↔ DJ (doc §11.5/§12.3). */ export const HANDOFF_MILESTONES = ['DEPARTED_FROM_DJIBOUTI', 'DEPARTED_TO_DJIBOUTI'];