e958f88a2d
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>
225 lines
9.1 KiB
Go
225 lines
9.1 KiB
Go
package database
|
|
|
|
// TimetableConstraintMigrations returns the DDL for all 15 constraint tables.
|
|
// Each table follows the same shape:
|
|
// - id / created_by_user_id / is_hard / weight / active / note / created_at
|
|
// - one or more FKs to tt_teacher / tt_class / tt_subject / tt_room
|
|
//
|
|
// FK ON DELETE CASCADE removes constraints when their parent (teacher/room/etc.)
|
|
// is deleted — the rules become meaningless without the referenced resource.
|
|
func TimetableConstraintMigrations() []string {
|
|
return []string{
|
|
// ---------- Teacher constraints (6) ----------
|
|
|
|
`CREATE TABLE IF NOT EXISTS tt_constraint_teacher_unavailable_day (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
created_by_user_id UUID NOT NULL,
|
|
teacher_id UUID NOT NULL REFERENCES tt_teacher(id) ON DELETE CASCADE,
|
|
day_of_week INT NOT NULL CHECK (day_of_week BETWEEN 1 AND 7),
|
|
is_hard BOOLEAN NOT NULL DEFAULT true,
|
|
weight INT NOT NULL DEFAULT 100 CHECK (weight BETWEEN 0 AND 100),
|
|
active BOOLEAN NOT NULL DEFAULT true,
|
|
note TEXT,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
UNIQUE(teacher_id, day_of_week)
|
|
)`,
|
|
|
|
`CREATE TABLE IF NOT EXISTS tt_constraint_teacher_unavailable_window (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
created_by_user_id UUID NOT NULL,
|
|
teacher_id UUID NOT NULL REFERENCES tt_teacher(id) ON DELETE CASCADE,
|
|
day_of_week INT NOT NULL CHECK (day_of_week BETWEEN 1 AND 7),
|
|
start_time TIME NOT NULL,
|
|
end_time TIME NOT NULL,
|
|
is_hard BOOLEAN NOT NULL DEFAULT true,
|
|
weight INT NOT NULL DEFAULT 100 CHECK (weight BETWEEN 0 AND 100),
|
|
active BOOLEAN NOT NULL DEFAULT true,
|
|
note TEXT,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
CHECK (end_time > start_time)
|
|
)`,
|
|
|
|
`CREATE TABLE IF NOT EXISTS tt_constraint_teacher_max_hours_day (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
created_by_user_id UUID NOT NULL,
|
|
teacher_id UUID NOT NULL REFERENCES tt_teacher(id) ON DELETE CASCADE,
|
|
max_hours INT NOT NULL CHECK (max_hours BETWEEN 1 AND 12),
|
|
is_hard BOOLEAN NOT NULL DEFAULT false,
|
|
weight INT NOT NULL DEFAULT 50 CHECK (weight BETWEEN 0 AND 100),
|
|
active BOOLEAN NOT NULL DEFAULT true,
|
|
note TEXT,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
UNIQUE(teacher_id)
|
|
)`,
|
|
|
|
`CREATE TABLE IF NOT EXISTS tt_constraint_teacher_max_hours_week (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
created_by_user_id UUID NOT NULL,
|
|
teacher_id UUID NOT NULL REFERENCES tt_teacher(id) ON DELETE CASCADE,
|
|
max_hours INT NOT NULL CHECK (max_hours BETWEEN 1 AND 40),
|
|
is_hard BOOLEAN NOT NULL DEFAULT true,
|
|
weight INT NOT NULL DEFAULT 100 CHECK (weight BETWEEN 0 AND 100),
|
|
active BOOLEAN NOT NULL DEFAULT true,
|
|
note TEXT,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
UNIQUE(teacher_id)
|
|
)`,
|
|
|
|
`CREATE TABLE IF NOT EXISTS tt_constraint_teacher_excluded_subject (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
created_by_user_id UUID NOT NULL,
|
|
teacher_id UUID NOT NULL REFERENCES tt_teacher(id) ON DELETE CASCADE,
|
|
subject_id UUID NOT NULL REFERENCES tt_subject(id) ON DELETE CASCADE,
|
|
is_hard BOOLEAN NOT NULL DEFAULT true,
|
|
weight INT NOT NULL DEFAULT 100 CHECK (weight BETWEEN 0 AND 100),
|
|
active BOOLEAN NOT NULL DEFAULT true,
|
|
note TEXT,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
UNIQUE(teacher_id, subject_id)
|
|
)`,
|
|
|
|
`CREATE TABLE IF NOT EXISTS tt_constraint_teacher_excluded_room (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
created_by_user_id UUID NOT NULL,
|
|
teacher_id UUID NOT NULL REFERENCES tt_teacher(id) ON DELETE CASCADE,
|
|
room_id UUID NOT NULL REFERENCES tt_room(id) ON DELETE CASCADE,
|
|
is_hard BOOLEAN NOT NULL DEFAULT true,
|
|
weight INT NOT NULL DEFAULT 100 CHECK (weight BETWEEN 0 AND 100),
|
|
active BOOLEAN NOT NULL DEFAULT true,
|
|
note TEXT,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
UNIQUE(teacher_id, room_id)
|
|
)`,
|
|
|
|
// ---------- Subject constraints (5) ----------
|
|
|
|
`CREATE TABLE IF NOT EXISTS tt_constraint_subject_min_day_gap (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
created_by_user_id UUID NOT NULL,
|
|
subject_id UUID NOT NULL REFERENCES tt_subject(id) ON DELETE CASCADE,
|
|
min_gap_days INT NOT NULL CHECK (min_gap_days BETWEEN 1 AND 4),
|
|
is_hard BOOLEAN NOT NULL DEFAULT false,
|
|
weight INT NOT NULL DEFAULT 70 CHECK (weight BETWEEN 0 AND 100),
|
|
active BOOLEAN NOT NULL DEFAULT true,
|
|
note TEXT,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
UNIQUE(subject_id)
|
|
)`,
|
|
|
|
`CREATE TABLE IF NOT EXISTS tt_constraint_subject_max_consecutive (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
created_by_user_id UUID NOT NULL,
|
|
subject_id UUID NOT NULL REFERENCES tt_subject(id) ON DELETE CASCADE,
|
|
max_consecutive INT NOT NULL CHECK (max_consecutive BETWEEN 1 AND 5),
|
|
is_hard BOOLEAN NOT NULL DEFAULT true,
|
|
weight INT NOT NULL DEFAULT 100 CHECK (weight BETWEEN 0 AND 100),
|
|
active BOOLEAN NOT NULL DEFAULT true,
|
|
note TEXT,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
UNIQUE(subject_id)
|
|
)`,
|
|
|
|
`CREATE TABLE IF NOT EXISTS tt_constraint_subject_contiguous_when_repeated (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
created_by_user_id UUID NOT NULL,
|
|
subject_id UUID NOT NULL REFERENCES tt_subject(id) ON DELETE CASCADE,
|
|
is_hard BOOLEAN NOT NULL DEFAULT true,
|
|
weight INT NOT NULL DEFAULT 100 CHECK (weight BETWEEN 0 AND 100),
|
|
active BOOLEAN NOT NULL DEFAULT true,
|
|
note TEXT,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
UNIQUE(subject_id)
|
|
)`,
|
|
|
|
`CREATE TABLE IF NOT EXISTS tt_constraint_subject_preferred_period (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
created_by_user_id UUID NOT NULL,
|
|
subject_id UUID NOT NULL REFERENCES tt_subject(id) ON DELETE CASCADE,
|
|
period_from INT NOT NULL CHECK (period_from BETWEEN 1 AND 12),
|
|
period_to INT NOT NULL CHECK (period_to BETWEEN 1 AND 12),
|
|
is_hard BOOLEAN NOT NULL DEFAULT false,
|
|
weight INT NOT NULL DEFAULT 40 CHECK (weight BETWEEN 0 AND 100),
|
|
active BOOLEAN NOT NULL DEFAULT true,
|
|
note TEXT,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
CHECK (period_to >= period_from)
|
|
)`,
|
|
|
|
`CREATE TABLE IF NOT EXISTS tt_constraint_subject_double_lesson (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
created_by_user_id UUID NOT NULL,
|
|
subject_id UUID NOT NULL REFERENCES tt_subject(id) ON DELETE CASCADE,
|
|
is_hard BOOLEAN NOT NULL DEFAULT false,
|
|
weight INT NOT NULL DEFAULT 60 CHECK (weight BETWEEN 0 AND 100),
|
|
active BOOLEAN NOT NULL DEFAULT true,
|
|
note TEXT,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
UNIQUE(subject_id)
|
|
)`,
|
|
|
|
// ---------- Class constraints (2) ----------
|
|
|
|
`CREATE TABLE IF NOT EXISTS tt_constraint_class_max_hours_day (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
created_by_user_id UUID NOT NULL,
|
|
class_id UUID NOT NULL REFERENCES tt_class(id) ON DELETE CASCADE,
|
|
max_hours INT NOT NULL CHECK (max_hours BETWEEN 1 AND 12),
|
|
is_hard BOOLEAN NOT NULL DEFAULT true,
|
|
weight INT NOT NULL DEFAULT 100 CHECK (weight BETWEEN 0 AND 100),
|
|
active BOOLEAN NOT NULL DEFAULT true,
|
|
note TEXT,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
UNIQUE(class_id)
|
|
)`,
|
|
|
|
`CREATE TABLE IF NOT EXISTS tt_constraint_class_no_gaps (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
created_by_user_id UUID NOT NULL,
|
|
class_id UUID NOT NULL REFERENCES tt_class(id) ON DELETE CASCADE,
|
|
is_hard BOOLEAN NOT NULL DEFAULT false,
|
|
weight INT NOT NULL DEFAULT 80 CHECK (weight BETWEEN 0 AND 100),
|
|
active BOOLEAN NOT NULL DEFAULT true,
|
|
note TEXT,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
UNIQUE(class_id)
|
|
)`,
|
|
|
|
// ---------- Room constraints (2) ----------
|
|
|
|
`CREATE TABLE IF NOT EXISTS tt_constraint_room_requires_type (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
created_by_user_id UUID NOT NULL,
|
|
subject_id UUID NOT NULL REFERENCES tt_subject(id) ON DELETE CASCADE,
|
|
room_type VARCHAR(30) NOT NULL,
|
|
is_hard BOOLEAN NOT NULL DEFAULT true,
|
|
weight INT NOT NULL DEFAULT 100 CHECK (weight BETWEEN 0 AND 100),
|
|
active BOOLEAN NOT NULL DEFAULT true,
|
|
note TEXT,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
UNIQUE(subject_id, room_type)
|
|
)`,
|
|
|
|
`CREATE TABLE IF NOT EXISTS tt_constraint_room_unavailable (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
created_by_user_id UUID NOT NULL,
|
|
room_id UUID NOT NULL REFERENCES tt_room(id) ON DELETE CASCADE,
|
|
day_of_week INT NOT NULL CHECK (day_of_week BETWEEN 1 AND 7),
|
|
period_index INT NOT NULL CHECK (period_index BETWEEN 1 AND 12),
|
|
is_hard BOOLEAN NOT NULL DEFAULT true,
|
|
weight INT NOT NULL DEFAULT 100 CHECK (weight BETWEEN 0 AND 100),
|
|
active BOOLEAN NOT NULL DEFAULT true,
|
|
note TEXT,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
UNIQUE(room_id, day_of_week, period_index)
|
|
)`,
|
|
|
|
// ---------- Indexes ----------
|
|
|
|
`CREATE INDEX IF NOT EXISTS idx_tt_c_teacher_unav_day_teacher ON tt_constraint_teacher_unavailable_day(teacher_id)`,
|
|
`CREATE INDEX IF NOT EXISTS idx_tt_c_teacher_unav_win_teacher ON tt_constraint_teacher_unavailable_window(teacher_id)`,
|
|
`CREATE INDEX IF NOT EXISTS idx_tt_c_teacher_excl_subj_teacher ON tt_constraint_teacher_excluded_subject(teacher_id)`,
|
|
`CREATE INDEX IF NOT EXISTS idx_tt_c_teacher_excl_room_teacher ON tt_constraint_teacher_excluded_room(teacher_id)`,
|
|
`CREATE INDEX IF NOT EXISTS idx_tt_c_room_unav_room ON tt_constraint_room_unavailable(room_id)`,
|
|
}
|
|
}
|