fix(scheduling): include reservations in GetTimeBlockersInRange and fix booking overlap query
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -420,7 +420,10 @@ func GetAvailableHours(w http.ResponseWriter, r *http.Request) {
|
||||
rows.Close()
|
||||
}
|
||||
|
||||
// Load bookings
|
||||
// Load bookings that overlap the query range.
|
||||
// Must catch bookings that STARTED before the range but EXTEND INTO it
|
||||
// (e.g. a booking at 23:00 the previous day lasting 120min crosses midnight).
|
||||
// Use the same overlap condition as the booking creation handlers.
|
||||
bookingRows, _ := db.Conn.Query(r.Context(), `
|
||||
SELECT
|
||||
b.start_time,
|
||||
@@ -440,7 +443,22 @@ func GetAvailableHours(w http.ResponseWriter, r *http.Request) {
|
||||
FROM booking_custom_services bcs LEFT JOIN custom_services cs ON bcs.custom_service_id = cs.id WHERE bcs.booking_id = b.id
|
||||
) sub), 0) AS total_duration
|
||||
FROM bookings b
|
||||
WHERE b.start_time >= $1 AND b.start_time <= $2
|
||||
WHERE b.start_time < $2
|
||||
AND b.start_time + (COALESCE((SELECT SUM(dur) FROM (
|
||||
SELECT CASE
|
||||
WHEN bs.override_duration_minutes IS NOT NULL AND bs.override_duration_minutes > 0
|
||||
THEN bs.override_duration_minutes
|
||||
ELSE s.duration_minutes
|
||||
END AS dur
|
||||
FROM booking_services bs LEFT JOIN services s ON bs.service_id = s.id WHERE bs.booking_id = b.id
|
||||
UNION ALL
|
||||
SELECT CASE
|
||||
WHEN bcs.override_duration_minutes IS NOT NULL AND bcs.override_duration_minutes > 0
|
||||
THEN bcs.override_duration_minutes
|
||||
ELSE cs.duration_minutes
|
||||
END
|
||||
FROM booking_custom_services bcs LEFT JOIN custom_services cs ON bcs.custom_service_id = cs.id WHERE bcs.booking_id = b.id
|
||||
) sub), 0) * INTERVAL '1 minute') > $1
|
||||
AND b.status NOT IN ('client_cancelled', 'we_cancelled', 'no_show', 'pending_release', 'deposit_lapsed')
|
||||
ORDER BY b.start_time
|
||||
`, start, end)
|
||||
|
||||
@@ -202,7 +202,6 @@ func GetTimeBlockersInRange(ctx context.Context, start, end time.Time) ([]TimeBl
|
||||
SELECT id, start_time, duration_minutes, description, cron_expression, created_at, created_by
|
||||
FROM time_blockers
|
||||
WHERE cron_expression IS NULL
|
||||
AND description NOT LIKE 'RESERVATION:%'
|
||||
AND start_time >= $1 AND start_time <= $2
|
||||
ORDER BY start_time
|
||||
`, start, end)
|
||||
|
||||
@@ -930,9 +930,10 @@ func TestCleanupOldReservations_MixedTypes(t *testing.T) {
|
||||
|
||||
// --- Tests for GetTimeBlockersInRange (Excludes Reservations) ---
|
||||
|
||||
// TestGetTimeBlockersInRange_ExcludesReservations verifies that reservation
|
||||
// blockers are excluded from the results.
|
||||
func TestGetTimeBlockersInRange_ExcludesReservations(t *testing.T) {
|
||||
// TestGetTimeBlockersInRange_IncludesReservations verifies that reservation
|
||||
// blockers ARE included in the results (needed for available-hours to correctly
|
||||
// exclude reserved slots).
|
||||
func TestGetTimeBlockersInRange_IncludesReservations(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx, tx := resetTestData(t)
|
||||
|
||||
@@ -968,14 +969,9 @@ func TestGetTimeBlockersInRange_ExcludesReservations(t *testing.T) {
|
||||
t.Fatalf("GetTimeBlockersInRange failed: %v", err)
|
||||
}
|
||||
|
||||
// Should return only 1 blocker (the regular one, not the reservation)
|
||||
if len(blockers) != 1 {
|
||||
t.Errorf("expected 1 blocker, got %d", len(blockers))
|
||||
}
|
||||
|
||||
// Verify the blocker is "Staff meeting"
|
||||
if len(blockers) > 0 && blockers[0].Description != "Staff meeting" {
|
||||
t.Errorf("expected blocker 'Staff meeting', got '%s'", blockers[0].Description)
|
||||
// Should return both blockers (regular + reservation)
|
||||
if len(blockers) != 2 {
|
||||
t.Errorf("expected 2 blockers (including reservation), got %d", len(blockers))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user