feat: Migration 008 - M:N Exercise Relations + Hierarchical Catalogs
Some checks failed
Deploy Development / deploy (push) Successful in 34s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 5s
Test Suite / playwright-tests (push) Has been cancelled

BREAKING CHANGE: Datenmodell-Umstellung von 1:1 auf M:N Beziehungen

Migration 008:
- Zielgruppen-Tabelle (target_groups) mit training_style_id Hierarchie
- M:N Zuordnungstabellen: exercise_focus_areas, exercise_styles, exercise_target_groups
- Altersgruppen-Dimension (exercise_age_groups) mit CHECK constraint
- Hierarchische Struktur: training_styles.focus_area_id → focus_areas
- Daten-Migration: Bestehende 1:1 Beziehungen zu M:N mit is_primary=true
- Seed-Daten: Beispiel-Zielgruppen für Shotokan

Architektur:
- Smart Cascade-Logik (RESTRICT, Rerouting, Move) vorbereitet
- Legacy-Spalten (focus_area_id, training_style_id) bleiben zur Rückwärtskompatibilität
- Primary/Secondary Assignments via is_primary Flag

Dokumentation:
- .claude/docs/technical/DATABASE_SCHEMA.md (kontinuierlich gepflegt)
- .claude/docs/functional/DOMAIN_MODEL.md (fachliche Anforderungen)
- Migrations-Historie aktualisiert

version: 0.3.0 (backend + frontend)
modules: exercises 0.3.0, catalogs 1.1.0
DB_SCHEMA_VERSION: 20260423

Konzept: shinkan_anforderungsdokument_entwurf.md (§8.1, §8.3, §10.7)
This commit is contained in:
Lars 2026-04-23 08:46:56 +02:00
parent d0e9b9b764
commit 63b1c09975
5 changed files with 846 additions and 5 deletions

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -1,8 +1,8 @@
# Shinkan Jinkendo Version Information # Shinkan Jinkendo Version Information
APP_VERSION = "0.2.0" APP_VERSION = "0.3.0"
BUILD_DATE = "2026-04-23" BUILD_DATE = "2026-04-23"
DB_SCHEMA_VERSION = "20260422" DB_SCHEMA_VERSION = "20260423"
MODULE_VERSIONS = { MODULE_VERSIONS = {
"auth": "1.0.0", "auth": "1.0.0",
@ -11,17 +11,30 @@ MODULE_VERSIONS = {
"groups": "0.1.0", "groups": "0.1.0",
"skills": "0.1.0", "skills": "0.1.0",
"methods": "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_units": "0.1.0",
"training_programs": "0.1.0", "training_programs": "0.1.0",
"planning": "0.1.0", "planning": "0.1.0",
"import_wiki": "0.1.0", "import_wiki": "0.1.0",
"admin": "1.0.0", "admin": "1.0.0",
"membership": "1.0.0", "membership": "1.0.0",
"catalogs": "1.0.0", # NEW: Admin-Kataloge "catalogs": "1.1.0", # Updated: Zielgruppen + Hierarchie
} }
CHANGELOG = [ 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", "version": "0.2.0",
"date": "2026-04-23", "date": "2026-04-23",

View File

@ -1,6 +1,6 @@
// Shinkan Jinkendo Frontend Version // 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 BUILD_DATE = "2026-04-23"
export const PAGE_VERSIONS = { export const PAGE_VERSIONS = {