Migrate console off jupiter.nearle.app to the Doormile Express API
Retires REACT_APP_URL/URL2/URL3 in favor of REACT_APP_DOORMILE_URL across every page (orders, deliveries, riders, tenants, pricing, profile, reports, dispatch). Fixes several field-mapping and envelope-check bugs found along the way, most notably that /admin/milers/:id routes (block, assign-vehicle, edit, notify) key off milerprofileid, not userid, and that a miler's real fields are phone/availabilitystatus/displayname, not contactno/status/ firstname+lastname (confirmed against a live read-only session). Also fixes several silent-failure bugs uncovered during that audit: order creation and order cancellation showed a success toast but gave no feedback at all on failure (createorder1.js had a dead notifyadmin() call that left the loading spinner stuck forever on every failed submit), and Tenants.js's pricing/profile updates never surfaced a failed response to the operator. The AI dispatch optimiser (routes.workolik.com/routemate.workolik.com) and its jupiter.nearle.app delivery-commit call remain untouched by design — separate solver service with no equivalent in the new API.
This commit is contained in:
254
jupiter2doormile.md
Normal file
254
jupiter2doormile.md
Normal file
@@ -0,0 +1,254 @@
|
||||
# jupiter → Doormile
|
||||
|
||||
What the old Nearle/jupiter API did, and what replaces it in Doormile. Two
|
||||
surfaces only — the **express console** and the **miler app**. Hub console, CRM
|
||||
and the B2C customer app are out of scope here.
|
||||
|
||||
Base URLs:
|
||||
|
||||
| | jupiter | Doormile |
|
||||
|---|---|---|
|
||||
| API | `jupiter.nearle.app/live/api/v1` | `api.doormile.com/api/v1` |
|
||||
| Write path | `queue.workolik.com` (TLS verify off, hardcoded IP pin) | same host, no side channel |
|
||||
|
||||
**Confidence marking.** Paths marked ✅ were read off real network logs from the
|
||||
live jupiter console. Paths marked ~ come from the prior-session analysis of the
|
||||
jupiter codebase and have not been re-confirmed against a live request — check
|
||||
the exact spelling before wiring anything to them.
|
||||
|
||||
Status: **Done** = built and hit with a real request · **Built** = written and
|
||||
compiled, never called · **Gap** = nothing replaces it yet · **Dropped** =
|
||||
deliberately not migrated.
|
||||
|
||||
---
|
||||
|
||||
## 1. Auth
|
||||
|
||||
| jupiter | Doormile | Status |
|
||||
|---|---|---|
|
||||
| ~ console login (undocumented in jupiter's own API docs — found only by reading the console source) | `POST /admin/login` → `{email, password}` | **Done** |
|
||||
| ~ rider login | `POST /miler/login` then `POST /miler/verify-pin` | **Done** |
|
||||
|
||||
Two real differences:
|
||||
|
||||
- Doormile splits rider login into **phone → PIN**, two calls. jupiter did it in
|
||||
one.
|
||||
- The Doormile console token carries **`tenantid`**. jupiter had no tenant
|
||||
concept on the login at all; every console user saw everything. This is the
|
||||
single biggest behavioural change for a client account.
|
||||
- `configid` must be **1001** on both miler calls. There is no jupiter
|
||||
equivalent — it's a Doormile login partition.
|
||||
|
||||
---
|
||||
|
||||
## 2. Express console
|
||||
|
||||
### 2.1 Rider screens
|
||||
|
||||
| jupiter | Doormile | Status |
|
||||
|---|---|---|
|
||||
| ✅ `GET /deliveries/getridersummary/?applocationid=&fromdate=&todate=` | `GET /admin/milers/summary?applocationid=&from=&to=&tenantid=&hubid=` | **Done** |
|
||||
| ~ rider list | `GET /admin/milers?applocationid=&hubid=&tenantid=` | **Done** |
|
||||
| ~ rider detail | `GET /admin/milers/:id` | **Done** |
|
||||
| ~ `riderlogs` (the 1.17M-row, zero-index table) | `GET /admin/milers/:id/logs?from=&to=&limit=` | **Done** |
|
||||
| ✅ `getriderlocationsummary` *(name confirmed, path inferred)* | covered by `milers/summary` (`currentlatitude/longitude`, `lastpingat`) and `milers/:id/logs` | **Done** |
|
||||
| — *(no jupiter equivalent)* | `GET /admin/milers/:id/activity?from=&to=` | **Done** |
|
||||
| ~ rider create/edit | `POST /admin/milers`, `PUT /admin/milers/:id` | **Done** |
|
||||
| ~ block rider | `PUT /admin/milers/:id/block` | **Built** |
|
||||
| ~ assign vehicle | `PUT /admin/milers/:id/assign-vehicle` | **Built** |
|
||||
| — | `POST /admin/milers/:id/notify` | **Done** |
|
||||
|
||||
Parameter translation: jupiter used `fromdate`/`todate`, Doormile uses
|
||||
`from`/`to`. Both `YYYY-MM-DD`. jupiter's `applocationid=0` meant "all cities";
|
||||
Doormile means the same by **omitting** the param.
|
||||
|
||||
### 2.2 Orders / deliveries
|
||||
|
||||
| jupiter | Doormile | Status |
|
||||
|---|---|---|
|
||||
| ✅ `GET /deliveries/getdeliveries/` | `GET /admin/bookings` + `GET /admin/consignments` | **Done** |
|
||||
| ~ `getdelivery` / `getorders` | `GET /admin/bookings/:id`, `GET /admin/consignments/:id` | **Done** |
|
||||
| ~ `POST /deliveries/createdeliveries` | `POST /admin/expressbooking` | **Done** |
|
||||
| ~ `createdeliveries` in bulk | `POST /admin/expressbooking/bulk` (max 200, per-row results) | **Built** |
|
||||
| ~ `PUT /deliveries/updatedelivery` | **split into 11 endpoints** — see §4 | **Done / partial** |
|
||||
| — | `GET /admin/bookings/:id/track` | **Done** |
|
||||
| — | `GET /admin/consignments/:id/logs` | **Done** |
|
||||
| — | `GET /admin/consignments/track/:trackingno` | **Built** |
|
||||
|
||||
Two jupiter bugs that do not carry over, by construction:
|
||||
|
||||
- `getdeliveries` returned **every row 21×** (unconstrained `LEFT JOIN
|
||||
tenantpricing`, `DISTINCT` over 87 columns that deduped nothing). Doormile's
|
||||
list endpoints are paginated (`pageno`/`pagesize`, default 500, cap 1000) and
|
||||
return one row per booking.
|
||||
- `createdeliveries` had a quadratic insert bug — a slice declared outside the
|
||||
loop kept accumulating, producing ~2× duplicate `deliveryqueues` rows
|
||||
(66,446 deliveries → 132,826 rows, confirmed live). `createExpressBooking` is
|
||||
a single transaction per booking; `/bulk` loops it and reports per-row.
|
||||
|
||||
### 2.3 Reporting
|
||||
|
||||
| jupiter | Doormile | Status |
|
||||
|---|---|---|
|
||||
| ✅ `GET /deliveries/getreportsummary/?applocationid=&tenantid=&locationid=&fromdate=&todate=` | `GET /admin/reports?from=&to=&tenantid=&locationid=&hubid=` | **Done** |
|
||||
| ~ `getlocationsummary` | `GET /admin/locations/summary?tenantid=&locationid=&from=&to=` | **Done** |
|
||||
| — | `GET /admin/dashboard?tenantid=` | **Done** |
|
||||
|
||||
`locationid` is supported: it narrows every figure to one client site, and
|
||||
`/admin/reports` now carries a `by_location` block alongside `by_hub`,
|
||||
`by_tenant` and `by_rider`.
|
||||
|
||||
**Attribution caveat.** Per-site figures group by `tenantlocationid` on the
|
||||
booking — a column added 2026-08-06. The pre-existing `pickuplocationid` column
|
||||
is *not* it: that one foreign-keys to `appcustomerlocations`, the B2C customer's
|
||||
saved address, so writing a client-site id into it fails the insert. Every
|
||||
booking created before 2026-08-06 has no site at all.
|
||||
|
||||
Since the console sends a kitchen's *address* rather than its id,
|
||||
`createExpressBooking` resolves the site itself — nearest stored location within
|
||||
150m, falling back to an address match. Bookings with no site are reported as
|
||||
their own `"Unattributed"` row rather than dropped, so per-site rows still add
|
||||
up to the summary total. Sending `tenantlocationid` explicitly is exact and
|
||||
always wins.
|
||||
|
||||
**`applocationid` (city) is still not a report parameter.** jupiter had it;
|
||||
Doormile filters by `hubid` instead. Only matters once one client runs in more
|
||||
than one city.
|
||||
|
||||
### 2.4 Tenants and their sites
|
||||
|
||||
| jupiter | Doormile | Status |
|
||||
|---|---|---|
|
||||
| ✅ `GET /tenants/gettenants/` | `GET /admin/tenants` | **Done** |
|
||||
| ✅ `GET /tenants/gettenantlocations/` | `GET /admin/tenants/:id/locations` | **Done** |
|
||||
| ~ `getlocations` / `getlocation` / `getlocationdetails` | same as above | **Done** |
|
||||
| ~ tenant create/edit | `POST /admin/tenants`, `PUT /admin/tenants/:id` | **Done** |
|
||||
| ~ location create/edit | `POST /admin/tenants/:id/locations`, `PUT /admin/tenantlocations/:id` | **Done** |
|
||||
| ~ `getbranches` | `GET /admin/hubs` — *jupiter "branches" ≈ Doormile hubs; verify this is the same concept before relying on it* | **Built** |
|
||||
| ~ `getlocationsummary` | `GET /admin/locations/summary` — see §2.3 | **Done** |
|
||||
|
||||
Doormile adds `locationname` on a tenant location. jupiter identified a site by
|
||||
its address alone, which does not distinguish two branches on one street.
|
||||
|
||||
---
|
||||
|
||||
## 3. Miler app
|
||||
|
||||
jupiter's rider app drove almost everything through one overloaded endpoint.
|
||||
Doormile gives each action its own route.
|
||||
|
||||
| jupiter action | Doormile | Status |
|
||||
|---|---|---|
|
||||
| ~ rider login | `POST /miler/login` + `POST /miler/verify-pin` | **Done** |
|
||||
| ~ PIN reset | `POST /miler/reset-pin` — **now admin-only**, see §5 | **Done** |
|
||||
| ~ location ping | `PUT /miler/location` | **Done** |
|
||||
| ~ availability toggle | `PUT /miler/availability` | **Done** |
|
||||
| ~ assignment list | `GET /miler/assignments`, `GET /miler/assignments/:id` | **Done** |
|
||||
| ~ accept | `POST /miler/assignments/:id/accept` | **Done** |
|
||||
| ~ reject | `POST /miler/assignments/:id/reject` | **Built** |
|
||||
| ~ rider logs write | `POST /miler/logs`, `POST /miler/status` | **Done** |
|
||||
| ~ per-delivery logs | `POST /miler/consignments/logs` | **Done** |
|
||||
| — | `POST /miler/duty/start`, `PUT /miler/duty/end`, `GET /miler/duty/current` | **Done** |
|
||||
| — | `POST /miler/breaks/start`, `PUT /miler/breaks/end` | **Done** |
|
||||
| — | `GET /miler/earnings` | **Done** |
|
||||
| — | `POST /miler/support`, `GET /miler/support` | **Built** |
|
||||
| — | `GET /miler/notifications` | **Done** |
|
||||
| — | `PATCH /miler/notifications/:id/read` | **Gap** — stub, persists nothing |
|
||||
|
||||
---
|
||||
|
||||
## 4. `PUT /deliveries/updatedelivery` — the 11-way split
|
||||
|
||||
This is the centre of the migration. jupiter overloaded one endpoint for **11
|
||||
distinct real actions**, distinguished only by which JSON fields happened to be
|
||||
non-empty. Each is now its own route with its own validation and its own status
|
||||
transition.
|
||||
|
||||
**Eight rider actions:**
|
||||
|
||||
| Action | Doormile |
|
||||
|---|---|
|
||||
| reached pickup | `POST /miler/bookings/:bookingid/reached` |
|
||||
| confirm parcel + dimensions | `POST /miler/bookings/:bookingid/parcel` |
|
||||
| collect payment | `POST /miler/bookings/:bookingid/payment` |
|
||||
| pickup complete | `POST /miler/bookings/:bookingid/pickup-complete` |
|
||||
| needs a bigger vehicle | `POST /miler/bookings/:bookingid/vehicle-required` |
|
||||
| cancel before pickup | `POST /miler/bookings/:bookingid/cancel` |
|
||||
| deliver | `POST /miler/consignments/:id/deliver` |
|
||||
| skip / failed attempt | `POST /miler/consignments/:id/skip` |
|
||||
|
||||
**Three console actions** that were bundled into the same rider endpoint:
|
||||
|
||||
| Action | Doormile |
|
||||
|---|---|
|
||||
| assign a rider | `POST /admin/bookings/:id/assign-miler` |
|
||||
| change status | `PUT /admin/bookings/:id/status` · `PUT /admin/consignments/:id/status` |
|
||||
| cancel | `POST /admin/bookings/:id/cancel` · `POST /admin/bookings/bulk-cancel` |
|
||||
|
||||
`pickup-complete` is the pivot the old system had no concept of: it converts the
|
||||
booking into a **consignment**, recomputes chargeable weight from the dimensions
|
||||
the rider entered, and decides routing — matching 3-digit pincode prefixes go
|
||||
straight to `Out_for_Delivery` (hyperlocal), everything else routes via a hub.
|
||||
|
||||
---
|
||||
|
||||
## 5. Behaviour changes that break a naive repoint
|
||||
|
||||
Response shapes are completely different — flat 87- and 92-column jupiter rows
|
||||
versus nested Doormile JSON. Every screen that parses a response needs
|
||||
rewriting, not repointing. Beyond that:
|
||||
|
||||
1. **Tenant scoping is real now.** A client console login sees only its own
|
||||
tenant. `?tenantid=` narrows for Doormile staff; a client passing another
|
||||
tenant's id gets **403**. Cross-tenant reads of a single resource return
|
||||
**404**, not 403, so ids aren't probeable. jupiter had none of this.
|
||||
2. **`configid` 1001** on every miler auth call. No jupiter equivalent.
|
||||
3. **PIN reset is admin-only.** jupiter let anyone reset a rider PIN with just a
|
||||
phone number, which is the login identifier, not a secret. Two calls took over
|
||||
any account. The rider app must not call `/miler/reset-pin` — route resets
|
||||
through ops.
|
||||
4. **Identity comes from the token, never the body.** jupiter's telemetry
|
||||
endpoints took `userid` from the request body. Doormile ignores it.
|
||||
5. **Telemetry lat/long/speed/battery are strings**, and
|
||||
`POST /miler/consignments/logs` takes a **bare JSON array**.
|
||||
6. **Delivery OTP is opt-in per tenant** (`Tenant.Requiredeliveryotp`), default
|
||||
off. Off for DailyGrubs. When on, it's verified server-side.
|
||||
7. **Dates**: `from`/`to`, not `fromdate`/`todate`. IST wall-clock throughout.
|
||||
8. **CityGate**: a booking's pickup pincode prefix must be an open city — `641`
|
||||
Coimbatore, `600` Chennai, `560` Bengaluru, `500` Hyderabad, `629` Nagercoil.
|
||||
jupiter had no such gate.
|
||||
|
||||
---
|
||||
|
||||
## 6. Gaps — jupiter did this, Doormile does not yet
|
||||
|
||||
| What | Detail |
|
||||
|---|---|
|
||||
| `applocationid` on reports | jupiter could filter a report by city. Doormile filters by `hubid`. Only bites when one client operates in several cities. |
|
||||
| **Route optimisation** | jupiter used external paid services (`routes.workolik.com`) for multi-stop sequencing. Nothing in Doormile replaces true stop-ordering. `HubBatchAssign` decides *who* gets a booking, not *what order* to run stops in. |
|
||||
| Notifications read-state | `PATCH /miler/notifications/:id/read` is a stub; no table exists. |
|
||||
| `riderkms` / `ridercharges` backfill | Populated on new deliveries only. Rows completed before 2026-08-06 read 0 and will not backfill themselves. |
|
||||
|
||||
---
|
||||
|
||||
## 7. Dropped on purpose
|
||||
|
||||
| What | Why |
|
||||
|---|---|
|
||||
| `/v1/substitutions` CRUD | Rider substitutions. Low traffic in the old system; Suriya's call. Revisit if it turns out to matter. |
|
||||
| jupiter's v2 endpoints | They wrote **only to Redis**, invisible to the v1/v3 Postgres reads — genuine split-brain, with a Redis `INCR` id space that could collide with the Postgres sequence. Doormile keeps Redis for ephemeral telemetry only; durable state is always Postgres. |
|
||||
| `queue.workolik.com` write path | Separate host with TLS verification disabled and a hardcoded IP pin. Not reproduced. |
|
||||
| ~20 never-populated columns on `orders`, 6 lat/lng pairs for 3 real points on `deliveries`, status spread across 6 text+timestamp column pairs | Replaced by a normalised schema with a real event-log table (`consignmenthistory`). |
|
||||
|
||||
---
|
||||
|
||||
## 8. What is not migrated at all
|
||||
|
||||
Nothing on the client side has moved. The rider Flutter app and
|
||||
`doormile_express_console` still call `jupiter.nearle.app`. Doormile having the
|
||||
endpoint does not mean traffic uses it.
|
||||
|
||||
Suggested order: pick one net-new console screen (the rider summary, or
|
||||
reports) and wire it to Doormile first — it replaces nothing live, so it is the
|
||||
cheapest real proof the cutover works. Then the higher-traffic screens
|
||||
(deliveries list, rider status updates), then the rider app.
|
||||
Reference in New Issue
Block a user