219 lines
6.5 KiB
Markdown
219 lines
6.5 KiB
Markdown
# Delivery windows — the app contract
|
||
|
||
Shoppers now choose when their order arrives: one of three windows a day —
|
||
morning, afternoon, evening — set per branch by the shop.
|
||
|
||
Two things to build: show the choice at checkout, and send it with the order.
|
||
Everything else is done.
|
||
|
||
---
|
||
|
||
## 1. What a shopper may pick
|
||
|
||
```
|
||
GET https://fiesta.nearle.app/live/api/v1/mob/deliveryslots/available?tenantid=&locationid=
|
||
```
|
||
|
||
No authentication. Call it at checkout, once the branch is known.
|
||
|
||
**Real response** (branch 1179, taken at 19:02 IST):
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"message": "Success",
|
||
"status": true,
|
||
"details": [
|
||
{ "deliveryslotid": 3, "slotkey": "evening", "name": "Evening",
|
||
"starttime": "17:00", "endtime": "20:00",
|
||
"slotdate": "2026-10-06", "istomorrow": false },
|
||
{ "deliveryslotid": 1, "slotkey": "morning", "name": "Morning",
|
||
"starttime": "08:00", "endtime": "10:00",
|
||
"slotdate": "2026-10-07", "istomorrow": true },
|
||
{ "deliveryslotid": 2, "slotkey": "afternoon", "name": "Afternoon",
|
||
"starttime": "12:00", "endtime": "15:00",
|
||
"slotdate": "2026-10-07", "istomorrow": true },
|
||
{ "deliveryslotid": 3, "slotkey": "evening", "name": "Evening",
|
||
"starttime": "17:00", "endtime": "20:00",
|
||
"slotdate": "2026-10-07", "istomorrow": true }
|
||
]
|
||
}
|
||
```
|
||
|
||
Morning and afternoon are absent from today because both had ended by 19:02.
|
||
**That filtering is already done — render the list as given.**
|
||
|
||
### Do no time arithmetic
|
||
|
||
Do not compare `starttime`/`endtime` against the device clock to decide what to
|
||
show. The server owns that rule, and the device's clock, timezone and locale are
|
||
all things we do not control. If the app re-derives it, the two will disagree
|
||
and the shopper will be offered a window the server then rejects.
|
||
|
||
The fields are there to display — "Evening, 5–8pm" — not to filter on.
|
||
|
||
### Ordering
|
||
|
||
Already sorted: today's remaining windows first, then tomorrow's, each by start
|
||
time. Render in the order given.
|
||
|
||
`istomorrow` is there so you can put "Tomorrow" beside a name without comparing
|
||
dates yourself.
|
||
|
||
---
|
||
|
||
## 2. An empty list is normal
|
||
|
||
```json
|
||
{ "code": 200, "message": "Success", "status": true, "details": [] }
|
||
```
|
||
|
||
**This is not an error, and it is the common case today.** Most branches have
|
||
not set windows yet, and they are trading normally right now.
|
||
|
||
When `details` is empty:
|
||
|
||
- Do not show the window picker
|
||
- Do not show an error, a retry, or "this shop is closed"
|
||
- **Let the order go through with no window**, exactly as before this feature
|
||
|
||
The whole rollout depends on this. A branch with no windows is an ordinary
|
||
branch, and treating it as broken would take every shop on the platform offline.
|
||
|
||
It is always `[]`, never `null`.
|
||
|
||
---
|
||
|
||
## 3. Sending the choice
|
||
|
||
```
|
||
POST https://fiesta.nearle.app/live/api/v1/mob/orders/createorder
|
||
```
|
||
|
||
Two new **optional** fields on the existing body:
|
||
|
||
```json
|
||
{
|
||
"deliveryslotid": 3,
|
||
"deliveryslotdate": "2026-10-06"
|
||
}
|
||
```
|
||
|
||
Send both or neither. Copy them straight from the chosen entry — do not
|
||
recompute the date.
|
||
|
||
Omitting them creates an order with no window, which is valid and unchanged
|
||
from today's behaviour.
|
||
|
||
---
|
||
|
||
## 4. The one error to handle
|
||
|
||
A window takes orders **right up until it ends**, then stops. So a shopper who
|
||
opens checkout at 09:58 and pays at 10:02 has chosen a window that closed while
|
||
they were deciding.
|
||
|
||
The server re-checks on every order and answers:
|
||
|
||
```json
|
||
{
|
||
"code": 409,
|
||
"status": false,
|
||
"message": "the morning window has closed for today — please choose another"
|
||
}
|
||
```
|
||
|
||
**On 409:** re-fetch `available`, show the fresh list, ask again. The `message`
|
||
is written to be shown to the shopper as-is.
|
||
|
||
Other 409 messages from the same check, all safe to display:
|
||
|
||
- `that delivery window is not one this shop offers`
|
||
- `the evening window is not currently available` — the shop switched it off
|
||
- `that delivery window has already passed`
|
||
- `a delivery date is required with a delivery window`
|
||
|
||
This is worth handling properly rather than as a generic failure. It is the one
|
||
case that will happen to real people in normal use.
|
||
|
||
---
|
||
|
||
## 5. Reading it back
|
||
|
||
Orders carry what was chosen:
|
||
|
||
```json
|
||
{ "deliveryslotid": 3, "deliveryslotdate": "2026-10-06" }
|
||
```
|
||
|
||
Both absent or `0`/empty on orders placed without a window. Show the window on
|
||
the confirmation screen and in order history; treat absence as "no window was
|
||
asked for", never as missing data.
|
||
|
||
---
|
||
|
||
## 6. What the window means
|
||
|
||
**A preference, not a promise.**
|
||
|
||
- Every order is accepted. A window never fills up and never blocks a sale.
|
||
- There is no capacity limit, and no "slots remaining".
|
||
- It tells the shop when to group the drop, and the shopper roughly when to
|
||
expect it.
|
||
|
||
Please do not word it in the app as a guaranteed delivery time. "Arrives
|
||
between 5 and 8pm" is right; "Guaranteed by 8pm" is not something the backend
|
||
can honour.
|
||
|
||
---
|
||
|
||
## 7. Done on our side
|
||
|
||
| | |
|
||
|---|---|
|
||
| `deliveryslots` table, per branch | ✅ live |
|
||
| `GET /v1/mob/deliveryslots/available` | ✅ live, filtering and dating already applied |
|
||
| `orders.deliveryslotid` + `deliveryslotdate` | ✅ live |
|
||
| `createorder` accepts and validates both | ✅ live, 409 on a closed window |
|
||
| Server timezone (IST) | ✅ fixed — windows close on the shop's clock |
|
||
| Shops set their windows at onboarding | ✅ live in both consoles |
|
||
| Shops edit them later (Store profile → Settings) | ✅ live |
|
||
| Window shown on the order in the console | ✅ live |
|
||
|
||
Verified end to end on live data: saved through the console, served to the app
|
||
already filtered, with today's closed windows correctly absent.
|
||
|
||
**Not done:** grouping the dispatch queue by window. That is a console concern
|
||
and does not affect anything above.
|
||
|
||
---
|
||
|
||
## 8. A branch you can test against
|
||
|
||
**Tenant `1141`, branch `1179`** — three windows configured:
|
||
|
||
| | |
|
||
|---|---|
|
||
| Morning | 08:00–10:00 |
|
||
| Afternoon | 12:00–15:00 |
|
||
| Evening | 17:00–20:00 |
|
||
|
||
```
|
||
GET /live/api/v1/mob/deliveryslots/available?tenantid=1141&locationid=1179
|
||
```
|
||
|
||
Call it at different times of day and the list shortens as windows close —
|
||
that is the easiest way to see the rule working.
|
||
|
||
For the empty-list path, use any other branch: most have no windows set, which
|
||
is exactly the case you need to handle.
|
||
|
||
---
|
||
|
||
## Questions
|
||
|
||
The rule lives in one place server-side (`services/deliverySlotService.go`), so
|
||
if anything about open/closed looks wrong, it is one function and not a
|
||
disagreement between us. Ask rather than working around it in the app — a
|
||
workaround on the device is how the two clocks drift apart.
|