This repository has been archived on 2026-08-11. You can view files and clone it, but cannot push or open issues or pull requests.
Gishen-Dispatch-Bot/docs/BACKEND_SPEC.md
kirukib b4293887a8 Add dispatch bot feature brief and living backend spec.
Documents rider Telegram flows and the API/events this client needs so the shared backend can consolidate later.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-07 01:25:46 +03:00

9.4 KiB
Raw Blame History

Gishen Dispatch Bot — Backend Spec Sheet

Living document. Update this file whenever this repo adds or finalizes a feature, flow, scaffold, or payload change. Append a Changelog entry in the same change set. If the change needs nothing new from the backend, log no backend delta.

Consumers: shared platform backend team (consolidation with other client repos’ spec sheets).
Client: Gishen-Dispatch-Bot (Telegram rider bot).
Does not implement backend — describes what the bot needs.

Supersedes for v1: Flutter rider-app assumptions in Gishen-Ecom/docs/backend.md §10 (JWT rider app + limited status enum). Auth and status machine below replace that sketch for the rider surface.


1. Auth & rider enrollment

Model

Concept Description
Rider Platform staff/driver record (role: rider or equivalent)
Telegram link telegram_user_id (required), optional telegram_username
Enrollment Ops admin links Telegram identity → rider; no password / OTP for drivers

Bot → backend auth

Recommended: service credentials for the bot process (API key / mTLS / signed service JWT), plus every rider-facing call includes telegram_user_id so the backend resolves rider_id.

Alternative acceptable: short-lived rider token issued after the backend verifies a Telegram login deep-link — still must bind to enrolled telegram_user_id.

Unenrolled telegram_user_id → 403 with stable error code rider_not_enrolled.

Method Path (proposed) Purpose
POST /admin/riders Create rider profile (name, phone, branch/zone, active)
POST /admin/riders/:riderId/telegram Link { telegram_user_id, telegram_username? }
DELETE /admin/riders/:riderId/telegram Unlink Telegram identity
GET /admin/riders/:riderId Include telegram link status
GET /dispatch/riders/by-telegram/:telegramUserId Resolve rider for bot bootstrap (or bot-only equivalent under /bot/...)

2. Domain events — backend → bot

Bot must be notified when Ops mutates assignment. Prefer a push to the bot service (HTTP webhook the bot exposes, or a queue the bot consumes). Polling alone is insufficient for “prompt” assign UX.

Event When Payload (minimum)
trip.assigned Rider assigned to trip trip manifest (see §4), rider_id, telegram_user_id, assigned_at
trip.reassigned Rider changed Previous + new telegram_user_id, full trip for new rider, reason?
trip.cancelled Trip cancelled trip_id, telegram_user_id, cancelled_at, reason?
trip.updated Manifest material change (address, COD, stops) while in progress trip (full or patch), telegram_user_id

Webhook contract (proposed)

POST {DISPATCH_BOT_WEBHOOK_URL}/hooks/dispatch

{
  "event": "trip.assigned",
  "occurred_at": "2026-08-07T00:00:00Z",
  "idempotency_key": "trip.assigned:trip_123:rider_9",
  "data": { }
}

Bot responds 2xx after durable accept. Backend retries on non-2xx with backoff. Bot treats duplicate idempotency_key as no-op success.


3. Trip status machine

Statuses (v1)

Status Meaning
assigned On rider; not yet picked up
picked_up Parcel(s) collected from branch / pickup
en_route Heading to customer
arrived At delivery location
delivered Completed successfully (POD required)
failed Could not complete (exception reason required)

Delta vs Ecom sketch: add en_route and arrived; keep picked_up / delivered / failed.

Allowed transitions

assigned → picked_up → en_route → arrived → delivered
                                   arrived → failed
                          en_route → failed
                       picked_up → failed

Backend is source of truth. Bot only offers buttons for transitions the API allows.

Bot → backend

Method Path (proposed) Body
POST /rider/trips/:tripId/status See below
{
  "telegram_user_id": 123456789,
  "status": "en_route",
  "occurred_at": "2026-08-07T00:05:00Z",
  "idempotency_key": "trip_123:en_route:2026-08-07T00:05:00Z",
  "exception": null,
  "stop_id": null
}

For failed:

{
  "telegram_user_id": 123456789,
  "status": "failed",
  "occurred_at": "2026-08-07T00:10:00Z",
  "idempotency_key": "trip_123:failed:…",
  "exception": {
    "code": "unreachable_customer",
    "note": "Phone off after 3 attempts"
  },
  "stop_id": "stop_1"
}

Exception codes (v1): unreachable_customer | wrong_address | refused | partial_delivery | other

Responses

Code Meaning
200 Applied; return current trip snapshot
409 Illegal transition or trip no longer assigned to this rider
403 Not enrolled / not assignee
404 Unknown trip

Backend fan-out: update Dispatch Board + customer order tracking after successful transition.


4. Trip manifest shape

Returned on assign events and on GET /rider/trips/me (or GET /rider/trips/:tripId).

{
  "trip_id": "trip_123",
  "status": "assigned",
  "branch": {
    "id": "br_1",
    "name": "Gishen Bole",
    "address": "…"
  },
  "zone": "bole-east",
  "cod_total_etb": 450.0,
  "currency": "ETB",
  "notes": "Call on arrival",
  "assigned_at": "2026-08-07T00:00:00Z",
  "stops": [
    {
      "stop_id": "stop_1",
      "sequence": 1,
      "customer": {
        "display_name": "A. Bekele",
        "phone": "+2519…"
      },
      "address": {
        "line1": "…",
        "line2": null,
        "lat": 9.01,
        "lng": 38.79,
        "maps_url": "https://maps.google.com/…"
      },
      "cod_etb": 450.0,
      "items": [
        {
          "sku": "MED-001",
          "name": "Paracetamol 500mg 20s",
          "qty": 1
        }
      ],
      "order_id": "ord_99",
      "fulfilment_id": "ful_55"
    }
  ]
}

List active trips

Method Path Purpose
GET /rider/trips/me?telegram_user_id= Active + recently completed (window TBD)

5. Proof of delivery (POD)

Required before or with transition to delivered (backend enforces).

Method Path Purpose
POST /rider/trips/:tripId/pod Upload POD for a stop (or whole trip if single-stop)

Multipart or two-step:

  1. POST /rider/trips/:tripId/pod/media — binary photo (image/jpeg | image/png), returns media_id
  2. POST /rider/trips/:tripId/pod — JSON metadata
{
  "telegram_user_id": 123456789,
  "stop_id": "stop_1",
  "recipient_name": "Almaz Bekele",
  "media_id": "media_abc",
  "captured_at": "2026-08-07T00:12:00Z",
  "idempotency_key": "trip_123:stop_1:pod"
}

Bot obtains the photo from Telegram (getFile) and uploads bytes to the backend (backend should not depend on Telegram file URLs long-term).

Delivered transition may be combined:

POST /rider/trips/:tripId/status with status: "delivered" and embedded pod: { … } — acceptable if backend prefers one round-trip; document one approach as canonical. Canonical for this sheet: separate POD create, then status delivered (clearer retries).


6. What this bot does not need from backend (v1)

Capability Note
POST /rider/location continuous heartbeat Deferred — Telegram chat is a poor fit; revisit if Dispatch Board needs live map pins from riders
Customer notification send Owned by notification worker / Ecom channels
Catalog, cart, loyalty, B2B entitlements Other repos
Pharmacist / stock / finance admin APIs Admin Console

7. Idempotency & errors

  • All mutating bot → backend calls carry idempotency_key (stable per user action).
  • Backend → bot webhooks carry idempotency_key per event emission.
  • Error body shape (proposed): { "error": { "code": "rider_not_enrolled", "message": "…" } }
  • Bot maps known codes to Amharic/English driver-facing copy (copy can live in bot; codes must be stable).

8. Real-time fan-out (backend responsibility)

After successful status or POD write, backend must update:

  1. Admin Dispatch Board subscribers
  2. Customer order tracking (timeline; map when rider location exists — N/A until location is in scope)

Exact mechanism (SSE, websocket, poll) is backend-owned; this bot only requires durable API success.


9. Open items

# Item Proposal
1 Driver channel Telegram (confirmed default)
2 Spec ownership This repo’s sheet stays independent; Admin board has its own
3 Customer vs driver bots Separate Telegram bots
4 Live GPS Out of v1
5 Multi-stop POD POD per stop_id; trip delivered only when all stops terminal
6 Partial delivery Use failed + partial_delivery or a future partially_delivered status — prefer exception on failed for v1 until Ops defines partial completion rules
7 Path prefix /rider/... vs /bot/dispatch/... — backend may rename; semantics above matter more than literal paths

Changelog

Date Change
2026-08-07 Initial backend spec: Telegram rider auth/enrollment, assign/reassign/cancel events, status machine (en_route, arrived), manifest shape, POD upload, idempotency; defer live GPS; supersede Ecom Flutter-rider §10 for v1 rider surface.