112 lines
4.5 KiB
Go
112 lines
4.5 KiB
Go
package models
|
||
|
||
import (
|
||
"slices"
|
||
"time"
|
||
)
|
||
|
||
/*
|
||
When a shop delivers.
|
||
|
||
A branch offers at most three windows a day — morning, afternoon, evening — and
|
||
the shopper picks one at checkout. The window is a PREFERENCE, not a promise:
|
||
every order is accepted, there is no capacity limit, and a slot never fills up.
|
||
It tells the shop when to group a drop, and it tells the shopper roughly when to
|
||
expect one.
|
||
|
||
── Per branch, not per tenant ──────────────────────────────────────────────
|
||
|
||
Timings belong to a shop floor, not a company. A tenant with an outlet in a
|
||
market and another in an office park will run different hours, and discovering
|
||
that after the fact would mean migrating live rows. `locationid` is on the table
|
||
from the start for that reason, and onboarding simply fills it with the primary
|
||
branch it just created.
|
||
|
||
── A branch with no slots is not broken ────────────────────────────────────
|
||
|
||
Every tenant trading today has no slots at all, and must keep taking orders.
|
||
Absence means "order without a slot", exactly as before — never "this shop is
|
||
closed". The whole rollout rests on that, so nothing here may treat an empty
|
||
list as an error.
|
||
*/
|
||
type DeliverySlots struct {
|
||
Slotid int `json:"deliveryslotid" gorm:"primaryKey;autoIncrement;column:slotid"`
|
||
Tenantid int `json:"tenantid" gorm:"column:tenantid;index"`
|
||
Locationid int `json:"locationid" gorm:"column:locationid;index"`
|
||
|
||
// Which of the three this is. Fixed rather than free-form: the app shows
|
||
// them in a known order and may want an icon per slot, and a shop inventing
|
||
// a fourth would have nowhere to appear.
|
||
Slotkey string `json:"slotkey" gorm:"column:slotkey"`
|
||
|
||
// What the shopper reads. Separate from `slotkey` so a shop can say
|
||
// "Before work" without breaking the app's ordering.
|
||
Name string `json:"name" gorm:"column:name"`
|
||
|
||
// "HH:MM", 24-hour, in the shop's local time.
|
||
//
|
||
// Stored as text, not as a timestamp, because this is a time of DAY that
|
||
// recurs — it has no date until an order attaches one. A timestamp column
|
||
// would invite a timezone conversion on every read, which is the one thing
|
||
// this must not do: the shopkeeper typed 08:00 meaning eight in the morning
|
||
// where they are standing.
|
||
Starttime string `json:"starttime" gorm:"column:starttime"`
|
||
|
||
// Also the CUT-OFF. There is deliberately no separate cutoff column: a slot
|
||
// accepts orders right up to the moment it ends, and then stops being
|
||
// offered. Morning 08:00–10:00 takes an order at 09:59 and not at 10:01.
|
||
Endtime string `json:"endtime" gorm:"column:endtime"`
|
||
|
||
// "active" or "inactive". A shop that stops doing evenings turns the slot
|
||
// off rather than deleting it, so orders already placed against it still
|
||
// resolve to something with a name.
|
||
Status string `json:"status" gorm:"column:status"`
|
||
|
||
Created time.Time `json:"created" gorm:"column:created;autoCreateTime"`
|
||
Updated time.Time `json:"updated" gorm:"column:updated;autoUpdateTime"`
|
||
}
|
||
|
||
func (DeliverySlots) TableName() string {
|
||
return "deliveryslots"
|
||
}
|
||
|
||
// The three keys, in the order a shopper reads them.
|
||
const (
|
||
SlotMorning = "morning"
|
||
SlotAfternoon = "afternoon"
|
||
SlotEvening = "evening"
|
||
)
|
||
|
||
// SlotKeys is the whole set, in display order. Used to validate input and to
|
||
// seed a new branch.
|
||
var SlotKeys = []string{SlotMorning, SlotAfternoon, SlotEvening}
|
||
|
||
// IsSlotKey reports whether a key is one of the three.
|
||
func IsSlotKey(key string) bool {
|
||
return slices.Contains(SlotKeys, key)
|
||
}
|
||
|
||
/*
|
||
A slot offered to a shopper, with the day it falls on.
|
||
|
||
`DeliverySlots` describes a window that recurs; this is one concrete occurrence
|
||
of it. The app needs the date because "evening" alone cannot distinguish tonight
|
||
from tomorrow night, and once today's last window closes the next thing on offer
|
||
is tomorrow morning.
|
||
|
||
The app does NO time arithmetic. It renders what this list contains, and the
|
||
list already excludes anything that has closed.
|
||
*/
|
||
type AvailableDeliverySlot struct {
|
||
Slotid int `json:"deliveryslotid"`
|
||
Slotkey string `json:"slotkey"`
|
||
Name string `json:"name"`
|
||
Starttime string `json:"starttime"`
|
||
Endtime string `json:"endtime"`
|
||
// "YYYY-MM-DD", the day this window falls on.
|
||
Slotdate string `json:"slotdate"`
|
||
// True when `Slotdate` is not today. Saves the app comparing dates to
|
||
// decide whether to write "Tomorrow" beside the name.
|
||
IsTomorrow bool `json:"istomorrow"`
|
||
}
|