Files
doormile_backend/docs/test-booking-runbook.md
Suriya 588f84c344 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) <noreply@anthropic.com>
2026-07-27 12:38:05 +05:30

242 lines
8.3 KiB
Markdown

# 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`.