Files
doormile_backend/docs/jupiter2doormile.md
Suriya 90fa4fbb74 fix: per-site attribution needs its own column, not pickuplocationid
Caught by testing the previous commit against production: creating a booking
with a resolved site failed with

  pickupbookings_pickuplocationid_fkey
  FOREIGN KEY (pickuplocationid) REFERENCES appcustomerlocations(...)

pickuplocationid is the *customer's* saved address, a B2C concept. It never
referred to the client company's own kitchens or branches. The pre-existing
code that validated an incoming pickuplocationid against TenantLocation was
wrong on the same point and would have 500'd for any caller that used it — it
had simply never been called with a value.

Adds tenantlocationid to pickupbookings and consignments (nullable, indexed,
additive via AutoMigrate), carried across at pickup, and points the reporting
filter, the by_location breakdown and the Unattributed bucket at it.

The booking request accepts tenantlocationid, and still accepts
pickuplocationid as an alias so anything written against the earlier docs
starts working instead of failing.

Also gofmt on the two model files touched; booking.go was already failing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 13:08:57 +05:30

13 KiB
Raw Blame History

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/hubsjupiter "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-pinnow 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.