fix calendar again
This commit is contained in:
@@ -86,7 +86,7 @@ func UpdateDefaultHours(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// --- GetWorkingHours (merged default + applied exceptions) ---
|
||||
// --- GetWorkingHours (merged default + applied exceptions, UK-local) ---
|
||||
func GetWorkingHours(w http.ResponseWriter, r *http.Request) {
|
||||
startStr := r.URL.Query().Get("start")
|
||||
endStr := r.URL.Query().Get("end")
|
||||
@@ -94,17 +94,23 @@ func GetWorkingHours(w http.ResponseWriter, r *http.Request) {
|
||||
http.Error(w, "start and end query params required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
start, err := time.Parse("2006-01-02", startStr)
|
||||
|
||||
ukLocation, _ := time.LoadLocation("Europe/London")
|
||||
start, err := time.ParseInLocation("2006-01-02", startStr, ukLocation)
|
||||
if err != nil {
|
||||
http.Error(w, "invalid start date", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
end, err := time.Parse("2006-01-02", endStr)
|
||||
end, err := time.ParseInLocation("2006-01-02", endStr, ukLocation)
|
||||
if err != nil {
|
||||
http.Error(w, "invalid end date", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Set to local start/end of day
|
||||
start = time.Date(start.Year(), start.Month(), start.Day(), 0, 0, 0, 0, ukLocation)
|
||||
end = time.Date(end.Year(), end.Month(), end.Day(), 23, 59, 59, 999999999, ukLocation)
|
||||
|
||||
// Load default hours
|
||||
defaultMap := map[int]DefaultHours{}
|
||||
defRows, _ := db.DB.Query(r.Context(), `
|
||||
@@ -119,35 +125,31 @@ func GetWorkingHours(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
defRows.Close()
|
||||
|
||||
// Load exceptional applications in range
|
||||
// Load exceptional applications for Mondays in range
|
||||
appRows, _ := db.DB.Query(r.Context(), `
|
||||
SELECT a.group_id, a.week_start
|
||||
FROM exceptional_group_applications a
|
||||
WHERE a.week_start <= $1 AND a.week_start >= $2 - INTERVAL '6 days'
|
||||
`, end, start) // any week overlapping the range
|
||||
SELECT group_id, week_start
|
||||
FROM exceptional_group_applications
|
||||
WHERE week_start BETWEEN $1 AND $2
|
||||
`, start, end)
|
||||
type appEntry struct {
|
||||
GroupID int
|
||||
WeekStart time.Time
|
||||
}
|
||||
var apps []appEntry
|
||||
apps := []appEntry{}
|
||||
groupIDs := []int{}
|
||||
for appRows.Next() {
|
||||
var e appEntry
|
||||
if err := appRows.Scan(&e.GroupID, &e.WeekStart); err == nil {
|
||||
apps = append(apps, e)
|
||||
var a appEntry
|
||||
if err := appRows.Scan(&a.GroupID, &a.WeekStart); err == nil {
|
||||
a.WeekStart = time.Date(a.WeekStart.Year(), a.WeekStart.Month(), a.WeekStart.Day(), 0, 0, 0, 0, ukLocation)
|
||||
apps = append(apps, a)
|
||||
groupIDs = append(groupIDs, a.GroupID)
|
||||
}
|
||||
}
|
||||
appRows.Close()
|
||||
|
||||
// Load exceptional
|
||||
// Load exceptional hours for all relevant groups
|
||||
groupIDs := []int{}
|
||||
for _, a := range apps {
|
||||
groupIDs = append(groupIDs, a.GroupID)
|
||||
}
|
||||
|
||||
exHoursMap := map[int]map[int]ExceptionalHours{} // groupID -> weekday -> hours
|
||||
exHoursMap := map[int]map[int]ExceptionalHours{}
|
||||
if len(groupIDs) > 0 {
|
||||
query, args, _ := sqlIn("SELECT group_id, weekday, start_time::text, end_time::text, is_open FROM exceptional_working_hours WHERE group_id IN (?)", groupIDs)
|
||||
query, args, _ := sqlIn("SELECT group_id, weekday, start_time::text, end_time::text, is_open FROM exceptional_working_hours WHERE group_id IN (%s)", groupIDs)
|
||||
rows, _ := db.DB.Query(r.Context(), query, args...)
|
||||
for rows.Next() {
|
||||
var h ExceptionalHours
|
||||
@@ -168,14 +170,22 @@ func GetWorkingHours(w http.ResponseWriter, r *http.Request) {
|
||||
if weekday == 0 {
|
||||
weekday = 6 // Go Sunday=0 -> our Sunday=6
|
||||
} else {
|
||||
weekday -= 1 // shift Monday=0 ... Sunday=6
|
||||
weekday -= 1
|
||||
}
|
||||
|
||||
// find applied group for this week
|
||||
// Calculate the Monday of this week
|
||||
daysSinceMonday := int(d.Weekday()) - 1
|
||||
if daysSinceMonday < 0 {
|
||||
daysSinceMonday = 6 // Sunday
|
||||
}
|
||||
weekStart := d.AddDate(0, 0, -daysSinceMonday)
|
||||
weekStart = time.Date(weekStart.Year(), weekStart.Month(), weekStart.Day(), 0, 0, 0, 0, ukLocation)
|
||||
|
||||
var applied *ExceptionalHours
|
||||
weekStart := d.AddDate(0, 0, -weekday) // Monday of current week
|
||||
weekStartStr := weekStart.Format("2006-01-02")
|
||||
for _, a := range apps {
|
||||
if a.WeekStart.Equal(weekStart) {
|
||||
appWeekStartStr := a.WeekStart.Format("2006-01-02")
|
||||
if appWeekStartStr == weekStartStr {
|
||||
if dayHours, ok := exHoursMap[a.GroupID][weekday]; ok {
|
||||
applied = &dayHours
|
||||
}
|
||||
@@ -240,7 +250,7 @@ type DayAvailableHours struct {
|
||||
Source string `json:"source"`
|
||||
}
|
||||
|
||||
// --- GetAvailableHours (working hours with gaps for lunch, bookings, etc.) ---
|
||||
// --- GetAvailableHours (with bookings, UK-local) ---
|
||||
func GetAvailableHours(w http.ResponseWriter, r *http.Request) {
|
||||
startStr := r.URL.Query().Get("start")
|
||||
endStr := r.URL.Query().Get("end")
|
||||
@@ -249,35 +259,17 @@ func GetAvailableHours(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// Load UK timezone
|
||||
ukLocation, err := time.LoadLocation("Europe/London")
|
||||
if err != nil {
|
||||
http.Error(w, "failed to load timezone", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
ukLocation, _ := time.LoadLocation("Europe/London")
|
||||
start, _ := time.ParseInLocation("2006-01-02", startStr, ukLocation)
|
||||
end, _ := time.ParseInLocation("2006-01-02", endStr, ukLocation)
|
||||
|
||||
// Parse dates in UK timezone
|
||||
start, err := time.ParseInLocation("2006-01-02", startStr, ukLocation)
|
||||
if err != nil {
|
||||
http.Error(w, "invalid start date", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
end, err := time.ParseInLocation("2006-01-02", endStr, ukLocation)
|
||||
if err != nil {
|
||||
http.Error(w, "invalid end date", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Set to start of day (00:00:00) and end of day (23:59:59)
|
||||
// set start/end of day
|
||||
start = time.Date(start.Year(), start.Month(), start.Day(), 0, 0, 0, 0, ukLocation)
|
||||
end = time.Date(end.Year(), end.Month(), end.Day(), 23, 59, 59, 999999999, ukLocation)
|
||||
|
||||
// Load default hours
|
||||
defaultMap := map[int]DefaultHours{}
|
||||
defRows, _ := db.DB.Query(r.Context(), `
|
||||
SELECT weekday, start_time::text, end_time::text, is_open
|
||||
FROM working_hours
|
||||
`)
|
||||
defRows, _ := db.DB.Query(r.Context(), `SELECT weekday, start_time::text, end_time::text, is_open FROM working_hours`)
|
||||
for defRows.Next() {
|
||||
var d DefaultHours
|
||||
if err := defRows.Scan(&d.Weekday, &d.StartTime, &d.EndTime, &d.IsOpen); err == nil {
|
||||
@@ -286,34 +278,31 @@ func GetAvailableHours(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
defRows.Close()
|
||||
|
||||
// Load exceptional applications in range
|
||||
// Load exceptional applications for Mondays in range
|
||||
appRows, _ := db.DB.Query(r.Context(), `
|
||||
SELECT a.group_id, a.week_start
|
||||
FROM exceptional_group_applications a
|
||||
WHERE a.week_start <= $1 AND a.week_start >= $2 - INTERVAL '6 days'
|
||||
`, end, start)
|
||||
SELECT group_id, week_start
|
||||
FROM exceptional_group_applications
|
||||
WHERE week_start BETWEEN $1 AND $2
|
||||
`, start, end)
|
||||
type appEntry struct {
|
||||
GroupID int
|
||||
WeekStart time.Time
|
||||
}
|
||||
var apps []appEntry
|
||||
apps := []appEntry{}
|
||||
groupIDs := []int{}
|
||||
for appRows.Next() {
|
||||
var e appEntry
|
||||
if err := appRows.Scan(&e.GroupID, &e.WeekStart); err == nil {
|
||||
apps = append(apps, e)
|
||||
var a appEntry
|
||||
if err := appRows.Scan(&a.GroupID, &a.WeekStart); err == nil {
|
||||
a.WeekStart = time.Date(a.WeekStart.Year(), a.WeekStart.Month(), a.WeekStart.Day(), 0, 0, 0, 0, ukLocation)
|
||||
apps = append(apps, a)
|
||||
groupIDs = append(groupIDs, a.GroupID)
|
||||
}
|
||||
}
|
||||
appRows.Close()
|
||||
|
||||
// Load exceptional hours for all relevant groups
|
||||
groupIDs := []int{}
|
||||
for _, a := range apps {
|
||||
groupIDs = append(groupIDs, a.GroupID)
|
||||
}
|
||||
|
||||
exHoursMap := map[int]map[int]ExceptionalHours{} // groupID -> weekday -> hours
|
||||
exHoursMap := map[int]map[int]ExceptionalHours{}
|
||||
if len(groupIDs) > 0 {
|
||||
query, args, _ := sqlIn("SELECT group_id, weekday, start_time::text, end_time::text, is_open FROM exceptional_working_hours WHERE group_id IN (?)", groupIDs)
|
||||
query, args, _ := sqlIn("SELECT group_id, weekday, start_time::text, end_time::text, is_open FROM exceptional_working_hours WHERE group_id IN (%s)", groupIDs)
|
||||
rows, _ := db.DB.Query(r.Context(), query, args...)
|
||||
for rows.Next() {
|
||||
var h ExceptionalHours
|
||||
@@ -327,58 +316,56 @@ func GetAvailableHours(w http.ResponseWriter, r *http.Request) {
|
||||
rows.Close()
|
||||
}
|
||||
|
||||
// Load bookings with their total duration (sum of all services)
|
||||
bookingRows, err := db.DB.Query(r.Context(), `
|
||||
SELECT
|
||||
b.start_time,
|
||||
COALESCE(SUM(s.duration_minutes), 0) as total_duration
|
||||
FROM bookings b
|
||||
LEFT JOIN booking_services bs ON b.id = bs.booking_id
|
||||
LEFT JOIN services s ON bs.service_id = s.id
|
||||
WHERE b.start_time >= $1
|
||||
AND b.start_time <= $2
|
||||
GROUP BY b.id, b.start_time
|
||||
ORDER BY b.start_time
|
||||
`, start, end)
|
||||
// Load bookings
|
||||
bookingRows, _ := db.DB.Query(r.Context(), `
|
||||
SELECT b.start_time, COALESCE(SUM(s.duration_minutes),0) as total_duration
|
||||
FROM bookings b
|
||||
LEFT JOIN booking_services bs ON b.id = bs.booking_id
|
||||
LEFT JOIN services s ON bs.service_id = s.id
|
||||
WHERE b.start_time >= $1 AND b.start_time <= $2
|
||||
GROUP BY b.id, b.start_time
|
||||
ORDER BY b.start_time
|
||||
`, start, end)
|
||||
|
||||
if err != nil {
|
||||
http.Error(w, "failed to query bookings", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
bookings := map[string][]TimeSlot{} // date -> booked slots
|
||||
bookings := map[string][]TimeSlot{}
|
||||
for bookingRows.Next() {
|
||||
var startTime time.Time
|
||||
var durationMinutes int
|
||||
if err := bookingRows.Scan(&startTime, &durationMinutes); err == nil {
|
||||
// Convert to UK timezone for date formatting
|
||||
startTimeUK := startTime.In(ukLocation)
|
||||
dateStr := startTimeUK.Format("2006-01-02")
|
||||
endTime := startTimeUK.Add(time.Duration(durationMinutes) * time.Minute)
|
||||
|
||||
var t time.Time
|
||||
var dur int
|
||||
if err := bookingRows.Scan(&t, &dur); err == nil {
|
||||
t = t.In(ukLocation)
|
||||
dateStr := t.Format("2006-01-02")
|
||||
endTime := t.Add(time.Duration(dur) * time.Minute)
|
||||
bookings[dateStr] = append(bookings[dateStr], TimeSlot{
|
||||
StartTime: startTimeUK.Format("15:04"),
|
||||
StartTime: t.Format("15:04"),
|
||||
EndTime: endTime.Format("15:04"),
|
||||
})
|
||||
}
|
||||
}
|
||||
bookingRows.Close()
|
||||
|
||||
// Generate final result per day with gaps
|
||||
// Generate available slots per day
|
||||
var results []DayAvailableHours
|
||||
for d := start; !d.After(end); d = d.AddDate(0, 0, 1) {
|
||||
weekday := int(d.Weekday())
|
||||
if weekday == 0 {
|
||||
weekday = 6 // Go Sunday=0 -> our Sunday=6
|
||||
weekday = 6
|
||||
} else {
|
||||
weekday -= 1 // shift Monday=0 ... Sunday=6
|
||||
weekday -= 1
|
||||
}
|
||||
|
||||
// Find applied group for this week
|
||||
// Calculate the Monday of this week
|
||||
daysSinceMonday := int(d.Weekday()) - 1
|
||||
if daysSinceMonday < 0 {
|
||||
daysSinceMonday = 6 // Sunday
|
||||
}
|
||||
weekStart := d.AddDate(0, 0, -daysSinceMonday)
|
||||
weekStart = time.Date(weekStart.Year(), weekStart.Month(), weekStart.Day(), 0, 0, 0, 0, ukLocation)
|
||||
|
||||
var applied *ExceptionalHours
|
||||
weekStart := d.AddDate(0, 0, -weekday) // Monday of current week
|
||||
weekStartStr := weekStart.Format("2006-01-02")
|
||||
for _, a := range apps {
|
||||
if a.WeekStart.Equal(weekStart) {
|
||||
appWeekStartStr := a.WeekStart.Format("2006-01-02")
|
||||
if appWeekStartStr == weekStartStr {
|
||||
if dayHours, ok := exHoursMap[a.GroupID][weekday]; ok {
|
||||
applied = &dayHours
|
||||
}
|
||||
@@ -390,7 +377,6 @@ func GetAvailableHours(w http.ResponseWriter, r *http.Request) {
|
||||
day.Date = d.Format("2006-01-02")
|
||||
day.Weekday = weekday
|
||||
|
||||
// Determine base working hours and source
|
||||
var baseStart, baseEnd string
|
||||
var isOpen bool
|
||||
if applied != nil {
|
||||
@@ -411,18 +397,12 @@ func GetAvailableHours(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
day.IsOpen = isOpen
|
||||
|
||||
if isOpen {
|
||||
// Start with the full working hours as one slot
|
||||
allSlots := []TimeSlot{{StartTime: baseStart, EndTime: baseEnd}}
|
||||
|
||||
// Apply gaps from bookings
|
||||
dateStr := day.Date
|
||||
if booked, ok := bookings[dateStr]; ok {
|
||||
allSlots = subtractTimeSlots(allSlots, booked)
|
||||
slots := []TimeSlot{{StartTime: baseStart, EndTime: baseEnd}}
|
||||
if booked, ok := bookings[day.Date]; ok {
|
||||
slots = subtractTimeSlots(slots, booked)
|
||||
}
|
||||
|
||||
day.Slots = allSlots
|
||||
day.Slots = slots
|
||||
} else {
|
||||
day.Slots = []TimeSlot{}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user