Foodops
All docs

Guides · Updated 2026-06-29 · v36f8068

Rider App (Delivery & Waiter Mobile)

Audio narration for this article is generated with the weekly docs refresh.

Warning: DORMANT / LEGACY surface — not a primary July-10 launch product. This standalone Flutter app is built manually from master (no CI) and sideloaded; its original backend (foodops.delivery.api) was retired 2025-11-27. It still functions because it points at the live sales API (sales-api.hubits.io / hubits-api-sales), and it got a re-point commit on 2026-06-15, but it is kept on life-support, not actively shipped. For the launch, delivery is operated from the counter delivery queue + the sales API delivery service (see Deliveries). Treat this entry as documentation of a legacy tool that still works, not a supported launch surface.

Plain-English summary

The rider app (foodops.delivery.mobile, Flutter, Android + iOS) is the phone in the delivery rider's pocket — and, in waiter mode, the same app takes dine-in orders at the table. A rider signs in with email + password, picks the organisation/outlet, sees the delivery queue, taps Mark as Enroute when leaving with an order (which also self-assigns unassigned orders), Mark as Delivered at the door, Settle to record the payment (cash, card, or transfer with a slip photo), and at end of shift hands the collected cash over so the cashier can acknowledge it into the register session. Live updates arrive over the same SignalR hub the POS uses. The app now talks to the core sales API (sales-api.hubits.io) — its original dedicated backend repo is dormant.

When you'd use this

  • Onboarding a new delivery rider or waiter onto the app
  • Answering "the rider marked it delivered — where's the cash?" (the handover/acknowledge loop)
  • Tracing why a rider can't log in (role requirements) or can't see orders (org/outlet selection)
  • Understanding what the rider sees at each order status, for support calls
  • Checking which backend the app actually calls (it changed)

Install & login

  • Distribution: there is no CI/CD on the repo — builds are made manually from master and sideloaded/distributed by the team (Android com.delivery.hubits.foodops_delivery; iOS project present). No public store listing was verified in this pass. [repo: no .github/workflows at 3d9ff2c]
  • Login: email + password against the identity API (identity-api.hubits.ioauth/login); the sales API accepts the session token issued there. The login response carries roles.isWaiter / roles.isDelivery flags. [code: lib/Screens/DeliveryScreen/LoginScreen/controller/login_controller.dart; lib/Helper/Api/api_constant.dart:17-23]
    • Note: the in-repo user-flow doc (docs/FoodOps_Delivery_User_Flow.md, "January 2026") describes phone + OTP login — that is stale; current code is email + password.
  • Roles decide the UI: delivery-only users get the delivery screens; waiter-only users get the waiter screens; dual-role users get both as tabs (Waiter first, Delivery second). A user with neither role can't use the app. [code: lib/Screens/MainNavigationScreen/controller/main_navigation_controller.dart]
  • Org/outlet selection: after login the rider picks an organisation, then an outlet; outlets are fetched per org (GET /api/v1/outlets with the org id header) and the selection persists on-device. Switchable later via Profile → Manage Organisations. [code: api_constant.dart:62-68; lib/Screens/DeliveryScreen/OrganizationScreen]

Walkthrough — the rider's loop

Reconstructed from controllers + the in-repo flow doc at 3d9ff2c; cross-checked against sales-api controllers at 20fc7ca. Screens need a real device/emulator for capture.

  1. See the queue — the delivery home lists the outlet's delivery orders (GET api/delivery/orders) with status filter tabs Open / Ready / Enroute / Delivered and local text search; a separate Assigned Orders screen shows the rider's own (paginated delivery-management/orders). Header icons: unassigned orders, assigned orders, cash on hand, more. [code: lib/Screens/DeliveryScreen/OrderScreen/controller/order_controller.dart:26-34; AssignedOrderScreen/controller/assigned_order_controller.dart]
  2. Get assigned — or self-assign — a dispatcher can assign from the counter's driver-assignment modal (see Deliveries), which pushes an OrderAssigned SignalR event to the app; or the rider opens an unassigned order and taps Mark as Enroute, which assigns it to them and sets the status in one step (the call carries deliveryRiderId + orderId). [code: lib/Helper/Api/signalr_service.dart; api_requests_helper.dart (makeAsEnRoute → POST api/delivery/orders/mark-enroute); hubits-api-sales: DeliveryOrdersController.cs]
  3. Enroute → Delivered — at the customer, Mark as Delivered (POST api/delivery/orders/mark-delivered). The POS delivery queue mirrors these transitions live; the rider flow deliberately owns them (kitchen item-served events don't advance delivery orders). [code: api_constant.dart:55-56]
  4. Settle (collect payment)Settle opens the payment sheet: pick Cash, Card, or Transfer (transfer prompts a camera/gallery photo of the slip, uploaded as an invoice document). The amounts must equal the gross total before confirm; payment posts via Orders/make-payment and the order closes. Cash settlements add to the rider's Cash On Hand. [code: lib/Screens/DeliveryScreen/PaymentScreen/controller/payment_controller.dart; api_constant.dart:30,44]
  5. Cash On Hand — the dollar icon shows total cash held, completed-order counts, a weekly calendar, and per-day transaction lists (cash-on-hand/cash-summary, cash-on-hand/day-transactions). [code: lib/Screens/DeliveryScreen/CashInHandScreen/controller/cash_in_hand_controller.dart]
  6. Handover — end of shift: Handover Cash pre-fills the held amount (editable for partial handovers), confirm posts cash-on-hand/handover. [code: cash_in_hand_controller.dart:73-95]
  7. Acknowledge (cashier side) — the handed-over cash appears in the register session's Delivery Cash tab where the cashier acknowledges it into the session's Expected balance — unacknowledged rider cash shows as an unexplained variance at session close. See Cash Handling and Deliveries.

Waiter mode (brief)

The same app contains a full dine-in order-taking surface: floor plan/table selection, menu with variants + modifiers (30-minute on-device menu cache per outlet), order confirm with customer/discount/sector pickers, per-item kitchen status actions, receipts, KOT print triggers via the sales-api print-job pipeline, and register-session join. It mirrors the counter POS contract (Orders/create, Orders/list, Sessions/join, floor-plan and printer-status endpoints). [code: lib/Screens/WaiterScreen/**; lib/Helper/Cache/menu_cache_service.dart; api_constant.dart:80-135]

Connectivity & offline behaviour

The rider app is online-required — unlike the counter PWA it has no offline order queue:

  • Before every API call it checks connectivity (DNS lookup); offline, it shows an amber "No Internet / check your internet connection" snackbar and the action simply doesn't execute. [code: lib/Helper/Api/dio_client.dart:55-83]
  • Mitigations: automatic request retry (up to 3 attempts with backoff), a 10-second in-memory response cache on the hot list endpoints (Orders/counts, delivery/orders), a 30-minute on-device menu cache (waiter mode), and SignalR automatic reconnect with 2-second debounced refresh. [code: dio_client.dart:29-39; menu_cache_service.dart; signalr_service.dart]
  • Practical effect: a rider in a dead zone can look at already-loaded screens but cannot mark enroute/delivered or settle until signal returns; nothing queues for later.

Backend transition — read this before verifying anything

The shipped app points at sales-api.hubits.io (hubits-api-sales) for everything except auth (identity-api.hubits.io), including the /orderHub SignalR hub. [code: api_constant.dart:4-8]

  • The app's original backend repo, foodops.delivery.api (project foodops.app.api), is dormant (master HEAD 2025-11-27) — kept in source_repos only as the historical API-contract reference. Do not verify rider behaviour against it.
  • The live controller surface lives in hubits-api-sales: DeliveryController, DeliveryOrdersController (mark-enroute/mark-delivered), DeliveryManagementController, CashOnHandController (summary/day-transactions/handover), plus the shared Orders* endpoints the waiter mode uses.
  • Some old paths were removed server-side and the app adapted in place — e.g. the old delivery/orders/status endpoint is gone; the app now uses the waiter-side Orders/change-status for generic status updates, and outlet selection became a client-side concern (/api/v1/outlets per org). [code: api_constant.dart:40-44,62-68]

Drift-detection note: the repo has no CI — watch commits on master, not workflow runs (master HEAD 1ff0a759f3, last commit 2026-06-15 "Refactor API endpoints and improve order details handling" — a re-point/maintenance commit, not a feature push).

Key concepts

  • Self-assignment via Mark as Enroute — picking up an unassigned order and marking it enroute assigns it to the rider in one step; dispatch from the counter is optional, not required.
  • Settle ≠ Delivered — Delivered is the logistics terminal state; Settle records the money and closes the order. A rider can settle at any status (per the in-repo flow doc), but the designed loop is deliver-then-settle.
  • Cash On Hand — running total of cash the rider holds; grows on cash settlements, shrinks on handover. The cashier's acknowledgement (session Delivery Cash tab) is what moves it into the register's Expected balance.
  • Roles gate modesisDelivery / isWaiter from the identity login decide which halves of the app exist for the user; both roles = both tabs.
  • One SignalR hub — the app listens on the sales-api /orderHub for the same org-group events the POS uses (OrderAdded, OrderAssigned, OrderStatusUpdated, DeliveryEnRoute, DeliveryCompleted, …), debounced into list refreshes. [code: signalr_service.dart]
  • Status tabs — Open / Ready / Enroute / Delivered (the in-repo doc's "All" tab and a Preparing tab exist in code only as commented-out options at this pin). [code: order_controller.dart:26-34]

Common questions

Q: How does a rider get the app? A: From the team — there's no CI pipeline or verified store listing; builds are produced manually from the repo and distributed/sideloaded. Ask the org admin for the current build.

Q: What does a rider log in with? A: Email + password (the account must have the Delivery role). The in-app error if the account lacks the role: "Login failed! Either no account exists or an account with Delivery role does not exist." Note the in-repo PDF/user-flow doc still says phone+OTP — it's outdated.

Q: The rider logs in but sees no orders. Why? A: Check (1) the right organisation and outlet are selected (Profile → Manage Organisations), (2) the order is a delivery order in the outlet's queue, (3) connectivity — the app doesn't load anything offline.

Q: Does the rider have to be assigned by the cashier? A: No — assignment can come from the counter's driver modal, but a rider can open an unassigned order and tap Mark as Enroute, which self-assigns it.

Q: How does the rider record a cash payment? A: Open the order → Settle → choose Cash → confirm. The amount joins their Cash On Hand total automatically.

Q: What if the customer paid by bank transfer at the door? A: Settle → Transfer → photograph the slip (camera or gallery); the image uploads against the order as the payment proof.

Q: Where does the rider's cash go at the end of a shift? A: Cash On Hand screen → Handover Cash → confirm the (editable, possibly partial) amount. The cashier then acknowledges it in the session's Delivery Cash tab — only then does it count into the register's Expected cash. See Cash Handling.

Q: Can the rider hand over only part of the cash? A: Yes — the handover amount field is pre-filled with the full Cash On Hand but editable, so partial handovers are supported. [code: cash_in_hand_controller.dart]

Q: Does the app work offline? A: No — it warns ("No Internet") and blocks actions. Short caches smooth flaky moments, but enroute/delivered/settle all need a live connection; nothing is queued for retry later.

Q: Can one person be both waiter and rider? A: Yes — with both roles, the app shows Waiter and Delivery as two tabs in one session.

Q: Does the rider see navigation/maps? A: A delivery map screen exists in the app (Google Maps key configured) showing delivery locations. Live turn-by-turn and customer-facing live rider tracking were not verified in this pass. [code: lib/Screens/DeliveryScreen/DeliveryMapScreen]

Q: Which statuses can the rider change? A: The designed transitions are Ready → Enroute (mark-enroute) and Enroute → Delivered (mark-delivered), plus Settle to close. Generic status changes go through the shared Orders/change-status endpoint.

Edge cases and known issues

  • Stale in-repo documentationdocs/FoodOps_Delivery_User_Flow.md (v1.0, Jan 2026) predates the auth rework: it describes OTP login and an "All" filter tab that aren't in current code. Useful for flow shape, wrong on specifics.
  • No offline queue — a rider who taps Delivered in a dead zone loses the action (snackbar only); statuses on the POS will lag reality until the rider retries with signal.
  • Handover sends sessionId = outlet id — the handover call passes the stored outlet id as sessionId [code: cash_in_hand_controller.dart:80-82]; pairing of handovers to the correct register session is resolved server-side. If a handover doesn't appear in the expected session's Delivery Cash tab, check which session the server attached it to.
  • Dormant backend repofoodops.delivery.api looks plausible as a verification target (it has matching controllers) but is dead since 2025-11-27; everything live is in hubits-api-sales.
  • Client credentials are compiled in — API keys for the sales/identity APIs (and Google/OneSignal keys) are hardcoded in the app source rather than injected at build time; rotating them requires shipping a new build.
  • Everything visual is device-only — no emulator/device walk was done for this entry; all screen descriptions derive from code + the in-repo doc.

Related

  • Deliveries — the counter-side delivery queue, sector filter, driver assignment, and the same cash loop from the cashier's seat

  • Cash Handling — acknowledging rider cash into the session's Expected balance

  • Online Ordering Storefront — where many delivery orders come from

  • Sales Sessions — the register session the waiter mode joins and the handover lands in

  • User Stories: Online Ordering — US-ORD-014 (driver flow), US-ORD-017 (cash on delivery)

  • 2026-06-29 — codebase: NEW-STACK launch re-verification (July-10). Re-pinned hubits-api-sales@19190b9a04; foodops.delivery.mobile master HEAD now 1ff0a759f3 (commit 2026-06-15 "Refactor API endpoints", no CI). DORMANT/LEGACY launch posture FLAGGED prominently at the top: this Flutter app is manually built/sideloaded, its old backend foodops.delivery.api is retired (removed from source_repos), and the live delivery surface for launch is the counter delivery queue + hubits-api-sales (DeliveryController/DeliveryOrdersController/DeliveryManagementController/CashOnHandController all present on main). The app still points at sales-api.hubits.io + identity-api, so its documented loop (email+password login, self-assign via mark-enroute, settle incl. transfer-slip upload, Cash On Hand → handover → cashier acknowledge, waiter mode, online-required) remains accurate against the live sales API — but it is not a supported launch product. Corrected the stale "last commit 2026-05-22" note.

Additional verification events are appended here by the weekly KB sync and by manual re-verification passes. See _meta/failure-log.md for the global failure index.

Go beyond POS.
Choose the total solution.

Focus on creating the best experience for your guests while we handle the rest.