98 lines
3.2 KiB
Go
98 lines
3.2 KiB
Go
package repositories
|
|
|
|
import (
|
|
"errors"
|
|
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/clause"
|
|
|
|
"nearle/models"
|
|
)
|
|
|
|
type DeliverySlotRepository interface {
|
|
ListForBranch(tenantID, locationID int) ([]models.DeliverySlots, error)
|
|
FindForBranch(tenantID, locationID, slotID int) (*models.DeliverySlots, error)
|
|
Save(slots []models.DeliverySlots) error
|
|
}
|
|
|
|
type deliverySlotRepository struct {
|
|
db *gorm.DB
|
|
}
|
|
|
|
func NewDeliverySlotRepository(db *gorm.DB) DeliverySlotRepository {
|
|
return &deliverySlotRepository{db: db}
|
|
}
|
|
|
|
// Ordered by start time so every caller — the console's editor and the app's
|
|
// list alike — sees morning before evening without sorting it again.
|
|
func (r *deliverySlotRepository) ListForBranch(tenantID, locationID int) ([]models.DeliverySlots, error) {
|
|
slots := make([]models.DeliverySlots, 0, len(models.SlotKeys))
|
|
|
|
err := r.db.Table("deliveryslots").
|
|
Where("tenantid = ? AND locationid = ?", tenantID, locationID).
|
|
Order("starttime ASC").
|
|
Find(&slots).Error
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return slots, nil
|
|
}
|
|
|
|
/*
|
|
One window, scoped to the branch that claims it.
|
|
|
|
The tenant and location are in the WHERE and not checked afterwards: a slot id
|
|
arrives from the app, which carries no session, so it is a claim about which
|
|
shop it belongs to. Looking it up by id alone and trusting the row would let one
|
|
shop's checkout name another shop's window.
|
|
*/
|
|
func (r *deliverySlotRepository) FindForBranch(tenantID, locationID, slotID int) (*models.DeliverySlots, error) {
|
|
var slot models.DeliverySlots
|
|
|
|
err := r.db.Table("deliveryslots").
|
|
Where("slotid = ? AND tenantid = ? AND locationid = ?", slotID, tenantID, locationID).
|
|
First(&slot).Error
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
// Not an error: "this shop does not offer that" is an answer, and the
|
|
// service turns it into something a shopper can read.
|
|
return nil, nil
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &slot, nil
|
|
}
|
|
|
|
/*
|
|
Write a branch's windows, all of them or none.
|
|
|
|
── Upsert, not delete-then-insert ──────────────────────────────────────────
|
|
|
|
Slot ids are referenced by `orders.deliveryslotid`. Replacing the rows would
|
|
renumber them, and every order already placed would point at a window that no
|
|
longer means what it did — or at nothing. The unique index on
|
|
(tenantid, locationid, slotkey) is what makes the conflict target work, so a
|
|
branch keeps one morning however many times it is edited.
|
|
|
|
── One transaction ─────────────────────────────────────────────────────────
|
|
|
|
A shop editing all three and getting two is worse than getting none: the two
|
|
that took are live and serving shoppers, and nothing on screen says which.
|
|
*/
|
|
func (r *deliverySlotRepository) Save(slots []models.DeliverySlots) error {
|
|
if len(slots) == 0 {
|
|
return nil
|
|
}
|
|
|
|
return r.db.Transaction(func(tx *gorm.DB) error {
|
|
return tx.Table("deliveryslots").
|
|
Clauses(clause.OnConflict{
|
|
Columns: []clause.Column{
|
|
{Name: "tenantid"}, {Name: "locationid"}, {Name: "slotkey"},
|
|
},
|
|
DoUpdates: clause.AssignmentColumns([]string{"name", "starttime", "endtime", "status", "updated"}),
|
|
}).
|
|
Create(&slots).Error
|
|
})
|
|
}
|