Files
breakpilot-lehrer/school-service/internal/services/timetable_relations.go
T
Benjamin Admin e958f88a2d 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>
2026-05-21 22:12:23 +02:00

113 lines
4.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package services
import (
"context"
"github.com/breakpilot/school-service/internal/models"
)
// Curriculum and Assignment operations.
// Ownership is enforced by joining against tt_class.created_by_user_id.
// ---------- Curriculum (class × subject → weekly hours) ----------
func (s *TimetableService) CreateCurriculum(ctx context.Context, userID string, req *models.CreateTimetableCurriculumRequest) (*models.TimetableCurriculum, error) {
var c models.TimetableCurriculum
err := s.db.QueryRow(ctx, `
INSERT INTO tt_curriculum (class_id, subject_id, weekly_hours)
SELECT $1, $2, $3
WHERE EXISTS (SELECT 1 FROM tt_class WHERE id = $1 AND created_by_user_id = $4)
AND EXISTS (SELECT 1 FROM tt_subject WHERE id = $2 AND created_by_user_id = $4)
RETURNING id, class_id, subject_id, weekly_hours, created_at
`, req.ClassID, req.SubjectID, req.WeeklyHours, userID).Scan(
&c.ID, &c.ClassID, &c.SubjectID, &c.WeeklyHours, &c.CreatedAt,
)
return &c, err
}
func (s *TimetableService) ListCurriculum(ctx context.Context, userID string) ([]models.TimetableCurriculum, error) {
rows, err := s.db.Query(ctx, `
SELECT cu.id, cu.class_id, cu.subject_id, cu.weekly_hours, cu.created_at,
sub.name, cl.name
FROM tt_curriculum cu
JOIN tt_class cl ON cu.class_id = cl.id
JOIN tt_subject sub ON cu.subject_id = sub.id
WHERE cl.created_by_user_id = $1
ORDER BY cl.grade_level, cl.name, sub.name
`, userID)
if err != nil {
return nil, err
}
defer rows.Close()
var out []models.TimetableCurriculum
for rows.Next() {
var c models.TimetableCurriculum
if err := rows.Scan(&c.ID, &c.ClassID, &c.SubjectID, &c.WeeklyHours, &c.CreatedAt, &c.SubjectName, &c.ClassName); err != nil {
return nil, err
}
out = append(out, c)
}
return out, nil
}
func (s *TimetableService) DeleteCurriculum(ctx context.Context, id, userID string) error {
_, err := s.db.Exec(ctx, `
DELETE FROM tt_curriculum cu
USING tt_class cl
WHERE cu.id = $1 AND cu.class_id = cl.id AND cl.created_by_user_id = $2
`, id, userID)
return err
}
// ---------- Assignment (teacher × class × subject) ----------
func (s *TimetableService) CreateAssignment(ctx context.Context, userID string, req *models.CreateTimetableAssignmentRequest) (*models.TimetableAssignment, error) {
var a models.TimetableAssignment
err := s.db.QueryRow(ctx, `
INSERT INTO tt_assignment (teacher_id, class_id, subject_id)
SELECT $1, $2, $3
WHERE EXISTS (SELECT 1 FROM tt_teacher WHERE id = $1 AND created_by_user_id = $4)
AND EXISTS (SELECT 1 FROM tt_class WHERE id = $2 AND created_by_user_id = $4)
AND EXISTS (SELECT 1 FROM tt_subject WHERE id = $3 AND created_by_user_id = $4)
RETURNING id, teacher_id, class_id, subject_id, created_at
`, req.TeacherID, req.ClassID, req.SubjectID, userID).Scan(
&a.ID, &a.TeacherID, &a.ClassID, &a.SubjectID, &a.CreatedAt,
)
return &a, err
}
func (s *TimetableService) ListAssignments(ctx context.Context, userID string) ([]models.TimetableAssignment, error) {
rows, err := s.db.Query(ctx, `
SELECT a.id, a.teacher_id, a.class_id, a.subject_id, a.created_at,
t.last_name || ', ' || t.first_name, cl.name, sub.name
FROM tt_assignment a
JOIN tt_teacher t ON a.teacher_id = t.id
JOIN tt_class cl ON a.class_id = cl.id
JOIN tt_subject sub ON a.subject_id = sub.id
WHERE t.created_by_user_id = $1
ORDER BY cl.grade_level, cl.name, sub.name
`, userID)
if err != nil {
return nil, err
}
defer rows.Close()
var out []models.TimetableAssignment
for rows.Next() {
var a models.TimetableAssignment
if err := rows.Scan(&a.ID, &a.TeacherID, &a.ClassID, &a.SubjectID, &a.CreatedAt, &a.TeacherName, &a.ClassName, &a.SubjectName); err != nil {
return nil, err
}
out = append(out, a)
}
return out, nil
}
func (s *TimetableService) DeleteAssignment(ctx context.Context, id, userID string) error {
_, err := s.db.Exec(ctx, `
DELETE FROM tt_assignment a
USING tt_teacher t
WHERE a.id = $1 AND a.teacher_id = t.id AND t.created_by_user_id = $2
`, id, userID)
return err
}