# EDR Freight — System Flow (API) How the freight API drives a shipment end to end: **Onboarding → Contract → Booking → Clearance → Operation → Schedule → Delivery** This document tracks the **API logic only** — exact statuses, transitions, branches, and endpoints as the `edr-freight-api` implements them. Read arrows (`→`) as "then". **IF** blocks are the branches. > Two facts shape everything below: > 1. **Clearance is per-booking, not pre-booking** (except the one-time-customs special case). The contract agrees terms; each booking carries its own customs document loop. > 2. **Who creates the booking depends on customs.** No customs → the customer. Customs (Path B) → Global Logistics (GL) on the customer's behalf. --- ## 1) Onboarding > Register the company so a profile can transact. Only an **active profile** may create contracts or bookings. ``` Company → status PENDING Each operation profile (importer | exporter | freight_forwarder) → status PENDING Backoffice approves each profile → ACTIVE + reference (IM-00001 / EX-00001 / FF-00001) → only an ACTIVE profile can create contracts / bookings ``` Documents required depend **only on nationality**: | Nationality | Company documents | |-------------|-------------------| | Ethiopian | TIN Certificate · Commercial License · National ID | | Foreign | TIN Certificate · Investment License · National ID · Passport | Operation type adds one business-license card per profile — nothing else. --- ## 2) Contract > Agree terms (route, cargo, price), run the approval chain, sign. Service: `ContractTransitionService`. ### Setup choices that decide later paths ``` contractKind: ONE_TIME (one shipment per contract) GENERAL (many shipments over a validity window) tradeDirection: IMPORT | EXPORT | DOMESTIC customsClearingEnabled: true → Path B (GL clears + books) — IMPORT/EXPORT false → Path A (customer self-clears + books) — IMPORT/EXPORT (DOMESTIC → no clearance at all) ``` ### Status enum (`CONTRACT_STATUSES`) ``` DRAFT · SUBMITTED · PRICE_CHANGED_PENDING_CONFIRM · CHANGES_REQUESTED PENDING_APPROVAL · APPROVED · APPROVED_PENDING_SIGNATURE · CONTRACT_READY SIGNED_CUSTOMER · FULLY_EXECUTED · CONTRACT_ACTIVE AWAITING_CLEARANCE_DOCUMENTS · CLEARANCE_UNDER_REVIEW · CLEARANCE_READY_FOR_BOOKING ACTIVE_SHIPMENT_IN_PROGRESS · CONTRACT_CLOSED · EXPIRED REJECTED · CANCELLED RENEWAL_DRAFT · RENEWAL_SUBMITTED · RENEWAL_PENDING_APPROVAL · AMENDMENTS_PROPOSED · ARCHIVED ``` Clearance enum (`CONTRACT_CLEARANCE_STATUSES`): `NOT_APPLICABLE · AWAITING_DOCUMENTS · DOCUMENTS_UNDER_REVIEW · CLEARANCE_READY_FOR_BOOKING · SELF_CLEARED · ACTIVE_SHIPMENT_IN_PROGRESS` ### Transition table | Method | Guard (allowed status) | Result | |--------|------------------------|--------| | `submit()` | DRAFT, CHANGES_REQUESTED | freeze rates → `SUBMITTED` | | `confirmSubmit()` | PRICE_CHANGED_PENDING_CONFIRM | freeze rates → `SUBMITTED` | | `staffAccept(validityDays)` | SUBMITTED | set validity window, build approval chain → `PENDING_APPROVAL` | | `requestChanges()` | SUBMITTED | review note → `CHANGES_REQUESTED` | | `reject()` | SUBMITTED, PENDING_APPROVAL | → `REJECTED` | | `approveStep(stepId, role)` | PENDING_APPROVAL, APPROVED_PENDING_SIGNATURE | complete step in order; all done → `APPROVED` | | `generateContract()` | APPROVED, APPROVED_PENDING_SIGNATURE | render PDF → `CONTRACT_READY` | | `sign(CUSTOMER)` | CONTRACT_READY | apply signature → `SIGNED_CUSTOMER` → auto `counterSign()` | | `counterSign(STAFF/DIRECTOR/CEO)` | SIGNED_CUSTOMER | **branch on path** ↓ | | `renew()` | any | clone with `renewalOfId`, version++ → `RENEWAL_DRAFT` | **Approval chain** (`instantiateApprovalSteps`): `LINE_STAFF` → optional `DIRECTOR` → optional `CEO`. Director required when `freightType = BULK` OR `cargoType.requiresDirectorApproval`. ### The counter-sign branch — THIS is where the model differs from "clearance first" ``` IF GENERAL + customsClearingEnabled (Path B): NO contract-level clearance cycle. status → CONTRACT_ACTIVE, clearanceStatus → NOT_APPLICABLE → customer requests shipments; GL creates + clears each booking (per-booking) IF ONE_TIME + customs (Path A self-clear OR one-time-customs): open a contract clearance cycle status → AWAITING_CLEARANCE_DOCUMENTS, clearanceStatus → AWAITING_DOCUMENTS → contract-level clearance loop (section 3), then booking IF DOMESTIC (no customs): status → FULLY_EXECUTED (ONE_TIME) or CONTRACT_ACTIVE (GENERAL) → customer books immediately (section 4) ``` > So contract-level clearance (section 3) only runs for the **one-time + customs** case. The common GENERAL-customs case goes straight to `CONTRACT_ACTIVE` and defers all clearance to the booking (section 5). ### Side branches ``` staff requestChanges → CHANGES_REQUESTED → customer edits → submit → SUBMITTED staff reject → REJECTED GENERAL contract → renew → RENEWAL_DRAFT (clone of prior version) ``` ### Endpoints (`contracts.controller.ts`) ``` POST /contracts/:id/submit submit() POST /contracts/:id/confirm-submit confirmSubmit() POST /contracts/:id/staff/accept staffAccept() POST /contracts/:id/staff/request-changes requestChanges() POST /contracts/:id/staff/reject reject() POST /contracts/:id/approval-steps/:stepId/approve approveStep() POST /contracts/:id/contract/generate generateContract() GET /contracts/:id/contract/view view PDF/HTML POST /contracts/:id/contract/sign sign() POST /contracts/:id/renew renew() GET /contracts/:id/capacity remaining drawdown (GENERAL) POST /contracts/:id/bookings create booking under contract (section 4) GET /contracts/list-summary list GET /contracts/booking-requests/queue staff shipment-request queue ``` --- ## 3) Contract-level clearance (ONE_TIME + customs only) > Runs only when the counter-sign branch opened a contract clearance cycle. > Service: `ContractClearanceService`. Loop: **upload → review (approve/query) → re-upload → finalize.** ### Who reviews ``` Path A (customsClearingEnabled = false) → OPERATIONS Path B (customsClearingEnabled = true) → GLOBAL LOGISTICS Ethiopia (GL ET) ``` ### Document states & loop ``` each document: PENDING → APPROVED (reviewer approves) → QUERIED (reviewer demands re-upload, note required) → contract back to AWAITING_CLEARANCE_DOCUMENTS (only queried docs re-upload; approved stay) 1. customer uploads all required docs → CLEARANCE_UNDER_REVIEW, each doc PENDING 2. reviewer goes doc by doc (approve / query) 3. customer re-uploads queried docs → back to step 2 4. all required docs APPROVED → finalize ``` ### Required-doc resolution (`contract-clearance.util.ts`) ``` input (customer uploads): Path B: contract_clearance_{import|export}_{container|bulk} Path A: contract_clearance_selfclear_{import|export}_{container|bulk} DOMESTIC: null (no gate) output (GL uploads, container customs only): contract_clearance_output_{import|export}_container (bulk or non-customs → null) ``` ### Finalize ``` opsFinalize() Path A — requires every required input doc APPROVED → clearanceStatus = SELF_CLEARED → contract status = CONTRACT_ACTIVE (GENERAL) | FULLY_EXECUTED (ONE_TIME) → CUSTOMER creates booking finalize() Path B — requires every required input APPROVED + every required output uploaded → clearanceStatus = CLEARANCE_READY_FOR_BOOKING → GL creates booking ``` ### Endpoints ``` GET /contracts/:id/clearance document grid POST /contracts/:id/clearance/documents customer upload (multipart) POST /contracts/:id/clearance/review GL approve | query POST /contracts/:id/clearance/output-documents GL upload output docs POST /contracts/:id/clearance/finalize GL finalize → CLEARANCE_READY_FOR_BOOKING POST /contracts/:id/clearance/ops-review Ops approve | query POST /contracts/:id/clearance/ops-finalize Ops finalize → SELF_CLEARED GET /contracts/clearance/queue GL ET queue (customs contracts) GET /contracts/clearance/ops-queue Operations queue (self-clear) GET /contracts/clearance/history GL completed GET /contracts/clearance/ops-history Ops completed ``` --- ## 4) Booking — who creates it & the gate > Turn a ready contract into a shipment. Service: `ContractBookingService` (creation gate), `BookingTransitionService` (lifecycle). > Endpoint: `POST /contracts/:id/bookings`. ### The gate (`assertGate`) ``` Customs (Path B) — only GL ET (needs contracts.createBooking permission): ONE_TIME : contract.clearanceStatus = CLEARANCE_READY_FOR_BOOKING GENERAL : contract.status = CONTRACT_ACTIVE booking starts in AWAITING_DOCUMENTS (per-booking clearance), createdByRole = GL_ET No customs (Path A / DOMESTIC) — customer or staff: contract.status = FULLY_EXECUTED or CONTRACT_ACTIVE booking starts in OPERATION_REQUEST_PENDING (no clearance gate), createdByRole = CUSTOMER | STAFF ``` So per-booking clearance applies to **every customs booking** — both the GENERAL drawdown and the one-time case (whose contract cycle already ran). Path A / domestic bookings skip straight to the operation request. ### Booking status enum (`BOOKING_STATUSES`) ``` DRAFT · SUBMITTED · PRICE_CHANGED_PENDING_CONFIRM · CHANGES_REQUESTED PENDING_APPROVAL · APPROVED_PENDING_SIGNATURE · APPROVED · CONTRACT_READY SIGNED_CUSTOMER · FULLY_EXECUTED AWAITING_DOCUMENTS · DOCUMENTS_UNDER_REVIEW · CLEARANCE_READY OPERATION_REQUEST_PENDING · OPERATION_CHANGES_REQUESTED SELECTED_FOR_BATCH · ROAD_DISPATCH_PENDING · PAID · IN_TRANSIT · COMPLETED REJECTED · CANCELLED · EXPIRED · PENDING_CONSOLIDATION · CONSOLIDATED ``` Payment (`paymentStatus`): `PENDING → PNR_GENERATED → VERIFICATION_IN_PROGRESS → PAID` (or `FAILED`). Scheduling (`schedulingStatus`): `NOT_SCHEDULED · HOLDING · ELIGIBLE · SCHEDULED · DISPATCHED`. ### Customer-self-booking lifecycle (Path A / domestic — same approval shape as a contract) ``` DRAFT → generate-price → submit price unchanged → SUBMITTED price changed → PRICE_CHANGED_PENDING_CONFIRM → confirm-submit → SUBMITTED → acceptIntake (staff) → PENDING_APPROVAL (validity window + approval steps) → approveStep ×N (LINE_STAFF → DIRECTOR → CEO) → APPROVED (auto-generates contract) → CONTRACT_READY → customerSign → SIGNED_CUSTOMER → marketingApprove → FULLY_EXECUTED (sets fullyExecutedAt, lockedAt) → (then operation request, section 5) ``` Side branches: `requestChanges → CHANGES_REQUESTED`; `staffReject / reject → REJECTED`; `cancel → CANCELLED` (DRAFT…CONTRACT_READY). ### Transition methods (`BookingTransitionService`) ``` submit · confirmSubmit · requestChanges · acceptIntake · staffReject · reject approveStep · rejectStep · generateContract · customerSign · marketingApprove governmentExpedite (govt fast-path → PAID) requestOperation · proceedToOperation · reviewOperationRequest submitClearanceDocuments · reviewDocument · uploadClearanceOutputDocuments · finalizeClearance startTransit · complete · cancel · requestConsolidation · removeConsolidation ``` --- ## 5) Per-booking clearance + operation request > The customs booking's own document loop, then everyone funnels into the operation request that puts the shipment in the schedule pool. ### Per-booking clearance (customs bookings only) ``` GL creates booking → AWAITING_DOCUMENTS submitClearanceDocuments (customer upload) → DOCUMENTS_UNDER_REVIEW reviewDocument (GL) → doc APPROVED | QUERIED (note) uploadClearanceOutputDocuments (GL, customs output) finalizeClearance requires 100% required inputs APPROVED + required outputs present → CLEARANCE_READY proceedToOperation → OPERATION_REQUEST_PENDING ``` inputCode / outputCode resolve from `(tradeDirection, freightType, customsClearingEnabled)` — same scheme as the contract loop. Doc review status: `PENDING · APPROVED · QUERIED`. ### Operation request (all paths) ``` requestOperation() guard CLEARANCE_READY | OPERATION_CHANGES_REQUESTED, valid schedule day → OPERATION_REQUEST_PENDING (Path A / domestic bookings begin life here directly) reviewOperationRequest(decision): ACCEPT → acceptOperationRequest(): train service → FULLY_EXECUTED + enqueue batch fill (origin, dest, day) truck service → ROAD_DISPATCH_PENDING (skips train pool, section 7) REQUEST_CHANGES → OPERATION_CHANGES_REQUESTED (note; customer resubmits) ``` ### Clearance / operation endpoints (`bookings.controller.ts`) ``` GET /bookings/:id/clearance docs grid + GL review state POST /bookings/:id/clearance/documents submitClearanceDocuments → DOCUMENTS_UNDER_REVIEW POST /bookings/:id/clearance/review reviewClearanceDocument (approve | query) POST /bookings/:id/clearance/output-documents GL upload output POST /bookings/:id/clearance/finalize finalizeClearance → CLEARANCE_READY POST /bookings/:id/clearance/proceed proceedToOperation → OPERATION_REQUEST_PENDING POST /bookings/:id/operation/review reviewOperationRequest (ACCEPT | REQUEST_CHANGES) ``` Booking lifecycle endpoints (selected): ``` POST /bookings · PATCH /bookings/:id · DELETE /bookings/:id (DRAFT only) POST /bookings/:id/generate-price · /submit · /confirm-submit · /reject POST /bookings/:id/staff/accept · /staff/request-changes · /staff/reject POST /bookings/:id/approval-steps/:stepId/approve · /reject POST /bookings/:id/contract/generate · /contract/sign · GET /contract/view POST /bookings/:id/marketing/approve → FULLY_EXECUTED POST /bookings/:id/government-expedite govt → PAID POST /bookings/:id/operations/start-transit → IN_TRANSIT POST /bookings/:id/operations/complete → COMPLETED POST /bookings/:id/cancel → CANCELLED GET /bookings/my customer payable list GET /bookings/queues/:queue intake | approval | signatures | marketing | finance GET /bookings/:id/tracking shipment tracking POST /bookings/:id/consolidation · DELETE · GET pair partial-wagon bookings ``` --- ## 6) GL Phase 2 — post-booking milestones (customs / Path B) > Once GL creates a customs booking, the system seeds the **post-booking milestone timeline**. > Services: `ClearanceMilestoneService` (seed/advance), `GlOperationsService` (structured actions). > `seedPostBookingMilestones(booking)` runs at GL booking creation. Each milestone: `PENDING → COMPLETED` (or `SKIPPED`). ### Milestone owners `ET` (GL Ethiopia) · `DJ` (GL Djibouti) · `OPS` (Operations) · `CUST` (Customer) ### Import timeline (catalog order, post-booking segment) ``` WAGON_REQUESTED → FREIGHT_PAYMENT_SETTLED → WAGON_ALLOCATED → GATEPASS_GRANTED → READY_FOR_LOADING → LOADED → DEPARTED_FROM_DJIBOUTI [HANDOFF ET↔DJ] → ARRIVED_ETHIOPIA → OFFLOADED → T1_CLOSED → RISK_ASSIGNED → IMPORT_RELEASE_GRANTED → IMPORT_PROCESS_COMPLETED → STORAGE_INVOICE_RAISED → EXIT_NOTE_GENERATED ``` (Pre-booking import milestones — `IMPORT_DOCS_UPLOADED · PENDING_DOCUMENT_REVIEW · DOCUMENTS_APPROVED · UNDER_CUSTOMS_CLEARANCE · DECLARED · DUTY_TAXES_ADVISED · DUTY_TAX_PAID · DO_COLLECTED` — track the clearance loop and end at `DO_COLLECTED`.) ### Export timeline (post-booking segment) ``` WAGON_REQUESTED → FREIGHT_PAYMENT_PENDING → FREIGHT_PAYMENT_SETTLED → WAGON_ALLOCATED → CARGO_ARRIVED → READY_FOR_LOADING → LOADED → DEPARTED_TO_DJIBOUTI [HANDOFF] → ARRIVED_AT_DJIBOUTI → GATEPASS_GRANTED → OFFLOADED ``` (Pre-booking export: `EXPORT_DOCS_UPLOADED · PENDING_DOCUMENT_REVIEW · DOCUMENTS_APPROVED · RELEASE_ORDER_SECURED · UNDER_CUSTOMS_CLEARANCE · DECLARED · EXPORT_RELEASED`.) ### Advance logic ``` completeForBooking(bookingId, code, userId?, note?) mark COMPLETED + triggeredAt/By completeForContract(contractId, code, …) pre-booking contract milestones completeByDocTrigger(scope, code) auto-complete from a doc upload onHandoff(bookingId, code) fires on DEPARTED_* (notification reserved) ``` ### Structured GL actions (`GlOperationsService` + `gl-operations.dto.ts`) ``` assignRisk riskLevel = GREEN | YELLOW | RED → completes RISK_ASSIGNED adviseDuty {amount, currency, declarationSerial?} → completes DUTY_TAXES_ADVISED customer uploads slip → DUTY_TAX_PAID assignStation {stationYardId, staffId?} sets glStationYardId/glAssignedStaffId (GL US-02) reportIncident incidentType = SEAL_BROKEN | CONTAINER_OPENED | CONTAINER_DAMAGED | FLUID_LEAKING + description + photos → ClearanceIncident uploadDocuments / uploadDutySlip → doc-triggered milestone auto-complete ``` ### Doc-triggered milestones (`DOC_CODE_TO_MILESTONE`) | Upload (code) | Completes | Owner | |---------------|-----------|-------| | `delivery_order` | DO_COLLECTED | GL DJ | | `release_order` | RELEASE_ORDER_SECURED | GL DJ | | `t1_transport_document` | T1_CLOSED | GL ET | | `import_release` | IMPORT_RELEASE_GRANTED | GL ET | | `full_in_interchange` | OFFLOADED | GL DJ | | `final_declaration` | IMPORT_PROCESS_COMPLETED | GL ET | | `duty_tax_receipt` | DUTY_TAX_PAID | **Customer** | | `incident_photo` | (logging only, no milestone) | — | ### Endpoints ``` POST /contracts/bookings/:bookingId/milestones/:code/complete manual complete POST /contracts/:id/milestones/:code/complete pre-booking contract milestone POST /contracts/bookings/:bookingId/risk assignRisk POST /contracts/bookings/:bookingId/duty adviseDuty POST /contracts/bookings/:bookingId/station-assign assignStation POST /contracts/bookings/:bookingId/documents GL doc upload (DO/RO/T1/…) POST /contracts/bookings/:bookingId/duty-slip customer duty slip GET /contracts/bookings/:bookingId/incidents list POST /contracts/bookings/:bookingId/incidents reportIncident GET /contracts/bookings/:bookingId/milestones timeline ``` > Still manual-only (no fee engine): `STORAGE_INVOICE_RAISED`, `EXIT_NOTE_GENERATED`. Out of scope: demurrage auto-calc, finance AP closure, multimodal, truck waybill/POD. --- ## 7) Schedule — demand batching (`booking-batch.service.ts`) > Day-level pooling. Customer picks a **day**; the batch engine assigns the actual **train** later. > Cron groups bookings by `(origin yard, destination yard, day)`. EAT timezone, 3-hour windows (00–03 … 21–24). ### Pool states (`boardState`, read-only view) ``` READY FULLY_EXECUTED + fullyExecutedAt, no train link yet SELECTED_FOR_BATCH picked by fill, in pay window (trainScheduleId set, paymentDeadline set) ALLOCATED linked to train via TrainScheduleBooking, PAID (or govt) WAITING PAID but not yet linked (staff-reconciled) EXPIRED failed to pay in window PENDING_CONTRACT any other non-terminal state ``` ### The cron cycle (every 3h; prod `0 */3 * * *`) ``` 1. Fill distribute (route, dest, day) pool across OPEN schedules by priorityScore pick earliest train; fit bookings (govt preempts commercial) commercial → reserve: SELECTED_FOR_BATCH + paymentDeadline = now + 1h govt → allocate: PAID, SCHEDULED 2. Settle 1h after window closes — allocate paid reservations, expire unpaid (→ EXPIRED, unpin train), top up from waiting list 3. Reconcile link orphaned PAID bookings to a schedule 4. Allocate auto-assign wagon slots to allocated bookings ``` `reserve()` → SELECTED_FOR_BATCH. `allocate()` → create `TrainScheduleBooking`, PAID, schedulingStatus SCHEDULED. `expire()` → trainScheduleId null, EXPIRED, schedulingStatus ELIGIBLE (back in pool). ### Payment → transit ``` SELECTED_FOR_BATCH / AWAITING_PAYMENT → pay (Telebirr) → PAID PAID → startTransit → IN_TRANSIT → complete → COMPLETED ``` ### Wagon math ``` wagonsRequired = ⌈ Σ over containers (qty × wagonsPerUnit) ⌉ ``` ### Key endpoints (`train-scheduling.controller.ts`) ``` GET /train-scheduling/available-days days with OPEN departures GET /train-scheduling/available-days-for-cargo capacity-aware bookable days GET /train-scheduling/bookable-schedules OPEN same-route schedules GET /train-scheduling/batch-board monitoring board (states + counts) GET /train-scheduling/eligible-bookings PAID/FULLY_EXECUTED ready to allocate POST /train-scheduling/{container|bulk}/schedules create schedule POST /train-scheduling/schedules/:id/assign-bookings POST /train-scheduling/schedules/:id/run-batch staff manual fill POST /train-scheduling/schedules/:id/run-allocation staff manual wagon allocation POST /train-scheduling/schedules/:id/finalize · /dispatch PATCH /train-scheduling/schedules/:id/booking-window OPEN | CLOSE POST /train-scheduling/bookings/:bookingId/mark-paid · /expire · /move-schedule GET /train-scheduling/schedules/:id/checkpoints · POST /checkpoints tracking events ``` --- ## 8) Delivery / last mile ``` Road service: reviewOperationRequest ACCEPT → ROAD_DISPATCH_PENDING (skips train pool) billed by KM, dispatched by truck (First-Mile operations) First/last mile: pickup + delivery addresses captured at booking; equipment return WITH | WITHOUT last-mile: PAYMENT_PENDING → READY_TO_TRANSIT → IN_TRANSIT → RECEIVED_TO_PORT ``` --- ## The whole thing on one page ``` ONBOARD profile (importer/exporter/FF) approved → ACTIVE → can transact CONTRACT (ContractTransitionService) submit → staffAccept → approveStep×N (LINE_STAFF→DIRECTOR→CEO) → CONTRACT_READY → sign(CUSTOMER) → counterSign → BRANCH: GENERAL + customs → CONTRACT_ACTIVE (NOT_APPLICABLE) — clearance deferred to booking ONE_TIME + customs → AWAITING_CLEARANCE_DOCUMENTS — contract clearance cycle (section 3) DOMESTIC / no customs → FULLY_EXECUTED | CONTRACT_ACTIVE — book now BOOKING (POST /contracts/:id/bookings, gate in ContractBookingService) customs (Path B) → only GL → starts AWAITING_DOCUMENTS no customs (Path A/domestic) → customer/staff → starts OPERATION_REQUEST_PENDING PER-BOOKING CLEARANCE (customs only, BookingTransitionService) upload → review(approve/query) → finalize → CLEARANCE_READY → proceed → OPERATION_REQUEST_PENDING OPERATION REQUEST (all paths) requestOperation → OPERATION_REQUEST_PENDING reviewOperationRequest ACCEPT → FULLY_EXECUTED (train) | ROAD_DISPATCH_PENDING (truck) GL PHASE 2 (customs, post-booking) — ClearanceMilestoneService + GlOperationsService milestone timeline: wagon → pay → allocate → load → depart [handoff] → arrive → offload → T1 close → risk → release → complete GL actions: risk (G/Y/R) · duty advise · station assign · DO/RO/T1 upload · incident customer: watch read-only · upload duty slip · pay freight SCHEDULE (booking-batch.service.ts, 3h EAT cron) fill (route,dest,day) → SELECTED_FOR_BATCH (+1h pay) → pay → PAID/ALLOCATED → IN_TRANSIT → COMPLETED (road → ROAD_DISPATCH_PENDING → truck) ``` --- ### Code map | Area | Key files (`apps/edr-freight-api/src/modules/…`) | |------|---------------------------------------------------| | Contract state machine | `contracts/contract-transition.service.ts`, `contracts/entities/contract.entity.ts`, `contracts/contracts.controller.ts` | | Contract clearance | `contracts/contract-clearance.service.ts`, `contracts/contract-clearance.util.ts` | | Booking gate | `contracts/contract-booking.service.ts` | | Booking lifecycle + per-booking clearance | `bookings/booking-transition.service.ts`, `bookings/entities/booking.entity.ts`, `bookings/bookings.controller.ts` | | GL Phase 2 | `contracts/clearance-milestone.service.ts`, `contracts/clearance-milestone.catalog.ts`, `contracts/gl-operations.service.ts`, `contracts/dto/gl-operations.dto.ts`, `contracts/entities/clearance-incident.entity.ts` | | Schedule / batch engine | `train-scheduling/booking-batch.service.ts`, `train-scheduling/train-scheduling.service.ts`, `train-scheduling/train-scheduling.controller.ts` | ```