- Create migration for booking_requests table with necessary fields and indexes. - Implement BookingRequestRepository for database operations related to booking requests. - Develop BookingRequestService to handle business logic for submitting, accepting, rejecting, and canceling booking requests. - Create DTOs for creating booking requests and reviewing them. - Define BookingRequest entity to map to the booking_requests table. - Add UI components for managing shipment requests, including detail and list pages. - Implement OperationDatePicker component for selecting available shipment days.
25 KiB
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:
- 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.
- 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_ACTIVEand 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(orSKIPPED).
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 |