package services import ( "context" "github.com/breakpilot/school-service/internal/models" ) // Class- and room-scoped constraint CRUD. Ownership is via tt_class / // tt_subject / tt_room.created_by_user_id. // ---------- Class Max Hours / Day ---------- func (s *TimetableService) CreateClassMaxHoursDay(ctx context.Context, userID string, req *models.CreateClassMaxHoursDayRequest) (*models.ClassMaxHoursDay, error) { var c models.ClassMaxHoursDay err := s.db.QueryRow(ctx, ` INSERT INTO tt_constraint_class_max_hours_day (created_by_user_id, class_id, max_hours, is_hard, weight, active, note) SELECT $1, $2, $3, $4, $5, $6, $7 WHERE EXISTS (SELECT 1 FROM tt_class WHERE id = $2 AND created_by_user_id = $1) RETURNING id, created_by_user_id, class_id, max_hours, is_hard, weight, active, COALESCE(note,''), created_at `, userID, req.ClassID, req.MaxHours, req.IsHard, req.Weight, req.Active, req.Note).Scan( &c.ID, &c.CreatedByUserID, &c.ClassID, &c.MaxHours, &c.IsHard, &c.Weight, &c.Active, &c.Note, &c.CreatedAt, ) return &c, err } func (s *TimetableService) ListClassMaxHoursDay(ctx context.Context, userID string) ([]models.ClassMaxHoursDay, error) { rows, err := s.db.Query(ctx, ` SELECT id, created_by_user_id, class_id, max_hours, is_hard, weight, active, COALESCE(note,''), created_at FROM tt_constraint_class_max_hours_day WHERE created_by_user_id = $1 ORDER BY class_id `, userID) if err != nil { return nil, err } defer rows.Close() var out []models.ClassMaxHoursDay for rows.Next() { var c models.ClassMaxHoursDay if err := rows.Scan(&c.ID, &c.CreatedByUserID, &c.ClassID, &c.MaxHours, &c.IsHard, &c.Weight, &c.Active, &c.Note, &c.CreatedAt); err != nil { return nil, err } out = append(out, c) } return out, nil } func (s *TimetableService) DeleteClassMaxHoursDay(ctx context.Context, id, userID string) error { _, err := s.db.Exec(ctx, `DELETE FROM tt_constraint_class_max_hours_day WHERE id = $1 AND created_by_user_id = $2`, id, userID) return err } // ---------- Class No Gaps ---------- func (s *TimetableService) CreateClassNoGaps(ctx context.Context, userID string, req *models.CreateClassNoGapsRequest) (*models.ClassNoGaps, error) { var c models.ClassNoGaps err := s.db.QueryRow(ctx, ` INSERT INTO tt_constraint_class_no_gaps (created_by_user_id, class_id, is_hard, weight, active, note) SELECT $1, $2, $3, $4, $5, $6 WHERE EXISTS (SELECT 1 FROM tt_class WHERE id = $2 AND created_by_user_id = $1) RETURNING id, created_by_user_id, class_id, is_hard, weight, active, COALESCE(note,''), created_at `, userID, req.ClassID, req.IsHard, req.Weight, req.Active, req.Note).Scan( &c.ID, &c.CreatedByUserID, &c.ClassID, &c.IsHard, &c.Weight, &c.Active, &c.Note, &c.CreatedAt, ) return &c, err } func (s *TimetableService) ListClassNoGaps(ctx context.Context, userID string) ([]models.ClassNoGaps, error) { rows, err := s.db.Query(ctx, ` SELECT id, created_by_user_id, class_id, is_hard, weight, active, COALESCE(note,''), created_at FROM tt_constraint_class_no_gaps WHERE created_by_user_id = $1 ORDER BY class_id `, userID) if err != nil { return nil, err } defer rows.Close() var out []models.ClassNoGaps for rows.Next() { var c models.ClassNoGaps if err := rows.Scan(&c.ID, &c.CreatedByUserID, &c.ClassID, &c.IsHard, &c.Weight, &c.Active, &c.Note, &c.CreatedAt); err != nil { return nil, err } out = append(out, c) } return out, nil } func (s *TimetableService) DeleteClassNoGaps(ctx context.Context, id, userID string) error { _, err := s.db.Exec(ctx, `DELETE FROM tt_constraint_class_no_gaps WHERE id = $1 AND created_by_user_id = $2`, id, userID) return err } // ---------- Room Requires Type ---------- func (s *TimetableService) CreateRoomRequiresType(ctx context.Context, userID string, req *models.CreateRoomRequiresTypeRequest) (*models.RoomRequiresType, error) { var c models.RoomRequiresType err := s.db.QueryRow(ctx, ` INSERT INTO tt_constraint_room_requires_type (created_by_user_id, subject_id, room_type, is_hard, weight, active, note) SELECT $1, $2, $3, $4, $5, $6, $7 WHERE EXISTS (SELECT 1 FROM tt_subject WHERE id = $2 AND created_by_user_id = $1) RETURNING id, created_by_user_id, subject_id, room_type, is_hard, weight, active, COALESCE(note,''), created_at `, userID, req.SubjectID, req.RoomType, req.IsHard, req.Weight, req.Active, req.Note).Scan( &c.ID, &c.CreatedByUserID, &c.SubjectID, &c.RoomType, &c.IsHard, &c.Weight, &c.Active, &c.Note, &c.CreatedAt, ) return &c, err } func (s *TimetableService) ListRoomRequiresTypes(ctx context.Context, userID string) ([]models.RoomRequiresType, error) { rows, err := s.db.Query(ctx, ` SELECT id, created_by_user_id, subject_id, room_type, is_hard, weight, active, COALESCE(note,''), created_at FROM tt_constraint_room_requires_type WHERE created_by_user_id = $1 ORDER BY subject_id `, userID) if err != nil { return nil, err } defer rows.Close() var out []models.RoomRequiresType for rows.Next() { var c models.RoomRequiresType if err := rows.Scan(&c.ID, &c.CreatedByUserID, &c.SubjectID, &c.RoomType, &c.IsHard, &c.Weight, &c.Active, &c.Note, &c.CreatedAt); err != nil { return nil, err } out = append(out, c) } return out, nil } func (s *TimetableService) DeleteRoomRequiresType(ctx context.Context, id, userID string) error { _, err := s.db.Exec(ctx, `DELETE FROM tt_constraint_room_requires_type WHERE id = $1 AND created_by_user_id = $2`, id, userID) return err } // ---------- Room Unavailable ---------- func (s *TimetableService) CreateRoomUnavailable(ctx context.Context, userID string, req *models.CreateRoomUnavailableRequest) (*models.RoomUnavailable, error) { var c models.RoomUnavailable err := s.db.QueryRow(ctx, ` INSERT INTO tt_constraint_room_unavailable (created_by_user_id, room_id, day_of_week, period_index, is_hard, weight, active, note) SELECT $1, $2, $3, $4, $5, $6, $7, $8 WHERE EXISTS (SELECT 1 FROM tt_room WHERE id = $2 AND created_by_user_id = $1) RETURNING id, created_by_user_id, room_id, day_of_week, period_index, is_hard, weight, active, COALESCE(note,''), created_at `, userID, req.RoomID, req.DayOfWeek, req.PeriodIndex, req.IsHard, req.Weight, req.Active, req.Note).Scan( &c.ID, &c.CreatedByUserID, &c.RoomID, &c.DayOfWeek, &c.PeriodIndex, &c.IsHard, &c.Weight, &c.Active, &c.Note, &c.CreatedAt, ) return &c, err } func (s *TimetableService) ListRoomUnavailable(ctx context.Context, userID string) ([]models.RoomUnavailable, error) { rows, err := s.db.Query(ctx, ` SELECT id, created_by_user_id, room_id, day_of_week, period_index, is_hard, weight, active, COALESCE(note,''), created_at FROM tt_constraint_room_unavailable WHERE created_by_user_id = $1 ORDER BY room_id, day_of_week, period_index `, userID) if err != nil { return nil, err } defer rows.Close() var out []models.RoomUnavailable for rows.Next() { var c models.RoomUnavailable if err := rows.Scan(&c.ID, &c.CreatedByUserID, &c.RoomID, &c.DayOfWeek, &c.PeriodIndex, &c.IsHard, &c.Weight, &c.Active, &c.Note, &c.CreatedAt); err != nil { return nil, err } out = append(out, c) } return out, nil } func (s *TimetableService) DeleteRoomUnavailable(ctx context.Context, id, userID string) error { _, err := s.db.Exec(ctx, `DELETE FROM tt_constraint_room_unavailable WHERE id = $1 AND created_by_user_id = $2`, id, userID) return err }