feat: integrate new desktop shell and extend backend & ADRs

- Added `meldestelle-desktop` module using JVM/Compose Desktop, registered in `settings.gradle.kts`.
- Integrated new screens and desktop navigation into core: `Veranstaltungen`, `TurnierDetail`, etc.
- Expanded backend with `ExposedFunktionaerRepository` in `officials-infrastructure`.
- Completed ADRs for bounded context mapping (`ADR-0014`) and context map (`ADR-0015`).
- Updated and extended project documentation with session logs and architecture decisions.

Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
This commit is contained in:
2026-03-24 18:22:15 +01:00
parent c624df8744
commit 354bd49de6
75 changed files with 7616 additions and 48 deletions
@@ -0,0 +1,75 @@
---
type: SessionLog
date: 2026-03-24
agents:
- Lead Architect
- Curator
status: ABGESCHLOSSEN
---
# Session Log: ADR-0014 & ADR-0015 — Bounded Context Mapping & Context Map
🏗️ **[Lead Architect]** | 🧹 **[Curator]** | 24. März 2026
---
## Ziel der Session
ADRs für Bounded Context Mapping und Context Map vervollständigen (PHASE 4, Task 1).
---
## Ergebnisse
### ADR-0014: Bounded Context Mapping (SCS-Architektur)
**Datei:** `docs/01_Architecture/adr/0014-bounded-context-mapping-de.md`
Dokumentiert die 6 Bounded Contexts als Self-Contained Systems:
| Context | Domänen-Typ | Priorität |
|----------------------------|-------------------|-----------|
| `registration-context` | Core Domain | P1 |
| `actor-context` | Supporting Domain | P1 |
| `competition-context` | Supporting Domain | P2 |
| `event-management-context` | Supporting Domain | P2 |
| `billing-context` | Generic Domain | P3 |
| `identity-context` | Generic Domain | P3 |
Für jeden Context dokumentiert: Aggregate Roots, Ubiquitous Language (Auswahl), Kern-Invarianten.
Begründung für Ablehnung der alten technischen Modulaufteilung (`masterdata`, `members`, `horses`, `events`).
---
### ADR-0015: Context Map & Integration Patterns
**Datei:** `docs/01_Architecture/adr/0015-context-map-de.md`
Dokumentiert 7 Context-Beziehungen mit ASCII-Diagramm und Detailtabellen:
| Beziehung | Pattern |
|-----------------------------------------------------|-----------------------------------|
| ZNS → `actor-context` | Upstream/Downstream + ACL |
| `actor-context``registration-context` | Customer/Supplier + Shared Kernel |
| `event-management-context``registration-context` | Customer/Supplier + Shared Kernel |
| `registration-context``competition-context` | Domain Events (asynchron) |
| `registration-context``billing-context` | Domain Events + ACL |
| `competition-context``billing-context` | Domain Events + ACL |
| Keycloak → alle Contexts | Conformist (OIDC/JWT) |
Enthält: ACL-Implementierungsrichtlinien, Offline-First-Verhalten pro Szenario.
---
## MASTER_ROADMAP Updates
- `[x]` **ADRs vervollständigen** (PHASE 4, Lead Architect) — abgeschlossen
- ADR-Tabelle: Einträge #8 (ADR-0014) und #9 (ADR-0015) hinzugefügt
---
## Offene Punkte (nächste Session)
- `[ ]` **API-Design:** Schnittstellen zwischen den Contexts definieren (Anti-Corruption Layer) — Lead Architect
- `[ ]` **`actor-context`:** `DomPferd`, `DomFunktionär`, `DomVerein` implementieren — Backend Developer
@@ -0,0 +1,92 @@
---
type: SessionLog
date: 2026-03-24
agent: Lead Architect
phase: PHASE 4
task: API-Design & Anti-Corruption Layer
status: ABGESCHLOSSEN
---
# 🧹 [Curator] Session Log API-Design & ACL
**Datum:** 24. März 2026
**Agent:** 🏗️ Lead Architect
**Phase:** PHASE 4 MVP-Implementierung
**Aufgabe:** API-Design: Schnittstellen zwischen den Contexts definieren (Anti-Corruption Layer)
---
## Ergebnisse
### Erstellt
- **`ADR-0016`** (`docs/01_Architecture/adr/0016-api-design-acl-de.md`)
- Architektur-Muster: Ports & Adapters (Hexagonal) für alle Contexts
- REST-API-Katalog für alle 3 P1-Contexts (`actor`, `event-management`, `registration`)
- Vollständige DTO-Definitionen (Inbound Commands + Outbound DTOs)
- ACL-Port-Interfaces: `AktorReferenzPort`, `TurnierReferenzPort`
- ACL-Adapter-Implementierung mit Übersetzungslogik (DTO → Referenz-Objekt)
- Domain Events-Katalog: 8 Events über 3 Contexts
- ZNS-Schnittstelle: `ZnsPort` mit A-Satz / B-Satz
- Offline-First Cache-Strategie für ACL-Adapter
- Implementierungs-Reihenfolge (P1-Priorität)
### Aktualisiert
- **`MASTER_ROADMAP.md`**: Task `[x]` abgehakt, ADR #10 in Tabelle eingetragen
- **`docs/01_Architecture/adr/README.md`**: ADR-0016 eingetragen
---
## Kern-Entscheidungen (ADR-0016)
| Bereich | Entscheidung |
|---------------|--------------------------------------------------|
| Muster | Ports & Adapters (Hexagonal Architecture) |
| Kommunikation | REST (synchron) + Domain Events (asynchron) |
| ACL-Regel | Domain-Objekte verlassen den Context **niemals** |
| DTOs | Flach, serialisierbar, ohne Domänen-Logik |
| Offline | Lokale SQLite-Caches in ACL-Adaptern |
| ZNS | Eigener `ZnsPort` im `actor-context` (isoliert) |
---
## REST-API Übersicht
| Context | Base-URL | Endpunkte |
|----------------------------|-------------------------|------------------------------------------|
| `actor-context` | `/api/v1/actors` | 8 (Reiter, Pferde, Funktionäre, Vereine) |
| `event-management-context` | `/api/v1/events` | 6 (Veranstaltungen, Turniere, Bewerbe) |
| `registration-context` | `/api/v1/registrations` | 6 (Nennungen, Transfer) |
---
## Domain Events Übersicht
| Context | Events |
|----------------------------|------------------------------------------------------------------|
| `actor-context` | `ReiterAktualisiert`, `PferdAktualisiert`, `ReiterGesperrt` |
| `registration-context` | `NennungEingereicht`, `NennungStorniert`, `NennungTransferiert` |
| `event-management-context` | `TurnierEroeffnet`, `NennungsschlussErreicht`, `TurnierAbgesagt` |
---
## Status PHASE 4: Lead Architect Tasks
- [x] ADRs vervollständigen (Bounded Context Mapping + Context Map) → ADR-0014, ADR-0015
- [x] API-Design & ACL definieren → ADR-0016
**Lead Architect Tasks PHASE 4: ✅ ABGESCHLOSSEN**
---
## Nächste Schritte (Backend Developer)
Gemäß Implementierungs-Reihenfolge aus ADR-0016:
1. `actor-context` REST API (`/api/v1/actors`) implementieren
2. `event-management-context` REST API (`/api/v1/events`) implementieren
3. ACL-Adapter im `registration-context` implementieren
4. `registration-context` REST API (`/api/v1/registrations`) implementieren
5. Domain Event `NennungEingereicht` als erstes Event
6. Offline-Cache (Bulk-Sync beim Turnier-Download)
@@ -0,0 +1,70 @@
---
type: Session Log
date: 2026-03-24
agent: Backend Developer
status: ABGESCHLOSSEN
roadmap_phase: PHASE 4 MVP-Implementierung
---
# Session Log: actor-context Domain-Modelle
👷 **[Backend Developer]** | 24. März 2026
## Ziel
Domain-Modelle für `DomPferd`, `DomFunktionär` und `DomVerein` im `actor-context` implementieren (PHASE 4,
MASTER_ROADMAP).
---
## Ergebnis
### ✅ DomPferd
- Bereits vollständig und ÖTO-konform im Modul `backend/services/horses/horses-domain` vorhanden.
- Keine Änderungen notwendig.
### ✅ DomFunktionaer (neu)
- **Datei:**
`backend/services/officials/officials-domain/src/main/kotlin/at/mocode/officials/domain/model/DomFunktionaer.kt`
- Aggregate Root des `officials`-Bounded Context.
- Felder: `richterNummer` (ZNS RICHT01.dat), `vorname`, `nachname`, `geburtsdatum`, `rollen`, `richterQualifikation`,
`qualifiziertFuerSparten`, `email`, `telefon`, `vereinsNummer`, `istAktiv`, `bemerkungen`, `datenQuelle`.
- Domain-Methoden: `getDisplayName()`, `istRichterFuerSparte()`, `istTba()`, `validateFuerTurniereinsatz()` (Warn-Logik,
kein harter Fehler).
### ✅ DomVerein (neu)
- **Datei:** `backend/services/clubs/clubs-domain/src/main/kotlin/at/mocode/clubs/domain/model/DomVerein.kt`
- Aggregate Root des `clubs`-Bounded Context.
- Felder: `vereinsNummer` (ZNS VEREIN01.dat, 4-stellig), `name`, `kurzname`, `bundesland`, `ort`, `plz`, `strasse`,
`email`, `telefon`, `website`, `oepsRegionNummer`, `istVeranstalter`, `istAktiv`, `bemerkungen`, `datenQuelle`.
- Domain-Methoden: `getDisplayName()`, `getDisplayNameWithNummer()`, `hasCompleteAddress()`,
`validateFuerVeranstaltung()` (Warn-Logik).
### ✅ Neue Enums in core-domain
- **Datei:** `core/core-domain/src/commonMain/kotlin/at/mocode/core/domain/model/Enums.kt`
- `FunktionaerRolleE`: TBA, RICHTER, PARCOURSBAUER, STRECKENDESIGNER, TIERARZT, STEWARD, STARTER, ZEITNEHMER,
PROTOKOLLFUEHRER, SONSTIGE
- `RichterQualifikationE`: GA, G3, G2, G1, INTERNATIONAL, SONSTIGE (gemäß ZNS RICHT01.dat)
---
## Design-Entscheidungen
| Entscheidung | Begründung |
|------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `DomFunktionaer` statt Erweiterung von `DomOfficial` | `DomOfficial` war ein minimaler ZNS-Import-Stub. `DomFunktionaer` ist das vollständige ÖTO-konforme Domain-Modell mit Rollen und Sparten-Qualifikation. |
| `DomVerein` statt Erweiterung von `DomClub` | Analog: `DomClub` war ein ZNS-Import-Stub. `DomVerein` enthält Adresse, Kontakt, Veranstalter-Flag und OEPS-Region. |
| Warn-Logik statt harter Fehler | Konsistent mit ADR-0016 und Override-Event-Prinzip: `validateFuerTurniereinsatz()` und `validateFuerVeranstaltung()` geben Warnungen zurück, kein Exception-Throwing. |
| `rollen: Set<FunktionaerRolleE>` | Eine Person kann mehrere Rollen haben (z.B. TBA + Richter). Set verhindert Duplikate. |
---
## Nächste Schritte (PHASE 4)
- [ ] `registration-context`: `DomBewerb`, `DomAbteilung`, `DomStartliste` implementieren.
- [ ] `event-management-context`: `DomVeranstaltung`, `DomTurnier`, `DomAusschreibung` implementieren.
- [ ] Persistenz: Repository-Interfaces und DB-Migrationen (Flyway/Liquibase).
@@ -67,10 +67,15 @@ die ÖTO-konforme Terminologie und die Erstellung der offiziellen Ubiquitous Lan
## Erstellte / Aktualisierte Dokumente
| Dokument | Aktion | Beschreibung |
|-----------------------------------------------------|----------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `docs/03_Domain/01_Glossary/Ubiquitous_Language.md` | ✅ NEU ERSTELLT | Offizielle Domänen-Terminologie mit ÖTO-Referenzen, Bounded Context Zuordnung, Hierarchie-Diagramm, Reglement-Hinweis für Cups/Serien/Meisterschaften und MVP-Scope-Tabelle |
| `docs/03_Domain/01_Glossary/Ubiquitous_Language.md` | ✅ KORRIGIERT | Drei Korrekturen eingearbeitet (📜 ÖTO/FEI Rulebook Expert Review): **Abteilung** als kleinste Einheit für Nennungen/Startlisten/Ergebnisse mit Abteilungsnummer und Referenzformat `BW:9 Abt:1`; **Bewerb** korrigiert (nicht mehr „kleinste Einheit"); **Kopfnummer** als nicht eindeutige ID markiert; **Lebensnummer** mit Hinweis auf inkonsistente ZNS-Daten ergänzt |
| Dokument | Aktion | Beschreibung |
|----------------------------------------------------------------------|----------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `docs/03_Domain/01_Glossary/Ubiquitous_Language.md` | ✅ NEU ERSTELLT | Offizielle Domänen-Terminologie mit ÖTO-Referenzen, Bounded Context Zuordnung, Hierarchie-Diagramm, Reglement-Hinweis für Cups/Serien/Meisterschaften und MVP-Scope-Tabelle |
| `docs/03_Domain/01_Glossary/Ubiquitous_Language.md` | ✅ KORRIGIERT | Drei Korrekturen eingearbeitet (📜 ÖTO/FEI Rulebook Expert Review): **Abteilung** als kleinste Einheit für Nennungen/Startlisten/Ergebnisse mit Abteilungsnummer und Referenzformat `BW:9 Abt:1`; **Bewerb** korrigiert (nicht mehr „kleinste Einheit"); **Kopfnummer** als nicht eindeutige ID markiert; **Lebensnummer** mit Hinweis auf inkonsistente ZNS-Daten ergänzt |
| `core/core-domain/.../Enums.kt` | ✅ ERWEITERT | Neue Enums: `SparteE`, `TurnierkategorieE`, `VeranstaltungsTypE`, `LizenzKlasseE`, `NennungsStatusE`, `StartwunschE` alle ÖTO-konform mit KDoc-Kommentaren |
| `backend/services/persons/persons-domain/.../DomReiter.kt` | ✅ NEU ERSTELLT | Reiter-Domänenmodell (actor-context): Satznummer als ZNS-Primärschlüssel, LizenzKlasse, Startkarte, Sparten-Lizenz, Gastreiter-Flag, `validateForNennung()` gibt nur Warnungen (kein harter Fehler) |
| `backend/services/entries/entries-domain/.../DomNennung.kt` | ✅ NEU ERSTELLT | Nennungs-Domänenmodell (registration-context): Referenz auf Abteilung (kleinste Einheit), Reiter, Pferd, Zahler; Nachnennung-Flag, Gebühren-Verzicht, Status-Lifecycle |
| `backend/services/entries/entries-domain/.../DomNennungsTransfer.kt` | ✅ NEU ERSTELLT | Transfer-Domänenmodell: explizites Audit-Trail (alter/neuer Reiter, altes/neues Pferd), Override-Event-Referenz, `isValid()` prüft dass mindestens Reiter oder Pferd getauscht wurde |
| `backend/services/entries/entries-domain/build.gradle.kts` | ✅ NEU ERSTELLT | Neues KMP-Modul nach Muster `horses-domain`; in `settings.gradle.kts` registriert; `compileKotlinJvm` erfolgreich |
---
@@ -87,11 +92,12 @@ die ÖTO-konforme Terminologie und die Erstellung der offiziellen Ubiquitous Lan
## Nächste Schritte (Empfehlung)
- [ ] 👷 **[Backend Developer]**: Kotlin Domain-Modelle für `registration-context` und `actor-context` definieren
- [ ] 🏗️ **[Lead Architect]**: MASTER_ROADMAP mit den 6 Bounded Contexts aktualisieren
- [ ] 🎨 **[Frontend Expert]**: KMP/Compose Desktop Projektstruktur aufsetzen
- [ ] 📜 **[ÖTO/FEI Rulebook Expert]**: Abteilungs-Trennungs-Schwellenwerte (sparten- und klassenabhängig) recherchieren
und dokumentieren
- [x] 👷 **[Backend Developer]**: Kotlin Domain-Modelle für `registration-context` und `actor-context` definieren
- [x] 🏗️ **[Lead Architect]**: MASTER_ROADMAP mit den 6 Bounded Contexts aktualisieren
`docs/01_Architecture/MASTER_ROADMAP.md`
- [x] 🎨 **[Frontend Expert]**: KMP/Compose Desktop Projektstruktur aufsetzen
- [x] 📜 **[ÖTO/FEI Rulebook Expert]**: Abteilungs-Trennungs-Schwellenwerte (sparten- und klassenabhängig) recherchieren
und dokumentieren → `docs/03_Domain/02_Reference/OETO_Regelwerk/Abteilungs-Trennungs-Schwellenwerte.md`
---
@@ -0,0 +1,117 @@
---
date: 2026-03-24
type: Session Log
agents: Frontend Expert, Curator
status: COMPLETED
---
# Session Log KMP/Compose Desktop Shell aufsetzen
🎨 **[Frontend Expert]** & 🧹 **[Curator]** | 24. März 2026
---
## Zusammenfassung der Session
Aufsetzen der dedizierten Desktop-Shell `meldestelle-desktop` als eigenständiges
JVM/Compose-Desktop-Modul gemäß Desktop-First-Strategie (ADR-0009) und Vision_03-Design-Baseline.
---
## Durchgeführte Aktivitäten
### 1. Neues Modul: `frontend/shells/meldestelle-desktop`
- Eigenständige Desktop-Shell, getrennt vom Web-Portal (`meldestelle-portal`)
- Reines JVM-Modul (kein JS/WASM) Desktop-First gemäß MASTER_ROADMAP
- In `settings.gradle.kts` registriert
- `compileKotlinJvm` ✅ BUILD SUCCESSFUL
### 2. Projektstruktur
```
frontend/shells/meldestelle-desktop/
├── build.gradle.kts # JVM-only, compose.desktop, nativeDistributions
└── src/jvmMain/kotlin/at/mocode/desktop/
├── main.kt # application {} Entry-Point, Koin-Init, Window
├── DesktopApp.kt # Login-Gate + Haupt-Composable
├── di/
│ └── DesktopModule.kt # Koin: DesktopNavigationPort, CurrentUserProvider, DeepLinkHandler
├── navigation/
│ └── DesktopNavigationPort.kt # StateFlow-basierte Navigation
└── screens/
├── DesktopMainLayout.kt # Sidebar (220dp) + Content-Bereich
├── PlaceholderContent.kt # Wiederverwendbarer Platzhalter
├── VeranstaltungenScreen.kt # Übersicht + "Neue Veranstaltung"-Button
├── VeranstaltungNeuScreen.kt # Tabs: Übersicht | Stammdaten* | Organisation | Preisliste
├── VeranstaltungDetailScreen.kt # Übersicht-Tab + Turniere-Section
├── TurnierNeuScreen.kt # Tabs: Übersicht | Stammdaten | Organisation | Bewerbe⭐* | Preisliste
├── TurnierDetailScreen.kt # Bewerbe-Tab integriert NennungsMaske (nennung-feature)
└── AktorScreens.kt # Reiter, Pferde, Funktionäre, Meisterschaften, Cups
```
### 3. Navigation gemäß Vision_03
Sidebar-Navigation mit 6 Einträgen (links, 220dp, Material3 `surfaceVariant`):
| Eintrag | Route | Status |
|-----------------|--------------------|---------------------------------|
| Veranstaltungen | `/veranstaltungen` | ✅ Screen implementiert |
| Reiter | `/reiter` | ✅ Placeholder |
| Pferde | `/pferde` | ✅ Placeholder |
| Funktionäre | `/funktionaere` | ✅ Placeholder |
| Meisterschaften | `/meisterschaften` | ✅ Placeholder (Phase 2+) |
| Cups | `/cups` | ✅ Placeholder (Phase 2+) |
| Logout | — | ✅ Löscht Token, zurück zu Login |
### 4. Neue `AppScreen`-Einträge (core/navigation)
Folgende Screens wurden in `AppScreen.kt` ergänzt:
- `Veranstaltungen`, `VeranstaltungNeu`, `VeranstaltungDetail(id)`
- `TurnierNeu(veranstaltungId)`, `TurnierDetail(veranstaltungId, turnierId)`
- `Reiter`, `Pferde`, `Funktionaere`, `Meisterschaften`, `Cups`
### 5. Nennungs-Integration
- `TurnierDetailScreen` → Bewerbe-Tab (⭐ Standard-Tab) integriert `NennungsMaske` aus `nennung-feature`
- Callbacks für Startliste, Ergebnisse, Abrechnung als TODO vorbereitet
---
## Erstellte / Aktualisierte Dokumente
| Dokument | Aktion | Beschreibung |
|-----------------------------------------------------------------------------------|----------------|---------------------------------------------------------------------|
| `frontend/shells/meldestelle-desktop/build.gradle.kts` | ✅ NEU | JVM-only Shell, compose.desktop, nativeDistributions (Deb/Msi/Dmg) |
| `frontend/shells/meldestelle-desktop/src/.../main.kt` | ✅ NEU | application {} Entry-Point, Koin-Init, Window 1400×900 |
| `frontend/shells/meldestelle-desktop/src/.../DesktopApp.kt` | ✅ NEU | Login-Gate, delegiert an DesktopMainLayout |
| `frontend/shells/meldestelle-desktop/src/.../di/DesktopModule.kt` | ✅ NEU | Koin-Modul mit Navigation, CurrentUserProvider, DeepLinkHandler |
| `frontend/shells/meldestelle-desktop/src/.../navigation/DesktopNavigationPort.kt` | ✅ NEU | StateFlow-Navigation |
| `frontend/shells/meldestelle-desktop/src/.../screens/*.kt` | ✅ NEU | 8 Screen-Dateien (Layout, Placeholders, Veranstaltung/Turnier-Flow) |
| `frontend/core/navigation/.../AppScreen.kt` | ✅ ERWEITERT | 10 neue Desktop-Screens ergänzt |
| `settings.gradle.kts` | ✅ ERWEITERT | `:frontend:shells:meldestelle-desktop` registriert |
| `docs/01_Architecture/MASTER_ROADMAP.md` | ✅ AKTUALISIERT | Frontend-Expert-Tasks als abgeschlossen markiert |
---
## Wichtige Entscheidungen
1. **Eigenständiges Modul** statt Erweiterung des Portal-Shells → saubere Trennung Desktop/Web
2. **Login-Gate** in `DesktopApp.kt` → Desktop startet immer mit Login, kein Landing-Screen
3. **`PrimaryTabRow`** statt deprecated `TabRow` verwendet
4. **`NennungsMaske`** direkt im Bewerbe-Tab des `TurnierDetailScreen` integriert
---
## Nächste Schritte (Empfehlung)
- [ ] 👷 **[Backend Developer]**: `DomVeranstaltung`, `DomTurnier`, `DomBewerb`, `DomAbteilung` implementieren
- [ ] 🎨 **[Frontend Expert]**: `VeranstaltungenScreen` mit echten Daten aus Repository befüllen
- [ ] 🎨 **[Frontend Expert]**: Startlisten- und Ergebnisse-Screens implementieren (competition-context)
- [ ] 🎨 **[Frontend Expert]**: Formular-Felder für `VeranstaltungNeuScreen` und `TurnierNeuScreen` ausbauen
---
*Session-Dauer: 24. März 2026*
*Curator: Junie (KI-Agent)*
@@ -0,0 +1,78 @@
---
type: Session Log
date: 2026-03-24
agent: Backend Developer
context: event-management-context
status: ABGESCHLOSSEN
---
# Session Log: event-management-context Domain-Modelle
👷 **[Backend Developer]** | 24. März 2026
## Ziel
Implementierung der Domain-Modelle `DomVeranstaltung`, `DomTurnier` und `DomAusschreibung`
für den `event-management-context` gemäß ÖTO § 2 Abs. 1 und ADR-0003 (`Veranstaltung ≠ Turnier`).
---
## Implementierte Modelle
### `DomVeranstaltung` (`events-domain`)
- **Aggregate Root** für den organisatorischen Rahmen einer pferdesportlichen Veranstaltung.
- Felder: `veranstaltungId`, `name`, `veranstaltungsTyp`, `sparten`, `veranstalterVereinId`,
`verantwortlicheFunktionaerId`, `startDatum`, `endDatum`, `ort`, `nennschluss`, `status`,
`ausschreibungsId`, `oepsGenehmigungsNummer`, `bemerkungen`, Audit-Felder.
- Warn-Logik: `validateNennungsmoeglichkeit()` (Status GENEHMIGT + Nennschluss vorhanden),
`validateFuerEinreichung()` (Pflichtfelder, Datum-Konsistenz, Ausschreibung verknüpft).
### `DomTurnier` (`events-domain`)
- **Aggregate Root** für ein einzelnes Turnier innerhalb einer Veranstaltung.
- Felder: `turnierId`, `veranstaltungId` (FK), `name`, `sparte`, `kategorie`, `datum`,
`richterObmannId`, `parcoursbauerId`, `status`, `maxBewerbe`, `istMeisterschaft`, `bemerkungen`, Audit-Felder.
- Warn-Logik: `validateFunktionaerBesetzung()` (Richter-Obmann Pflicht; Parcoursbauer Pflicht bei Springen),
`validateFuerPlanung()` (Pflichtfelder, positive maxBewerbe).
### `DomAusschreibung` (`events-domain`)
- **Aggregate Root** für das offizielle Ausschreibungs-Dokument.
- Felder: `ausschreibungsId`, `veranstaltungId` (FK), `titel`, `sparten`, `nennschluss`,
`nachnennung`, `nachnennungBis`, Gebühren in Cent (Integer, kein Float), `tierwohleuroAktiv`,
Veranstaltungsort, Stallplätze, Kontakt, `status`, `eingereichtAm`, `genehmigungsNummer`, Audit-Felder.
- Warn-Logik: `validateFuerEinreichung()` (Pflichtfelder, Gebühren ≥ 0, Nachnennungs-Datum-Konsistenz).
- Hilfsmethoden: `getNenngebuehrAlsEuroString()`, `getGesamtgebuehrCent()`.
---
## Neue Enums in `Enums.kt` (`core-domain`)
| Enum | Werte |
|-------------------------|----------------------------------------------------------------------------------------------------------|
| `VeranstaltungsStatusE` | `IN_PLANUNG`, `EINGEREICHT`, `GENEHMIGT`, `NENNSCHLUSS_ABGELAUFEN`, `AKTIV`, `ABGESCHLOSSEN`, `ABGESAGT` |
| `TurnierStatusE` | `GEPLANT`, `AKTIV`, `ABGESCHLOSSEN`, `ABGESAGT` |
| `AusschreibungsStatusE` | `ENTWURF`, `EINGEREICHT`, `GENEHMIGT`, `ABGELEHNT`, `VEROEFFENTLICHT` |
---
## Design-Entscheidungen
- **Veranstaltung ≠ Turnier** (ADR-0003): `DomVeranstaltung` ist der Rahmen, `DomTurnier` die Durchführungseinheit.
- **Gebühren in Cent (Integer)**: Vermeidung von Floating-Point-Fehlern bei Geldbeträgen.
- **Warn-Logik statt Exceptions** (ADR-0007): Alle Validierungen geben `List<String>` zurück.
- **Bestehende `Veranstaltung.kt`** bleibt als Legacy-Scaffold erhalten; `DomVeranstaltung.kt` ist das neue ÖTO-konforme
Modell.
---
## Geänderte Dateien
| Datei | Aktion |
|------------------------------------------|----------------------|
| `events-domain/.../DomVeranstaltung.kt` | NEU |
| `events-domain/.../DomTurnier.kt` | NEU |
| `events-domain/.../DomAusschreibung.kt` | NEU |
| `core-domain/.../Enums.kt` | ERWEITERT (+3 Enums) |
| `docs/01_Architecture/MASTER_ROADMAP.md` | AKTUALISIERT |
@@ -0,0 +1,121 @@
---
date: 2026-03-24
agent: Backend Developer
phase: PHASE 4 MVP-Implementierung
status: ABGESCHLOSSEN
---
# Session Log: REST-Endpunkte Nennungs-Workflow
👷 **[Backend Developer]** | 24. März 2026
## Ziel
Implementierung der REST-Endpunkte für den Nennungs-Workflow (registration-context) gemäß MASTER_ROADMAP Phase 4.
---
## Erledigte Aufgaben
### 1. `entries-service` aktiviert
- `settings.gradle.kts`: `:backend:services:entries:entries-service` aus dem ON-HOLD-Kommentar befreit und aktiv
eingebunden.
### 2. `entries-api` Fachliche DTOs (`NennungDtos.kt`)
Neue Datei: `entries-api/src/commonMain/kotlin/at/mocode/entries/api/NennungDtos.kt`
| DTO | Zweck |
|-------------------------------|-------------------------------------|
| `NennungEinreichenRequest` | POST Neue Nennung einreichen |
| `NennungStatusAendernRequest` | PUT Status ändern |
| `NennungTransferRequest` | POST /transfer Transfer-Operation |
| `NennungSummaryDto` | GET Liste kompakte Ansicht |
| `NennungDetailDto` | GET Detail / POST Response |
| `NennungsTransferDto` | Transfer-Response mit Audit-Trail |
**Dependency ergänzt:** `projects.core.coreDomain` in `entries-api/build.gradle.kts` (für `NennungsStatusE`,
`StartwunschE`, `UuidSerializer`).
### 3. `entries-domain` `NennungsTransferRepository`
Neues Interface: `entries-domain/.../repository/NennungsTransferRepository.kt`
- `findById`, `findByUrsprungsNennungId`, `save`
### 4. `entries-service` Persistence-Schicht
| Datei | Inhalt |
|-------------------------------------|-----------------------------------------------------------------|
| `NennungTable.kt` | Exposed-Tabelle `nennungen` mit allen Feldern + Indizes |
| `NennungsTransferTable.kt` | Exposed-Tabelle `nennungs_transfers` mit Audit-Trail |
| `NennungRepositoryImpl.kt` | Vollständige Implementierung aller `NennungRepository`-Methoden |
| `NennungsTransferRepositoryImpl.kt` | Implementierung aller `NennungsTransferRepository`-Methoden |
### 5. `entries-service` Use Cases (`NennungUseCases.kt`)
`@Service`-Klasse mit folgenden Use Cases:
| Methode | Fachliche Logik |
|-------------------------------------------------|------------------------------------------------------------------------------------------------------------------------|
| `getNennungById` | Query by ID |
| `getNennungenByTurnier/Bewerb/Abteilung/Reiter` | Gefilterte Listen |
| `nennungEinreichen` | Neue Nennung, Warn-Log bei Nachnennung |
| `statusAendern` | Status-Transition mit Audit-Timestamp |
| `nennungZurueckziehen` | Soft-Delete → Status `ZURUECKGEZOGEN` |
| `nennungTransferieren` | **Atomare Transfer-Operation** (ÖTO-konform): Ursprung → TRANSFERIERT, neue Nennung anlegen, Transfer-Record speichern |
**ÖTO-Konformität:** Warn-Logik statt harter Fehler. TBA hat das letzte Wort.
### 6. `entries-service` REST-Controller (`NennungController.kt`)
Basis-URL: `/api/v1/registrations/nennungen`
| Methode | Endpunkt | Beschreibung |
|----------|--------------------------|------------------------------------------------------------|
| `GET` | `/` | Liste (Filter: turnierId, bewerbId, abteilungId, reiterId) |
| `GET` | `/{nennungsId}` | Detail |
| `POST` | `/` | Neue Nennung einreichen (201) |
| `PUT` | `/{nennungsId}/status` | Status ändern |
| `DELETE` | `/{nennungsId}` | Zurückziehen |
| `POST` | `/{nennungsId}/transfer` | Transfer (201) |
### 7. `entries-service` Konfiguration
| Datei | Inhalt |
|-----------------------------------|---------------------------------------------------------------|
| `EntriesBeansConfiguration.kt` | Spring-Beans für Repository-Implementierungen |
| `EntriesDatabaseConfiguration.kt` | Exposed-Schema-Init (`NennungTable`, `NennungsTransferTable`) |
| `EntriesExceptionHandler.kt` | RFC 9457 Problem Details (404, 400, 409) |
### 8. `entries-service` Build-Dependencies ergänzt
```kotlin
implementation(projects.backend.services.entries.entriesDomain)
implementation(projects.core.coreUtils)
implementation(projects.core.coreDomain)
implementation(libs.exposed.core)
implementation(libs.exposed.jdbc)
implementation(libs.exposed.kotlin.datetime)
```
---
## Architektur-Entscheidungen
- **Warn-Logik:** Nachnennungen und Transfers nach Nennschluss werden geloggt (`log.warn`), aber nicht blockiert gemäß
ADR-7 (Warn-Logik statt harter Fehler).
- **Transfer = atomare Operation:** Keine Storno + Neunennung, sondern: Ursprung → TRANSFERIERT + neue Nennung +
Transfer-Record. Entspricht ÖTO-Regelwerk.
- **Soft-Delete:** Nennungen werden nie physisch gelöscht, sondern auf `ZURUECKGEZOGEN` gesetzt.
- **Repository-Pattern:** Interfaces in `entries-domain`, Implementierungen in `entries-service` (Dependency Inversion).
---
## Nächste Schritte
- **ÖTO/FEI Rulebook Expert:** Voltigieren (CVN) und Fahren (CAN) Abteilungs-Trennungsregeln auswerten (offene Fragen
#3, #4).
- **QA Specialist:** Integrationstests für den Nennungs-Workflow schreiben.
- **Backend Developer:** `competition-context` (Bewerbe, Startlisten, Ergebnisse) PHASE 5.
@@ -0,0 +1,107 @@
---
type: Session Log
date: 2026-03-24
agent: Backend Developer
topic: Persistenz Repository-Interfaces und erste DB-Migrationen
status: ABGESCHLOSSEN
---
# 👷 Session Log: Persistenz Repository-Interfaces & DB-Migrationen
**Datum:** 24. März 2026
**Agent:** 👷 Backend Developer
**Aufgabe:** Repository-Interfaces und erste DB-Migrationen (Flyway) für `actor-context`, `registration-context`.
---
## Erledigte Aufgaben
### 1. Repository-Interfaces (Domain-Layer)
Vier neue Repository-Interfaces als Ports (Hexagonale Architektur) in den `-domain`-Modulen:
| Interface | Modul | Aggregate Root |
|-------------------------|--------------------|------------------|
| `FunktionaerRepository` | `officials-domain` | `DomFunktionaer` |
| `VereinRepository` | `clubs-domain` | `DomVerein` |
| `ReiterRepository` | `persons-domain` | `DomReiter` |
| `NennungRepository` | `entries-domain` | `DomNennung` |
Alle Interfaces folgen dem gleichen Muster:
- `findById`, `findByXxx`, `findAll`, `findAllActive` (paginiert)
- `save` (Upsert-Semantik)
- `delete`, `countXxx`, `existsByXxx`
- `suspend fun` für Coroutine-Kompatibilität
### 2. Exposed-Tabellendefinitionen (Infrastructure-Layer)
Drei neue `Table`-Objekte (Exposed 1.0.0, `org.jetbrains.exposed.v1.core.Table` + `javaUUID`):
| Table | Modul | DB-Tabelle |
|--------------------|----------------------------|----------------|
| `FunktionaerTable` | `officials-infrastructure` | `funktionaere` |
| `VereinTable` | `clubs-infrastructure` | `vereine` |
| `ReiterTable` | `persons-infrastructure` | `reiter` |
Technische Details:
- UUID-PK via `javaUUID("id").autoGenerate()` (konsistent mit `HorseTable`)
- JSON-Spalten (`TEXT`) für `rollen`, `qualifiziert_fuer_sparten`, `lizenziert_fuer_sparten`
- `timestamp()` aus `org.jetbrains.exposed.v1.datetime` für Audit-Felder
- Partial Unique Indexes für nullable Felder (Richternummer, Satznummer, FEI-ID)
### 3. Repository-Implementierungen (Infrastructure-Layer)
Drei neue `Exposed*Repository`-Klassen:
| Implementierung | Modul |
|--------------------------------|----------------------------|
| `ExposedFunktionaerRepository` | `officials-infrastructure` |
| `ExposedVereinRepository` | `clubs-infrastructure` |
| `ExposedReiterRepository` | `persons-infrastructure` |
Technische Details:
- Imports: `org.jetbrains.exposed.v1.core.eq`, `.like`, `.and`, `.or` (Top-Level-Funktionen in Exposed 1.0.0)
- Pagination: `.limit(n).offset(m.toLong())` (Exposed 1.0.0 API)
- JSON-Serialisierung via `kotlinx.serialization.json.Json`
- `kotlin.time.Clock` (nicht `kotlinx.datetime.Clock`) für Timestamp-Kompatibilität mit Exposed
### 4. Flyway-SQL-Migrationen (Service-Layer)
Drei neue V001-Migrationen im Stil der bestehenden `masterdata-service`-Migrationen:
| Migration | Service | Tabelle |
|---------------------------------------|---------------------|----------------|
| `V001__Create_Funktionaere_Table.sql` | `officials-service` | `funktionaere` |
| `V001__Create_Vereine_Table.sql` | `clubs-service` | `vereine` |
| `V001__Create_Reiter_Table.sql` | `persons-service` | `reiter` |
Alle Migrationen enthalten:
- `CREATE TABLE IF NOT EXISTS` mit vollständiger Spaltendefinition
- Partial Unique Indexes für nullable Identifikationsfelder
- Performance-Indizes für häufige Suchabfragen
- `COMMENT ON TABLE/COLUMN` für Dokumentation
---
## Architektur-Entscheidungen
- **Hexagonale Architektur:** Repository-Interfaces im Domain-Layer, Implementierungen im Infrastructure-Layer keine
Abhängigkeit der Domain von Exposed.
- **JSON für Collections:** Rollen und Sparten als JSON-Arrays in TEXT-Spalten (kein Join-Table) pragmatisch für MVP,
erweiterbar.
- **Upsert-Semantik:** `save()` prüft Existenz via `selectAll().where { id eq ... }` und führt Insert oder Update durch.
- **Partial Unique Indexes:** Nullable Felder (Richternummer, Satznummer, FEI-ID) mit `WHERE field IS NOT NULL`
erlaubt mehrere NULL-Werte.
---
## Offene Punkte
- `entries-infrastructure`-Modul existiert noch nicht → `NennungRepository`-Implementierung ausstehend.
- Flyway-Konfiguration in `officials-service`, `clubs-service`, `persons-service` `application.yml` noch nicht
geprüft/ergänzt.
- Spring-DI-Konfiguration (Bean-Registrierung der Repository-Implementierungen) noch ausstehend.
@@ -0,0 +1,76 @@
---
type: Session Log
date: 2026-03-24
agent: Backend Developer
status: ABGESCHLOSSEN
roadmap_phase: PHASE 4 MVP-Implementierung
---
# Session Log: registration-context Domain-Modelle
👷 **[Backend Developer]** | 24. März 2026
## Ziel
Domain-Modelle `DomBewerb`, `DomAbteilung` und `DomStartliste` im `registration-context` implementieren (PHASE 4,
MASTER_ROADMAP).
---
## Ergebnis
### ✅ DomBewerb (neu)
- **Datei:** `backend/services/entries/entries-domain/src/main/kotlin/at/mocode/entries/domain/model/DomBewerb.kt`
- Aggregate Root für den Bewerbs-Workflow im `registration-context`.
- Felder: `turnierId`, `bewerbNummer`, `bezeichnung`, `sparte`, `turnierkategorie`, `pruefungsTyp`, `hoeheCm`,
`teilungsTyp`, `maxStarterProAbteilung`, `istMeisterschaft`, `istNachnennungErlaubt`.
- Domain-Methoden: `getDisplayName()`, `getPflichtTeilungsSchwellenwert()` (§ 39-konform),
`getKannTeilungsSchwellenwert()`, `validateAbteilungsSchwellenwerte()` (Warn-Logik).
### ✅ DomAbteilung (neu)
- **Datei:** `backend/services/entries/entries-domain/src/main/kotlin/at/mocode/entries/domain/model/DomAbteilung.kt`
- Kleinste startbare Einheit innerhalb eines Bewerbs.
- Felder: `bewerbId`, `abteilungsNummer`, `bezeichnung`, `teilungsTyp`, `teilnehmerkreisBeschreibung`, `starterAnzahl`,
`maxStarter`, `startzeit`.
- Domain-Methoden: `getDisplayName()`, `hatFreiePlaetze()`, `validateStarterLimit()` (Warn-Logik für > 80 Starter, § 39
Abs. 2).
### ✅ DomStartliste (neu)
- **Datei:** `backend/services/entries/entries-domain/src/main/kotlin/at/mocode/entries/domain/model/DomStartliste.kt`
- Enthält geordnete Liste von `StartlistenEintrag` (Startnummer → Nennung).
- Workflow: `NICHT_ERSTELLT → ENTWURF → VEROEFFENTLICHT → GESPERRT → ARCHIVIERT`.
- Domain-Methoden: `getStarterAnzahl()`, `getEintragByStartnummer()`, `istBearbeitbar()`, `istSichtbar()`,
`veroeffentlichen()`, `sperren()` (Warn-Logik für ungültige Status-Übergänge).
- `StartlistenEintrag`: Denormalisierte Felder `reiterName`, `pferdeName` für schnelle Anzeige; `istGestrichen`-Flag für
Abmeldungen nach Startlistenerstellung.
### ✅ Neue Enums in core-domain
- **Datei:** `core/core-domain/src/commonMain/kotlin/at/mocode/core/domain/model/Enums.kt`
- `PruefungsTypE`: STIL_SPRINGEN, SPRINGPFERDE, DRESSURPFERDE, VIELSEITIGKEIT, SPRINGEN_UEBRIG, DRESSUR, CAPRILLI,
FAHREN, VOLTIGIEREN, SONSTIGE mit Schwellenwert-Kommentaren gemäß § 39.
- `AbteilungsTeilungsTypE`: KEINE, NACH_LIZENZ, NACH_PLATZ, NACH_PFERDEALTER, STRUKTURELL, NACH_AUSSCHREIBUNG.
- `StartlistenStatusE`: NICHT_ERSTELLT, ENTWURF, VEROEFFENTLICHT, GESPERRT, ARCHIVIERT.
---
## Design-Entscheidungen
| Entscheidung | Begründung |
|----------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `DomBewerb` im `entries-domain`-Modul | Bewerbe sind direkt mit Nennungen verknüpft; `entries-domain` ist das bestehende KMP-Modul für den `registration-context`. |
| Schwellenwerte als Domain-Methoden, nicht hard-coded | Konsistent mit Implementierungs-Hinweisen in `Abteilungs-Trennungs-Schwellenwerte.md` (§ 4.2). Konfigurierbare Parameter für spätere Persistenz vorbereitet. |
| `StartlistenEintrag` als Value Object in `DomStartliste` | Einträge haben keine eigene Identität außerhalb der Startliste; Denormalisierung von `reiterName`/`pferdeName` für performante Anzeige ohne Join. |
| Warn-Logik statt harter Fehler | Konsistent mit ADR-0016 und Override-Event-Prinzip: alle `validate*()`-Methoden geben Warnungen zurück, kein Exception-Throwing. |
| `istMeisterschaft`-Flag auf `DomBewerb` | Meisterschaftsbewerbe sind von der Pflicht-Teilung ausgenommen (§ 39 Abs. 4) Flag ermöglicht direkte Prüfung in `getPflichtTeilungsSchwellenwert()`. |
---
## Nächste Schritte (PHASE 4)
- [ ] `event-management-context`: `DomVeranstaltung`, `DomTurnier`, `DomAusschreibung` implementieren.
- [ ] Persistenz: Repository-Interfaces und DB-Migrationen (Flyway/Liquibase).
- [ ] API: REST-Endpunkte für Nennungs-Workflow (Kern-Use-Cases).
@@ -0,0 +1,82 @@
---
type: Session Log
date: 2026-03-24
agent: ÖTO/FEI Rulebook Expert
status: ABGESCHLOSSEN
---
# Session Log: Warn-Logik-Spezifikation `competition-context`
🧹 **[Curator]** | 24. März 2026
---
## Aufgabe
Spezifikation der `competition-context` Warn-Logik für Abteilungs-Schwellenwerte
(MASTER_ROADMAP Phase 4, ÖTO/FEI Rulebook Expert).
## Ergebnis
### Neues Dokument erstellt
**`docs/03_Domain/02_Reference/OETO_Regelwerk/Warn-Logik-Spezifikation-competition-context.md`**
Verbindliche Implementierungs-Spezifikation für die Warn-Logik im `competition-context`.
### Inhalt der Spezifikation
**6 Warn-Codes definiert:**
| Warn-Code | Typ |
|--------------------------------------------|----------------------------------------------|
| `WARN_PFLICHT_TEILUNG_UEBERSCHRITTEN` | Starter-Schwellenwert überschritten |
| `WARN_KANN_TEILUNG_EMPFOHLEN` | Kann-Teilung empfohlen (Dressur) |
| `WARN_ABTEILUNG_ZU_GROSS` | Abteilung nach Teilung > 80 Starter |
| `WARN_ABTEILUNG_MAX_UEBERSCHRITTEN` | Konfiguriertes Starter-Limit überschritten |
| `WARN_STRUKTURELLE_TEILUNG_FEHLT` | Strukturelle Pflicht-Teilung nicht vorhanden |
| `WARN_STRUKTURELLE_TEILUNG_UNVOLLSTAENDIG` | Strukturelle Teilung unvollständig |
**3 Warn-Typen mit vollständigem Entscheidungsbaum:**
1. Starter-Schwellenwerte (`DomBewerb.validateAbteilungsSchwellenwerte`)
2. Abteilungs-Größe nach Teilung (`DomAbteilung.validateStarterLimit`)
3. Strukturelle Pflicht-Teilungen (`DomBewerb.validateStrukturellesTeilung`) NEU
**Strukturelle Prüfungen abgedeckt:**
- CSN Stil-/Idealzeitspringen ≤ 95 cm: ohne Lizenz vs. R1 (§ 200 Abs. 5.3)
- Springpferdeprüfung 95110 cm / Dressurpferdeprüfung Kl. A: Pferdealter 4 vs. 56 (§ 200 Abs. 6 / § 100 Abs. 5)
- CSN-C-NEU ≤ 95 cm: ohne Lizenz vs. mit Lizenz (§ 231)
- CSN-C-NEU ≥ 100 cm: R1 vs. R2+ (§ 231)
- CCN-C-NEU Gelände ≤ 80 cm: 3 Abteilungen (§ 300)
- CCN-C-NEU Gelände ≥ 90 cm: 2 Abteilungen (§ 300)
- CCN Welcome / 80 cm: R2+ eigene Abteilung (§ 301 Abs. 1.4)
- Caprilli: lizenzfrei vs. RD1+ (§ 803 Abs. 2)
- Fahren/Fahrertreffen: F1+ eigene Abteilung (§ 850 Abs. 9)
**Implementierungs-Vorgaben:**
- Typisiertes Value Object `AbteilungsWarnung` (statt roher Strings)
- `AbteilungsWarnungCodeE` Enum
- `AbteilungsWarnungOverrideEvent` mit Pflicht-Begründungsfeld
- `AbteilungsSchwellenwertConfig` (konfigurierbare Schwellenwerte, nicht hard-coded)
- Aufruf-Zeitpunkte (Trigger) definiert
### Aktualisierte Dokumente
- `docs/01_Architecture/MASTER_ROADMAP.md` Task als `[x]` markiert, Referenz ergänzt, Referenz-Tabelle aktualisiert
## Offene Fragen (weiterhin offen)
| # | Frage |
|---|--------------------------------------------------------------------|
| 1 | Gelten § 39-Schwellenwerte auch für Reitertreffen? |
| 2 | Pflicht-Teilung bei kombinierten Turnieren (§ 4)? |
| 3 | Voltigieren (CVN): Eigene Abteilungs-Trennungsregeln? |
| 4 | Fahren (CAN): Eigene Starter-Schwellenwerte? |
| 5 | Abbildung „Geldpreise > Doppeltes Gebührenordnung" im Datenmodell? |
---
*Session: 2026-03-24 | Agent: ÖTO/FEI Rulebook Expert + Curator (Junie)*