---
type: DOMAIN_SPEC
status: ACTIVE
owner: Lead Architect
last_update: 2026-04-02
---

# Domänen‑Modell: Veranstaltung → Turnier → Bewerb → Abteilung

Ziel: Dieses Dokument fixiert das offizielle Kern‑Modell für die Event‑Struktur sowie Kassa/Konten. Es ist die Single Source of Truth für Backend‑Schema, Frontend‑ViewModels und Schnittstellen.

Quellen/Verweise:
- Ubiquitous Language: `docs/03_Domain/01_Glossary/Ubiquitous_Language.md`
- ÖTO/FEI Referenz: `docs/03_Domain/02_Reference/` (insb. Abteilungs‑Schwellenwerte)
- ADR‑0021 Tenant‑Resolution (Event‑Isolation): `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]
  └─ Veranstaltungs‑Kassa (event_cashbox_id = event_id) [1:1]
```

Leitlinien:
- Jede Veranstaltung ist ein eigener Tenant (Schema‑per‑Tenant gemäß ADR‑0021).
- 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`)
- Aggregate‑Grenze: umfasst Metadaten der Veranstaltung, Kassa, TeilnehmerKonto‑Katalog.
- Invarianten:
  - `status ∈ {draft, active, archived}`
  - Archivierte Veranstaltungen sind read‑only.

### 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 (Multi‑Turnier).
- 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 Veranstaltungs‑Kassa (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. Abteilungs‑Typen

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/Platz‑Gründen), ohne eigenständige sportliche Wertung/Preisgeldlogik.

Hinweis: Die genaue Ausgestaltung von `SEPARATE_SIEGEREHRUNG` (Preisgeld‑Aggregation, Ranking‑Anzeige, Protokoll) wird im Rulebook‑Dokument ergänzt und kann weitere Felder/Beziehungen erfordern (z. B. Verweis auf „gemeinsame Siegerehrung für Bewerbe X/Y“).

## 4. Datenmodell‑Skizze (relationale Sicht je Tenant)

```sql
-- Veranstaltung (im Tenant‑Schema)
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 Teilnehmer‑Stammdatensatz 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)
);

-- Veranstaltungs‑Kassa
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)
- Abteilungs‑Typ `ORGANISATORISCH` darf keine eigenständige Preisgeldlogik auslösen.
- `SEPARATE_SIEGEREHRUNG` kann Ergebnisse bündeln/verschieben; Detailregeln werden im Rulebook spezifiziert. Bis dahin bleiben API‑Felder stabil, Verhalten konservativ (keine automatische Zusammenlegung ohne explizite Verknüpfung).
- TeilnehmerKonto‑Saldo = Summe aller bestätigten `participant_ledger_entry.amount_cents`.
- Event‑Kassa‑Bestand = Summe `IN` − Summe `OUT`; regelmäßige Abstimmung mit Summe aller Teilnehmer‑Offenen Posten.

## 6. API/DTO Richtlinien (High‑Level)
- Alle API‑Ressourcen 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 Read‑Views.
- Enum `DivisionType` wird exakt wie oben benannt; neue Typen erfordern Versionserhöhung des Schemas.

## 7. ToDos und Folgearbeiten
- 📜 Rulebook Expert: Detail‑Spezifikation `SEPARATE_SIEGEREHRUNG` (Preisgeld, Ranking, UI‑Hinweise) ergänzen.
- 🧹 Curator: `Ubiquitous_Language.md` um obige Begriffe/Definitionen erweitern.
- 👷 Backend: Schema‑Migrationen pro Tenant gemäß obiger Tabellen; Repositories/Services entsprechend zuschneiden.
- 🎨 Frontend: ViewModels/Stores entlang dieser Struktur aktualisieren (Navigation: Veranstaltung → Turnier → Bewerb → Abteilung).
