-- Migration 063: Phasen und parallele Streams pro Trainingseinheit (Grundlage Breakout). -- Bestehende Sektionen werden einer Default-whole_group-Phase zugeordnet. -- UNIQUE (training_unit_id, order_index) auf Sektionen entfällt zugunsten -- eindeutiger order_index je Phase bzw. je parallel_stream. -- ── Phasen ─────────────────────────────────────────────────────────────── CREATE TABLE IF NOT EXISTS training_unit_phases ( id SERIAL PRIMARY KEY, training_unit_id INT NOT NULL REFERENCES training_units(id) ON DELETE CASCADE, order_index INT NOT NULL, phase_kind VARCHAR(20) NOT NULL CHECK (phase_kind IN ('whole_group', 'parallel')), title VARCHAR(200), guidance_notes TEXT, UNIQUE (training_unit_id, order_index) ); CREATE INDEX IF NOT EXISTS idx_training_unit_phases_unit ON training_unit_phases(training_unit_id); -- ── Streams innerhalb einer Parallelphase ────────────────────────────────── CREATE TABLE IF NOT EXISTS training_unit_parallel_streams ( id SERIAL PRIMARY KEY, phase_id INT NOT NULL REFERENCES training_unit_phases(id) ON DELETE CASCADE, order_index INT NOT NULL, title VARCHAR(200), notes TEXT, assigned_trainer_profile_ids JSONB, UNIQUE (phase_id, order_index) ); CREATE INDEX IF NOT EXISTS idx_training_unit_parallel_streams_phase ON training_unit_parallel_streams(phase_id); COMMENT ON COLUMN training_unit_parallel_streams.assigned_trainer_profile_ids IS 'Optionale Co-Trainer-IDs (JSON-Array von Profil-IDs) für diese Teilstrecke; MVP+'; -- ── Sektionen: Zuordnung zu Phase (gemeinsam) oder Stream (parallel) ───── ALTER TABLE training_unit_sections ADD COLUMN IF NOT EXISTS phase_id INT REFERENCES training_unit_phases(id) ON DELETE CASCADE, ADD COLUMN IF NOT EXISTS parallel_stream_id INT REFERENCES training_unit_parallel_streams(id) ON DELETE CASCADE; -- Backfill: je Einheit mit Sektionen eine whole_group-Phase, alle Sektionen dorthin INSERT INTO training_unit_phases (training_unit_id, order_index, phase_kind, title) SELECT tu.id, 0, 'whole_group', NULL FROM training_units tu WHERE EXISTS (SELECT 1 FROM training_unit_sections s WHERE s.training_unit_id = tu.id) AND NOT EXISTS ( SELECT 1 FROM training_unit_phases p WHERE p.training_unit_id = tu.id AND p.order_index = 0 AND p.phase_kind = 'whole_group' ); UPDATE training_unit_sections tus SET phase_id = p.id FROM training_unit_phases p WHERE tus.phase_id IS NULL AND p.training_unit_id = tus.training_unit_id AND p.order_index = 0 AND p.phase_kind = 'whole_group'; -- Alte globale Reihenfolge-Eindeutigkeit pro Einheit entfernen ALTER TABLE training_unit_sections DROP CONSTRAINT IF EXISTS training_unit_sections_training_unit_id_order_index_key; -- Genau eine Zielspalte gesetzt: gemeinsame Phase ODER paralleler Stream ALTER TABLE training_unit_sections DROP CONSTRAINT IF EXISTS training_unit_sections_phase_or_stream_chk; ALTER TABLE training_unit_sections ADD CONSTRAINT training_unit_sections_phase_or_stream_chk CHECK ( (phase_id IS NOT NULL AND parallel_stream_id IS NULL) OR (phase_id IS NULL AND parallel_stream_id IS NOT NULL) ); CREATE UNIQUE INDEX IF NOT EXISTS uq_training_unit_sections_phase_order ON training_unit_sections (phase_id, order_index) WHERE phase_id IS NOT NULL; CREATE UNIQUE INDEX IF NOT EXISTS uq_training_unit_sections_stream_order ON training_unit_sections (parallel_stream_id, order_index) WHERE parallel_stream_id IS NOT NULL; CREATE INDEX IF NOT EXISTS idx_training_unit_sections_phase ON training_unit_sections(phase_id) WHERE phase_id IS NOT NULL; CREATE INDEX IF NOT EXISTS idx_training_unit_sections_parallel_stream ON training_unit_sections(parallel_stream_id) WHERE parallel_stream_id IS NOT NULL;