171 lines
5.1 KiB
Go
171 lines
5.1 KiB
Go
package repositories
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"nearle/models"
|
|
)
|
|
|
|
// Shift windows for till staff.
|
|
//
|
|
// Scoped by tenant *and* outlet in every statement rather than checked first,
|
|
// the same shape the till-user queries use: a console naming somebody else's
|
|
// shift id updates no rows and is told so, instead of quietly editing another
|
|
// shop's hours.
|
|
|
|
var posTimeOfDay = regexp.MustCompile(`^([01]\d|2[0-3]):[0-5]\d$`)
|
|
|
|
// normaliseShiftTime accepts what a time input actually sends.
|
|
//
|
|
// `<input type="time">` gives "07:00", some browsers and most hand-typed values
|
|
// give "07:00:00", and `ridershifts` already stores the seconds form. Both are
|
|
// reduced to `HH:MM` so a shift written by one client reads the same to another.
|
|
func normaliseShiftTime(raw string) (string, error) {
|
|
t := strings.TrimSpace(raw)
|
|
if t == "" {
|
|
return "", fmt.Errorf("a start and end time are required")
|
|
}
|
|
if len(t) == 8 && strings.Count(t, ":") == 2 {
|
|
t = t[:5]
|
|
}
|
|
if !posTimeOfDay.MatchString(t) {
|
|
return "", fmt.Errorf("time must be 24-hour HH:MM; got %q", raw)
|
|
}
|
|
return t, nil
|
|
}
|
|
|
|
// ListStaffShifts returns an outlet's shifts, newest last so a picker reads in
|
|
// the order they were created rather than alphabetically by name.
|
|
func (r *posRepository) ListStaffShifts(tenantID, locationID int, includeInactive bool) ([]models.StaffShifts, error) {
|
|
if tenantID <= 0 || locationID <= 0 {
|
|
return nil, fmt.Errorf("tenantid and locationid are required")
|
|
}
|
|
|
|
shifts := make([]models.StaffShifts, 0)
|
|
query := `SELECT * FROM staffshifts WHERE tenantid = ? AND locationid = ?`
|
|
if !includeInactive {
|
|
query += ` AND LOWER(COALESCE(status,'active')) <> 'inactive'`
|
|
}
|
|
query += ` ORDER BY staffshiftid`
|
|
|
|
if err := r.db.Raw(query, tenantID, locationID).Scan(&shifts).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return shifts, nil
|
|
}
|
|
|
|
// CreateStaffShift adds a window at one outlet.
|
|
func (r *posRepository) CreateStaffShift(tenantID, locationID int, req models.StaffShifts) (*models.StaffShifts, error) {
|
|
if tenantID <= 0 || locationID <= 0 {
|
|
return nil, fmt.Errorf("tenantid and locationid are required")
|
|
}
|
|
|
|
name := strings.TrimSpace(req.Name)
|
|
if name == "" {
|
|
return nil, fmt.Errorf("a shift name is required")
|
|
}
|
|
|
|
start, err := normaliseShiftTime(req.Starttime)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
end, err := normaliseShiftTime(req.Endtime)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// An end before a start is allowed on purpose — a night shift runs 22:00 to
|
|
// 06:00 and wrapping midnight is ordinary in retail. Only the equal case is
|
|
// refused, because a zero-length window cannot be what anyone meant.
|
|
if start == end {
|
|
return nil, fmt.Errorf("a shift cannot start and end at the same time")
|
|
}
|
|
|
|
weekdays := strings.TrimSpace(req.Weekdays)
|
|
if weekdays != "" && !regexp.MustCompile(`^[01]{7}$`).MatchString(weekdays) {
|
|
return nil, fmt.Errorf("weekdays must be seven 0/1 characters starting Monday, e.g. 1111100")
|
|
}
|
|
|
|
shift := models.StaffShifts{
|
|
Tenantid: tenantID,
|
|
Locationid: locationID,
|
|
Name: name,
|
|
Starttime: start,
|
|
Endtime: end,
|
|
Weekdays: weekdays,
|
|
Status: "Active",
|
|
}
|
|
if err := r.db.Table("staffshifts").Create(&shift).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return &shift, nil
|
|
}
|
|
|
|
// UpdateStaffShift edits a window. Every field is optional; only the ones sent
|
|
// are written, matching how the till-user update behaves.
|
|
func (r *posRepository) UpdateStaffShift(tenantID, locationID int, req models.StaffShifts) (*models.StaffShifts, error) {
|
|
if req.Staffshiftid <= 0 {
|
|
return nil, fmt.Errorf("staff_shift_id is required")
|
|
}
|
|
|
|
sets := []string{}
|
|
args := []interface{}{}
|
|
|
|
if name := strings.TrimSpace(req.Name); name != "" {
|
|
sets = append(sets, "name = ?")
|
|
args = append(args, name)
|
|
}
|
|
if strings.TrimSpace(req.Starttime) != "" {
|
|
t, err := normaliseShiftTime(req.Starttime)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
sets = append(sets, "starttime = ?")
|
|
args = append(args, t)
|
|
}
|
|
if strings.TrimSpace(req.Endtime) != "" {
|
|
t, err := normaliseShiftTime(req.Endtime)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
sets = append(sets, "endtime = ?")
|
|
args = append(args, t)
|
|
}
|
|
if w := strings.TrimSpace(req.Weekdays); w != "" {
|
|
if !regexp.MustCompile(`^[01]{7}$`).MatchString(w) {
|
|
return nil, fmt.Errorf("weekdays must be seven 0/1 characters starting Monday")
|
|
}
|
|
sets = append(sets, "weekdays = ?")
|
|
args = append(args, w)
|
|
}
|
|
if s := strings.TrimSpace(req.Status); s != "" {
|
|
sets = append(sets, "status = ?")
|
|
args = append(args, s)
|
|
}
|
|
|
|
if len(sets) == 0 {
|
|
return nil, fmt.Errorf("nothing to change")
|
|
}
|
|
|
|
sets = append(sets, "updated = NOW()")
|
|
args = append(args, req.Staffshiftid, tenantID, locationID)
|
|
|
|
res := r.db.Exec(
|
|
fmt.Sprintf(`UPDATE staffshifts SET %s WHERE staffshiftid = ? AND tenantid = ? AND locationid = ?`,
|
|
strings.Join(sets, ", ")), args...)
|
|
if res.Error != nil {
|
|
return nil, res.Error
|
|
}
|
|
if res.RowsAffected == 0 {
|
|
return nil, fmt.Errorf("no shift %d at this outlet", req.Staffshiftid)
|
|
}
|
|
|
|
var out models.StaffShifts
|
|
if err := r.db.Raw(`SELECT * FROM staffshifts WHERE staffshiftid = ?`, req.Staffshiftid).Scan(&out).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return &out, nil
|
|
}
|