Finalize domain model: define event → tournament → class → division hierarchy, multi-tournament account structures, and event-wide cashbox aggregation logic. Add division types and update glossary tasks.

This commit is contained in:
2026-04-02 17:53:46 +02:00
parent 898d249d41
commit bbe5b1a357
5 changed files with 294 additions and 9 deletions
@@ -0,0 +1,179 @@
---
type: DOMAIN_SPEC
status: ACTIVE
owner: Lead Architect
last_update: 2026-04-02
---
# DomänenModell: Veranstaltung → Turnier → Bewerb → Abteilung
Ziel: Dieses Dokument fixiert das offizielle KernModell für die EventStruktur sowie Kassa/Konten. Es ist die Single Source of Truth für BackendSchema, FrontendViewModels und Schnittstellen.
Quellen/Verweise:
- Ubiquitous Language: `docs/03_Domain/01_Glossary/Ubiquitous_Language.md`
- ÖTO/FEI Referenz: `docs/03_Domain/02_Reference/` (insb. AbteilungsSchwellenwerte)
- ADR0021 TenantResolution (EventIsolation): `docs/01_Architecture/adr/0021-tenant-resolution-strategy-de.md`
## 1. Struktur und Kardinalitäten
Hierarchie und Identifikatoren (kanonisch):
```
Veranstaltung (event_id)
├─ Turnier (tournament_id) [1:N pro Veranstaltung]
│ └─ Bewerb (class_id) [1:N pro Turnier]
│ └─ Abteilung (division_id) [1:N pro Bewerb]
└─ TeilnehmerKonto (account_id) [1:N pro Veranstaltung, referenziert Teilnehmer]
└─ VeranstaltungsKassa (event_cashbox_id = event_id) [1:1]
```
Leitlinien:
- Jede Veranstaltung ist ein eigener Tenant (SchemaperTenant gemäß ADR0021).
- IDs sind innerhalb des Tenants eindeutig; globale Adressen entstehen durch `{event_id}/{local_id}`.
## 2. Entitäten und Aggregate
### 2.1 Veranstaltung
- Schlüssel: `event_id` (Slug, z. B. `2026-moc-open`)
- AggregateGrenze: umfasst Metadaten der Veranstaltung, Kassa, TeilnehmerKontoKatalog.
- Invarianten:
- `status ∈ {draft, active, archived}`
- Archivierte Veranstaltungen sind readonly.
### 2.2 Turnier
- Schlüssel: `tournament_id` (innerhalb Veranstaltung eindeutig)
- Attribute (Auszug): Titel, Datum(e), Ort, Status.
- Invarianten:
- Ein Turnier gehört genau zu einer Veranstaltung.
- Löschen nur erlaubt, wenn keine Nennungen/Ergebnisse bestätigt sind.
### 2.3 Bewerb
- Schlüssel: `class_id`
- Attribute: Disziplin, Klasse, Lizenzanforderungen, max Starter, Wertungsmodus.
- Invarianten:
- Ein Bewerb gehört genau zu einem Turnier.
- Abteilungsbildung erfolgt gemäß Regelwerk/Schwellenwerten.
### 2.4 Abteilung
- Schlüssel: `division_id`
- Attribute: Lauf/Startzeit, Parcours/Bahn, Typ (siehe unten), Ergebnisstatus.
- Invarianten:
- Eine Abteilung gehört genau zu einem Bewerb.
- Typen steuern UI, Zeitplan und Preisgeld-/Siegerehrungslogik.
### 2.5 TeilnehmerKonto (auf Veranstaltungsebene)
- Zweck: Vereinheitlichte finanzielle Sicht je Teilnehmer über mehrere Turniere derselben Veranstaltung (MultiTurnier).
- Schlüssel: `account_id`
- Beziehungen:
- `Teilnehmer` (z. B. Reiter, Verein, Team) 1:1 ↔ TeilnehmerKonto (pro Veranstaltung)
- Buchungen entstehen aus Nennungen, Startgeldern, Gebühren, Gutschriften, Rückzahlungen turnierübergreifend.
- Invarianten:
- Ein Teilnehmer hat höchstens ein Konto pro Veranstaltung.
- Saldo ist Summe aller bestätigten Buchungen innerhalb des Tenants.
### 2.6 VeranstaltungsKassa (Turnier‑übergreifender Saldo)
- Zweck: Aggregierte Kasse der gesamten Veranstaltung; spiegelt Einzahlungen/Auszahlungen und Summen über alle Turniere.
- Schlüssel: `event_cashbox_id` = `event_id`
- Komponenten:
- Journal (Belege): Ein/Auszahlungen, Umbuchungen, Korrekturen.
- Summen: aktueller Bestand, Reserven, offene Posten (aggregiert aus TeilnehmerKonten).
- Invarianten:
- Jede Buchung betrifft genau ein Gegenkonto (TeilnehmerKonto oder internes Konto).
- Journal ist unveränderlich; Korrekturen erfolgen als Gegenbuchung.
## 3. AbteilungsTypen
Definiert als `enum DivisionType`:
- `STANDARD`: Normale Abteilung mit regulärer Siegerehrung innerhalb des Bewerbs.
- `SEPARATE_SIEGEREHRUNG`: Abteilung, deren Siegerehrung separat organisiert wird (z. B. zusammengelegt/zeitlich entkoppelt) — STATUS: vorläufig, Detailregeln folgen durch 📜 Rulebook Expert.
- `ORGANISATORISCH`: Rein organisatorische Abteilung (z. B. Aufteilung aus Zeit/PlatzGründen), ohne eigenständige sportliche Wertung/Preisgeldlogik.
Hinweis: Die genaue Ausgestaltung von `SEPARATE_SIEGEREHRUNG` (PreisgeldAggregation, RankingAnzeige, Protokoll) wird im RulebookDokument ergänzt und kann weitere Felder/Beziehungen erfordern (z. B. Verweis auf „gemeinsame Siegerehrung für Bewerbe X/Y“).
## 4. DatenmodellSkizze (relationale Sicht je Tenant)
```sql
-- Veranstaltung (im TenantSchema)
CREATE TABLE event (
event_id TEXT PRIMARY KEY,
title TEXT NOT NULL,
status TEXT NOT NULL CHECK (status IN ('draft','active','archived'))
);
CREATE TABLE tournament (
tournament_id TEXT PRIMARY KEY,
event_id TEXT NOT NULL REFERENCES event(event_id),
title TEXT NOT NULL,
start_date DATE,
end_date DATE,
status TEXT NOT NULL
);
CREATE TABLE class (
class_id TEXT PRIMARY KEY,
tournament_id TEXT NOT NULL REFERENCES tournament(tournament_id),
discipline TEXT NOT NULL,
level TEXT NOT NULL,
max_starters INT,
scoring_mode TEXT NOT NULL
);
CREATE TABLE division (
division_id TEXT PRIMARY KEY,
class_id TEXT NOT NULL REFERENCES class(class_id),
type TEXT NOT NULL CHECK (type IN ('STANDARD','SEPARATE_SIEGEREHRUNG','ORGANISATORISCH')),
scheduled_at TIMESTAMP,
status TEXT NOT NULL
);
-- TeilnehmerKonto (veranstaltungsweit)
CREATE TABLE participant_account (
account_id TEXT PRIMARY KEY,
event_id TEXT NOT NULL REFERENCES event(event_id),
participant_ref TEXT NOT NULL, -- verweist auf TeilnehmerStammdatensatz im Tenant
UNIQUE(event_id, participant_ref)
);
CREATE TABLE participant_ledger_entry (
entry_id TEXT PRIMARY KEY,
account_id TEXT NOT NULL REFERENCES participant_account(account_id),
booking_ts TIMESTAMP NOT NULL,
amount_cents BIGINT NOT NULL,
currency TEXT NOT NULL DEFAULT 'EUR',
source TEXT NOT NULL, -- z. B. Nennung, Startgeld, Rückzahlung
tournament_id TEXT NULL REFERENCES tournament(tournament_id)
);
-- VeranstaltungsKassa
CREATE TABLE event_cashbox (
event_cashbox_id TEXT PRIMARY KEY REFERENCES event(event_id),
created_at TIMESTAMP NOT NULL
);
CREATE TABLE cashbox_journal (
journal_id TEXT PRIMARY KEY,
event_cashbox_id TEXT NOT NULL REFERENCES event_cashbox(event_cashbox_id),
booking_ts TIMESTAMP NOT NULL,
amount_cents BIGINT NOT NULL,
direction TEXT NOT NULL CHECK (direction IN ('IN','OUT')),
counterparty TEXT NOT NULL, -- account_id oder internes Konto
memo TEXT
);
```
## 5. Invarianten und Geschäftsregeln (Auszug)
- AbteilungsTyp `ORGANISATORISCH` darf keine eigenständige Preisgeldlogik auslösen.
- `SEPARATE_SIEGEREHRUNG` kann Ergebnisse bündeln/verschieben; Detailregeln werden im Rulebook spezifiziert. Bis dahin bleiben APIFelder stabil, Verhalten konservativ (keine automatische Zusammenlegung ohne explizite Verknüpfung).
- TeilnehmerKontoSaldo = Summe aller bestätigten `participant_ledger_entry.amount_cents`.
- EventKassaBestand = Summe `IN` Summe `OUT`; regelmäßige Abstimmung mit Summe aller TeilnehmerOffenen Posten.
## 6. API/DTO Richtlinien (HighLevel)
- Alle APIRessourcen werden unterhalb des Tenants adressiert (Header `X-Event-Id`).
- DTOs tragen stabile `*_id` Felder entsprechend diesem Modell; Referenzen sind per ID, keine eingebetteten Aggregate außer ReadViews.
- Enum `DivisionType` wird exakt wie oben benannt; neue Typen erfordern Versionserhöhung des Schemas.
## 7. ToDos und Folgearbeiten
- 📜 Rulebook Expert: DetailSpezifikation `SEPARATE_SIEGEREHRUNG` (Preisgeld, Ranking, UIHinweise) ergänzen.
- 🧹 Curator: `Ubiquitous_Language.md` um obige Begriffe/Definitionen erweitern.
- 👷 Backend: SchemaMigrationen pro Tenant gemäß obiger Tabellen; Repositories/Services entsprechend zuschneiden.
- 🎨 Frontend: ViewModels/Stores entlang dieser Struktur aktualisieren (Navigation: Veranstaltung → Turnier → Bewerb → Abteilung).