package models import ( "strings" "time" ) /* Is a branch taking orders right now? Two things have to be true: the branch exists as far as the platform is concerned, and the shop has not closed itself for the day. They are different questions with different owners — Nearle decommissions a branch, a shopkeeper closes for an afternoon — and this is the only place they are answered together. ── Reopening is worked out, not scheduled ────────────────────────────────── A shop that said "back on Monday" is open on Monday because this function says so, not because a job ran. Nothing has to fire, nothing can be missed, and a server that was down over the weekend still gets it right. The alternative — a nightly task flipping the flag — fails silently exactly once and leaves a shop shut with nobody watching. ── Why the date means the day it REOPENS ─────────────────────────────────── "Closed until the 12th" reads, in English, as open on the 12th. So the date names the first day back, and a shop closing for today alone sets tomorrow. The console says "Reopens on" rather than "Closed until" so nobody has to work that out. */ func StoreIsOpen(status string, isOpen bool, closedUntil string, now time.Time) bool { // Decommissioned beats everything. A branch Nearle has switched off does // not come back because a date passed. if !strings.EqualFold(strings.TrimSpace(status), "active") { return false } if isOpen { return true } // Closed, with a date to come back on. reopen, ok := parseStoreDate(closedUntil) if !ok { // Closed with no end in sight — a power cut nobody can put a date on. // Stays closed until a person says otherwise, which is the safe way // round: a shop wrongly shown open takes orders it cannot fill. return false } today := now.Truncate(24 * time.Hour) return !today.Before(reopen.Truncate(24 * time.Hour)) } /* Why a shopper cannot order, in words fit to show them. Empty when the branch is open. Returned straight to the app, so it says what a person needs to know and not what the database holds. */ func StoreClosedReason(status string, isOpen bool, closedUntil string, now time.Time) string { if StoreIsOpen(status, isOpen, closedUntil, now) { return "" } if !strings.EqualFold(strings.TrimSpace(status), "active") { return "This store is not currently available" } if reopen, ok := parseStoreDate(closedUntil); ok { return "This store is closed today and reopens on " + reopen.Format("2 Jan") } return "This store is closed today" } // Accepts a bare date or a timestamp, because the column is a DATE but GORM // and the console have both been known to hand over a full RFC3339 string. // Anything unreadable is "no date", never today. func parseStoreDate(value string) (time.Time, bool) { trimmed := strings.TrimSpace(value) if trimmed == "" { return time.Time{}, false } if len(trimmed) >= 10 { if parsed, err := time.Parse("2006-01-02", trimmed[:10]); err == nil { return parsed, true } } return time.Time{}, false }