Files
edr-platform/apps/edr-freight-api/src/modules/contracts/clearance-milestone.catalog.ts
2026-07-02 18:15:16 +00:00

122 lines
6.7 KiB
TypeScript

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<string, Omit<MilestoneDef, 'code'>> = {
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<string, Omit<MilestoneDef, 'code'>> = {
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<string, Omit<MilestoneDef, 'code'>>,
): 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'];