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