diff --git a/docs/jupiter2doormile.md b/docs/jupiter2doormile.md new file mode 100644 index 0000000..af911bd --- /dev/null +++ b/docs/jupiter2doormile.md @@ -0,0 +1,238 @@ +# 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=&hubid=` | **Done** | +| — | `GET /admin/dashboard?tenantid=` | **Done** | + +**One parameter has no equivalent yet: `locationid`.** jupiter could report per +client *site* — per kitchen, for a food client. Doormile carries +`pickuplocationid` on both the booking and the consignment, so the data is +there, but `/admin/reports` does not group or filter by it. See §6. + +### 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 | +|---|---| +| **Per-site reporting** | jupiter's `getreportsummary` took `locationid`. Doormile stores `pickuplocationid` on bookings and consignments but neither groups nor filters by it. For a food client this is "how many orders went out of which kitchen" — likely the first thing DailyGrubs asks for. | +| `getlocationsummary` | No per-site rollup endpoint. | +| **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.