feat: check staged default hours in all booking creation paths

Integrate getClosingTimeForDate into AdminReserveSlotHandler, CreateBookingHandler, AdminRescheduleBookingHandler, and AdminCreateBookingForUserHandler. Rejects bookings when the staged schedule marks a day as closed (00:00).
This commit is contained in:
2026-08-22 00:34:48 +01:00
parent 52caf1b7b5
commit 8f99b8b5c3
3 changed files with 28 additions and 8 deletions
+8 -6
View File
@@ -119,16 +119,18 @@ func AdminReserveSlotHandler(w http.ResponseWriter, r *http.Request) {
localStart := req.StartTime.In(londonLocation)
// DB uses 0=Monday..6=Sunday; Go uses 0=Sunday..6=Saturday. Convert.
weekday := int((localStart.Weekday() + 6) % 7)
var closeStr string
if err := db.Conn.QueryRow(r.Context(), `SELECT end_time FROM working_hours WHERE weekday = $1`, weekday).Scan(&closeStr); err != nil {
if errors.Is(err, pgx.ErrNoRows) {
http.Error(w, "Not open on this day", http.StatusBadRequest)
return
}
closeStr, err := getClosingTimeForDate(r.Context(), db.Conn, weekday, localStart)
if err != nil {
log.Printf("Failed to get hours: %v", err)
http.Error(w, "Could not verify hours", http.StatusInternalServerError)
return
}
// 00:00 means the day is closed under the staged schedule — reject outright
if closeStr == "00:00" || closeStr == "00:00:00" {
http.Error(w, "Not open on this day", http.StatusBadRequest)
return
}
localEndLondon := localStart.Add(time.Duration(svcDuration) * time.Minute).In(londonLocation)
if err := checkClosingHours(localEndLondon, closeStr); err != nil {
+12 -2
View File
@@ -2197,12 +2197,17 @@ func CreateBookingHandler(w http.ResponseWriter, r *http.Request) {
localStart := req.StartTime.In(londonLocation)
// DB uses 0=Monday..6=Sunday; Go uses 0=Sunday..6=Saturday. Convert.
weekday := int((localStart.Weekday() + 6) % 7)
var closeStr string
if err := db.Conn.QueryRow(r.Context(), `SELECT end_time FROM working_hours WHERE weekday = $1`, weekday).Scan(&closeStr); err != nil {
closeStr, err := getClosingTimeForDate(r.Context(), db.Conn, weekday, localStart)
if err != nil {
log.Printf("Failed to get hours: %v", err)
http.Error(w, "Could not verify hours", http.StatusInternalServerError)
return
}
if closeStr == "00:00" || closeStr == "00:00:00" {
http.Error(w, "Not open on this day", http.StatusBadRequest)
return
}
endTime := req.StartTime.Add(time.Duration(svcDuration) * time.Minute)
localEndLondon := localStart.Add(time.Duration(svcDuration) * time.Minute).In(londonLocation)
@@ -4287,6 +4292,11 @@ func AdminRescheduleBookingHandler(w http.ResponseWriter, r *http.Request) {
http.Error(w, "Cannot reschedule to a closed day", http.StatusBadRequest)
return
}
// Also check the day isn't closed under a staged default hours change
if closeStr, err := getClosingTimeForDate(r.Context(), tx, weekday, localStart); err == nil && (closeStr == "00:00" || closeStr == "00:00:00") {
http.Error(w, "This day will be closed under the upcoming schedule change", http.StatusBadRequest)
return
}
if _, evictErr := EvictPendingReleaseOverlapping(r.Context(), tx, req.StartTime, newEndTime); evictErr != nil {
log.Printf("Failed to evict pending_release bookings on reschedule: %v", evictErr)
http.Error(w, "Internal server error", http.StatusInternalServerError)
+8
View File
@@ -643,6 +643,14 @@ func AdminCreateBookingForUserHandler(w http.ResponseWriter, r *http.Request) {
http.Error(w, "Cannot book during holiday hours when the salon is closed", http.StatusConflict)
return
}
// Also check if the day is closed under a staged default hours change
// whose effective_date has been reached.
closeStr, err := getClosingTimeForDate(r.Context(), db.Conn, weekday, localStart)
if err == nil && (closeStr == "00:00" || closeStr == "00:00:00") {
http.Error(w, "Not open on this day under upcoming schedule change", http.StatusConflict)
return
}
}
// Check for overlapping confirmed/in_progress/completed bookings