jupiter's getreportsummary took a locationid — per kitchen, per branch. That was the one report parameter with no Doormile equivalent, and for a food client with 23 kitchens it is the difference between one number and a usable report. GET /admin/reports?locationid= narrows every figure to one site GET /admin/reports now carries a by_location block GET /admin/locations/summary the standalone per-site table The filter alone would have been useless: pickuplocationid was null on every booking in the system, because the console sends a kitchen's address rather than its id. createExpressBooking now resolves the site itself — nearest stored location within 150m, falling back to an address match, nil when nothing matches confidently, since a wrong attribution silently moves orders between kitchens. An explicit pickuplocationid still wins. Bookings that named no site are reported as their own "Unattributed" row rather than dropped, so per-site rows add up to the summary total. Two fixes found while in here: - the payments join in the per-site query fanned out, counting a booking once per payment row; payments are now pre-aggregated per booking - by_rider was empty for every client login, which reads as "your riders did nothing". Riders are tenant-scoped now, so a client sees its own. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
251 lines
13 KiB
Markdown
251 lines
13 KiB
Markdown
# 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 depend on `pickuplocationid` being set
|
||
on the booking. Every booking created before 2026-08-06 has it null, and the
|
||
console sends a kitchen's *address*, not its id. So `createExpressBooking` now
|
||
resolves the site itself — nearest stored location within 150m, falling back to
|
||
an address match — and unattributed bookings are reported as their own
|
||
`"Unattributed"` row rather than dropped, so the per-site rows still add up to
|
||
the summary total. Sending `pickuplocationid` explicitly is still better 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` | **Gap** — no per-site rollup, same missing piece as `locationid` above | **Gap** |
|
||
|
||
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.
|