status fix

This commit is contained in:
2026-10-09 17:22:27 +05:30
parent f0361d8343
commit f6d5c7c87b
2 changed files with 179 additions and 1 deletions

172
docs/STORE_OPEN_APP.md Normal file
View 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.

View File

@@ -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