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,104 @@
|
||||
package database
|
||||
|
||||
// TimetableMigrations returns the SQL statements that create all timetable-related
|
||||
// tables. They are applied idempotently via CREATE TABLE IF NOT EXISTS.
|
||||
func TimetableMigrations() []string {
|
||||
return []string{
|
||||
// Classes (school-wide, distinct from per-teacher `classes` table)
|
||||
`CREATE TABLE IF NOT EXISTS tt_class (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
created_by_user_id UUID NOT NULL,
|
||||
name VARCHAR(50) NOT NULL,
|
||||
grade_level INT NOT NULL,
|
||||
student_count INT DEFAULT 0,
|
||||
notes TEXT,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
UNIQUE(created_by_user_id, name)
|
||||
)`,
|
||||
|
||||
// Time periods (Mon=1..Sun=7, period_index = 1..N)
|
||||
`CREATE TABLE IF NOT EXISTS tt_period (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
created_by_user_id UUID NOT NULL,
|
||||
day_of_week INT NOT NULL CHECK (day_of_week BETWEEN 1 AND 7),
|
||||
period_index INT NOT NULL CHECK (period_index >= 1),
|
||||
start_time TIME NOT NULL,
|
||||
end_time TIME NOT NULL,
|
||||
is_break BOOLEAN DEFAULT false,
|
||||
label VARCHAR(50),
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
UNIQUE(created_by_user_id, day_of_week, period_index)
|
||||
)`,
|
||||
|
||||
// Rooms
|
||||
`CREATE TABLE IF NOT EXISTS tt_room (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
created_by_user_id UUID NOT NULL,
|
||||
name VARCHAR(50) NOT NULL,
|
||||
room_type VARCHAR(30),
|
||||
capacity INT DEFAULT 30,
|
||||
floor_level INT DEFAULT 0,
|
||||
has_elevator BOOLEAN DEFAULT true,
|
||||
notes TEXT,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
UNIQUE(created_by_user_id, name)
|
||||
)`,
|
||||
|
||||
// Subjects (school-wide, distinct from per-teacher `subjects`)
|
||||
`CREATE TABLE IF NOT EXISTS tt_subject (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
created_by_user_id UUID NOT NULL,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
short_code VARCHAR(10) NOT NULL,
|
||||
color VARCHAR(7),
|
||||
is_main_subject BOOLEAN DEFAULT false,
|
||||
required_room_type VARCHAR(30),
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
UNIQUE(created_by_user_id, short_code)
|
||||
)`,
|
||||
|
||||
// Teachers (planning resource, NOT a BreakPilot user)
|
||||
`CREATE TABLE IF NOT EXISTS tt_teacher (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
created_by_user_id UUID NOT NULL,
|
||||
first_name VARCHAR(100) NOT NULL,
|
||||
last_name VARCHAR(100) NOT NULL,
|
||||
short_code VARCHAR(10) NOT NULL,
|
||||
employment_percentage INT DEFAULT 100,
|
||||
max_hours_week INT DEFAULT 28,
|
||||
notes TEXT,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
UNIQUE(created_by_user_id, short_code)
|
||||
)`,
|
||||
|
||||
// Curriculum: weekly hour count per class+subject
|
||||
`CREATE TABLE IF NOT EXISTS tt_curriculum (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
class_id UUID NOT NULL REFERENCES tt_class(id) ON DELETE CASCADE,
|
||||
subject_id UUID NOT NULL REFERENCES tt_subject(id) ON DELETE CASCADE,
|
||||
weekly_hours INT NOT NULL CHECK (weekly_hours >= 1),
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
UNIQUE(class_id, subject_id)
|
||||
)`,
|
||||
|
||||
// Assignment: which teacher teaches which subject in which class
|
||||
`CREATE TABLE IF NOT EXISTS tt_assignment (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
teacher_id UUID NOT NULL REFERENCES tt_teacher(id) ON DELETE CASCADE,
|
||||
class_id UUID NOT NULL REFERENCES tt_class(id) ON DELETE CASCADE,
|
||||
subject_id UUID NOT NULL REFERENCES tt_subject(id) ON DELETE CASCADE,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
UNIQUE(teacher_id, class_id, subject_id)
|
||||
)`,
|
||||
|
||||
// Indexes
|
||||
`CREATE INDEX IF NOT EXISTS idx_tt_class_user ON tt_class(created_by_user_id)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_tt_period_user ON tt_period(created_by_user_id)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_tt_room_user ON tt_room(created_by_user_id)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_tt_subject_user ON tt_subject(created_by_user_id)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_tt_teacher_user ON tt_teacher(created_by_user_id)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_tt_curriculum_class ON tt_curriculum(class_id)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_tt_assignment_teacher ON tt_assignment(teacher_id)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_tt_assignment_class ON tt_assignment(class_id)`,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user