Add timetable scheduler Phases 1 + 2 to school-service
Phase 1 — Stammdaten (7 tables):
tt_class, tt_period, tt_room, tt_subject, tt_teacher,
tt_curriculum, tt_assignment with CRUD endpoints.
Phase 2 — Constraints (15 typed tables):
Teacher (6): unavailable_day, unavailable_window, max_hours_day,
max_hours_week, excluded_subject, excluded_room
Subject (5): min_day_gap, max_consecutive, contiguous_when_repeated,
preferred_period, double_lesson
Class (2): max_hours_day, no_gaps
Room (2): requires_type, unavailable
Each constraint row carries is_hard / weight / active / note /
created_by_user_id; ownership enforced via WHERE EXISTS against the
parent tt_teacher/tt_class/tt_subject/tt_room row.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,202 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/breakpilot/school-service/internal/models"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// Class- and Room-constraint HTTP handlers.
|
||||
|
||||
// ---------- Class Max Hours / Day ----------
|
||||
|
||||
func (h *Handler) CreateClassMaxHoursDay(c *gin.Context) {
|
||||
uid := getUserID(c)
|
||||
if uid == "" {
|
||||
respondError(c, http.StatusUnauthorized, "User not authenticated")
|
||||
return
|
||||
}
|
||||
var req models.CreateClassMaxHoursDayRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
respondError(c, http.StatusBadRequest, "Invalid request: "+err.Error())
|
||||
return
|
||||
}
|
||||
out, err := h.timetableService.CreateClassMaxHoursDay(c.Request.Context(), uid, &req)
|
||||
if err != nil {
|
||||
respondError(c, http.StatusInternalServerError, "Failed to create constraint: "+err.Error())
|
||||
return
|
||||
}
|
||||
respondCreated(c, out)
|
||||
}
|
||||
|
||||
func (h *Handler) ListClassMaxHoursDay(c *gin.Context) {
|
||||
uid := getUserID(c)
|
||||
if uid == "" {
|
||||
respondError(c, http.StatusUnauthorized, "User not authenticated")
|
||||
return
|
||||
}
|
||||
out, err := h.timetableService.ListClassMaxHoursDay(c.Request.Context(), uid)
|
||||
if err != nil {
|
||||
respondError(c, http.StatusInternalServerError, "Failed to list constraints: "+err.Error())
|
||||
return
|
||||
}
|
||||
respondSuccess(c, out)
|
||||
}
|
||||
|
||||
func (h *Handler) DeleteClassMaxHoursDay(c *gin.Context) {
|
||||
uid := getUserID(c)
|
||||
if uid == "" {
|
||||
respondError(c, http.StatusUnauthorized, "User not authenticated")
|
||||
return
|
||||
}
|
||||
if err := h.timetableService.DeleteClassMaxHoursDay(c.Request.Context(), c.Param("id"), uid); err != nil {
|
||||
respondError(c, http.StatusInternalServerError, "Failed to delete constraint: "+err.Error())
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Constraint deleted"})
|
||||
}
|
||||
|
||||
// ---------- Class No Gaps ----------
|
||||
|
||||
func (h *Handler) CreateClassNoGaps(c *gin.Context) {
|
||||
uid := getUserID(c)
|
||||
if uid == "" {
|
||||
respondError(c, http.StatusUnauthorized, "User not authenticated")
|
||||
return
|
||||
}
|
||||
var req models.CreateClassNoGapsRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
respondError(c, http.StatusBadRequest, "Invalid request: "+err.Error())
|
||||
return
|
||||
}
|
||||
out, err := h.timetableService.CreateClassNoGaps(c.Request.Context(), uid, &req)
|
||||
if err != nil {
|
||||
respondError(c, http.StatusInternalServerError, "Failed to create constraint: "+err.Error())
|
||||
return
|
||||
}
|
||||
respondCreated(c, out)
|
||||
}
|
||||
|
||||
func (h *Handler) ListClassNoGaps(c *gin.Context) {
|
||||
uid := getUserID(c)
|
||||
if uid == "" {
|
||||
respondError(c, http.StatusUnauthorized, "User not authenticated")
|
||||
return
|
||||
}
|
||||
out, err := h.timetableService.ListClassNoGaps(c.Request.Context(), uid)
|
||||
if err != nil {
|
||||
respondError(c, http.StatusInternalServerError, "Failed to list constraints: "+err.Error())
|
||||
return
|
||||
}
|
||||
respondSuccess(c, out)
|
||||
}
|
||||
|
||||
func (h *Handler) DeleteClassNoGaps(c *gin.Context) {
|
||||
uid := getUserID(c)
|
||||
if uid == "" {
|
||||
respondError(c, http.StatusUnauthorized, "User not authenticated")
|
||||
return
|
||||
}
|
||||
if err := h.timetableService.DeleteClassNoGaps(c.Request.Context(), c.Param("id"), uid); err != nil {
|
||||
respondError(c, http.StatusInternalServerError, "Failed to delete constraint: "+err.Error())
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Constraint deleted"})
|
||||
}
|
||||
|
||||
// ---------- Room Requires Type ----------
|
||||
|
||||
func (h *Handler) CreateRoomRequiresType(c *gin.Context) {
|
||||
uid := getUserID(c)
|
||||
if uid == "" {
|
||||
respondError(c, http.StatusUnauthorized, "User not authenticated")
|
||||
return
|
||||
}
|
||||
var req models.CreateRoomRequiresTypeRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
respondError(c, http.StatusBadRequest, "Invalid request: "+err.Error())
|
||||
return
|
||||
}
|
||||
out, err := h.timetableService.CreateRoomRequiresType(c.Request.Context(), uid, &req)
|
||||
if err != nil {
|
||||
respondError(c, http.StatusInternalServerError, "Failed to create constraint: "+err.Error())
|
||||
return
|
||||
}
|
||||
respondCreated(c, out)
|
||||
}
|
||||
|
||||
func (h *Handler) ListRoomRequiresTypes(c *gin.Context) {
|
||||
uid := getUserID(c)
|
||||
if uid == "" {
|
||||
respondError(c, http.StatusUnauthorized, "User not authenticated")
|
||||
return
|
||||
}
|
||||
out, err := h.timetableService.ListRoomRequiresTypes(c.Request.Context(), uid)
|
||||
if err != nil {
|
||||
respondError(c, http.StatusInternalServerError, "Failed to list constraints: "+err.Error())
|
||||
return
|
||||
}
|
||||
respondSuccess(c, out)
|
||||
}
|
||||
|
||||
func (h *Handler) DeleteRoomRequiresType(c *gin.Context) {
|
||||
uid := getUserID(c)
|
||||
if uid == "" {
|
||||
respondError(c, http.StatusUnauthorized, "User not authenticated")
|
||||
return
|
||||
}
|
||||
if err := h.timetableService.DeleteRoomRequiresType(c.Request.Context(), c.Param("id"), uid); err != nil {
|
||||
respondError(c, http.StatusInternalServerError, "Failed to delete constraint: "+err.Error())
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Constraint deleted"})
|
||||
}
|
||||
|
||||
// ---------- Room Unavailable ----------
|
||||
|
||||
func (h *Handler) CreateRoomUnavailable(c *gin.Context) {
|
||||
uid := getUserID(c)
|
||||
if uid == "" {
|
||||
respondError(c, http.StatusUnauthorized, "User not authenticated")
|
||||
return
|
||||
}
|
||||
var req models.CreateRoomUnavailableRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
respondError(c, http.StatusBadRequest, "Invalid request: "+err.Error())
|
||||
return
|
||||
}
|
||||
out, err := h.timetableService.CreateRoomUnavailable(c.Request.Context(), uid, &req)
|
||||
if err != nil {
|
||||
respondError(c, http.StatusInternalServerError, "Failed to create constraint: "+err.Error())
|
||||
return
|
||||
}
|
||||
respondCreated(c, out)
|
||||
}
|
||||
|
||||
func (h *Handler) ListRoomUnavailable(c *gin.Context) {
|
||||
uid := getUserID(c)
|
||||
if uid == "" {
|
||||
respondError(c, http.StatusUnauthorized, "User not authenticated")
|
||||
return
|
||||
}
|
||||
out, err := h.timetableService.ListRoomUnavailable(c.Request.Context(), uid)
|
||||
if err != nil {
|
||||
respondError(c, http.StatusInternalServerError, "Failed to list constraints: "+err.Error())
|
||||
return
|
||||
}
|
||||
respondSuccess(c, out)
|
||||
}
|
||||
|
||||
func (h *Handler) DeleteRoomUnavailable(c *gin.Context) {
|
||||
uid := getUserID(c)
|
||||
if uid == "" {
|
||||
respondError(c, http.StatusUnauthorized, "User not authenticated")
|
||||
return
|
||||
}
|
||||
if err := h.timetableService.DeleteRoomUnavailable(c.Request.Context(), c.Param("id"), uid); err != nil {
|
||||
respondError(c, http.StatusInternalServerError, "Failed to delete constraint: "+err.Error())
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Constraint deleted"})
|
||||
}
|
||||
Reference in New Issue
Block a user