status fix
This commit is contained in:
172
docs/STORE_OPEN_APP.md
Normal file
172
docs/STORE_OPEN_APP.md
Normal file
@@ -0,0 +1,172 @@
|
||||
# Store open / closed — the app contract
|
||||
|
||||
A shop can now close itself for the day. When it does, customers must not be
|
||||
able to order from it.
|
||||
|
||||
Two things to build: show the closed state, and handle the refusal if someone
|
||||
orders anyway. Everything else is done.
|
||||
|
||||
---
|
||||
|
||||
## 1. Four new fields on the store list
|
||||
|
||||
```
|
||||
GET https://fiesta.nearle.app/live/api/v1/mob/tenants/getcustomertenants
|
||||
?customerid=&tenant=0&latitude=&longitude=&categoryid=
|
||||
```
|
||||
|
||||
Every store in `details[]` now carries:
|
||||
|
||||
```json
|
||||
{
|
||||
"isopen": true,
|
||||
"closeduntil": "",
|
||||
"isaccepting": true,
|
||||
"closedreason": ""
|
||||
}
|
||||
```
|
||||
|
||||
### Read `isaccepting`, not `isopen`
|
||||
|
||||
This is the one thing to get right.
|
||||
|
||||
| field | what it is |
|
||||
|---|---|
|
||||
| **`isaccepting`** | **the answer.** Can this store take an order right now? |
|
||||
| `isopen` | the raw switch — what the shopkeeper last pressed |
|
||||
| `closeduntil` | the first day back, `"YYYY-MM-DD"`, or `""` |
|
||||
| `closedreason` | why not, written to show the customer. `""` when open |
|
||||
|
||||
They differ. A shop that closed on Friday "until Monday" has `isopen: false`
|
||||
every day including Monday — the flag is never flipped back. On Monday the
|
||||
server works out that the date has passed and sets `isaccepting: true`.
|
||||
|
||||
Branch the UI on `isaccepting`. `isopen` and `closeduntil` are there if you
|
||||
want to say more, not to decide with.
|
||||
|
||||
### `closedreason` is already written for the customer
|
||||
|
||||
```
|
||||
"This store is closed today"
|
||||
"This store is closed today and reopens on 12 Oct"
|
||||
"This store is not currently available"
|
||||
```
|
||||
|
||||
Show it as it comes. Don't build the sentence from `closeduntil` — the server
|
||||
already handles the cases where there is no date, where the branch has been
|
||||
switched off by Nearle rather than by the shop, and where the date is
|
||||
unreadable.
|
||||
|
||||
---
|
||||
|
||||
## 2. A closed store is still returned
|
||||
|
||||
It is **not** filtered out of the list. You decide whether to grey it or hide
|
||||
it.
|
||||
|
||||
**I would grey it.** A shop that vanishes reads to a regular customer as gone
|
||||
for good; one marked "Closed today — reopens 12 Oct" brings them back
|
||||
tomorrow. But it is your call, and the data supports either.
|
||||
|
||||
What the store must not do is look ordinary. If `isaccepting` is false,
|
||||
whatever you render has to stop a customer getting as far as a basket.
|
||||
|
||||
---
|
||||
|
||||
## 3. Delivery windows follow automatically
|
||||
|
||||
```
|
||||
GET /v1/mob/deliveryslots/available?tenantid=&locationid=
|
||||
```
|
||||
|
||||
A closed store now returns an **empty list**, plus the reason:
|
||||
|
||||
```json
|
||||
{ "code": 200, "status": true, "details": [], "closedreason": "This store is closed today" }
|
||||
```
|
||||
|
||||
You already handle an empty `details` as "no windows here" — see
|
||||
`DELIVERY_SLOTS_APP.md` §2 — so this needs nothing new. It stops a customer
|
||||
picking tomorrow morning from a shop that is shut and only finding out at
|
||||
checkout.
|
||||
|
||||
---
|
||||
|
||||
## 4. The refusal
|
||||
|
||||
```
|
||||
POST /v1/mob/orders/createorder
|
||||
```
|
||||
|
||||
If the store is closed, the order is refused:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 409,
|
||||
"status": false,
|
||||
"message": "This store is closed today and reopens on 12 Oct"
|
||||
}
|
||||
```
|
||||
|
||||
**Handle this properly rather than as a generic failure.** It is not a bug or
|
||||
an edge case — it happens to real people in normal use: a customer with the
|
||||
app open when the shopkeeper closes, or a screen left open since this morning.
|
||||
|
||||
On 409: show the `message`, and refresh the store list so the UI catches up.
|
||||
|
||||
The server checks on every order because `/v1/mob/*` carries no session —
|
||||
anything arriving is a claim. Hiding the store in the app is presentation;
|
||||
this is the enforcement.
|
||||
|
||||
---
|
||||
|
||||
## 5. Nothing changes for an open store
|
||||
|
||||
Every store on the platform has `isopen: true` and `isaccepting: true` right
|
||||
now. There was no backfill and there will not be one. A shop that never
|
||||
touches the switch behaves exactly as it does today.
|
||||
|
||||
---
|
||||
|
||||
## 6. Done on our side
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| `tenantlocations.isopen` + `closeduntil` | ✅ live, default open |
|
||||
| Four fields on `getcustomertenants` | ✅ live, verified on all 9 stores |
|
||||
| `PUT /v1/web/tenants/storeopen` (guarded) | ✅ live — 401 unauthenticated, absent from `/v1/mob` |
|
||||
| `createorder` refuses a closed store | ✅ built, 409 with the shop's wording |
|
||||
| Closed store returns no delivery windows | ✅ built |
|
||||
| Merchant console: switch in the shop profile header | ✅ live |
|
||||
| Nearle console: "Trading" column per branch | ✅ live |
|
||||
| Auto-reopen on the date | ✅ computed on read — no job, nothing to miss |
|
||||
|
||||
**Verified against production:** the four fields, and that the write endpoint
|
||||
is live and guarded.
|
||||
|
||||
**Not yet exercised against production:** the 409 and the empty window list.
|
||||
Both are covered by unit tests and need a real store closed to confirm
|
||||
end to end. If you want to test, ask for tenant `1141` / branch `1179` to be
|
||||
closed — it is a test store with delivery windows already set.
|
||||
|
||||
---
|
||||
|
||||
## 7. Two rules worth knowing
|
||||
|
||||
**Closed with no date stays closed.** A power cut has no end date. The shop
|
||||
reopens when a person says so, not on a timer. `closeduntil: ""` with
|
||||
`isaccepting: false` is a normal state, not missing data.
|
||||
|
||||
**"Closed until the 12th" means open ON the 12th.** The date names the first
|
||||
day back, which is how the phrase reads in English. The console says
|
||||
"Reopens on" so nobody has to work it out.
|
||||
|
||||
---
|
||||
|
||||
## Questions
|
||||
|
||||
The rule lives in one function server-side (`models/storeopen.go`), and the
|
||||
store list, order creation and the delivery-window endpoint all call it. So if
|
||||
anything about open/closed looks inconsistent between those three, it is one
|
||||
place to fix and not three to reconcile. Ask rather than working around it in
|
||||
the app — a local override is how the two sides drift apart.
|
||||
@@ -915,7 +915,13 @@ func (r *tenantRepository) GetTenantByID(tid int, locationid int, userid int) (m
|
||||
|
||||
q1 := `
|
||||
SELECT a.*,b.categoryname,c.locationname AS applocation, d.allocationid AS allocationmode,e.typename AS allocationtype,e.mapid AS allocationid,f.locationid,
|
||||
f.locationname, f.contactno as locationcontact
|
||||
f.locationname, f.contactno as locationcontact,
|
||||
-- Whether the branch is trading today, so the shop profile can show
|
||||
-- its own switch in the right position. Without these the console
|
||||
-- reads no value, defaults the switch to open, and a shop that just
|
||||
-- closed itself looks open again on the next page load.
|
||||
COALESCE(f.isopen, true) AS isopen,
|
||||
COALESCE(f.closeduntil::text, '') AS closeduntil
|
||||
FROM tenants a
|
||||
LEFT JOIN app_category b ON a.categoryid = b.categoryid
|
||||
LEFT JOIN app_location c ON a.applocationid = c.applocationid
|
||||
|
||||
Reference in New Issue
Block a user