Adds the backend half of the ExpressDispatchAgent flow. Express orders can now
be created batch by batch (bulk create only accumulates them, unassigned), then
an operator hits one endpoint to hand the whole pending set to the agent for
tenant-scoped assignment + road sequencing. The normal B2C flow is untouched.
- POST /admin/expressbooking/dispatch: manual trigger. Console-auth, tenant-
scoped; gathers the tenant's pending unassigned express orders (or a chosen
subset) and publishes express.dispatch_requested.
- internal API for the agent: GET /internal/express/riders (tenant's available
riders), GET /internal/express/bookings, POST /internal/express/assign (writes
the agent's decided assignments with their sequence; re-checks the already-
assigned guard so the agent can't double-assign).
- booking_assignment_service.go: extracted a behavior-preserving assignMilerTx
core; AssignMilerToBooking is unchanged in behavior. assignExpressStops writes
a batch, one FCM per rider instead of one per stop.
- EXPRESS JetStream stream / express.dispatch_requested subject.
- Gated behind EXPRESS_AGENT_ENABLED (default off): deploying this changes
nothing until the agent is confirmed running and the flag is flipped.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The admin customer page collected an address (door no, street, suburb,
city, state, postcode, landmark, lat/lng) that the backend had nowhere to
store, so it was silently dropped on save. Add those fields flat onto
appcustomers, matching the reference console's shape, plus an applocationid
for zone scoping.
- GET /admin/customers: add ?applocationid= filter; emit firstname/lastname
split and the address fields alongside the existing joined name.
- GET /admin/customers/summary (new): stat-tile counts (total/active/blocked)
scoped like the list, so the client stops deriving them from the full page.
- PATCH /admin/customers/🆔 accept firstname/lastname directly (single name
still splits as a fallback) and persist every address field; pointer fields
so an omitted field is not confused with one cleared to empty.
Pagination and keyword search were already present. Additive, nullable
columns — AutoMigrate handles it, no data rewrite.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>