feat: Migration 008 - M:N Exercise Relations + Hierarchical Catalogs
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:
parent
d0e9b9b764
commit
63b1c09975
403
.claude/docs/functional/DOMAIN_MODEL.md
Normal file
403
.claude/docs/functional/DOMAIN_MODEL.md
Normal 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
|
||||||
260
.claude/docs/technical/DATABASE_SCHEMA.md
Normal file
260
.claude/docs/technical/DATABASE_SCHEMA.md
Normal 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
|
||||||
165
backend/migrations/008_mn_exercise_relations.sql
Normal file
165
backend/migrations/008_mn_exercise_relations.sql
Normal 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.
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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 = {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user