Files
edr-platform/apps/edr-freight-api/docs/FREIGHT_FLOW_VARIANTS.md
2026-07-06 11:37:57 +00:00

25 KiB
Raw Blame History

EDR Freight — Major Flow Variants (each self-contained)

The single master graph lives in FREIGHT_MASTER_FLOW.md. This file breaks the business logic into one comprehensive, self-contained diagram per major scenario, each organised with phase subgraphs so it can be read on its own.

Axes covered

Axis Values
Origin One-time booking · General contract (Path A transport-only / Path B GENERAL+customs)
Trade direction Export · Import · Intercity / Domestic
Customs With customs · Without customs

Legend — (P) Portal (customer) · (B) Backoffice (staff) · (sys) System/event · (green) rounded = success end · (red) rounded = fail end · <> decision.

Which diagram do I read?

flowchart LR
  classDef dec fill:#f8fafc,stroke:#475569,color:#111
  classDef box fill:#e0e7ff,stroke:#3730a3,color:#111
  A{"Origin?"}:::dec
  A -->|"one-time"| B{"Trade direction?<br/>(gate is direction-driven, NOT a customs toggle)"}:::dec
  A -->|"framework agreement"| C{"Contract type?"}:::dec
  B -->|"DOMESTIC"| D1["§1 One-time · DOMESTIC (no gate)"]:::box
  B -->|"IMPORT / EXPORT<br/>(with or without customs)"| D2["§2 One-time · IMPORT/EXPORT (gate)"]:::box
  C -->|"transport-only (self-clearance)"| D3["§3 Contract · Path A"]:::box
  C -->|"GENERAL + customs"| D4["§4 Contract · Path B"]:::box
  D1 --> E{"Physical direction?"}:::dec
  D2 --> E
  D3 --> E
  D4 --> E
  E -->|"export"| F5["§5 EXPORT operations"]:::box
  E -->|"import"| F6["§6 IMPORT operations"]:::box
  E -->|"domestic"| F7["§7 INTERCITY operations"]:::box

How the two halves connect: §1§4 are the commercial journeys (intake → approval → contract → clearance → operation → payment). §5§7 are the physical journeys (mile legs → warehouse → train → delivery). A shipment = one commercial variant + one physical variant. Each diagram fully details its own half and summarises the other so it stands alone.


§1 — One-time booking · DOMESTIC (no clearance gate)

The commercial lifecycle when the counter-sign gate resolves to no clearance — which, in code, means trade direction = DOMESTIC (not a customs toggle). Counter-sign goes straight to FULLY_EXECUTED and the booking is enqueued directly into the scheduling batch pipeline, skipping the operation-request/clearance phase. NOTE: Import/export bookings — even with customs off — do not land here; they always hit the clearance gate (§2, just with a lighter "without customs" document set).

flowchart TD
  classDef port fill:#dbeafe,stroke:#2563eb,color:#111
  classDef back fill:#fef3c7,stroke:#b45309,color:#111
  classDef sys fill:#dcfce7,stroke:#15803d,color:#111
  classDef dec fill:#f8fafc,stroke:#475569,color:#111
  classDef good fill:#86efac,stroke:#166534,color:#062e14
  classDef bad fill:#fecaca,stroke:#991b1b,color:#450a0a

  pre(["Company ACTIVE (approved profile)"]):::sys

  subgraph DRAFT["1 · Create & price"]
    direction TB
    d1["POST /bookings → DRAFT<br/>reference, containers, cargo modifiers, files (P)"]:::port
    d2["POST /bookings/:id/generate-price<br/>rule-engine: LIVE rates + surcharges (P)"]:::port
    dW{"weight-limit-rules"}:::dec
    dWx(["HARD BLOCK 400 — VGM > capacity"]):::bad
    d3["POST /bookings/:id/submit → SUBMITTED<br/>freeze booking_rate_snapshot (P)"]:::port
    dP{"price moved?"}:::dec
    d3c["confirm-submit → SUBMITTED (P)"]:::port
    d1 --> d2 --> dW
    dW -->|"over capacity"| dWx
    dW -->|"ok / warn+surcharge"| d3 --> dP
    dP -->|"yes"| d3c
    dP -->|"no"| out1
    d3c --> out1
    d1 -.->|"delete draft"| ddx(["removed"]):::bad
    d3 -.->|"reject price"| drx(["REJECTED"]):::bad
  end
  out1[" "]:::sys

  subgraph INTAKE["2 · Staff intake & approval"]
    direction TB
    g{"government?"}:::dec
    gexp["governmentExpedite → PAID + Eligible (B)"]:::back
    i{"staff/accept | request-changes | reject (B)"}:::dec
    ir["CHANGES_REQUESTED (B)"]:::back
    irx(["REJECTED"]):::bad
    ia["→ PENDING_APPROVAL<br/>instantiate approval steps + validity window (B)"]:::back
    ac{"chain: LINE_STAFF → DIRECTOR → CEO (B)"}:::dec
    acx(["rejectStep → REJECTED"]):::bad
    g -->|"yes"| gexp
    g -->|"no"| i
    i -->|"request-changes"| ir
    i -->|"reject"| irx
    i -->|"accept"| ia --> ac
    ac -->|"rejectStep"| acx
  end

  subgraph SIGN["3 · Contract doc & sign (DOMESTIC → no gate)"]
    direction TB
    s1["contract/generate → CONTRACT_READY (B)"]:::back
    s2["customer sign → SIGNED_CUSTOMER (P)"]:::port
    s3["staff counter-sign (DOMESTIC) → FULLY_EXECUTED<br/>enqueueScheduleProcessing (no op-request) (B)(sys)"]:::back
    s1 --> s2 --> s3
  end

  subgraph OPPAY["4 · Batch pipeline & payment"]
    direction TB
    fe["FULLY_EXECUTED enters day batch pool (sys)"]:::sys
    b1["batch engine offers wagons → SELECTED_FOR_BATCH<br/>invoice generated (sys)"]:::sys
    p1["customer pays → gateway → PAID (P)"]:::port
    pexp(["pay window lapses → reservation EXPIRED"]):::bad
    fe --> b1 --> p1
    p1 -.->|"unpaid"| pexp
  end

  phys(["Physical execution:<br/>§7 intercity → COMPLETED (done)"]):::good

  pre --> DRAFT
  out1 --> INTAKE
  ac -->|"APPROVED"| SIGN
  SIGN --> OPPAY
  p1 --> phys
  gexp -.->|"gov → PAID/Eligible"| phys

Road-mode note: a domestic booking billed by road (truck) instead of rail goes through operation/reviewROAD_DISPATCH_PENDING (the road branch shown in the master graph), not the rail batch pool above.


§2 — One-time booking · IMPORT / EXPORT (clearance gate)

Every IMPORT/EXPORT one-time booking traverses the clearance gate — whether or not customs is enabled (the customs flag only selects a heavier vs lighter clearance_* document set; both go through AWAITING_DOCUMENTS). Commercial spine as §1 (phases 13) plus the gate: counter-sign → AWAITING_DOCUMENTS → document review loop → CLEARANCE_READY, phased ET/DJ actions, then operation-request → payment.

flowchart TD
  classDef port fill:#dbeafe,stroke:#2563eb,color:#111
  classDef back fill:#fef3c7,stroke:#b45309,color:#111
  classDef sys fill:#dcfce7,stroke:#15803d,color:#111
  classDef dec fill:#f8fafc,stroke:#475569,color:#111
  classDef good fill:#86efac,stroke:#166534,color:#062e14
  classDef bad fill:#fecaca,stroke:#991b1b,color:#450a0a

  a0(["Booking APPROVED & signed by customer<br/>(see §1 phases 13)"]):::sys

  subgraph CS["Counter-sign with customs"]
    direction TB
    cs1["staff counter-sign (IMPORT/EXPORT) → AWAITING_DOCUMENTS (B)"]:::back
  end

  subgraph DOCS["5 · Document clearance gate"]
    direction TB
    x1["customer clearance/documents<br/>→ DOCUMENTS_UNDER_REVIEW (P)"]:::port
    x2{"GL clearance/review each doc (B)"}:::dec
    x2q["doc Queried → customer re-uploads (B)"]:::back
    x3["clearance/finalize (100% approved) → CLEARANCE_READY (B)"]:::back
    x1 --> x2
    x2 -->|"query"| x2q --> x1
    x2 -->|"approve all"| x3
  end

  subgraph PHASED["6 · Phased ET / DJ clearance (as applicable)"]
    direction TB
    ph1["upload declaration (serial) (B)"]:::back
    ph2["duty/tax advise → customer duty-slip (P)(B)"]:::back
    ph3["transit permit (ET) (B)"]:::back
    ph4["delivery order / release order (DJ) (B)"]:::back
    ph5["T1 docs → T1 close (B)"]:::back
    ph6["export release / finalize-pre-clearance (B)"]:::back
    ph1 --> ph2 --> ph3 --> ph4 --> ph5 --> ph6
  end

  subgraph OPPAY2["7 · Operation request & payment"]
    direction TB
    o1["clearance/proceed → OPERATION_REQUEST_PENDING (P)"]:::port
    o2{"operation/review (B)"}:::dec
    o2c["OPERATION_CHANGES_REQUESTED (B)"]:::back
    om{"mode?"}:::dec
    ot["accept=train: invoice → FULLY_EXECUTED<br/>→ batch offer → SELECTED_FOR_BATCH (B)(sys)"]:::back
    orr["accept=road: invoice → ROAD_DISPATCH_PENDING (B)"]:::back
    p1["customer pays → PAID (sys)(P)"]:::sys
    pexp(["pay window lapses → EXPIRED"]):::bad
    o1 --> o2
    o2 -->|"request-changes"| o2c --> o1
    o2 -->|"accept"| om
    om -->|"train"| ot --> p1
    om -->|"road"| orr --> p1
    p1 -.->|"unpaid"| pexp
  end

  cancel(["CANCELLED — staff-only, only from<br/>OPERATION_REQUEST_PENDING here (not from<br/>AWAITING_DOCUMENTS / DOCUMENTS_UNDER_REVIEW)"]):::bad
  phys(["Physical execution:<br/>§5 export · §6 import → COMPLETED (done)"]):::good

  a0 --> CS --> DOCS
  x3 --> PHASED
  ph6 --> OPPAY2
  p1 --> phys
  o1 -.->|"cancel"| cancel

§3 — General contract · Path A (transport-only, self-clearance)

Framework agreement where the customer clears customs independently. After the contract is active and operations verify self-clearance (SELF_CLEARED), the customer books directly under the contract; each booking then runs the operation/payment/physical flow.

flowchart TD
  classDef port fill:#dbeafe,stroke:#2563eb,color:#111
  classDef back fill:#fef3c7,stroke:#b45309,color:#111
  classDef sys fill:#dcfce7,stroke:#15803d,color:#111
  classDef dec fill:#f8fafc,stroke:#475569,color:#111
  classDef good fill:#86efac,stroke:#166534,color:#062e14
  classDef bad fill:#fecaca,stroke:#991b1b,color:#450a0a

  pre(["Company ACTIVE"]):::sys

  subgraph CTR["1 · Contract lifecycle"]
    direction TB
    c1["POST /contracts → DRAFT<br/>routes + cargo scope + unit rates (NO quantities) (P)"]:::port
    c2["generate-price → submit → SUBMITTED<br/>freeze contract_rate_snapshots (P)"]:::port
    c3{"staff/accept | request-changes | reject (B)"}:::dec
    c3r["CHANGES_REQUESTED (B)"]:::back
    c3x(["Contract REJECTED"]):::bad
    c4["→ PENDING_APPROVAL (B)"]:::back
    c4a{"approval chain (B)"}:::dec
    c4x(["rejectStep → REJECTED"]):::bad
    c5["generate-contract → CONTRACT_READY (B)"]:::back
    c6["customer sign → SIGNED_CUSTOMER (P)"]:::port
    c7["staff counter-sign (IMPORT/EXPORT self-clearance) →<br/>AWAITING_CLEARANCE_DOCUMENTS (B)"]:::back
    c1 --> c2 --> c3
    c3 -->|"request-changes"| c3r --> c2
    c3 -->|"reject"| c3x
    c3 -->|"accept"| c4 --> c4a
    c4a -->|"reject"| c4x
    c4a -->|"approve"| c5 --> c6 --> c7
    c7 -.->|"lapse"| cexp(["EXPIRED"]):::bad
    c7 -.->|"renew"| cren(["RENEWAL_DRAFT → new cycle"]):::bad
  end

  subgraph SELF["2 · Self-clearance verification"]
    direction TB
    o1["customer uploads self-clearance docs (P)"]:::port
    o2{"ops-review each doc (B)"}:::dec
    o2q["query → re-upload (B)"]:::back
    o3["ops-finalize → clearanceStatus SELF_CLEARED (B)"]:::back
    o1 --> o2
    o2 -->|"query"| o2q --> o1
    o2 -->|"approve"| o3
  end

  subgraph BK["3 · Book directly under contract"]
    direction TB
    b1["customer POST /contracts/:id/bookings (P)"]:::port
    bv{"validate-shipment:<br/>window + capacity draw-down + pairing"}:::dec
    bvx(["rejected: over capacity /<br/>20ft pairing hard-block"]):::bad
    b2["Booking created under contract<br/>(bookings.contract_id) (sys)"]:::sys
    b1 --> bv
    bv -->|"fail"| bvx
    bv -->|"ok"| b2
  end

  op(["Booking runs operation + payment<br/>(see §1 phase 4) then §5/§6/§7 → COMPLETED (done)"]):::good

  pre --> CTR
  c7 --> SELF
  o3 --> BK
  b2 --> op

§4 — General contract · Path B (GENERAL + customs)

Framework agreement with customs. The customer cannot book directly — they submit a BookingRequest (date + quantities only); GL Ethiopia accepts it and creates the booking, which then runs per-booking phased customs on the /contracts/bookings/:bookingId/* surface.

flowchart TD
  classDef port fill:#dbeafe,stroke:#2563eb,color:#111
  classDef back fill:#fef3c7,stroke:#b45309,color:#111
  classDef sys fill:#dcfce7,stroke:#15803d,color:#111
  classDef dec fill:#f8fafc,stroke:#475569,color:#111
  classDef good fill:#86efac,stroke:#166534,color:#062e14
  classDef bad fill:#fecaca,stroke:#991b1b,color:#450a0a

  pre(["Company ACTIVE"]):::sys

  subgraph CTR["1 · Contract lifecycle (customs)"]
    direction TB
    c1["POST /contracts → DRAFT → submit → approve → sign (P)(B)"]:::port
    c7["staff counter-sign (GENERAL + customs) → CONTRACT_ACTIVE<br/>(contract clearance cycle SKIPPED — runs per-booking) (B)"]:::back
    c1 --> c7
    c1 -.->|"reject / lapse"| cx(["REJECTED / EXPIRED"]):::bad
  end

  subgraph REQ["2 · Booking request → GL creates booking"]
    direction TB
    r1["customer POST /contracts/:id/booking-requests<br/>(date + quantities, no per-unit data) (P)"]:::port
    r2{"GL booking-request queue (B)"}:::dec
    r2x(["reject / customer cancel →<br/>REJECTED / CANCELLED"]):::bad
    r3["GL accept → GL creates booking under contract<br/>(ct:create_booking) (B)"]:::back
    r1 --> r2
    r2 -->|"reject/cancel"| r2x
    r2 -->|"accept"| r3
  end

  subgraph GLC["3 · Per-booking GL clearance & milestones"]
    direction TB
    g1["station-assign (route + bind staff) (B)"]:::back
    g2["declaration → duty advise (GREEN/YELLOW/RED risk) (B)"]:::back
    g3["customer duty-slip → transit / delivery / release order (P)(B)"]:::back
    g4["T1 docs → T1 close (B)"]:::back
    g5["final-invoice → customer slip → confirm paid (P)(B)"]:::back
    g6["second-duty (post-arrival import) → slip (P)(B)"]:::back
    gi["incident reports (photos) as needed (B)"]:::back
    g1 --> g2 --> g3 --> g4 --> g5 --> g6
    g4 -.-> gi
  end

  op(["Booking runs operation + payment (see §1 phase 4)<br/>then §5/§6 physical → COMPLETED (done)"]):::good

  pre --> CTR
  c7 --> REQ
  r3 --> GLC
  g6 --> op

§5 — EXPORT operations (physical execution)

Given a PAID, scheduled export booking: optional first-mile road leg → origin warehouse inbound → train build & dispatch → corridor transit → Djibouti port unload → interchange handover. Cargo leaves the country at the port; the booking's terminal here is dispatched/handed-over at Djibouti.

flowchart TD
  classDef port fill:#dbeafe,stroke:#2563eb,color:#111
  classDef back fill:#fef3c7,stroke:#b45309,color:#111
  classDef sys fill:#dcfce7,stroke:#15803d,color:#111
  classDef dec fill:#f8fafc,stroke:#475569,color:#111
  classDef good fill:#86efac,stroke:#166534,color:#062e14
  classDef bad fill:#fecaca,stroke:#991b1b,color:#450a0a

  pre(["Export booking PAID & Eligible<br/>(see §1/§2/§3/§4)"]):::sys

  subgraph FM["1 · First-mile (if EXPORT + requested)"]
    direction TB
    fmq{"first-mile requested?"}:::dec
    fm1["leg auto-created firstMile.acceptBooking<br/>READY_TO_TRANSIT (sys)"]:::sys
    fm2["setVehicles → vehicle BUSY, SMS driver, fleet_events (B)"]:::back
    fm3["IN_TRANSIT (needs vehicle) → RECEIVED_TO_PORT (B)"]:::back
    fm4["first-mile invoice (FIRST_MILE fee); distances lock once invoiced (B)"]:::back
    fmq -->|"yes"| fm1 --> fm2 --> fm3 --> fm4
    fmq -->|"no"| fmskip[" "]:::sys
  end

  subgraph WH["2 · Origin warehouse inbound"]
    direction TB
    w1["receive / bulkReceive → RECEIVED<br/>capacity assert, GRN, notify owner SMS (B)"]:::back
    w2{"inspection"}:::dec
    w2f["FAILED / NEEDS_REVIEW → hold + re-inspect (B)"]:::back
    w3["store (allocation rule → yard/zone) → STORED (B)"]:::back
    w4["reserve (booking PAID) → RESERVED (B)"]:::back
    w5["mark-ready-for-loading (inspection PASSED) → READY_FOR_LOADING (B)"]:::back
    w6["load onto wagon → LOADED (+ warehouse_loadings) (B)"]:::back
    w1 --> w2
    w2 -->|"fail"| w2f --> w2
    w2 -->|"PASSED"| w3 --> w4 --> w5 --> w6
  end

  subgraph SCHED["3 · Train build & schedule"]
    direction TB
    s1["schedule DRAFT (≥2 locos, derive EXPORT direction) (B)"]:::back
    s2["assign-bookings + run-allocation (wagons) (B)"]:::back
    s3["pin wagons → finalize → SCHEDULED (bookings Scheduled) (B)"]:::back
    s3x["cancel schedule → bookings Eligible (B)"]:::back
    s1 --> s2 --> s3
    s3 -.->|"cancel"| s3x -.-> s1
    s3 -.->|"gov preempt / maintenance"| sr["reschedule: retained/displaced/readmitted (B)"]:::back
    sr -.-> s2
  end

  subgraph RUN["4 · Dispatch → Djibouti"]
    direction TB
    r1["dispatch → DISPATCHED<br/>train_number, locos ASSIGNED, window CLOSED, unpaid EXPIRED (B)"]:::back
    r2["checkpoints (corridor) → train_checkpoint_events (B)"]:::back
    rc["customer tracking page GET /tracking/:id (JWT) (P)<br/>NOTE: tracking_events has no writer — timeline empty"]:::port
    r3["arrive → ARRIVED (bookings IN_TRANSIT, wagons/locos freed) (B)"]:::back
    ru["export/auto-unload-at-djibouti →<br/>UNLOADED_AT_DJIBOUTI_PORT (B)"]:::back
    ri["interchange document generate-from-schedule → GENERATED (B)"]:::back
    ria{"port acknowledges?"}:::dec
    r1 --> r2 --> r3 --> ru --> ri --> ria
    r2 -.-> rc
  end

  done(["Export dispatched & handed over at Djibouti (done)"]):::good
  disp(["interchange DISPUTED → remarks / re-issue"]):::bad

  pre --> FM
  fm4 --> WH
  fmskip --> WH
  w6 --> SCHED
  s3 --> RUN
  ria -->|"acknowledge"| done
  ria -->|"dispute"| disp

§6 — IMPORT operations (physical execution)

Given a PAID, scheduled import booking arriving by train from Djibouti: destination warehouse unload → inspection → import customs finalization → optional last-mile → fee gate-clearance → release → delivery → COMPLETED.

flowchart TD
  classDef port fill:#dbeafe,stroke:#2563eb,color:#111
  classDef back fill:#fef3c7,stroke:#b45309,color:#111
  classDef sys fill:#dcfce7,stroke:#15803d,color:#111
  classDef dec fill:#f8fafc,stroke:#475569,color:#111
  classDef good fill:#86efac,stroke:#166534,color:#062e14
  classDef bad fill:#fecaca,stroke:#991b1b,color:#450a0a

  pre(["Import booking PAID & scheduled<br/>(see §1/§2/§4)"]):::sys

  subgraph RAIL["1 · Import train arrival"]
    direction TB
    t1["import train dispatched from Djibouti → DISPATCHED (B)"]:::back
    t2["checkpoints → train_checkpoint_events (B)"]:::back
    tc["customer tracking page GET /tracking/:id (JWT) (P)<br/>NOTE: tracking_events has no writer — timeline empty"]:::port
    t3["arrive → ARRIVED (bookings IN_TRANSIT) → warehouse arrival automation (B)"]:::back
    t1 --> t2 --> t3
    t2 -.-> tc
  end

  subgraph WH["2 · Destination warehouse"]
    direction TB
    w1["import/auto-unload-arrived-bookings (assign WH/yard/zone) → UNLOADED (B)"]:::back
    w2{"inspection PASSED?"}:::dec
    w2f["FAILED → hold / re-inspect / djibouti-incident report (B)"]:::back
    w3["→ READY_FOR_PICKUP (IMPORT) (B)"]:::back
    w1 --> w2
    w2 -->|"no"| w2f --> w2
    w2 -->|"yes"| w3
  end

  subgraph CUST["3 · Import customs finalization (timestamp-driven)"]
    direction TB
    u1["upload docs (IM4/IM5/T1_CLOSURE/TRANSIT_PERMIT/…) (B)"]:::back
    u2["record declaration serial (B)"]:::back
    u3["notify duties/taxes (B)"]:::back
    u4["mark duties paid (needs CUSTOMER_PAYMENT_SLIP) (B)"]:::back
    u5["assign risk (GREEN/YELLOW/BLUE/RED) (B)"]:::back
    u6{"release gates satisfied?<br/>T1 + release permit + declaration + risk + paid"}:::dec
    u6x["blocked — missing gate → resolve (B)"]:::back
    u7["release-permitted → completedAt (B)"]:::back
    u1 --> u2 --> u3 --> u4 --> u5 --> u6
    u6 -->|"no"| u6x --> u6
    u6 -->|"yes"| u7
  end

  subgraph LM["4 · Last-mile (if requested)"]
    direction TB
    lq{"last-mile requested?"}:::dec
    l1["leg auto-created lastMile.acceptBooking<br/>READY_TO_TRANSIT (sys)"]:::sys
    l2["setVehicles → IN_TRANSIT → DELIVERED (free vehicles) (B)"]:::back
    l3["last-mile invoice (LAST_MILE fee) (B)"]:::back
    lq -->|"yes"| l1 --> l2 --> l3
    lq -->|"no"| lskip[" "]:::sys
  end

  subgraph DEL["5 · Release & delivery"]
    direction TB
    d0{"warehouse/storage fees fully PAID?"}:::dec
    d0x["gate-clearance BLOCKED (findBlockingInvoice) (B)"]:::back
    d0p["customer pays storage/demurrage online (P)"]:::port
    d1["release order (DO) + gate-clearance → deliver (B)"]:::back
    d2["customer approve-delivery (saved signature) → POD (P)"]:::port
    d3["inventory DELIVERED, POD to cargo, container freed (sys)"]:::sys
    d0 -->|"no"| d0x --> d0p --> d0
    d0 -->|"yes"| d1 --> d2 --> d3
  end

  ecr(["empty-container-return chain (post-import):<br/>RETURNED → … → HANDOVER_ISSUED → COMPLETED"]):::sys
  done(["Booking COMPLETED (done) (operations/complete)"]):::good

  pre --> RAIL
  t3 --> WH
  w3 --> CUST
  u7 --> LM
  l3 --> DEL
  lskip --> DEL
  d3 --> done
  done -.-> ecr

§7 — INTERCITY / DOMESTIC operations (no cross-border customs)

Rail movement between Ethiopian yards (e.g. inland dry ports). No import/export customs, no Djibouti port unload or interchange handover. Optional road mile legs if the service includes door delivery.

flowchart TD
  classDef port fill:#dbeafe,stroke:#2563eb,color:#111
  classDef back fill:#fef3c7,stroke:#b45309,color:#111
  classDef sys fill:#dcfce7,stroke:#15803d,color:#111
  classDef dec fill:#f8fafc,stroke:#475569,color:#111
  classDef good fill:#86efac,stroke:#166534,color:#062e14
  classDef bad fill:#fecaca,stroke:#991b1b,color:#450a0a

  pre(["Domestic booking PAID & Eligible<br/>(see §1 — customs OFF)"]):::sys

  subgraph FMD["1 · Optional origin pickup (road)"]
    direction TB
    fq{"door pickup / first-mile requested?"}:::dec
    f1["first-mile leg → vehicle assign → RECEIVED_TO_PORT (B)"]:::back
    fq -->|"yes"| f1 --> fnext[" "]:::sys
    fq -->|"no (drop at origin yard)"| fnext
  end

  subgraph WHO["2 · Origin warehouse"]
    direction TB
    w1["receive → RECEIVED (B)"]:::back
    w2{"inspection PASSED?"}:::dec
    w2f["hold + re-inspect (B)"]:::back
    w3["store → STORED → reserve (PAID) → RESERVED (B)"]:::back
    w4["ready-for-loading → LOADED onto wagon (B)"]:::back
    w1 --> w2
    w2 -->|"no"| w2f --> w2
    w2 -->|"yes"| w3 --> w4
  end

  subgraph RUN["3 · Train (ET yard → ET yard)"]
    direction TB
    s1["schedule DRAFT (direction DOMESTIC) → assign → finalize → SCHEDULED (B)"]:::back
    r1["dispatch → DISPATCHED (unpaid EXPIRED) (B)"]:::back
    r2["checkpoints → train_checkpoint_events; customer tracking (JWT) (P)(B)"]:::back
    r3["arrive → ARRIVED (bookings IN_TRANSIT) (B)"]:::back
    s1 --> r1 --> r2 --> r3
  end

  subgraph WHD["4 · Destination warehouse & delivery"]
    direction TB
    d1["auto-unload arrived → UNLOADED / RECEIVED (B)"]:::back
    di{"inspection PASSED?"}:::dec
    dif["hold + re-inspect (B)"]:::back
    d2["READY_FOR_PICKUP (B)"]:::back
    fee{"storage fees paid?"}:::dec
    feex["gate-clearance blocked → customer pays (P)"]:::port
    d3["release order → deliver (B)"]:::back
    lq{"door delivery / last-mile?"}:::dec
    l1["last-mile leg → DELIVERED (B)"]:::back
    d4["customer approve-delivery → POD → inventory DELIVERED (P)(sys)"]:::sys
    d1 --> di
    di -->|"no"| dif --> di
    di -->|"yes"| d2 --> fee
    fee -->|"no"| feex --> fee
    fee -->|"yes"| d3 --> lq
    lq -->|"yes"| l1 --> d4
    lq -->|"no (pickup at yard)"| d4
  end

  done(["Booking COMPLETED (done)"]):::good

  pre --> FMD
  fnext --> WHO
  w4 --> RUN
  r3 --> WHD
  d4 --> done

Cross-reference

Variant Distinctive gate(s) Terminal ends unique to it
§1 One-time · DOMESTIC counter-sign → FULLY_EXECUTED (skips clearance and op-request; enters batch directly) HARD BLOCK, price/intake/approval REJECTED, EXPIRED
§2 One-time · IMPORT/EXPORT AWAITING_DOCUMENTS → review loop → phased ET/DJ → op-request (gate applies with or without customs) + doc-query loop, CANCELLED (only from OPERATION_REQUEST_PENDING)
§3 Contract · Path A SELF_CLEARED via ops-review; customer books direct contract REJECTED/EXPIRED/RENEWAL, capacity/pairing block
§4 Contract · Path B BookingRequest → GL creates booking; per-booking milestones BookingRequest REJECTED/CANCELLED
§5 Export ops first-mile → Djibouti unload → interchange handover dispatched@Djibouti (success), interchange DISPUTED
§6 Import ops import customs finalization gates → last-mile → gate-clearance COMPLETED (+ empty-container-return chain)
§7 Intercity ops ET→ET rail, no cross-border customs, optional mile legs COMPLETED

Full endpoint tables & per-domain state machines: FREIGHT_SYSTEM_FLOW.md. Single all-in-one branching graph: FREIGHT_MASTER_FLOW.md.