diff --git a/backend/handlers/bookings/admin_reserve.go b/backend/handlers/bookings/admin_reserve.go index 60abfcb..a1a53a3 100644 --- a/backend/handlers/bookings/admin_reserve.go +++ b/backend/handlers/bookings/admin_reserve.go @@ -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 { diff --git a/backend/handlers/bookings/bookings.go b/backend/handlers/bookings/bookings.go index 5c2457d..902084f 100644 --- a/backend/handlers/bookings/bookings.go +++ b/backend/handlers/bookings/bookings.go @@ -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) diff --git a/backend/handlers/bookings/manage.go b/backend/handlers/bookings/manage.go index a774b99..df3d5d4 100644 --- a/backend/handlers/bookings/manage.go +++ b/backend/handlers/bookings/manage.go @@ -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