Repository navigation
Conversation
- Accepting an adhoc order is one conditional update, so of two drivers only the first gets it. The driver must work for the order's company and be online, a signed-in driver can only accept for themselves, and a canceled order can't be accepted.
- Drivers pinged or offered an order are of the order's company, not on another job, and of the vehicle class booked (`meta.vehicle_class`).
- `fleetops:dispatch-adhoc` re-pings again: both ends of its window were moved back 72 hours by one shared Carbon. It no longer re-dispatches, which pinged every driver twice.
- An order config with `meta.offers.enabled` offers an order to one driver at a time, nearest first, for `meta.offers.timeout` seconds. Drivers decline with `POST orders/{id}/decline`. With nobody left, the order is canceled as "No driver found".
- `fleetops:create-ride-config` adds a strict ride flow: dispatched, started (driver on the way), arrived, in_progress, completed, or no_show.
- Strict transitions are enforced on the public update-activity route too.
- Fare previews with `availability=1` add each class's seats and how soon its nearest free car could arrive.
- Customers can no longer subscribe to a driver's socket channel, whose location events carry the driver's phone number.
…count deletion
- Rides: a rider's order on a config with timed offers is adhoc, needs a fare, and takes its vehicle class from the fare. Server-set meta keys can't come from the rider.
- Chat: `customers/orders/{id}/messages` reads and sends messages with the ride's driver, as the rider. The channel is a core chat channel that Navigator shows as the order's customer thread.
- Ratings: `customers/orders/{id}/rating` rates the driver of a finished ride once. The driver's average is kept on the driver and returned with the ride's driver.
- Sharing: `customers/orders/{id}/share` returns a signed tracking link. The tracking page redeems it (`public/track/{number}/share`) for the rider's live view, without a code.
- Push: riders get a push to their own devices at each step of a ride and when the car is about to arrive. No Storefront or company notification setting is needed.
- Final fare: a completed ride is priced again from its car's positions and the trip time, through the rate the rider booked. The breakdown is kept on the order and bought as the order's price.
- A rider can't book while a card trip is unpaid (`meta.paid_at`, set by Ledger).
- `customers/ride-settings` returns the operator's emergency number and support contacts, set with `fleetops:create-ride-config`.
- `DELETE customers/me` deletes the rider's account, unless they have a ride under way.
Base automatically changed from
feat/passenger-rider-access
to
release/v0.7.0
November 6, 2026 02:41
…ers by default Timed offers were switched on by an order config meta key and only covered rider orders. Offering is now a dispatch strategy for every adhoc order. - `AdhocDispatchStrategy` contract with `broadcast` (every nearby driver at once) and `sequential` (one at a time, nearest first, with a timeout and "No driver found"). `AdhocDispatch::register()` adds more. - Sequential is the default. An order uses its own `adhoc_strategy`, else its config's, else the company's `fleetops.adhoc_strategy` option. - Both strategies record offers, so decline works for any adhoc order (a broadcast decline stops that driver being offered it again), and on accept everyone else's offer is withdrawn. - `HandleOrderDispatched` and `fleetops:dispatch-adhoc` hand adhoc orders to the strategy. Nearby drivers come from `DriverMatching::nearby()`. - Settings are columns, not meta. Orders get `adhoc_strategy` and `required_vehicle_type`, accepted on the order API and returned in its resource. Order configs get `adhoc_strategy`, `adhoc_offer_timeout`, `customer_orders_adhoc` and `match_vehicle_type`. - The rider route makes orders adhoc and sets their vehicle type from those config columns, instead of from the offers switch.
Open
…an take
Navigator lists offers with `GET orders?nearby=driver_…&adhoc=1&unassigned=1`. With one-at-a-time offers as the default, every nearby driver saw every adhoc order and all but one got a 409 on accept.
- For a driver asking with `nearby=driver_…`, an unassigned adhoc order shows only if they hold an open offer for it, or if its strategy lists orders openly and they haven't declined it. The strategy is worked out in SQL (the order's, then its config's, then the company default). Other orders are untouched.
- Each order carries `adhoc_offer: { status, expires_at }` for that driver, for a countdown.
- These lists skip the response cache, because they change with every offer.
- Strategies declare `listsOpenly()`: broadcast does, sequential doesn't.
…ts and rider data out of meta Strategies and settings: - Strategies have a `label()` and `description()`. `settings/adhoc-dispatch-strategies` lists them. - `settings/adhoc-dispatch-settings` reads and saves the company defaults: ping radius, strategy and offer time. - Order configs drop an unknown strategy and keep the offer time between 10 and 120 seconds. - `order.offer_closed` tells a driver's app when their offer expired or was withdrawn. Console: - A Settings → Dispatch page. - An "Adhoc dispatch" section in the order config editor. - A strategy and vehicle type on the order form, for adhoc orders. Rider data moves out of order meta, and #424's migrations become one: - The payment method is kept on the payload. - The final fare is the final quote, which records its distance and time, marked by `fare_finalized_at` and read with `Order::fare()`. - Ratings go in `driver_ratings`, with each driver's average and count on `drivers`. - The chat channel is `orders.chat_channel_uuid`. - Payment is `orders.paid_at`, set with `Order::markPaid()`. - The dispatch outcome is worked out from the offers. - Fare previews return seats and pickup ETA as quote fields. - Riders get these as top-level fields (`RiderOrder`).
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.
Adhoc dispatch strategies, safe accept, a ride flow, and what a rider app needs around a ride. Built for Passenger, but the dispatch part applies to every adhoc order. Stacked on #420, so review that first.
Adhoc dispatch strategies (every adhoc order)
AdhocDispatchStrategy. Two are built in:sequential(the default): one driver at a time, nearest first, for the offer timeout (default 20 s). When a driver declines or the time runs out, the next driver gets it. With nobody left, the order is canceled as "No driver found". Only the driver holding the offer may accept.broadcast: every nearby driver at once, which is the old behaviour. The first to accept gets the order.adhoc_strategy, then its order config'sadhoc_strategy, then the company'sfleetops.adhoc_strategyoption, thensequential.adhoc_offer_timeout, then the company'sfleetops.adhoc_offer_timeoutoption. It's kept between 10 and 120 seconds.AdhocDispatch::register($name, $class).order_offers):POST orders/{id}/declineworks for any adhoc order. A broadcast decline stops the driver being offered that order again.GET orders?nearby=driver_…(Navigator v2 and v3) only returns adhoc orders the driver can take:broadcast) that they haven't declined.adhoc_offer: { status, expires_at }for that driver.listsOpenly().HandleOrderDispatchedandfleetops:dispatch-adhocnow hand adhoc orders to the order's strategy. The ping and nearby-driver code moved out of them, into the strategies andDriverMatching::nearby().Settings and console
label()anddescription().int/v1/fleet-ops/settings/adhoc-dispatch-strategieslists every registered strategy, including ones an extension adds.int/v1/fleet-ops/settings/adhoc-dispatch-settings(GET/POST) reads and saves the ping radius, strategy and offer time in the company's Fleet-Ops options.@fleetbase/fleetops-databumped here. Until then the new fields aren't saved.order.offer_closedis broadcast on the driver's channel when their offer expires or is withdrawn, so their app drops it.New columns and tables (one migration)
adhoc_strategy: overrides the strategy for one order.required_vehicle_type: limits the order to drivers whose vehicletypematches.POST/PUT ordersand returned in the order resource.chat_channel_uuid,fare_finalized_atandpaid_at(Order::markPaid(), called by Ledger).rating_averageandrating_count.distanceandtime, the basis a quote was priced on.adhoc_strategy,adhoc_offer_timeout.customer_orders_adhoc: orders customers place are adhoc.match_vehicle_type: customer orders need a fare, and only go to drivers of the fare's vehicle type.Dispatch fixes (every adhoc order)
required_vehicle_type, when the order has one.fleetops:dispatch-adhocworks again. Both ends were taken from one shared Carbon instance, which moved them back 72 hours. It also no longer re-dispatches, which pinged every driver a second time.update-activityroute too.Rides
fleetops:create-ride-config {company}:customer_orders_adhoc,match_vehicle_typeandsequential.availability=1add seats and pickup ETA per vehicle type.Rider routes (Customer-Token)
match_vehicle_type, the order needs a fare and gets itsrequired_vehicle_typefrom the fare. A rider with an unpaid card trip gets 402.orders/{id}/messages(GET and POST) chats with the driver, always as the rider. It's a core chat channel that Navigator v3 shows as the order's customer thread.orders/{id}/ratingrates a finished ride once. The driver's average comes back with the ride's driver.orders/{id}/sharereturns a signed tracking-page link. The page redeems it atpublic/track/{number}/sharefor the rider's live view.ride-settingsreturns the operator's emergency number and support contacts.DELETE medeletes the account. It's refused during a ride.algorates take per-km, per-minute and minimum fares. The fare is bought as the order's price, so Ledger bills the final amount.Behaviour change
Adhoc orders now default to
sequential. To keep pinging every nearby driver at once, set the company'sfleetops.adhoc_strategyoption or an order config'sadhoc_strategytobroadcast.Rider data is no longer in order meta
payment_methodand keeps it on the payload it builds.fare_finalized_at.Order::fare()reads it.driver_ratings.orders.chat_channel_uuidpoints to the ride's channel.orders.paid_at.Order::dispatchOutcome()), not stored.availabilityis asked for.fare,rating,payment_method,paid_atanddispatch_outcomeare top-level fields (RiderOrder).Testing
DriverControllerAuthFlowsTest, which also fails without these changes (the local vendored core-api has noAppReview).