From 588f84c344c19725e378ac92ec8ee6e3843fa038 Mon Sep 17 00:00:00 2001 From: Suriya Date: Mon, 27 Jul 2026 12:38:05 +0530 Subject: [PATCH] docs: runbook for creating a test booking and assigning a miler Captures the working request bodies for the customer login/booking and the admin/hub assign-miler endpoints, plus the constraints that are easy to hit and hard to diagnose: - CityGate only accepts pickup pincodes in 641/600/560/500 - hub assign-miler 403s unless milerprofiles.hubid matches the calling hub, and most milers have hubid NULL - CreateCustomerBooking auto-assigns in the background, so a booking is often already on a miler before you assign one - the routing watchdog sweeps unaccepted assignments within ~a minute and may re-assign under a new bookingassignmentid, so accept IDs must be re-read - reject takes its reason in the body, not the query string Co-Authored-By: Claude Opus 5 (1M context) --- docs/test-booking-runbook.md | 241 +++++++++++++++++++++++++++++++++++ 1 file changed, 241 insertions(+) create mode 100644 docs/test-booking-runbook.md diff --git a/docs/test-booking-runbook.md b/docs/test-booking-runbook.md new file mode 100644 index 0000000..a908333 --- /dev/null +++ b/docs/test-booking-runbook.md @@ -0,0 +1,241 @@ +# Creating a test booking and assigning it to a miler + +How to create a pickup booking through the real customer API and put it on a +specific miler, against `https://api.doormile.com`. Every call below is the +production API — nothing here is a test harness or a shortcut. + +Base URL: `https://api.doormile.com/api/v1` + +--- + +## 1. Log in as a customer (two steps) + +Customer auth is phone lookup, then PIN. The first call only confirms the +account exists; it returns no token. + +```bash +curl -X POST https://api.doormile.com/api/v1/customer/login \ + -H 'Content-Type: application/json' \ + -d '{"phone":"9876543222"}' +``` + +```json +{ "success": true, "message": "PIN verification required", "phone": "9876543222" } +``` + +```bash +curl -X POST https://api.doormile.com/api/v1/customer/verify-pin \ + -H 'Content-Type: application/json' \ + -d '{"phone":"9876543222","pin":"1234"}' +``` + +The token is at the top level as `token` — **not** under `data`. + +```json +{ "success": true, "token": "eyJhbGciOi...", "user": { "appcustomerid": 1, "firstname": "Rahul", ... } } +``` + +Save it: `TOKEN=eyJhbGciOi...` + +### Customer test logins (all PIN `1234`) + +| City | Name | Phone | appcustomerid | +|---|---|---|---| +| Coimbatore | Rahul Sharma | 9876543222 | 1 | +| Hyderabad | Test Customer HYD | 9999900001 | 7 | +| Bangalore | Test Customer BLR | 9999999999 | 6 | + +--- + +## 2. Create the booking + +```bash +curl -X POST https://api.doormile.com/api/v1/customer/bookings \ + -H "Authorization: Bearer $TOKEN" \ + -H 'Content-Type: application/json' \ + -d '{ + "pickupaddress": "123 Gandhipuram Main Rd, Coimbatore", + "pickuppincode": "641012", + "pickuplatitude": 11.0168, + "pickuplongitude": 76.9558, + "deliveryaddress": "45 Saravanampatti, Coimbatore", + "deliverypincode": "641035", + "deliverylatitude": 11.0780, + "deliverylongitude": 77.0010, + "serviceoption": "Normal", + "parcels": [{ + "itemcategory": "Documents", + "itemdescription": "Test parcel", + "declaredvalue": 500, + "weight": 1.5, + "length": 20, "width": 15, "height": 10 + }] + }' +``` + +Returns the created booking under `data`, with `bookingid` and status +`Pending_Pickup`. Note the `bookingid` — step 3 needs it. + +### Required vs optional + +Only **`pickupaddress`**, **`pickuppincode`** and a non-empty **`parcels`** +array are enforced. Delivery fields are optional; if you give a +`deliverypincode` but no delivery lat/lon, the server geocodes the pincode for +you. Parcel weight and dimensions are optional too — the miler weighs at +pickup — but supplying them makes the volumetric-weight and pricing path +realistic. + +`serviceoption` is one of `Normal`, `Fast`, `Superfast`. + +### The pickup pincode must be in an operating city + +`CityGateMiddleware` rejects anything else with +`{"code":"CITY_NOT_SUPPORTED"}`. Allowed 3-digit prefixes: + +| Prefix | City | +|---|---| +| `641` | Coimbatore | +| `600` | Chennai | +| `560` | Bengaluru | +| `500` | Hyderabad | + +### Same-prefix pickup and delivery goes hyperlocal + +If pickup and delivery share the first 3 digits, `BookingPickupComplete` sends +the consignment straight to `Out_for_Delivery` and the same miler does the +final mile. Different prefixes park it at `Inwarded_at_Hub`, which currently +has no automated path onward. Keep both pincodes in the same prefix unless +you are specifically testing the cross-hub gap. + +--- + +## 3. Assign it to a specific miler + +**The AI routing layer usually gets there first.** `CreateCustomerBooking` +kicks off `AssignCustomerMiler` in a background goroutine, so a booking is +often auto-assigned within seconds of creation. Check before assigning +manually — you may already have what you want, on a miler you didn't pick. + +### Admin console — works for any miler + +```bash +curl -X POST https://api.doormile.com/api/v1/admin/bookings/26/assign-miler \ + -H "Authorization: Bearer $ADMIN_TOKEN" \ + -H 'Content-Type: application/json' \ + -d '{"mileruserid": 4}' +``` + +Admin token comes from `POST /admin/login` (roles 1, 3, 4). + +This path has **no hub check and no city check** — it will put any miler on +any booking. That is what makes it the reliable option. + +### Hub console — miler must belong to the calling hub + +```bash +curl -X POST https://api.doormile.com/api/v1/hub/bookings/26/assign-miler \ + -H "Authorization: Bearer $HUB_TOKEN" \ + -H 'Content-Type: application/json' \ + -d '{"mileruserid": 4}' +``` + +Two extra constraints, both common sources of confusion: + +- The booking must be in an assignable state, else `400 booking is not in an + assignable state`. +- The miler's `milerprofiles.hubid` must equal the hub the token belongs to, + else `403 miler does not belong to this hub`. **Most milers in production + have `hubid = NULL` and therefore cannot be assigned here at all** — they + are also invisible in the hub console's miler list. Use the admin endpoint, + or set a `hubid` first. + +Both endpoints take the same body and funnel into the same +`AssignMilerToBooking` function, so the resulting DB state is identical: +booking → `Miler_Assigned`, a new `bookingassignments` row with status +`Assigned`, and the miler's `availabilitystatus` → `Assigned`. + +--- + +## 4. Accept it as the miler — within about a minute + +A watchdog on the AI routing layer reverts assignments that are not accepted +promptly: the assignment row is deleted and the booking goes back to +`Created`. Observed turnaround is under a minute. + +In practice it often re-assigns rather than leaving the booking idle, so the +booking still looks assigned to the same miler — but under a **new +`bookingassignmentid`**. Observed on booking 26: assignment 42 was swept and +replaced by 43 within ~2 minutes, same miler, same booking. So never hardcode +an assignment ID you noted earlier; always re-read `GET /miler/assignments` +immediately before accepting, or the accept will 404. + +```bash +# Miler login mirrors the customer flow +curl -X POST https://api.doormile.com/api/v1/miler/login \ + -H 'Content-Type: application/json' -d '{"phone":"9876543255"}' + +curl -X POST https://api.doormile.com/api/v1/miler/verify-pin \ + -H 'Content-Type: application/json' \ + -d '{"phone":"9876543255","pin":"1234"}' + +# List what is actually actionable, then accept +curl -H "Authorization: Bearer $MILER_TOKEN" \ + https://api.doormile.com/api/v1/miler/assignments + +curl -X POST https://api.doormile.com/api/v1/miler/assignments/42/accept \ + -H "Authorization: Bearer $MILER_TOKEN" +``` + +`GET /miler/assignments` only returns assignments in `Assigned` or `Accepted`, +and accept only succeeds on one currently `Assigned` — a completed or rejected +job can no longer be revived. + +To reject instead, the reason goes in the **body**, not the query string: + +```bash +curl -X POST https://api.doormile.com/api/v1/miler/assignments/42/reject \ + -H "Authorization: Bearer $MILER_TOKEN" \ + -H 'Content-Type: application/json' \ + -d '{"reason":"too far"}' +``` + +--- + +## 5. Rest of the delivery flow + +In order, all as the miler, all `Authorization: Bearer $MILER_TOKEN`: + +| Step | Call | +|---|---| +| Arrived at customer | `POST /miler/bookings/:bookingid/reached` | +| Confirm parcels | `POST /miler/bookings/:bookingid/parcel-confirm` | +| Pickup complete | `POST /miler/bookings/:bookingid/pickup-complete` | +| Deliver | `POST /miler/consignments/:consignmentid/deliver` | + +`pickup-complete` is what converts the booking into a consignment, sets the +origin hub from the miler's own `hubid`, and applies the hyperlocal rule from +step 2. + +--- + +## Miler reference + +Milers with `hubid = NULL` work fine via the admin endpoint but not the hub +console. Verify the current roster before relying on this table — production +has ~24 milers and the seed data has been reshaped more than once. + +| userid | Name | Phone | Hub | City | +|---|---|---|---|---| +| 4 | Ramesh Kumar | 9876543255 | 1 — Jupiter | Coimbatore | +| 23 | Karthikeyan Vel | 9876500003 | 1 — Jupiter | Coimbatore | +| 6 | Rajesh Sekhar | 9876543256 | 2 — Neptune | Coimbatore | +| 7 | Karthi Keyan | 9876543257 | 7 — RS Puram | Coimbatore | +| 21 | Murugan Palani | 9876500001 | 8 — Saravanampatti | Coimbatore | +| 22 | Selvam Raja | 9876500002 | 17 — Test Hub | Coimbatore | +| 8 | Suresh Goud | 9876543261 | **NULL** | Hyderabad | +| 28 | Suresh Naik | 9876500008 | **NULL** | Bangalore | + +Coimbatore hubs: 1 Jupiter (641012, sorting centre), 2 Neptune (641004), +7 RS Puram (641002), 8 Saravanampatti (641035), 17 Test Hub (641099). + +All miler PINs above are `1234`.