From 991f3af54027492d24255ede0157090c96c5f77b Mon Sep 17 00:00:00 2001 From: Stephen Adamson Date: Thu, 30 Jul 2026 10:05:18 +0100 Subject: [PATCH] fix: add hours validation to user edit requests (was M8/L5 stubs) RequestEditHandler had empty M8/L5 placeholder comments where hours validation was planned. Implemented full validation: staged-hours-aware closing time check via getClosingTimeForDate, closed-day check, and closing-hours boundary check using the booking's total_duration_minutes. Removed placeholder comments. Updated test to propose an open-day time that doesn't extend past closing. --- backend/handlers/bookings/deposit_test.go | 9 ++++++- backend/handlers/bookings/manage.go | 32 ++++++++++++++++++++--- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/backend/handlers/bookings/deposit_test.go b/backend/handlers/bookings/deposit_test.go index c988068..485b376 100644 --- a/backend/handlers/bookings/deposit_test.go +++ b/backend/handlers/bookings/deposit_test.go @@ -425,7 +425,14 @@ func TestRequestEditHandler_NoticePeriod_AllowsWhenEnoughNotice(t *testing.T) { } token := jwt.GenerateUserToken(userID) - newTime := bookingTime.Add(48 * time.Hour) + // Set the new time to 10:00 London time on a weekday that's clearly within + // working hours. Compute from the current time to avoid DST boundary issues. + londonNow := clock.Now().In(clock.London) + nextWeekday := londonNow.AddDate(0, 0, 1) + for nextWeekday.Weekday() == time.Sunday || nextWeekday.Weekday() == time.Monday { + nextWeekday = nextWeekday.AddDate(0, 0, 1) + } + newTime := time.Date(nextWeekday.Year(), nextWeekday.Month(), nextWeekday.Day(), 10, 0, 0, 0, clock.London).UTC() handler := http.HandlerFunc(RequestEditHandler) w := makeRequest(handler, "POST", "/api/bookings/"+bookingID+"/edit-request", map[string]interface{}{ diff --git a/backend/handlers/bookings/manage.go b/backend/handlers/bookings/manage.go index 91774dd..38ba1ce 100644 --- a/backend/handlers/bookings/manage.go +++ b/backend/handlers/bookings/manage.go @@ -1310,9 +1310,6 @@ func RequestEditHandler(w http.ResponseWriter, r *http.Request) { return } - // M8 - // L5 - // Validate: at least one of new_start_time, new_services, or notes must be provided if req.NewStartTime == nil && len(req.NewServices) == 0 && req.Notes == nil { http.Error(w, "At least one of new_start_time, new_services, or notes is required", http.StatusBadRequest) @@ -1340,8 +1337,9 @@ func RequestEditHandler(w http.ResponseWriter, r *http.Request) { // Check booking is not already completed/cancelled var currentStatus string var currentStartTime time.Time + var currentDuration int var depositRequired bool - err = db.Conn.QueryRow(r.Context(), "SELECT status, start_time, deposit_required FROM bookings WHERE id = $1", bookingID).Scan(¤tStatus, ¤tStartTime, &depositRequired) + err = db.Conn.QueryRow(r.Context(), "SELECT status, start_time, total_duration_minutes, deposit_required FROM bookings WHERE id = $1", bookingID).Scan(¤tStatus, ¤tStartTime, ¤tDuration, &depositRequired) if err != nil { log.Printf("Failed to get booking status %s: %v", bookingID, err) http.Error(w, "Internal server error", http.StatusInternalServerError) @@ -1409,6 +1407,32 @@ func RequestEditHandler(w http.ResponseWriter, r *http.Request) { } } + // Validate proposed time is within working hours (including staged hours changes) + if req.NewStartTime != nil { + newTime := req.NewStartTime.In(londonLocation) + weekday := int((newTime.Weekday() + 6) % 7) + + // Get closing time (respects staged default hours changes) + closeStr, err := getClosingTimeForDate(r.Context(), db.Conn, weekday, newTime) + if err != nil { + log.Printf("Failed to get hours for edit request: %v", err) + http.Error(w, "Could not verify hours", http.StatusInternalServerError) + return + } + if closeStr == "00:00" || closeStr == "00:00:00" { + http.Error(w, "The salon is closed at this time on this day", http.StatusBadRequest) + return + } + + // Check the proposed end time (based on the booking's service duration) + // doesn't extend beyond closing hours. + localEnd := newTime.Add(time.Duration(currentDuration) * time.Minute) + if err := checkClosingHours(localEnd, closeStr); err != nil { + http.Error(w, "This time would extend beyond closing hours", http.StatusBadRequest) + return + } + } + tx, err := db.Conn.Begin(r.Context()) if err != nil { log.Printf("Failed to start transaction: %v", err)