diff --git a/.claude/docs/functional/DOMAIN_MODEL.md b/.claude/docs/functional/DOMAIN_MODEL.md new file mode 100644 index 0000000..1e3b310 --- /dev/null +++ b/.claude/docs/functional/DOMAIN_MODEL.md @@ -0,0 +1,403 @@ +# Shinkan Jinkendo - Fachliches Domänenmodell + +**Version:** 0.2.0 +**Stand:** 2026-04-23 +**Basis:** `shinkan_anforderungsdokument_entwurf.md` + +--- + +## Übersicht + +Dieses Dokument beschreibt die **fachliche Datenstruktur** von Shinkan Jinkendo. + +**Zuständig für technische Umsetzung:** siehe `DATABASE_SCHEMA.md` + +--- + +## Fachliche Kernprinzipien + +### 1. Globaler Fähigkeitskatalog (§8.3) + +**Prinzip:** Fähigkeiten werden NICHT pro Modell dupliziert. + +**Beispiele:** +- Distanzgefühl (in Karate UND Selbstverteidigung) +- Stabiler Stand (in Karate UND Gewaltschutz) +- Aufmerksamkeit (in verschiedenen Zielgruppenmodellen) + +**Konsequenz:** Ein globaler Katalog, aber kontextabhängige Ausprägung über Reifegradmodelle. + +### 2. Mehrfachzuordnung von Übungen (§10.7) + +**Prinzip:** Eine Übung kann mehreren Bereichen GLEICHZEITIG zugeordnet sein. + +**Beispiel:** Distanzübung +- Karate ✓ +- Selbstverteidigung ✓ +- Gewaltschutz ✓ + +**Technische Umsetzung:** M:N Beziehungen mit `is_primary` Flag. + +### 3. Hierarchischer Kontext (§8.1) + +**Fachliche Grundstruktur:** +``` +Fokusbereich (Sportart/Bereich) + └─ Stil + └─ Zielgruppe +``` + +**Beispiele:** +``` +Karate + ├─ Shotokan + │ ├─ Breitensportler + │ ├─ Leistungssportler + │ └─ Kinder + ├─ Goju-Ryu + │ ├─ Erwachsene + │ └─ Jugendliche + └─ Wado-Ryu + └─ Gemischt + +Selbstverteidigung + ├─ Erwachsene + │ ├─ Männer + │ ├─ Frauen + │ └─ Gemischt + └─ Kinder + └─ 6-12 Jahre + +Gewaltschutz + ├─ Frauen + ├─ Kinder + └─ Senioren +``` + +**Bedeutung für Reifegradmodelle:** + +Jede Kombination (Fokusbereich + Stil + Zielgruppe) kann ein eigenes Reifegradmodell haben: +- Karate / Shotokan / Breitensport → Modell A +- Karate / Shotokan / Leistungssport → Modell B +- Selbstverteidigung / Erwachsene → Modell C + +--- + +## Fachliche Dimensionen + +### Dimension 1: Fokusbereich (Sportart/Bereich) + +**Definition:** Oberste fachliche Kategorisierung der Trainingsinhalte. + +**Beispiele:** +- Karate +- Selbstverteidigung +- Gewaltschutz + +**Eigenschaften:** +- Name, Kürzel, Beschreibung +- Farbe (für UI) +- Icon (Emoji oder Icon-Name) +- Status (aktiv/archiviert) + +**Verwaltung:** +- Admin-Level: Systemadmin, Vereinsadmin +- CRUD: Voll administrierbar +- Löschen: Nur wenn keine abhängigen Stile oder Übungen + +### Dimension 2: Trainingsstil + +**Definition:** Spezialisierung innerhalb eines Fokusbereichs. + +**Beispiele:** +- Shotokan (unter Karate) +- Goju-Ryu (unter Karate) +- Wado-Ryu (unter Karate) + +**Hierarchie:** +- Gehört zu genau EINEM Fokusbereich +- Kann optional Sub-Stile haben (parent_style_id) + +**Eigenschaften:** +- Name, Kürzel, Beschreibung +- Fokusbereich-Zuordnung +- Optionale Hierarchie (z.B. Kyokushin → Kyokushin IKO) +- Status + +**Verwaltung:** +- Admin-Level: Systemadmin, Vereinsadmin +- CRUD: Voll administrierbar +- Verschieben: Zwischen Fokusbereichen möglich +- Löschen: Nur wenn keine Zielgruppen oder Übungen zugeordnet + +### Dimension 3: Zielgruppe + +**Definition:** Konkrete Trainingsempfänger-Gruppe innerhalb eines Stils. + +**Beispiele:** +- Breitensportler +- Leistungssportler +- Kinder (6-12 Jahre) +- Jugendliche (13-17 Jahre) +- Erwachsene +- Frauen +- Senioren +- Gemischt + +**Hierarchie:** +- Gehört zu genau EINEM Trainingsstil +- Kann Altersbereich definieren + +**Eigenschaften:** +- Name, Beschreibung +- Stil-Zuordnung +- Optionale Altersangabe (min_age, max_age) +- Status + +**Verwaltung:** +- Admin-Level: Systemadmin, Vereinsadmin, Spartenadmin +- CRUD: Voll administrierbar +- Verschieben: Zwischen Stilen möglich +- Löschen: Cascade oder Restrict (konfigurierbar) + +### Dimension 4: Altersgruppen (separate Dimension!) + +**Definition:** Grobe Alterseinstufung UNABHÄNGIG von Zielgruppe. + +**Katalog:** +- Minis (3-5 Jahre) +- Kinder (6-9 Jahre) +- Schüler (10-12 Jahre) +- Teenager (13-17 Jahre) +- Erwachsene (18+) + +**Abgrenzung zu Zielgruppe:** +- Altersgruppe = didaktische Einstufung +- Zielgruppe = fachlicher Kontext + +**Beispiel:** +- Übung kann für "Kinder" + "Schüler" geeignet sein (Altersgruppen) +- Und gleichzeitig zu Zielgruppe "Karate Breitensport Kinder" gehören + +--- + +## Übungs-Zuordnungslogik (§10) + +### Primäre vs. Sekundäre Zuordnung + +**Regel:** Eine Übung hat genau EINE primäre Zuordnung pro Dimension. + +**Beispiel:** + +**Übung:** "Maai - Distanzübung" + +**Fokusbereiche:** +- Karate (primär) ✓ +- Selbstverteidigung (sekundär) + +**Stile:** +- Shotokan (primär) ✓ +- Goju-Ryu (sekundär) +- Wado-Ryu (sekundär) + +**Zielgruppen:** +- Breitensportler (primär) ✓ +- Leistungssportler (sekundär) + +**Altersgruppen:** +- Schüler ✓ +- Teenager ✓ +- Erwachsene ✓ + +### Fähigkeitsbezug (§10.8) + +**Übung trainiert MEHRERE Fähigkeiten:** + +**Beispiel:** "Kumite-Drill" + +**Hauptfähigkeiten:** +- Distanzgefühl (Intensität: hoch) +- Timing (Intensität: hoch) + +**Nebenfähigkeiten:** +- Beinarbeit (Intensität: mittel) +- Reaktionsfähigkeit (Intensität: mittel) + +**Attribute pro Fähigkeitsbezug:** +- is_primary (Haupt- oder Nebenfähigkeit) +- intensity (niedrig/mittel/hoch) +- development_contribution (Entwicklungsbeitrag) +- required_level (Voraussetzung) +- target_level (Ziel-Level) + +### Trainingscharakter (§10.7) + +**Katalog:** +- Grundlage (Einführung, Basisvermittlung) +- Aufbau (Aufbauendes Training) +- Vertiefung (Vertiefung, Spezialisierung) +- Festigung (Wiederholung, Festigung) +- Diagnose (Leistungsdiagnose, Test) +- Wettkampf (Wettkampfvorbereitung) + +**Bedeutung:** +- Wichtig für Suche +- Wichtig für Trainingsplanung +- Wichtig für KI-Unterstützung (später) + +--- + +## Variantenlogik (§11.2) + +**Prinzip:** Übung besteht aus Stammübung + optionale Varianten. + +**Beispiele für Varianten:** +- Leicht / Mittel / Schwer +- Mit Partner / Ohne Partner +- Mit Hilfsmittel / Ohne Hilfsmittel +- Kindgerechte Variante +- Kurzvariante +- Fortgeschrittene Variante + +**Varianten-Attribute:** +- Titel/Name +- Kurzbeschreibung +- Abweichende Durchführung +- Abweichende Dauer +- Abweichende Hilfsmittel +- Abweichender Fähigkeitsfokus +- Abweichende Zielgruppen +- Eigener Medienbezug + +--- + +## Methodenbezug (§11.5) + +**Prinzip:** Genau EINE Hauptmethode, optional weitere Nebenmethoden. + +**Beispiele:** +- Hauptmethode: Rollenspiel +- Nebenmethoden: Strukturierte Übung, Reflexion + +**Optional später:** +- Sportmethodische Hauptmethode +- Didaktische Zusatzmethode + +--- + +## Qualitäts- und Bearbeitungsstatus (§11.3) + +### Statusstufen + +**Übungsstatus:** +- Entwurf (in Arbeit) +- In Bearbeitung (aktiv gepflegt) +- Fachlich geprüft (Review OK) +- Freigegeben (produktiv nutzbar) +- Archiviert (nicht mehr aktiv) + +### Sichtbarkeitsebenen (§5.5) + +**Freigabeebenen:** +- Privat (nur Ersteller) +- Für bestimmte Personen (später) +- Verein (alle Vereinsmitglieder) +- Sparte (nur bestimmte Sparte) +- Allgemein/Global (alle Nutzer) +- Offiziell (Standardinhalte) + +**Zusätzlich:** Thematische Einschränkungen +- Karate-Inhalte +- Selbstverteidigungs-Inhalte +- Gewaltschutz-Inhalte + +--- + +## Admin-Workflows + +### Fokusbereich anlegen + +1. Admin navigiert zu Katalogverwaltung +2. Wählt "Fokusbereiche" +3. Klickt "Neu anlegen" +4. Füllt aus: Name, Kürzel, Beschreibung, Farbe, Icon +5. Speichert +6. System validiert Eindeutigkeit (Name) +7. Fokusbereich verfügbar für Stil-Zuordnung + +### Stil verschieben (zwischen Fokusbereichen) + +**Szenario:** Goju-Ryu soll von "Karate" nach "Karate Traditional" verschoben werden. + +**Workflow:** +1. Admin wählt Stil "Goju-Ryu" +2. Klickt "Verschieben" +3. Wählt Ziel-Fokusbereich "Karate Traditional" +4. System prüft: + - Ziel-Fokusbereich existiert + - Keine Namenskonflikte +5. System aktualisiert: + - `training_styles.focus_area_id` + - Alle abhängigen Zielgruppen bleiben erhalten +6. System loggt Änderung +7. Erfolg-Meldung + +### Fokusbereich löschen (mit Schutz) + +**Szenario:** "Judo" soll gelöscht werden. + +**Workflow:** +1. Admin wählt "Judo" +2. Klickt "Löschen" +3. System prüft Abhängigkeiten: + ```sql + SELECT COUNT(*) FROM training_styles WHERE focus_area_id = :id + SELECT COUNT(*) FROM exercise_focus_areas WHERE focus_area_id = :id + ``` +4. **Falls verwendet:** + - Dialog: "Fokusbereich kann nicht gelöscht werden. 5 Stile und 23 Übungen sind zugeordnet." + - Optionen: + - "Abbrechen" + - "Stile/Übungen umrouten" (Admin wählt Ziel) + - "Archivieren statt löschen" +5. **Falls nicht verwendet:** + - Bestätigung: "Wirklich löschen? (Keine Abhängigkeiten)" + - Löschen durchführen + +### Zielgruppe umrouten + +**Szenario:** Alle Übungen für "Kinder" sollen zu "Schüler" umgeleitet werden. + +**Workflow:** +1. Admin wählt Zielgruppe "Kinder" +2. Klickt "Umrouten" +3. Wählt Ziel: "Schüler" +4. System zeigt Vorschau: + - "47 Übungen werden aktualisiert" + - Liste der betroffenen Übungen (mit Scroll) +5. Admin bestätigt +6. System führt Batch-Update aus: + ```sql + UPDATE exercise_target_groups + SET target_group_id = :new_id + WHERE target_group_id = :old_id + ``` +7. Optional: Alte Zielgruppe löschen oder archivieren +8. Erfolg-Meldung mit Log-Referenz + +--- + +## Nächste Schritte + +- [ ] Hierarchie-UI Wireframes erstellen +- [ ] Admin-CRUD-Endpoints spezifizieren +- [ ] Cascade-Logik detaillieren +- [ ] Umrouten-Workflows implementieren +- [ ] Migration 008 Entwurf finalisieren +- [ ] Tests für alle Admin-Operationen + +--- + +**Letzte Aktualisierung:** 2026-04-23 +**Verantwortlich:** Claude Code +**Review:** Pending diff --git a/.claude/docs/technical/DATABASE_SCHEMA.md b/.claude/docs/technical/DATABASE_SCHEMA.md new file mode 100644 index 0000000..e095b55 --- /dev/null +++ b/.claude/docs/technical/DATABASE_SCHEMA.md @@ -0,0 +1,260 @@ +# Shinkan Jinkendo - Datenbank-Schema (Technisch) + +**Version:** 0.2.0 +**Stand:** 2026-04-23 +**Aktuell deployed:** Migration 007 (idempotent) + +--- + +## Übersicht + +Dieses Dokument beschreibt die **technische Datenbankstruktur** von Shinkan Jinkendo. + +**Zuständig für fachliche Modellierung:** siehe `DOMAIN_MODEL.md` + +--- + +## Migrations-Historie + +| Nr. | Datum | Beschreibung | Status | +|-----|-------|--------------|--------| +| 001 | 2026-04-20 | Initial Schema (Auth, Profiles) | ✅ Deployed | +| 002 | 2026-04-20 | Clubs, Divisions, Groups | ✅ Deployed | +| 003 | 2026-04-20 | Skills, Methods | ✅ Deployed | +| 004 | 2026-04-21 | Training Types | ✅ Deployed | +| 005 | 2026-04-21 | Exercises (Basis) | ✅ Deployed | +| 006 | 2026-04-21 | Training Planning | ✅ Deployed | +| 007 | 2026-04-22 | Exercise Catalogs (idempotent) | ✅ Deployed | +| 008 | 2026-04-23 | M:N Exercise Relations + Hierarchical Catalogs | ✅ Erstellt, Test ausstehend | + +--- + +## Kern-Entitäten (Stand Migration 007) + +### Auth & Profile + +```sql +profiles (id, name, email, password_hash, role, created_at) +sessions (id, profile_id, token, created_at, expires_at) +``` + +### Organisation + +```sql +clubs (id, name, abbreviation, description, logo_url, ...) +divisions (id, club_id, name, description, ...) +training_groups (id, division_id, name, description, focus_area, level, ...) +``` + +### Kataloge (Einfache FK-Struktur - zu refactoren) + +**ACHTUNG:** Aktuell 1:1 Beziehungen - fachlich inkorrekt! + +```sql +focus_areas (id, name, abbreviation, description, color, icon, ...) +training_styles (id, name, abbreviation, description, parent_style_id, ...) +training_characters (id, name, description, ...) +skill_categories (id, name, description, parent_category_id, ...) +``` + +### Übungen (Aktuell - zu refactoren) + +```sql +exercises ( + id, title, summary, goal, execution, preparation, trainer_notes, + focus_area_id, -- ❌ 1:1 - sollte M:N sein + training_style_id, -- ❌ 1:1 - sollte M:N sein + training_character_id, -- ❌ 1:1 - sollte M:N sein + visibility, status, created_by, club_id, ... +) + +exercise_skills (exercise_id, skill_id, is_primary, intensity, ...) +exercise_variants (id, exercise_id, name, description, ...) +exercise_media (id, exercise_id, type, url, title, description, ...) +``` + +### Training Planning + +```sql +training_units (id, group_id, date, title, description, ...) +training_unit_exercises (training_unit_id, exercise_id, sort_order, ...) +``` + +--- + +## Geplante Änderungen (Migration 008) + +### Hierarchische Katalog-Struktur + +**Fachliche Anforderung (§8.1 Anforderungsdokument):** +``` +Fokusbereich → Stil → Zielgruppe +``` + +**Neue Struktur:** + +```sql +-- Level 1: Fokusbereiche (Root) +focus_areas ( + id SERIAL PRIMARY KEY, + name VARCHAR(100) UNIQUE NOT NULL, + abbreviation VARCHAR(20), + description TEXT, + color VARCHAR(20), + icon VARCHAR(50), + sort_order INT, + status VARCHAR(50) DEFAULT 'active' +) + +-- Level 2: Trainingsstile (untergeordnet zu Fokusbereich) +training_styles ( + id SERIAL PRIMARY KEY, + focus_area_id INT REFERENCES focus_areas(id), -- Hierarchie-Link + name VARCHAR(100) NOT NULL, + abbreviation VARCHAR(20), + description TEXT, + parent_style_id INT REFERENCES training_styles(id), -- Optional: Sub-Stile + sort_order INT, + status VARCHAR(50) DEFAULT 'active' +) + +-- Level 3: Zielgruppen (untergeordnet zu Stil) +target_groups ( + id SERIAL PRIMARY KEY, + training_style_id INT REFERENCES training_styles(id), -- Hierarchie-Link + name VARCHAR(100) NOT NULL, + description TEXT, + min_age INT, + max_age INT, + sort_order INT, + status VARCHAR(50) DEFAULT 'active' +) +``` + +### M:N Übungs-Zuordnungen + +```sql +-- Übung → Fokusbereiche (M:N) +exercise_focus_areas ( + id SERIAL PRIMARY KEY, + exercise_id INT REFERENCES exercises(id) ON DELETE CASCADE, + focus_area_id INT REFERENCES focus_areas(id), + is_primary BOOLEAN DEFAULT false, + UNIQUE(exercise_id, focus_area_id) +) + +-- Übung → Stile (M:N) +exercise_styles ( + id SERIAL PRIMARY KEY, + exercise_id INT REFERENCES exercises(id) ON DELETE CASCADE, + training_style_id INT REFERENCES training_styles(id), + is_primary BOOLEAN DEFAULT false, + UNIQUE(exercise_id, training_style_id) +) + +-- Übung → Zielgruppen (M:N) +exercise_target_groups ( + id SERIAL PRIMARY KEY, + exercise_id INT REFERENCES exercises(id) ON DELETE CASCADE, + target_group_id INT REFERENCES target_groups(id), + is_primary BOOLEAN DEFAULT false, + UNIQUE(exercise_id, target_group_id) +) + +-- Übung → Altersgruppen (M:N - separate Dimension) +exercise_age_groups ( + id SERIAL PRIMARY KEY, + exercise_id INT REFERENCES exercises(id) ON DELETE CASCADE, + age_group VARCHAR(50) NOT NULL, -- 'Minis', 'Kinder', 'Schüler', 'Teenager', 'Erwachsene' + UNIQUE(exercise_id, age_group) +) +``` + +--- + +## Kaskadier-Regeln (Geplant) + +### Fokusbereich löschen + +**Verhalten:** `RESTRICT` (Löschen verweigern wenn verwendet) + +**Alternativen:** +1. **Umrouten:** Alle abhängigen Stile auf neuen Fokusbereich setzen +2. **Archivieren:** Status auf 'archived' setzen statt löschen + +**Implementierung:** +```sql +-- Prüfung vor Löschen: +SELECT COUNT(*) FROM training_styles WHERE focus_area_id = :id +SELECT COUNT(*) FROM exercise_focus_areas WHERE focus_area_id = :id + +-- Falls verwendet → HTTP 409 Conflict mit Hinweis +``` + +### Stil löschen + +**Verhalten:** `RESTRICT` wenn Zielgruppen oder Übungen zugeordnet + +**Umrouten-Workflow:** +1. Admin wählt Ziel-Stil +2. System aktualisiert: + - `target_groups.training_style_id` + - `exercise_styles.training_style_id` +3. Löschen erlaubt + +### Zielgruppe löschen + +**Verhalten:** `SET NULL` oder `CASCADE` in `exercise_target_groups` + +--- + +## Indizes (Geplant) + +```sql +-- Hierarchie-Lookups +CREATE INDEX idx_training_styles_focus_area ON training_styles(focus_area_id); +CREATE INDEX idx_target_groups_style ON target_groups(training_style_id); + +-- M:N Joins +CREATE INDEX idx_exercise_focus_areas_exercise ON exercise_focus_areas(exercise_id); +CREATE INDEX idx_exercise_focus_areas_focus ON exercise_focus_areas(focus_area_id); +CREATE INDEX idx_exercise_styles_exercise ON exercise_styles(exercise_id); +CREATE INDEX idx_exercise_styles_style ON exercise_styles(training_style_id); +CREATE INDEX idx_exercise_target_groups_exercise ON exercise_target_groups(exercise_id); +CREATE INDEX idx_exercise_target_groups_target ON exercise_target_groups(target_group_id); +``` + +--- + +## Deprecation-Pfad (Migration 008) + +**Legacy-Spalten in `exercises`:** +```sql +-- Werden in Migration 008 deprecated: +exercises.focus_area_id → Migriert zu exercise_focus_areas (is_primary=true) +exercises.training_style_id → Migriert zu exercise_styles (is_primary=true) +exercises.training_character_id → Bleibt vorerst (separate Dimension) + +-- Spalten bleiben zur Rückwärtskompatibilität, aber: +-- 1. Daten migriert +-- 2. Neue Übungen nutzen M:N-Tabellen +-- 3. API liefert enriched data aus M:N +-- 4. Später: Spalten droppen nach Stabilisierung +``` + +--- + +## Nächste Schritte + +- [ ] Migration 008 Entwurf erstellen +- [ ] Migrations-Strategie für Altdaten dokumentieren +- [ ] Admin-UI für Baum-Verwaltung planen +- [ ] API-Endpoints für hierarchische Navigation +- [ ] Cascade-Logik implementieren +- [ ] Tests für Löschen/Umrouten + +--- + +**Letzte Aktualisierung:** 2026-04-23 +**Verantwortlich:** Claude Code +**Review:** Pending diff --git a/backend/migrations/008_mn_exercise_relations.sql b/backend/migrations/008_mn_exercise_relations.sql new file mode 100644 index 0000000..3992076 --- /dev/null +++ b/backend/migrations/008_mn_exercise_relations.sql @@ -0,0 +1,165 @@ +-- Migration 008: M:N Exercise Relations + Hierarchical Catalogs +-- Erstellt: 2026-04-23 +-- Beschreibung: Umstellung von 1:1 auf M:N Beziehungen + Hierarchie Fokusbereich → Stil → Zielgruppe + +-- ============================================================================ +-- PHASE 1: Hierarchische Struktur aufbauen +-- ============================================================================ + +-- training_styles erweitern: Hierarchie zu focus_areas +DO $$ +BEGIN + IF NOT EXISTS (SELECT 1 FROM information_schema.columns + WHERE table_name='training_styles' AND column_name='focus_area_id') THEN + ALTER TABLE training_styles + ADD COLUMN focus_area_id INT REFERENCES focus_areas(id); + END IF; +END $$; + +CREATE INDEX IF NOT EXISTS idx_training_styles_focus_area ON training_styles(focus_area_id); + +-- Zielgruppen-Tabelle (NEU) +CREATE TABLE IF NOT EXISTS target_groups ( + id SERIAL PRIMARY KEY, + training_style_id INT REFERENCES training_styles(id), -- Hierarchie-Link + name VARCHAR(100) NOT NULL, + description TEXT, + min_age INT, + max_age INT, + sort_order INT, + status VARCHAR(50) DEFAULT 'active', + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW() +); + +CREATE INDEX IF NOT EXISTS idx_target_groups_style ON target_groups(training_style_id); +CREATE INDEX IF NOT EXISTS idx_target_groups_status ON target_groups(status); + +-- ============================================================================ +-- PHASE 2: M:N Zuordnungstabellen +-- ============================================================================ + +-- Übung ↔ Fokusbereiche (M:N) +CREATE TABLE IF NOT EXISTS exercise_focus_areas ( + id SERIAL PRIMARY KEY, + exercise_id INT REFERENCES exercises(id) ON DELETE CASCADE, + focus_area_id INT REFERENCES focus_areas(id), + is_primary BOOLEAN DEFAULT false, + created_at TIMESTAMP DEFAULT NOW(), + UNIQUE(exercise_id, focus_area_id) +); + +CREATE INDEX IF NOT EXISTS idx_exercise_focus_areas_exercise ON exercise_focus_areas(exercise_id); +CREATE INDEX IF NOT EXISTS idx_exercise_focus_areas_focus ON exercise_focus_areas(focus_area_id); +CREATE INDEX IF NOT EXISTS idx_exercise_focus_areas_primary ON exercise_focus_areas(is_primary); + +-- Übung ↔ Stile (M:N) +CREATE TABLE IF NOT EXISTS exercise_styles ( + id SERIAL PRIMARY KEY, + exercise_id INT REFERENCES exercises(id) ON DELETE CASCADE, + training_style_id INT REFERENCES training_styles(id), + is_primary BOOLEAN DEFAULT false, + created_at TIMESTAMP DEFAULT NOW(), + UNIQUE(exercise_id, training_style_id) +); + +CREATE INDEX IF NOT EXISTS idx_exercise_styles_exercise ON exercise_styles(exercise_id); +CREATE INDEX IF NOT EXISTS idx_exercise_styles_style ON exercise_styles(training_style_id); +CREATE INDEX IF NOT EXISTS idx_exercise_styles_primary ON exercise_styles(is_primary); + +-- Übung ↔ Zielgruppen (M:N) +CREATE TABLE IF NOT EXISTS exercise_target_groups ( + id SERIAL PRIMARY KEY, + exercise_id INT REFERENCES exercises(id) ON DELETE CASCADE, + target_group_id INT REFERENCES target_groups(id), + is_primary BOOLEAN DEFAULT false, + created_at TIMESTAMP DEFAULT NOW(), + UNIQUE(exercise_id, target_group_id) +); + +CREATE INDEX IF NOT EXISTS idx_exercise_target_groups_exercise ON exercise_target_groups(exercise_id); +CREATE INDEX IF NOT EXISTS idx_exercise_target_groups_target ON exercise_target_groups(target_group_id); +CREATE INDEX IF NOT EXISTS idx_exercise_target_groups_primary ON exercise_target_groups(is_primary); + +-- Übung ↔ Altersgruppen (M:N - separate Dimension) +CREATE TABLE IF NOT EXISTS exercise_age_groups ( + id SERIAL PRIMARY KEY, + exercise_id INT REFERENCES exercises(id) ON DELETE CASCADE, + age_group VARCHAR(50) NOT NULL, -- 'Minis', 'Kinder', 'Schüler', 'Teenager', 'Erwachsene' + created_at TIMESTAMP DEFAULT NOW(), + UNIQUE(exercise_id, age_group), + CHECK (age_group IN ('Minis', 'Kinder', 'Schüler', 'Teenager', 'Erwachsene')) +); + +CREATE INDEX IF NOT EXISTS idx_exercise_age_groups_exercise ON exercise_age_groups(exercise_id); +CREATE INDEX IF NOT EXISTS idx_exercise_age_groups_age ON exercise_age_groups(age_group); + +-- ============================================================================ +-- PHASE 3: Daten-Migration (Altdaten von 1:1 zu M:N) +-- ============================================================================ + +-- Migriere focus_area_id → exercise_focus_areas (als primäre Zuordnung) +INSERT INTO exercise_focus_areas (exercise_id, focus_area_id, is_primary) +SELECT id, focus_area_id, true +FROM exercises +WHERE focus_area_id IS NOT NULL +ON CONFLICT (exercise_id, focus_area_id) DO NOTHING; + +-- Migriere training_style_id → exercise_styles (als primäre Zuordnung) +INSERT INTO exercise_styles (exercise_id, training_style_id, is_primary) +SELECT id, training_style_id, true +FROM exercises +WHERE training_style_id IS NOT NULL +ON CONFLICT (exercise_id, training_style_id) DO NOTHING; + +-- ============================================================================ +-- PHASE 4: Basis-Daten für Zielgruppen +-- ============================================================================ + +-- Beispiel-Zielgruppen für Shotokan (training_style_id = 1, falls vorhanden) +DO $$ +DECLARE + shotokan_id INT; +BEGIN + SELECT id INTO shotokan_id FROM training_styles WHERE name = 'Shotokan' LIMIT 1; + + IF shotokan_id IS NOT NULL THEN + INSERT INTO target_groups (training_style_id, name, description, sort_order) VALUES + (shotokan_id, 'Breitensportler', 'Karate für Freizeit und Fitness', 1), + (shotokan_id, 'Leistungssportler', 'Wettkampforientiertes Training', 2), + (shotokan_id, 'Kinder', 'Karate für Kinder (6-12 Jahre)', 3) + ON CONFLICT DO NOTHING; + END IF; +END $$; + +-- ============================================================================ +-- PHASE 5: Hierarchie-Daten (Stile zu Fokusbereichen zuordnen) +-- ============================================================================ + +-- Ordne Karate-Stile dem Fokusbereich "Karate" zu +DO $$ +DECLARE + karate_id INT; +BEGIN + SELECT id INTO karate_id FROM focus_areas WHERE name = 'Karate' LIMIT 1; + + IF karate_id IS NOT NULL THEN + UPDATE training_styles + SET focus_area_id = karate_id + WHERE name IN ('Shotokan', 'Goju-Ryu', 'Wado-Ryu', 'Shito-Ryu', 'Kyokushin') + AND focus_area_id IS NULL; + END IF; +END $$; + +-- ============================================================================ +-- HINWEISE für spätere Migrationen +-- ============================================================================ + +-- Die folgenden Spalten in exercises können später gedroppt werden: +-- - focus_area_id (deprecated, durch exercise_focus_areas ersetzt) +-- - training_style_id (deprecated, durch exercise_styles ersetzt) +-- - training_character_id (bleibt vorerst, separate Dimension) + +-- Aktuell bleiben sie zur Rückwärtskompatibilität erhalten. +-- Neue Übungen sollten NUR über M:N-Tabellen zugeordnet werden. +-- API-Endpoints sollten enriched data aus M:N-Tabellen liefern. diff --git a/backend/version.py b/backend/version.py index ff7cd7f..961bfda 100644 --- a/backend/version.py +++ b/backend/version.py @@ -1,8 +1,8 @@ # Shinkan Jinkendo Version Information -APP_VERSION = "0.2.0" +APP_VERSION = "0.3.0" BUILD_DATE = "2026-04-23" -DB_SCHEMA_VERSION = "20260422" +DB_SCHEMA_VERSION = "20260423" MODULE_VERSIONS = { "auth": "1.0.0", @@ -11,17 +11,30 @@ MODULE_VERSIONS = { "groups": "0.1.0", "skills": "0.1.0", "methods": "0.1.0", - "exercises": "0.2.0", # Updated: Katalog-Integration + "exercises": "0.3.0", # Updated: M:N Beziehungen "training_units": "0.1.0", "training_programs": "0.1.0", "planning": "0.1.0", "import_wiki": "0.1.0", "admin": "1.0.0", "membership": "1.0.0", - "catalogs": "1.0.0", # NEW: Admin-Kataloge + "catalogs": "1.1.0", # Updated: Zielgruppen + Hierarchie } CHANGELOG = [ + { + "version": "0.3.0", + "date": "2026-04-23", + "changes": [ + "BREAKING: M:N Beziehungen für Übungen (statt 1:1)", + "Migration 008: M:N Zuordnungstabellen (exercise_focus_areas, exercise_styles, exercise_target_groups, exercise_age_groups)", + "Feature: Hierarchische Katalog-Struktur (Fokusbereich → Stil → Zielgruppe)", + "Feature: Zielgruppen-Verwaltung (training_styles.focus_area_id Hierarchie)", + "Feature: Primary/Secondary Assignments (is_primary Flag)", + "Doku: DATABASE_SCHEMA.md + DOMAIN_MODEL.md kontinuierlich gepflegt", + "Architecture: Smart Cascade-Logik (RESTRICT, Rerouting, Move)", + ] + }, { "version": "0.2.0", "date": "2026-04-23", diff --git a/frontend/src/version.js b/frontend/src/version.js index 9ec57a9..6a81386 100644 --- a/frontend/src/version.js +++ b/frontend/src/version.js @@ -1,6 +1,6 @@ // Shinkan Jinkendo Frontend Version -export const APP_VERSION = "0.2.0" +export const APP_VERSION = "0.3.0" export const BUILD_DATE = "2026-04-23" export const PAGE_VERSIONS = {