Repository navigation
v3: realtime order changes, reassignment, order chat and bookings - #114
Merged
Merged
Conversation
Copy for the order-operations work that follows: the "this order changed" banner, item quantity / note / unavailable / substitute, cash on delivery, delivery instructions and schedule, customer and dispatch contact, booking jobs and the bring-to-the-job checklist, direct offers (countdown, expired, unsupported), reassignment notices, and order-thread roles. Booking statuses `booked` and `provider_enroute` get labels. Spanish kept in parity.
…signed orders - Which status finishes an order now comes from the order config's `complete` activity (data/orderStatus), falling back to the old hard-coded sets. The store's "active" view, Orders' segments and the stop screens all use it, so a booking flow that ends on its own code finishes correctly. Configs are cached outside React (data/orderConfigCache) so the store can read them, and a config arriving later recomputes the views. - Driver-scoped list queries stamp their rows and reconcile the store after each run: an order the query no longer returns (within its window) is removed; active ones are recorded as reassigned with a notice for the driver. upsertMany only ever added, so a reassigned order stayed on the device and could be advanced into a server refusal. - remove(id, 'reassigned') records why, for screens holding the order to leave; notices() / dismissNotice() back the "order was reassigned" banner. - useOrderRecord refetches on every live-refresh bump and on a reload token (focus), and treats an order now naming another driver as reassigned. - pendingOffers(driverId): orders whose meta.offer is `offered` to this driver. - List queries prefetch the configs behind their rows (once each).
…-ops and chat events
- App.tsx passes the SocketCluster config v2's provider builds (env defaults,
overridden by a linked instance) to V3App. Without it SocketProvider
mounted disabled and the app heard nothing.
- SocketProvider holds the socket in state, not a ref. Children subscribe in
effects that run before the provider's, so the driver channel was handed a
no-op and never subscribed; `listen` now changes identity when the socket
exists and every subscriber re-runs. A reconnect bumps live refresh, since
events broadcast while disconnected are gone.
- Event map gains order.changed (with summary/changes), order.updated,
order.driver_unassigned, order.offer_expired, the OrderAssigned
notification's `order.assigned_notification`, and chat message events.
The order id is also read from data.id. Unknown events are still dropped
but logged in development.
- applyRealtimeEvent is the one handler for socket and push: unassignment
removes the order (reassigned), order.changed keeps its summary for the
order screen's banner, order events bump live refresh, chat events bump a
separate chat counter.
- useOrderChannel follows `order.{id}` for the open order;
useChatChannelSubscriptions follows `chat.{id}` / `chat_channel.{id}`.
- Push plumbing: V3App takes `subscribeToPush` (App.tsx adapts v2's
NotificationProvider); parsePush / pushToEvent / destinationOf describe what
a push means, and a root navigation ref lets a tapped push open an order or
conversation from any tab, even before the container is ready.
Contract 1.4: an order put in a driver's name starts with meta.offer
`offered` and an `expires_at`; the driver answers with
POST v1/orders/{id}/accept or /decline. Ad-hoc offers keep start + assign.
- useRespondToOffer posts accept/decline through the adapter, never queued
(added to NEVER_QUEUE — an answer replayed after the window is not an
answer). Outcomes: accepted, declined (the order leaves the store, no
reassignment notice), expired, unsupported (404/405: the server does not
have the endpoint yet), failed.
- useCountdown / formatCountdown tick down to expires_at.
- OfferCard (previously unused) now translates its copy, shows an expired
state that withdraws Accept, and wires Decline and Details.
- DirectOfferCard wraps it with the countdown and the answer, for Today,
Offers and the order itself.
- OffersScreen lists direct offers above the nearby ad-hoc ones and reloads
on focus. Also fixes a pre-existing test that never unmounted its tree.
…xt step, proof loop
- The order, tracker and next step are refetched on order events (live
refresh, `order.{id}`) and on focus; previously the store copy was read
once and the tracker loaded once.
- "This order changed" banner from order.changed / order_changed using the
server's summary; when the changes include the drop-off it offers
"Re-open navigation" to the order's current stop.
- Navigate targets the stop being headed to (current_waypoint, else the
drop-off), not payload.dropoff.
- Items show quantity (meta.quantity), the item note, unavailable items and
substitutes (meta.substitute_for), per contract 4.
- Cash on delivery: payload.cod_amount, "to collect" per meta.payment.state
(absent on older orders: the amount alone means cash is due; paid /
refunded owe nothing). Completing the order waits for "I collected …".
- Delivery instructions (drop-off place meta/instructions), order notes, the
scheduled window or booking slot, and customer Call / Message plus Message
dispatch — through the order's own chat threads when they exist.
- Next step from GET orders/{id}/next-activity when the server answers; it
has evaluated the flow's logic, so a branch becomes a choice of buttons
instead of "ask dispatch". Falls back to the client sequencer otherwise.
"Finished" is the config's `complete` flag.
- Proof loop: a capture is recorded against its order and activity
(proofLedger, persisted); returning from capture advances once, sending the
proof id with the update, instead of opening capture again.
- Booking orders (meta.is_booking) read as a Job with their slot and a
"Bring to the job" checklist; booked / provider_enroute get registry
entries and `confirmed` ranks second in the lifecycle.
- A pending direct offer shows on the order and holds the flow until
accepted. A reassigned order calls onRemoved and says so.
- Orders and Today show reassignment notices; Orders links to Offers with a
waiting count, Today shows the first direct offer; Orders' completed
segment uses the config-driven finished check.
- Chat data: channel meta (order_public_id, kind), participant meta.role,
message meta.system; orderThreadsOf picks the dispatch (`driver`) and
customer (`group`) threads; lists refresh on chat events; an unread store
for the Inbox badge.
…eads, v3 inbox badge - PushRouter: a foreground push is applied as the socket event it duplicates (refresh; remove a reassigned order; keep an order change's summary); a tapped push opens the order (order_changed, order_assigned, …), the Orders list (order_unassigned) or the conversation (chat_message_received). - Screens holding a reassigned order (detail, item, timeline, proof capture, destination, hand-off) pop to the top of their stack; the list there says the order was reassigned. - The navigation hand-off takes the order id and reads the order's current destination from the store, so a drop-off moved after the tap (or while the picker is open) still sends the driver to the right place. - Order detail refetches on focus, opens order threads in the Inbox, and falls back to a new conversation for "Message dispatch". Offers is reachable from Orders and Today. - ChatWatcher keeps the driver's channels loaded and followed on the socket; the Inbox badge is v3's own unread total instead of v2's ChatContext. - Conversation: system messages (meta.system) render as notices, order threads label each side (Store, Customer, You), and the header and inbox row name the order the thread is about.
5 of 16 tasks
An order assigned to a driver is simply theirs. Only ad-hoc orders are offered, through Fleet-Ops' existing ping, and taken with `start` + `assign` (the Offers screen and its Accept / Dismiss). The direct-offer layer built on `meta.offer` duplicated that and is removed with its Fleet-Ops side: - Reverts "Offers: direct offers with a countdown, accept and decline". - Today, Orders and the order no longer show a direct offer or hold the flow for one; the Orders screen keeps a plain button to the ad-hoc Offers screen. - The order store, realtime (`order.offer_expired`) and strings lose the direct-offer pieces.
Draft
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What and why
Navigator v3's side of the order operations program (shared contract: realtime
order.changed, reassignment, order chat, service bookings, per-item meta). An audit of v3 found realtime switched off, order screens that never refetched, no v3 push handling, reassigned orders left on the device, an unreachable Offers screen, and a proof-capture loop. This PR fixes those and adds the contract's driver-facing surfaces.1. Realtime was off
App.tsxnow passes the same SocketCluster config v2 builds (resolveConnectionConfig('SOCKETCLUSTER_*')) toV3App; before,SocketProvidermounted withenabled=false.SocketProviderholds the socket in state. Child effects run before the provider's, souseDriverChannelgot a no-op and never subscribed again;listennow changes identity once the socket exists and subscribers re-run. A reconnect bumps live refresh. (Test:SocketProvider.test.tsxmounts a subscriber before the socket and receives an event.)2. Event map
Added
order.changed(summary, changes),order.updated,order.driver_unassigned,order.assigned_notification(OrderAssigned notification), and chat message events. The order id is also read fromdata.id. Unknown events are still dropped but logged in__DEV__. The open order is followed onorder.{id}.3. Order detail refetches
The order (
GET orders/{id}), tracker and next step reload on order events and on focus.4. v3 push handling
V3ApptakessubscribeToPush(App.tsx adapts v2's NotificationProvider). A foreground push is applied as the socket event it duplicates. A tapped push opens the order (order_changed,order_assigned, …), the Orders list (order_unassigned) or the conversation (chat_message_received) through a root navigation ref. If the container isn't ready yet, the navigation waits for it.5. Reassignment
order.driver_unassignedandorder_unassignedremove the order. Driver-scoped list queries now reconcile the store: rows they listed before and no longer return (within the page's window) are removed. Active ones are marked reassigned. Screens holding the order pop to the top of their stack, and Orders and Today show "Order FLE… was reassigned".6. Changed-order banner and navigation
The order shows a "This order changed" banner with the server's
summary. Whenchangesincludesdropoff, the banner offers Re-open navigation. Navigate targets the current stop (current_waypoint, else the drop-off). The hand-off now takes the order id and reads the order's current destination from the store, so it no longer relies on a snapshot in route params.7. On the order
meta.quantity), item notes, unavailable items and substitutes (meta.substitute_for).payload.cod_amount, shown as "to collect" permeta.payment.state. Orders withoutmeta.payment(older ones) treat the amount alone as due. The completing step waits for I collected ….meta.instructions/instructions) and order notes.8. Offers
Only adhoc orders are offered: they appear on the Offers screen and are taken with
start+assign, as before. An order assigned to the driver is simply theirs; there is nothing to accept. Offers is reachable from the Orders screen. (Direct offers with a countdown were built here and removed in d6282b1, together with fleetbase/fleetops#398.)9. Chat
chat.{id}andchat_channel.{id}, and chat pushes are routed.meta.order_public_id) are labelled in the Inbox and the conversation header. On the order, thedriverthread backs "Message dispatch" and thegroupthread backs "Message customer".meta.system) render as notices, and senders carry role labels (Store, Customer, You).10. Flow
completeflag, falling back to the old sets. This replaces the hard-coded sets inorderStore,OrdersScreenanduseOrderRecord.GET orders/{id}/next-activitywhen the server answers. It has already evaluated the flow's logic, so a branch is offered as one button per option instead of "ask dispatch". Otherwise the app falls back to the client-side sequencer.11. Bookings
Orders with
meta.is_bookingread as a Job, with the slot and a Bring to the job checklist built from the order's entities.bookedandprovider_enroutehave registry entries and labels, andconfirmedranks second in the lifecycle.Blocked on / depends on server work
order.changedbroadcast and theorder_changed/order_unassignedpushes, plusorder.driver_unassignedondriver.{public_id}(Storefront edit service and Fleet-Ops, 1.1–1.3). Until then the banner never shows and reassignment relies on list reconciliation.POST v1/chat-channelsaccepts nometa, and no driver-sideorders/{id}/chatroute exists, so "Message customer" uses agroupthread only when one exists (else SMS to the customer's phone) and "Message dispatch" falls back to a new conversation. Channelmetais already returned. Participantmeta.roleand messagemeta.systemare not yet on core-api's publicChatParticipant/ChatMessageresources, so role labels and system notices appear once those ship.meta.payment(contract 3) for the "to collect" state. Older orders fall back topayload.cod_amount.meta.is_booking/meta.bookingand thestorefront-bookingflow (contract 6).Validation
yarn typecheck(tsc -p tsconfig.v3.json): 15 errors, all pre-existing in v2 files pulled in by v3 (src/components/CameraCapture.tsx,src/hooks/use-app-theme.ts). The base branch has the same 15 and there are none in v3.yarn lint: 600 errors / 419 warnings, against 600 / 411 on the base. The errors are all pre-existing (v2 tree, scripts, jest setup), and running eslint on the files changed here reports no errors.yarn test: 69 suites, 1016 tests pass, up from 965. New tests:Docs
No docs or API spec changes from this PR. The endpoints and events it uses are defined by the order-operations contract and documented with the server PRs (fleetops, storefront, core-api).