diff --git a/backend/handlers/scheduling/default-hours.go b/backend/handlers/scheduling/default-hours.go index 9b8a7b2..df13b84 100644 --- a/backend/handlers/scheduling/default-hours.go +++ b/backend/handlers/scheduling/default-hours.go @@ -17,28 +17,6 @@ type DefaultHours struct { IsOpen bool `json:"isOpen"` } -type ExceptionalHours struct { - ID int `json:"id"` - GroupID int `json:"groupId"` - Weekday int `json:"weekday"` - StartTime string `json:"startTime"` - EndTime string `json:"endTime"` - IsOpen bool `json:"isOpen"` -} - -type ExceptionalGroup struct { - ID int `json:"id"` - Name string `json:"name"` - Description string `json:"description"` - Hours []ExceptionalHours `json:"hours,omitempty"` // 7 entries -} - -type ExceptionalApplication struct { - ID int `json:"id"` - GroupID int `json:"groupId"` - WeekStart string `json:"weekStart"` // Monday date -} - type DayWorkingHours struct { Date string `json:"date"` Weekday int `json:"weekday"` @@ -108,149 +86,6 @@ func UpdateDefaultHours(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNoContent) } -// --- Exceptional Groups & Hours --- -func ListExceptionalGroups(w http.ResponseWriter, r *http.Request) { - rows, err := db.DB.Query(r.Context(), ` - SELECT id,name,description - FROM exceptional_working_hours_groups - ORDER BY id DESC - `) - if err != nil { - http.Error(w, "failed to fetch groups", http.StatusInternalServerError) - return - } - defer rows.Close() - - var groups []ExceptionalGroup - for rows.Next() { - var g ExceptionalGroup - if err := rows.Scan(&g.ID, &g.Name, &g.Description); err != nil { - http.Error(w, "failed to scan group", http.StatusInternalServerError) - return - } - // load 7-day hours - hoursRows, _ := db.DB.Query(r.Context(), ` - SELECT id, weekday, start_time::text, end_time::text, is_open - FROM exceptional_working_hours - WHERE group_id=$1 ORDER BY weekday - `, g.ID) - for hoursRows.Next() { - var h ExceptionalHours - if err := hoursRows.Scan(&h.ID, &h.Weekday, &h.StartTime, &h.EndTime, &h.IsOpen); err == nil { - h.GroupID = g.ID - g.Hours = append(g.Hours, h) - } - } - hoursRows.Close() - groups = append(groups, g) - } - - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(groups) -} - -func CreateExceptionalGroup(w http.ResponseWriter, r *http.Request) { - var g ExceptionalGroup - if err := json.NewDecoder(r.Body).Decode(&g); err != nil { - http.Error(w, "invalid payload", http.StatusBadRequest) - return - } - if len(g.Hours) != 7 { - http.Error(w, "must provide 7 weekday entries", http.StatusBadRequest) - return - } - - tx, err := db.DB.Begin(r.Context()) - if err != nil { - http.Error(w, "failed to start tx", http.StatusInternalServerError) - return - } - defer tx.Rollback(r.Context()) - - err = tx.QueryRow(r.Context(), ` - INSERT INTO exceptional_working_hours_groups (name, description) - VALUES ($1,$2) RETURNING id - `, g.Name, g.Description).Scan(&g.ID) - if err != nil { - http.Error(w, "failed to create group", http.StatusInternalServerError) - return - } - - for _, h := range g.Hours { - _, err := tx.Exec(r.Context(), ` - INSERT INTO exceptional_working_hours (group_id, weekday, start_time, end_time, is_open) - VALUES ($1,$2,$3,$4,$5) - `, g.ID, h.Weekday, h.StartTime, h.EndTime, h.IsOpen) - if err != nil { - http.Error(w, "failed to insert group hours", http.StatusInternalServerError) - return - } - } - - if err := tx.Commit(r.Context()); err != nil { - http.Error(w, "failed to commit", http.StatusInternalServerError) - return - } - - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(g) -} - -// --- Exceptional Applications (assign a group to a week) --- -func ListExceptionalApplications(w http.ResponseWriter, r *http.Request) { - rows, err := db.DB.Query(r.Context(), ` - SELECT id, group_id, week_start - FROM exceptional_group_applications - ORDER BY week_start DESC - `) - if err != nil { - http.Error(w, "failed to fetch applications", http.StatusInternalServerError) - return - } - defer rows.Close() - - var list []ExceptionalApplication - for rows.Next() { - var a ExceptionalApplication - var weekStart time.Time - if err := rows.Scan(&a.ID, &a.GroupID, &weekStart); err != nil { - http.Error(w, "failed to scan application", http.StatusInternalServerError) - return - } - a.WeekStart = weekStart.Format("2006-01-02") - list = append(list, a) - } - - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(list) -} - -func CreateExceptionalApplication(w http.ResponseWriter, r *http.Request) { - var a ExceptionalApplication - if err := json.NewDecoder(r.Body).Decode(&a); err != nil { - http.Error(w, "invalid payload", http.StatusBadRequest) - return - } - weekStart, err := time.Parse("2006-01-02", a.WeekStart) - if err != nil { - http.Error(w, "invalid week_start format, expected YYYY-MM-DD", http.StatusBadRequest) - return - } - - err = db.DB.QueryRow(r.Context(), ` - INSERT INTO exceptional_group_applications (group_id, week_start) - VALUES ($1,$2) - RETURNING id - `, a.GroupID, weekStart).Scan(&a.ID) - if err != nil { - http.Error(w, "failed to create application", http.StatusInternalServerError) - return - } - - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(a) -} - // --- GetWorkingHours (merged default + applied exceptions) --- func GetWorkingHours(w http.ResponseWriter, r *http.Request) { startStr := r.URL.Query().Get("start") @@ -401,8 +236,8 @@ type DayAvailableHours struct { Date string `json:"date"` Weekday int `json:"weekday"` IsOpen bool `json:"isOpen"` - Slots []TimeSlot `json:"slots"` // Can have gaps (lunch, bookings, etc.) - Source string `json:"source"` // "default" or "exceptional" + Slots []TimeSlot `json:"slots"` + Source string `json:"source"` } // --- GetAvailableHours (working hours with gaps for lunch, bookings, etc.) --- diff --git a/backend/handlers/scheduling/exceptional-hours.go b/backend/handlers/scheduling/exceptional-hours.go new file mode 100644 index 0000000..1b0e5d6 --- /dev/null +++ b/backend/handlers/scheduling/exceptional-hours.go @@ -0,0 +1,174 @@ +package scheduling + +import ( + "encoding/json" + "net/http" + "time" + + "crussell/db" +) + +type ExceptionalHours struct { + ID int `json:"id"` + GroupID int `json:"groupId"` + Weekday int `json:"weekday"` + StartTime string `json:"startTime"` + EndTime string `json:"endTime"` + IsOpen bool `json:"isOpen"` +} + +type ExceptionalGroup struct { + ID int `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Hours []ExceptionalHours `json:"hours,omitempty"` // opening hours per day, 7 (day) entries per week +} + +type ExceptionalApplication struct { + ID int `json:"id"` + GroupID int `json:"groupId"` + WeekStart string `json:"weekStart"` // week beginning monday the Xth +} + +// --- Exceptional Groups & Hours --- +func ListExceptionalGroups(w http.ResponseWriter, r *http.Request) { + rows, err := db.DB.Query(r.Context(), ` + SELECT id,name,description + FROM exceptional_working_hours_groups + ORDER BY id DESC + `) + if err != nil { + http.Error(w, "failed to fetch groups", http.StatusInternalServerError) + return + } + defer rows.Close() + + var groups []ExceptionalGroup + for rows.Next() { + var g ExceptionalGroup + if err := rows.Scan(&g.ID, &g.Name, &g.Description); err != nil { + http.Error(w, "failed to scan group", http.StatusInternalServerError) + return + } + // load 7-day hours + hoursRows, _ := db.DB.Query(r.Context(), ` + SELECT id, weekday, start_time::text, end_time::text, is_open + FROM exceptional_working_hours + WHERE group_id=$1 ORDER BY weekday + `, g.ID) + for hoursRows.Next() { + var h ExceptionalHours + if err := hoursRows.Scan(&h.ID, &h.Weekday, &h.StartTime, &h.EndTime, &h.IsOpen); err == nil { + h.GroupID = g.ID + g.Hours = append(g.Hours, h) + } + } + hoursRows.Close() + groups = append(groups, g) + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(groups) +} + +func CreateExceptionalGroup(w http.ResponseWriter, r *http.Request) { + var g ExceptionalGroup + if err := json.NewDecoder(r.Body).Decode(&g); err != nil { + http.Error(w, "invalid payload", http.StatusBadRequest) + return + } + if len(g.Hours) != 7 { + http.Error(w, "must provide 7 weekday entries", http.StatusBadRequest) + return + } + + tx, err := db.DB.Begin(r.Context()) + if err != nil { + http.Error(w, "failed to start tx", http.StatusInternalServerError) + return + } + defer tx.Rollback(r.Context()) + + err = tx.QueryRow(r.Context(), ` + INSERT INTO exceptional_working_hours_groups (name, description) + VALUES ($1,$2) RETURNING id + `, g.Name, g.Description).Scan(&g.ID) + if err != nil { + http.Error(w, "failed to create group", http.StatusInternalServerError) + return + } + + for _, h := range g.Hours { + _, err := tx.Exec(r.Context(), ` + INSERT INTO exceptional_working_hours (group_id, weekday, start_time, end_time, is_open) + VALUES ($1,$2,$3,$4,$5) + `, g.ID, h.Weekday, h.StartTime, h.EndTime, h.IsOpen) + if err != nil { + http.Error(w, "failed to insert group hours", http.StatusInternalServerError) + return + } + } + + if err := tx.Commit(r.Context()); err != nil { + http.Error(w, "failed to commit", http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(g) +} + +// --- Exceptional Applications (assign a group to a week) --- +func ListExceptionalApplications(w http.ResponseWriter, r *http.Request) { + rows, err := db.DB.Query(r.Context(), ` + SELECT id, group_id, week_start + FROM exceptional_group_applications + ORDER BY week_start DESC + `) + if err != nil { + http.Error(w, "failed to fetch applications", http.StatusInternalServerError) + return + } + defer rows.Close() + + var list []ExceptionalApplication + for rows.Next() { + var a ExceptionalApplication + var weekStart time.Time + if err := rows.Scan(&a.ID, &a.GroupID, &weekStart); err != nil { + http.Error(w, "failed to scan application", http.StatusInternalServerError) + return + } + a.WeekStart = weekStart.Format("2006-01-02") + list = append(list, a) + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(list) +} + +func CreateExceptionalApplication(w http.ResponseWriter, r *http.Request) { + var a ExceptionalApplication + if err := json.NewDecoder(r.Body).Decode(&a); err != nil { + http.Error(w, "invalid payload", http.StatusBadRequest) + return + } + weekStart, err := time.Parse("2006-01-02", a.WeekStart) + if err != nil { + http.Error(w, "invalid week_start format, expected YYYY-MM-DD", http.StatusBadRequest) + return + } + + err = db.DB.QueryRow(r.Context(), ` + INSERT INTO exceptional_group_applications (group_id, week_start) + VALUES ($1,$2) + RETURNING id + `, a.GroupID, weekStart).Scan(&a.ID) + if err != nil { + http.Error(w, "failed to create application", http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(a) +}