(vision) SCS/DDD

This commit is contained in:
2025-07-01 23:53:29 +02:00
parent 6e50b9d32d
commit f4b11b220d
14 changed files with 1381 additions and 0 deletions
@@ -0,0 +1,56 @@
@startuml
title "Context Map: ÖTO Meldestellen-System"
!theme vibrant
' Definition der Bounded Contexts
package "Personen & Vereine" as PersonenContext {
[Personenstamm]
[Vereinsstamm]
}
package "Lizenzen & Qualifikationen" as LizenzContext {
[Lizenznehmer]
[Qualifikationen]
}
package "Veranstaltungsplanung" as VeranstaltungsContext {
[VeranstaltungsRahmen]
[Turnier]
[Prüfung (Bewerb)]
}
package "Nennungsabwicklung" as NennungsContext {
[Nennung]
[Startliste]
}
package "Ergebnisdienst" as ErgebnisContext {
[Ergebnis]
[Rangliste]
}
package "ZNS-Import (ACL)" as ZNS_ACL {
[ZNS Datentransfer]
}
' Beziehungen (Upstream/Downstream) und Kommunikationsmuster
' Der Pfeil zeigt in Richtung des Downstream-Partners (Konsument)
' ZNS ist der Upstream-Partner für Personen- und Vereinsdaten
ZNS_ACL ..> PersonenContext : Upstream/Downstream (Anti-Corruption Layer)
' Personen- und Veranstaltungsdaten sind Upstream für Nennungen
PersonenContext ..> NennungsContext : "Reiter-, Pferdebesitzerdaten" (Consumer)
VeranstaltungsContext ..> NennungsContext : "Verfügbare Prüfungen" (Consumer)
LizenzContext ..> NennungsContext : "Lizenz- & Qualifikationsstatus" (Consumer)
' Nennungen sind Upstream für Ergebnisse
NennungsContext ..> ErgebnisContext : "Angenommene Starter" (Consumer)
' Ergebnisdaten können wieder andere Kontexte beeinflussen (z.B. durch Events)
ErgebnisContext ..> LizenzContext : Event: "Erfolg für Lizenz-Upgrade erzielt"
ErgebnisContext ..> VeranstaltungsContext : Event: "Ergebnis für Siegerehrung verfügbar"
@enduml
@@ -0,0 +1,163 @@
@startuml
title "Detailliertes Datenmodell: Ergebnis_Context"
!theme vibrant
' Externe Referenzen werden der Übersichtlichkeit halber als vereinfachte Entitäten dargestellt.
package "Externe Referenzen (Andere Kontexte)" {
class Nennungs_Context_API
class Veranstaltungs_Context_API
class Personen_Context_API
}
package "Ergebnisdienst" as ErgebnisContext {
' #################### Aggregate Root: Bewerbsergebnis ####################
' Bündelt alle Ergebnisse einer Prüfungsabteilung zu einer konsistenten Einheit.
class Bewerbsergebnis <<(A,red) Aggregate Root>> {
+ bewerbsergebnisId : UUID <<PK>>
--
' Referenz zur Prüfung, für die dieses Ergebnis gilt.
+ pruefung : PruefungsReferenzVO
' Liste der eingesetzten Richter/Funktionäre gem. C-Satz
+ eingesetzteOffizielle : List<OffiziellerReferenzVO>
' Der aktuelle Zustand des Gesamtergebnisses.
+ status : ErgebnisStatusVO
}
' #################### Entitäten und VOs innerhalb des Aggregates ####################
' Repräsentiert die Teilnahme und das Ergebnis eines einzelnen Starters.
entity Einzelergebnis {
+ einzelergebnisId : UUID <<PK>>
--
' Referenz zur Nennung, zu der dieses Ergebnis gehört.
+ nennung : NennungsReferenzVO
' Die berechnete finale Platzierung.
' D-Satz, Stelle 2-4
+ platz : integer
' Status des einzelnen Starters.
' Abgeleitet aus PLATZ und AUSSCHLUSS (D-Satz)
+ teilnahmeStatus: TeilnahmeStatusVO
' D-Satz, Stelle 145 ('*')
+ istPlatziert : boolean
' D-Satz, Stelle 136-141
+ geldpreis : GeldbetragVO
' D-Satz, Stelle 176-183
+ link_id_ergebnis : VARCHAR(8)
' Die konkrete, spartenspezifische Leistung.
+ leistung : LeistungVO
}
' Polymorphes Value Object für die eigentliche Leistung
abstract class LeistungVO <<VO>> {
}
' Konkrete Ausprägungen der Leistung je nach Sparte
LeistungVO <|-- DressurLeistungVO
LeistungVO <|-- SpringenLeistungVO
LeistungVO <|-- VielseitigkeitLeistungVO
LeistungVO <|-- ReitervierkampfLeistungVO
class DressurLeistungVO {
' D-Satz, Stelle 121-126
+ wertnote : decimal
' D-Satz, Stelle 127-131
+ prozent : decimal
}
class SpringenLeistungVO {
' D-Satz, Stelle 121-126
+ fehlerpunkte : decimal
' D-Satz, Stelle 127-131
+ zeit : decimal
' D-Satz, Stelle 132-135
+ stechen_info : string
}
class VielseitigkeitLeistungVO {
+ minuspunkte_dressur : decimal
+ minuspunkte_gelaende_hindernis : decimal
+ minuspunkte_gelaende_zeit : decimal
+ minuspunkte_springen : decimal
+ gesamt_minuspunkte : decimal
}
class ReitervierkampfLeistungVO {
+ punkte_dressur : INTEGER
+ punkte_springen : INTEGER
+ punkte_laufen : INTEGER
+ punkte_schwimmen : INTEGER
+ gesamt_punkte : INTEGER
}
' #################### Value Objects für Referenzen und Beschreibungen ####################
class PruefungsReferenzVO <<VO>> {
' Referenz zur originalen Abteilung
+ pruefungAbteilungDbId : UUID
' Relevante Daten zum Zeitpunkt der Ergebniserfassung
+ bewerbBezeichnung : string
+ abteilungBezeichnung: string
}
class NennungsReferenzVO <<VO>> {
' Referenz zur originalen Nennung
+ nennungDbId : UUID
' Redundante Daten für die Ergebnisliste, wie im D-Satz spezifiziert
+ reiterName : string
+ pferdName : string
+ kopfnummer : string
+ nationCode : string ' D-Satz, Stelle 142-144
}
class OffiziellerReferenzVO <<VO>> {
' Referenz zur Person
+ oepsSatzNrPerson: VARCHAR(6)
' Rolle gemäß C-Satz
+ rolle: string ' z.B. "Richter-1", "Parcoursbau"
}
class GeldbetragVO <<VO>> {
+ wert : decimal
+ waehrung : string
}
enum ErgebnisStatusVO {
IN_ERFASSUNG
VORLAEUFIG
FINAL
KORRIGIERT
}
enum TeilnahmeStatusVO {
GESTARTET_GEWERTET
AUSGESCHIEDEN ' Code "A" aus D-Satz, Stelle 120
DISQUALIFIZIERT ' Code "D" aus D-Satz, Stelle 120
TEILNAHMEVERZICHT ' Code "T" aus D-Satz, Stelle 120
}
' #################### Beziehungen ####################
' Ein Bewerbsergebnis besteht aus vielen Einzelergebnissen (Komposition)
Bewerbsergebnis "1" *-- "1..*" Einzelergebnis : "enthält"
' Jedes Einzelergebnis hat genau eine spezifische Leistung (Komposition)
Einzelergebnis "1" *-- "1" LeistungVO : "hat Leistung"
}
' Beziehungen zu externen Kontexten (dargestellt als API-Aufrufe oder Events)
ErgebnisContext.Bewerbsergebnis ..> Nennungs_Context_API : "holt Starterliste"
ErgebnisContext.Bewerbsergebnis ..> Veranstaltungs_Context_API : "holt Prüfungsdetails"
ErgebnisContext.Bewerbsergebnis ..> Personen_Context_API : "holt Details zu Offiziellen"
note right of Bewerbsergebnis
**Aggregate Root: Bewerbsergebnis**
* **Verantwortung:** Dieses Aggregat garantiert die Konsistenz der gesamten Rangliste einer Abteilung.
* **Logik:** Eine Methode `berechneRangliste()` würde alle zugehörigen `Einzelergebnis`-Objekte anhand der Regeln der jeweiligen Sparte sortieren und die `platz`-Attribute neu vergeben.
* **Datenherkunft:** Die Liste der `eingesetzteOffizielle` wird aus dem C-Satz der Ergebnisdatei befüllt.
end note
note bottom of LeistungVO
**Polymorphe Leistung**
Das abstrakte `LeistungVO` ermöglicht eine saubere Modellierung der unterschiedlichen Ergebnisstrukturen. Je nach Disziplin der Prüfung (Information aus `PruefungsReferenzVO`) wird ein `Einzelergebnis` mit einem der konkreten Leistungs-Objekte (`DressurLeistungVO`, `SpringenLeistungVO` etc.) instanziiert. Die Daten dafür stammen primär aus den Feldern `PUNKTE/WERTNOTE`, `ZEIT/PROZENT` und `STECHEN` des D-Satzes.
end note
@enduml
@@ -0,0 +1,93 @@
@startuml
title "Datenmodell: Lizenzen_und_Qualifikationen_Context"
!theme vibrant
' Der Bounded Context wird als Paket dargestellt.
package "Lizenzen & Qualifikationen" as LizenzContext {
' Das Aggregate Root: Der Lizenznehmer ist die zentrale Entität.
class Lizenznehmer <<(A,violet) Aggregate Root>> {
' Referenz zum Personen-Context, KEINE vollständige Kopie der Person.
+ oepsSatzNrPerson : VARCHAR(6)
--
' Minimal notwendige Daten zur Identifikation im Fachkontext.
name : string
vorname : string
--
' Geschäftslogik wird hier gekapselt.
+ hatLizenz(lizenzTypCode) : boolean
+ hatQualifikation(qualTypCode) : boolean
+ fuegeLizenzHinzu(lizenzDetails)
+ fuegeQualifikationHinzu(qualDetails)
}
' Eine Entität innerhalb des Lizenznehmer-Aggregates.
entity Lizenz {
+ lizenzId : UUID
--
' Bezieht sich auf den Typ der Lizenz.
# lizenzTypCode : VARCHAR(4) <<FK>>
' Daten aus LIZENZ01.dat.
+ gueltigBis : Date
+ ausgestelltAm : Date
+ bezahltImJahr : INTEGER
}
' Eine weitere Entität innerhalb des Lizenznehmer-Aggregates.
entity Qualifikation {
+ qualifikationId : UUID
--
' Bezieht sich auf den Typ der Qualifikation.
# qualifikationsTypCode : VARCHAR <<FK>>
' Daten aus RICHT01.dat (indirekt).
+ erworbenAm : Date
+ gueltigBis : Date
}
' Die Definitionen der Lizenz- und Qualifikationstypen
' kommen aus einem anderen Bounded Context (OeTO-Verwaltung).
' Hier werden sie als Referenz oder Value Object verwendet.
' Wir stellen sie hier vereinfacht dar, um die Beziehung zu zeigen.
class LizenzTyp_Referenz <<VO>> {
+ code : string
+ bezeichnung : string
+ sparte : string
}
class QualifikationsTyp_Referenz <<VO>> {
+ code : string
+ bezeichnung : string
+ sparte : string
}
' -- Beziehungen --
' Der Lizenznehmer besitzt seine Lizenzen und Qualifikationen (Komposition).
Lizenznehmer "1" *-- "0..*" Lizenz
Lizenznehmer "1" *-- "0..*" Qualifikation
' Jede Lizenz und Qualifikation ist von einem bestimmten Typ.
' Dies ist eine Assoziation, da die Typen außerhalb des Aggregates existieren.
Lizenz ..> LizenzTyp_Referenz : "ist vom Typ"
Qualifikation ..> QualifikationsTyp_Referenz : "ist vom Typ"
}
note right of Lizenznehmer
**Aggregate Root: Lizenznehmer**
* **Verantwortung:** Stellt sicher, dass ein Lizenznehmer nur gültige und konsistente Lizenzen/Qualifikationen besitzen kann.
* **Referenz:** Die `oepsSatzNrPerson` ist der Schlüssel zur Verknüpfung mit dem `Personen_und_Vereine_Context`.
* **Isolation:** Dieser Context speichert bewusst *keine* Adress- oder Kontaktdaten. Wenn diese benötigt werden, müssen sie vom `Personen_und_Vereine_Context` angefragt werden.
end note
note top of LizenzTyp_Referenz
**Referenzen zu anderen Contexts**
`LizenzTyp_Referenz` und `QualifikationsTyp_Referenz`
sind keine Entitäten, die *hier* verwaltet werden.
Sie repräsentieren die Daten, die aus dem
`Service_OeTO_Verwaltung` stammen und hier
zur Beschreibung von Lizenzen und Qualifikationen
genutzt werden.
end note
@enduml
@@ -0,0 +1,66 @@
@startuml
title "Datenmodell: Lizenzen_und_Qualifikationen_Context"
!theme vibrant
package "Lizenzen & Qualifikationen" {
' Das Aggregate Root: Der Lizenznehmer ist die zentrale Entität,
' die Konsistenz für ihre Lizenzen und Qualifikationen sicherstellt.
class Lizenznehmer <<(A,violet) Aggregate Root>> {
' Referenz zum Personen-Context, keine vollständige Person
+ oepsSatzNrPerson : VARCHAR(6)
--
name : string
vorname : string
' Methode zur Überprüfung der Startberechtigung
+ hatStartberechtigungFuer(anforderungen) : boolean
}
' Entität innerhalb des Aggregates
class Lizenz {
+ gueltigBis : Date
+ ausgestelltAm : Date
+ bezahltImJahr : INTEGER
}
' Entität innerhalb des Aggregates
class Qualifikation {
+ erworbenAm : Date
+ bemerkung : string
}
' Value Object: Beschreibt einen Lizenztyp, hat keine eigene Identität
class LizenzTyp <<VO>> {
+ code : string
+ bezeichnung : string
+ sparte: string
}
' Value Object: Beschreibt einen Qualifikationstyp
class QualifikationsTyp <<VO>> {
+ code : string
+ bezeichnung : string
+ sparte : string
}
' Beziehungen innerhalb des Aggregates
Lizenznehmer "1" *-- "0..*" Lizenz : "besitzt"
Lizenznehmer "1" *-- "0..*" Qualifikation : "hat"
' Beziehungen zu Value Objects
Lizenz "1" -- "1" LizenzTyp
Qualifikation "1" -- "1" QualifikationsTyp
}
note right of Lizenznehmer
**Aggregate Root: Lizenznehmer**
Alle Änderungen an Lizenzen oder
Qualifikationen einer Person
sollten über das Lizenznehmer-Objekt
laufen, um die Geschäftsregeln
(z.B. "Darf diese Lizenz haben?")
zu wahren.
end note
@enduml
@@ -0,0 +1,59 @@
@startuml
title "Prozess & Event-Flow: Nennung wird abgegeben"
!theme vibrant
actor Reiter
' Die Teilnehmer sind unsere Bounded Contexts (Services) und ein Message Bus für Events.
participant "API Gateway / Frontend" as Gateway
participant "Nennungs_Context" as Nenn
participant "Veranstaltungs_Context" as Veranst
participant "Lizenzen_und_Qualifikationen_Context" as Lizenz
queue "Message Bus" as Bus
participant "Benachrichtigungs_Service" as Notify
Reiter -> Gateway : POST /nennungen\n(reiterId, pferdId, pruefungAbteilungId)
' 1. Command wird an den zuständigen Context gesendet
Gateway -> Nenn : **Command:** NennungAbgeben(daten)
activate Nenn
note right of Nenn: Empfängt den Befehl,\neine neue Nennung zu erstellen.
' 2. Synchrone Queries zur Validierung
Nenn -> Veranst : **Query:** getPruefungsAnforderungen(pruefungAbteilungId)
activate Veranst
Veranst --> Nenn : anforderungen {erf. Lizenzen, erf. Alter, ...}
deactivate Veranst
note left of Nenn: Holt die aktuellen Anforderungen\nfür die genannte Prüfung.
Nenn -> Lizenz : **Query:** hatStartberechtigung(reiterId, anforderungen)
activate Lizenz
Lizenz --> Nenn : {istBerechtigt: true}
deactivate Lizenz
note left of Nenn: Prüft die Startberechtigung des Reiters\ngegen die Anforderungen.
' 3. Interne Verarbeitung und Zustandsänderung
alt Startberechtigung erteilt
Nenn -> Nenn : Nennungs-Aggregat erstellen\n(Status: EINGEGANGEN)
note right of Nenn: Die Nennung wird intern gespeichert.\nDie Transaktion ist hier abgeschlossen.
' 4. Asynchrones Event wird veröffentlicht
Nenn ->> Bus : **Event:** NennungWurdeEingereicht {nennungId, reiterId, ...}
note left of Bus: Das Event wird auf den Bus gelegt.\nDer Nennungs-Context ist nun fertig\nund muss nicht auf die Verarbeitung\ndes Events warten.
Gateway --> Reiter : HTTP 202 Accepted (Nennung wird verarbeitet)
' 5. Andere Services reagieren auf das Event
Bus ->> Notify : **Event:** NennungWurdeEingereicht
activate Notify
Notify -> Notify : Sende Bestätigungs-E-Mail an Reiter
deactivate Notify
else Startberechtigung nicht erteilt
Nenn -> Gateway : Fehler: Startberechtigung fehlt (z.B. HTTP 400)
Gateway --> Reiter : Fehlermeldung
end
deactivate Nenn
@enduml
@@ -0,0 +1,132 @@
@startuml
title "Detailliertes Datenmodell: Nennungs_Context"
!theme vibrant
' Externe Referenzen werden der Übersichtlichkeit halber als vereinfachte Entitäten dargestellt.
package "Externe Referenzen (Andere Kontexte)" {
class Veranstaltungs_Context_API
class Personen_Context_API
class Lizenzen_Context_API
}
package "Nennungsabwicklung" as NennungsContext {
' #################### Aggregate Root: Nennung ####################
class Nennung <<(A,blue) Aggregate Root>> {
+ nennungId : UUID <<PK>>
--
' -- Snapshots von Daten aus anderen Kontexten --
' Daten zur Prüfung/Abteilung zum Zeitpunkt der Nennung
+ pruefung : PruefungsReferenzVO
' Daten zum Reiter zum Zeitpunkt der Nennung
+ reiter : ReiterReferenzVO
' Daten zum Pferd zum Zeitpunkt der Nennung
+ pferd : PferdeReferenzVO
' Optionaler Ersatzreiter gem. KKARTEI-Satz
+ ersatzreiter : ReiterReferenzVO
--
' -- Nennungsspezifische Attribute --
+ nennungsZeitpunkt : timestamp
+ status : NennungsStatusVO
' Kopfnummer gem. KKARTEI-Satz
+ zugewieseneKopfnummer : VARCHAR(4)
' Nenn- und Startgeld
+ nenngebuehr : GeldbetragVO
' Zahlungsstatus der Nenngebühr
+ bezahlStatus : BezahlStatusVO
' Betrag, der lt. Nennliste eingezahlt wurde
' KKARTEI, Stelle 161-165
+ bezahltBetragKontrolle : GeldbetragVO
' Betrag, der mit dem Veranstalter verrechnet wird
' KKARTEI, Stelle 118-122
+ accontoBetrag : GeldbetragVO
' Box bestellt? Gem. KKARTEI-Satz
+ istStallReserviert : boolean
' Grund, falls die Nennung abgelehnt wurde
+ ablehnungsGrund : string
}
' #################### Value Objects (VOs) für Snapshots und Beschreibungen ####################
' Snapshot der wichtigsten Prüfungsdaten aus dem Veranstaltungs_Context.
class PruefungsReferenzVO <<VO>> {
' Referenz zur originalen Abteilung
+ pruefungAbteilungDbId : UUID
' Relevante Daten zum Zeitpunkt der Nennung
+ turnierName : string
+ bewerbBezeichnung : string
+ abteilungBezeichnung : string
' Anforderungsprofil, das zum Zeitpunkt der Nennung galt
+ anforderungsProfil : AnforderungsProfilVO
}
' Snapshot der wichtigsten Reiterdaten aus dem Personen_Context.
class ReiterReferenzVO <<VO>> {
' Referenz zur originalen Person
+ oepsSatzNrPerson : VARCHAR(6)
' Relevante Daten zum Zeitpunkt der Nennung
+ name : string
+ vereinsName : string
' Snapshot der Lizenzen zur Validierung
+ lizenzSnapshot : List<string>
}
' Snapshot der wichtigsten Pferdedaten aus dem Personen_Context.
class PferdeReferenzVO <<VO>> {
' Referenz zum originalen Pferd
+ oepsSatzNrPferd : VARCHAR(10)
' Relevante Daten zum Zeitpunkt der Nennung
+ name : string
}
' Kapselt die Anforderungen, die zum Zeitpunkt der Nennung gültig waren.
class AnforderungsProfilVO <<VO>> {
+ erlaubteAltersklassen : List<string>
+ erforderlicheLizenzen : List<string>
}
class GeldbetragVO <<VO>> {
+ wert : decimal
+ waehrung : string
}
' Enum für den Lebenszyklus einer Nennung.
enum NennungsStatusVO {
EINGEGANGEN
IN_PRUEFUNG
STARTBERECHTIGT_BESTAETIGT
ABGELEHNT
ZURUECKGEZOGEN
}
' Enum für den Zahlungsstatus.
enum BezahlStatusVO {
OFFEN
BEZAHLT
}
' #################### Beziehungen ####################
' Die Nennung ist das einzige Aggregat und enthält ihre beschreibenden VOs (Komposition).
Nennung "1" o-- "1" PruefungsReferenzVO
Nennung "1" o-- "1" ReiterReferenzVO
Nennung "1" o-- "1" PferdeReferenzVO
Nennung "1" o-- "0..1" ReiterReferenzVO : "Ersatzreiter"
Nennung "1" o-- "1" GeldbetragVO : "Nenngebühr"
Nennung "1" -- "1" NennungsStatusVO
Nennung "1" -- "1" BezahlStatusVO
}
' Beziehungen zu externen Kontexten (dargestellt als API-Aufrufe)
Nennung ..> Veranstaltungs_Context_API : "holt Prüfungsdetails"
Nennung ..> Personen_Context_API : "holt Reiter-/Pferdedetails"
Nennung ..> Lizenzen_Context_API : "prüft Startberechtigung"
note right of Nennung
**Aggregate Root: Nennung**
* **Verantwortung:** Eine Nennung ist eine unteilbare, transaktionale Einheit. Sie repräsentiert den "Vertrag" zwischen Reiter und Veranstalter für die Teilnahme an einer Prüfung.
* **Datenherkunft:** Viele Attribute sind direkt auf den `KKARTEI`-Satz im OEPS Pflichtenheft zurückzuführen, z.B. `ERSATZREITER` , `ACCONTO` , `STALL` und `BEZAHLT`.
* **Validierungslogik:** Bei der Erstellung oder Prüfung einer Nennung ruft das Aggregat andere Kontexte auf, um die aktuellen Daten zu verifizieren (z.B. "Ist die Lizenz des Reiters noch gültig?"). Die Entscheidung ("Akzeptiert" / "Abgelehnt") wird aber hier im `Nennungs_Context` getroffen und gespeichert.
end note
@enduml
@@ -0,0 +1,98 @@
@startuml
title "Datenmodell: Personen_und_Vereine_Context"
!theme vibrant
package "Personen & Vereine" as PersonenContext {
' Eine Person ist ein Aggregate Root, das seine persönlichen Details,
' Adressen, Kontakte und Mitgliedschaften bündelt.
class Person <<(A,green) Aggregate Root>> {
' Primärschlüssel aus LIZENZ01.DAT
+ oepsSatzNrPerson : VARCHAR(6)
--
+ name : PersonenNameVO
+ geburtsdatum : Date
+ geschlecht : GeschlechtVO
+ nationalitaet : NationalitaetVO [cite: 6, 149, 181]
+ feiId : VARCHAR(10)
+ status : PersonenStatusVO ' z.B. Aktiv, Gesperrt
--
' Methoden des Aggregates
+ aendereAdresse(neueAdresse)
+ fuegeMitgliedschaftHinzu(verein, mitgliedsNr)
+ setzeStatus(neuerStatus)
}
' Ein Verein ist ebenfalls ein Aggregate Root.
class Verein <<(A,green) Aggregate Root>> {
' Primärschlüssel aus VEREIN01.DAT
+ oepsVereinsNr : VARCHAR(4)
--
+ name : VARCHAR(50)
+ bundesland : BundeslandVO
' Weitere Vereinsdetails wie Adresse, Kontakt...
}
' Eine Entität innerhalb des Person-Aggregates. Sie hat eine eigene Identität,
' wird aber immer über die Person verwaltet.
entity Mitgliedschaft {
+ mitgliedschaftId : UUID
--
' Referenz zum Verein-Aggregat
# oepsVereinsNr : VARCHAR(4) <<FK>>
' MITGLIEDSNUMMER aus LIZENZ01.DAT
+ mitgliedsNrImVerein : VARCHAR(8)
+ istHauptmitgliedschaft : boolean
+ von : Date
+ bis : Date
}
' -- Value Objects (VOs) --
' VOs haben keine eigene Identität, sie beschreiben Eigenschaften.
class PersonenNameVO <<VO>> {
+ familienname : string
+ vorname : string
}
class AdresseVO <<VO>> {
+ strasse : string
+ hausnummer: string
+ plz: string
+ ort: string
+ land: string
}
class KontaktVO <<VO>> {
+ typ: KontaktTyp ' Email, Telefon, Mobil
+ wert: string
}
' -- Beziehungen --
' Das Person-Aggregat besitzt seine Mitgliedschaften (Komposition).
Person "1" *-- "0..*" Mitgliedschaft
' Das Person-Aggregat nutzt Value Objects zur Beschreibung.
Person "1" o-- "1" PersonenNameVO
Person "1" o-- "0..*" AdresseVO
Person "1" o-- "0..*" KontaktVO
' Die Mitgliedschaft verweist auf das Verein-Aggregat.
' Dies ist eine lose Kopplung über die ID, keine Komposition.
Mitgliedschaft ..> Verein : "bezieht sich auf"
}
note right of Person
**Aggregate Root: Person**
Dieses Objekt ist der zentrale Einstiegspunkt
für alle Operationen, die eine Person betreffen.
**Beispiel:**
Um eine Mitgliedschaft hinzuzufügen, ruft man
`person.fuegeMitgliedschaftHinzu(...)` auf.
Das `Person`-Objekt stellt sicher, dass z.B.
nur eine Hauptmitgliedschaft existiert.
Man ändert nicht direkt das Mitgliedschafts-Objekt.
end note
@enduml
@@ -0,0 +1,27 @@
@startuml
title "Service-Interaktion: Nennung wird validiert"
actor Reiter
participant "Nennungs_Context" as Nenn
participant "Veranstaltungs_Context" as Veranst
participant "Lizenzen_und_Qualifikationen_Context" as Lizenz
Reiter -> Nenn : Nennung abgeben für Prüfung "A-Dressur"
activate Nenn
Nenn -> Veranst : anfrage: getPruefungsAnforderungen("A-Dressur")
activate Veranst
Veranst --> Nenn : antwort: {erf. Lizenz: "R1", erf. Alter: "U21"}
deactivate Veranst
Nenn -> Lizenz : anfrage: hatStartberechtigung(Reiter-ID, {erf. Lizenz: "R1", erf. Alter: "U21"})
activate Lizenz
Lizenz --> Nenn : antwort: {status: "OK"}
deactivate Lizenz
Nenn -> Nenn : Nennung speichern (Status: "Angenommen")
Nenn --> Reiter : Bestätigung: Nennung erfolgreich!
deactivate Nenn
@enduml
@@ -0,0 +1,109 @@
# Dokumentation: Service-orientiertes Datenbankmodell ÖTO
**Stand:** 02. Juli 2025
## 1. Einleitung und Überblick
Dieses Dokument beschreibt die Architektur und das Datenmodell für eine moderne, service-orientierte Meldestellen-Software. Der Entwurf basiert auf den Prinzipien des **Domain-Driven Design (DDD)** und einer Architektur von **Self-Contained Systems (SCS)**.
Das Ziel ist ein robustes, wartbares und erweiterbares System, das die komplexen Anforderungen der Österreichischen Turnierordnung (ÖTO) abbildet und die Datenflüsse des OEPS (Zentrales Nennservice, ZNS) sauber integriert. Die Datenstrukturen basieren maßgeblich auf dem **OEPS Pflichtenheft 2021 (Version 2.4 vom 28.07.2021)**.
## 2. Systemarchitektur: Bounded Contexts
Das System ist in fachliche, voneinander abgegrenzte Domänen, sogenannte **Bounded Contexts**, aufgeteilt. Jeder Context hat eine klare Verantwortung und ein eigenes, darauf optimiertes Datenmodell. Die Kommunikation zwischen den Contexts erfolgt über klar definierte Schnittstellen (APIs) und asynchrone Domänen-Events.
Die Kern-Contexte sind:
* **`ZNS_Import_ACL`**: Ein "Anti-Corruption Layer", der als Pufferzone und Übersetzer für die externen ZNS-Daten dient.
* **`Personen_und_Vereine_Context`**: Die Quelle der Wahrheit für alle Stammdaten von Personen und Vereinen.
* **`Lizenzen_und_Qualifikationen_Context`**: Verwaltet die Startberechtigungen (Lizenzen, Qualifikationen) von Personen.
* **`Veranstaltungs_Context`**: Dient der Planung und Definition von Veranstaltungen, Turnieren und Bewerben.
* **`Nennungs_Context`**: Wickelt den gesamten Nennungsprozess bis zur Erstellung der Startliste ab.
* **`Ergebnis_Context`**: Ist für die Erfassung, Berechnung und Veröffentlichung der Ergebnisse zuständig.
*(In einer vollständigen Dokumentation würde hier eine visuelle Context Map die Beziehungen zwischen diesen Services darstellen.)*
---
## 3. Detaillierte Modelle der Bounded Contexts
### 3.1. ZNS-Import als Anti-Corruption Layer (ACL)
* **Verantwortung:** Schutz des internen Domänenmodells vor den Details der externen ZNS-Schnittstelle. Dieser Context liest die `.dat`-Dateien ein, validiert und übersetzt sie in eine saubere, strukturierte Form für die anderen Services.
* **Aggregate Roots:** Dieser Context hat keine eigenen Aggregate, da er primär ein prozeduraler Übersetzer ist.
* **Kernentitäten:**
* [cite_start]**`ZNS_*_dat_Satz`**: Eine Reihe von Entitäten (`ZNS_LIZENZ01_dat_Satz`, `ZNS_PFERDE01_dat_Satz` etc.), die exakte Abbilder der Zeilen aus den jeweiligen OEPS-Dateien sind[cite: 20]. Sie speichern die Rohdaten.
* **`ZNS_Daten_Uebersetzer`**: Eine konzeptionelle Komponente, die die Logik zur Transformation der Rohdaten in Domänen-Events oder Objekte für die internen Kontexte kapselt.
### 3.2. Personen_und_Vereine_Context
* **Verantwortung:** Die zentrale Verwaltung der Stammdaten aller Akteure (Personen, Vereine).
* **Aggregate Roots:** `Person`, `Verein`.
* **Kernentitäten:**
* **`Person`**: Bündelt alle persönlichen Daten. [cite_start]Der Primärschlüssel `oepsSatzNrPerson` ist die 6-stellige Satznummer aus den ZNS-Dateien[cite: 149]. Enthält Value Objects wie `PersonenNameVO` und `AdresseVO` sowie eine Liste von `Mitgliedschaft`-Entitäten.
* **`Verein`**: Verwaltet Vereinsstammdaten. [cite_start]Der Primärschlüssel `oepsVereinsNr` ist die 4-stellige Nummer aus `VEREIN01.DAT`[cite: 189].
* **`Mitgliedschaft`**: Eine Entität innerhalb des `Person`-Aggregates, die die Beziehung einer Person zu einem `Verein` beschreibt.
### 3.3. Lizenzen_und_Qualifikationen_Context
* **Verantwortung:** Verwaltung der Startberechtigungen und offiziellen Qualifikationen einer Person.
* **Aggregate Roots:** `Lizenznehmer`.
* **Kernentitäten:**
* **`Lizenznehmer`**: Repräsentiert eine Person im Kontext von Berechtigungen. Wird durch die `oepsSatzNrPerson` identifiziert. Bündelt alle `Lizenz`- und `Qualifikation`-Objekte einer Person und stellt deren Konsistenz sicher.
* **`Lizenz`**: Eine konkrete Lizenzinstanz einer Person, die sich auf einen `LizenzTyp_OEPS` (definiert im OeTO-Verwaltungs-Context) bezieht. [cite_start]Daten wie `bezahltImJahr` werden aus dem Feld `LIZENZINFO` der `LIZENZ01.dat` abgeleitet[cite: 181].
* **`Qualifikation`**: Eine konkrete Qualifikation (z.B. als Richter), die sich auf einen `QualifikationsTyp` bezieht. [cite_start]Die Daten stammen aus der Interpretation des `QUALIFIKATIONEN`-Feldes der `RICHT01.dat`[cite: 166].
### 3.4. Veranstaltungs_Context
* **Verantwortung:** Detaillierte Planung und Definition aller reitsportlichen Veranstaltungen.
* **Aggregate Roots:** `VeranstaltungsRahmen`, `Turnier_OEPS`, `Meisterschaft_Cup_Serie`.
* **Veranstaltungshierarchie:**
1. **`VeranstaltungsRahmen`**: Die oberste Ebene, eine konkrete Veranstaltung an Ort und Zeit (z.B. "Reitertage 2025").
2. [cite_start]**`Turnier_OEPS`**: Ein offizielles OEPS-Turnier innerhalb des Rahmens, identifiziert durch die 5-stellige `oepsTurnierNr` aus dem A-Satz[cite: 144, 193].
3. [cite_start]**`Pruefung_OEPS` (Bewerb)**: Ein Bewerb innerhalb eines Turniers, identifiziert durch die 3-stellige `oepsBewerbNr` aus dem B-Satz[cite: 100, 151].
4. **`Pruefung_Abteilung`**: Eine Unterteilung einer Prüfung, für die separat genannt und gewertet werden kann. [cite_start]Gemäß Pflichtenheft ist für jede Abteilung ein eigener B-Satz zu stellen[cite: 197].
* **Weitere Entitäten:**
* **`Meisterschaft_Cup_Serie`**: Ein Aggregat zur Verwaltung von übergreifenden Wettbewerben.
* **`MCS_Wertungspruefung`**: Verknüpft Meisterschaften/Cups mit den spezifischen Prüfungsabteilungen, die als Wertungsprüfungen zählen.
* **`PruefungsAnforderungen`**: Definiert die Startberechtigungen (Lizenzen, Altersklassen) für eine Prüfung oder Abteilung.
* **Spartenspezifische Erweiterungen (`...Spezifika`)**: Erweitern `Pruefung_OEPS` um disziplinspezifische Details.
### 3.5. Nennungs_Context
* **Verantwortung:** Abwicklung des gesamten Nennungsprozesses von der Abgabe bis zur finalen Startliste.
* **Aggregate Roots:** `Nennung`, `Startliste`.
* **Kernentitäten:**
* **`Nennung`**: Eine unteilbare Transaktion, die ein Reiter-Pferd-Paar für eine `Pruefung_Abteilung` anmeldet. Verwendet Value Objects als **Snapshots**, um die Daten zum Zeitpunkt der Nennung historisch korrekt zu speichern. [cite_start]Attribute wie `ersatzreiter`, `accontoBetrag` oder `istStallReserviert` sind direkt auf den `KKARTEI`-Satz des Pflichtenhefts zurückzuführen[cite: 160].
* **`Startliste`**: Ein eigenständiges Aggregat, das die offizielle, geordnete Startreihenfolge für eine `Pruefung_Abteilung` verwaltet. Es stellt die Konsistenz der Startnummern sicher.
* **`Starter`**: Eine Entität innerhalb des `Startliste`-Aggregates; repräsentiert eine Zeile auf der Startliste mit `startnummer`, optionaler `startzeit` und einem Snapshot der Nennungsdaten.
### 3.6. Ergebnis_Context
* **Verantwortung:** Erfassung, Berechnung, Platzierung und Veröffentlichung der Ergebnisse.
* **Aggregate Roots:** `Bewerbsergebnis`.
* **Kernentitäten:**
* **`Bewerbsergebnis`**: Bündelt alle `Einzelergebnis`-Objekte einer `Pruefung_Abteilung`. Seine Hauptaufgabe ist die Berechnung der finalen, konsistenten `Rangliste`. [cite_start]Es speichert auch die Referenzen auf die eingesetzten Richter und Funktionäre gemäß C-Satz[cite: 201].
* **`Einzelergebnis`**: Repräsentiert das Ergebnis eines einzelnen Starters. [cite_start]Die Attribute (`platz`, `ausschluss_disq_code`, `geldpreis` etc.) sind direkt auf den **D-Satz** des Pflichtenhefts abgebildet[cite: 210].
* **`LeistungVO` (polymorph)**: Ein flexibles Value Object, das die je nach Sparte unterschiedlichen Leistungsdaten (z.B. Wertnote in der Dressur, Fehler/Zeit im Springen) kapselt. [cite_start]Die Daten stammen aus den Feldern `PUNKTE/WERTNOTE` und `ZEIT/PROZENT` im D-Satz[cite: 210].
---
## 4. Prozess- und Interaktions-Beispiele
Die Stärke dieses Designs liegt im Zusammenspiel der entkoppelten Services. Wir haben zwei zentrale Prozesse modelliert:
* **Nennung wird abgegeben:**
* Ein **Command** `NennungAbgeben` wird an den `Nennungs_Context` gesendet.
* Dieser validiert die Anfrage durch synchrone **Queries** an den `Veranstaltungs_Context` und den `Lizenzen_und_Qualifikationen_Context`.
* Nach erfolgreicher interner Speicherung wird ein asynchrones **Event** `NennungWurdeEingereicht` veröffentlicht, auf das z.B. ein Benachrichtigungs-Service reagieren kann.
* **Startliste wird erstellt:**
* Ein **Command** `ErstelleStartliste` von der **Meldestelle** an den `Nennungs_Context` stößt den Prozess an.
* Der Context sammelt alle akzeptierten Nennungen, wendet die Sortier-/Loslogik an und erstellt das `Startliste`-Aggregat.
* Ein asynchrones **Event** `StartlisteWurdeFinalisiert` wird veröffentlicht.
* Der `Ergebnis_Context` abonniert dieses Event und bereitet sich proaktiv auf die Ergebniserfassung vor, indem er für jeden Starter einen leeren `Einzelergebnis`-Datensatz anlegt.
## 5. Schlussbemerkung und Ausblick
Dieses Dokument fasst ein umfassendes, service-orientiertes Domänenmodell zusammen, das als Blaupause für die Entwicklung einer modernen, robusten und erweiterbaren Meldestellen-Software dient.
Die Architektur mit klar definierten Bounded Contexts, Aggregates und der ereignisgesteuerten Kommunikation ermöglicht es, die hohe fachliche Komplexität des Turniersports beherrschbar zu machen.
Die nächsten Schritte in einem realen Projekt wären:
* Die weitere Detaillierung der Attribute und Methoden in jedem Context.
* Die genaue Definition der APIs (Commands, Queries) und Event-Strukturen.
* Die Modellierung weiterer unterstützender Kontexte (z.B. für die Bezahlungsabwicklung oder das Berichtswesen).
@@ -0,0 +1,54 @@
@startuml
title "Prozess & Event-Flow: Startlisten-Generierung"
!theme vibrant
actor Meldestelle
participant "API Gateway / Frontend" as Gateway
participant "Nennungs_Context" as Nenn
queue "Message Bus" as Bus
participant "Ergebnis_Context" as Ergebnis
Meldestelle -> Gateway : POST /startlisten\n(fuer pruefungAbteilungId)
' 1. Command wird an den zuständigen Context gesendet
Gateway -> Nenn : **Command:** ErstelleStartliste(pruefungAbteilungId)
activate Nenn
note right of Nenn: Empfängt den Befehl,\ndie Startliste für eine\nPrüfungsabteilung zu generieren.
' 2. Interne Datenbeschaffung und Logik
Nenn -> Nenn : getAkzeptierteNennungen(pruefungAbteilungId)
note right of Nenn: Holt alle Nennungen mit Status\n"STARTBERECHTIGT_BESTAETIGT"\naus der eigenen Datenbank.
Nenn -> Nenn : wendeSortierUndLosverfahrenAn(nennungen)
note right of Nenn: Hier findet die Kernlogik statt:\n- Zufälliges Losen\n- oder Setzen nach Rangliste\n- Vergabe der Startnummern
Nenn -> Nenn : Startlisten-Aggregat erstellen\nund speichern (Status: Final)
note left of Nenn
**Neues Aggregat: Startliste**
Die erstellte Startliste könnte
ein eigenes Aggregate Root im
Nennungs_Context sein, das
eine geordnete Liste von
Startern enthält.
end note
' 3. Asynchrones Event wird veröffentlicht
Nenn ->> Bus : **Event:** StartlisteWurdeFinalisiert {startlisteId, pruefungAbteilungId, starter[]}
note right of Bus: Das Event informiert das restliche\nSystem über die finale Startliste.\nEs enthält alle nötigen Daten,\n damit die Empfänger nicht extra\nzurückfragen müssen.
Gateway --> Meldestelle : HTTP 200 OK (Startliste wurde erstellt)
deactivate Nenn
' 4. Der Ergebnis-Context reagiert auf das Event
Bus ->> Ergebnis : **Event:** StartlisteWurdeFinalisiert
activate Ergebnis
Ergebnis -> Ergebnis : erstelleBewerbsergebnis(event.pruefungAbteilungId)
Ergebnis -> Ergebnis : erstelleEinzelergebnisProStarter(event.starter[])
note right of Ergebnis: Der Ergebnis-Context bereitet sich vor.\nEr legt das `Bewerbsergebnis`-Aggregat an\nund erzeugt für jeden Starter einen leeren\n`Einzelergebnis`-Eintrag, der nun auf\ndie Eingabe der Leistung wartet.
deactivate Ergebnis
@enduml
@@ -0,0 +1,114 @@
@startuml
title "Detailliertes Datenmodell: Nennungs_Context (inkl. Startliste)"
!theme vibrant
package "Nennungsabwicklung" as NennungsContext {
' #################### Aggregat 1: Nennung ####################
' (Bekannt aus dem vorherigen Schritt, hier zur Veranschaulichung der Beziehung)
class Nennung <<(A,blue) Aggregate Root>> {
+ nennungId : UUID <<PK>>
--
+ pruefung : PruefungsReferenzVO
+ reiter : ReiterReferenzVO
+ pferd : PferdeReferenzVO
+ status : NennungsStatusVO
' ... weitere Attribute
}
' #################### Aggregat 2: Startliste ####################
class Startliste <<(A,green) Aggregate Root>> {
+ startlisteId : UUID <<PK>>
--
' Referenz auf die Prüfung/Abteilung, für die diese Liste gilt
+ pruefung : PruefungsReferenzVO
' Zeitstempel der Erstellung und letzten Änderung
+ erstellungsZeitpunkt : timestamp
+ letzteAenderung : timestamp
' Version zur Nachverfolgung von Änderungen
+ version : integer
' Status der Liste
+ status : StartlistenStatusVO
}
' Eine Entität innerhalb des Startlisten-Aggregates
entity Starter {
+ starterId : UUID <<PK>>
--
' Die zugewiesene Startnummer, muss innerhalb der Liste eindeutig sein
+ startnummer : integer
' Die zugewiesene Startzeit (optional)
+ startzeit : time
' Status des Starters (z.B. falls jemand nach der Erstellung zurückzieht)
+ status : StarterStatusVO
' Snapshot der Nennungsdaten, die für die Liste relevant sind
+ nennungsdaten : NennungsReferenzVO
}
' #################### Value Objects (VOs) für Snapshots und Beschreibungen ####################
' Enum für den Lebenszyklus einer Startliste
enum StartlistenStatusVO {
VORLAEUFIG
FINAL
GEAENDERT
STORNIERT
}
' Enum für den Status eines einzelnen Starters auf der Liste
enum StarterStatusVO {
GELISTET
ZURUECKGEZOGEN
NACHGERUECKT
DISQUALIFIZIERT_VOR_START
}
' Ein Snapshot der wichtigsten Nennungsdaten für einen Starter
class NennungsReferenzVO <<VO>> {
' Referenz zur originalen Nennung
+ nennungDbId : UUID
' Relevante Daten für die Anzeige auf der Startliste
+ reiterName : string
+ pferdName : string
+ kopfnummer : string
+ vereinName : string
+ nationCode : string
}
' Referenz auf die Prüfung (bekannt aus Nennung, hier wiederverwendet)
class PruefungsReferenzVO <<VO>> {
+ pruefungAbteilungDbId : UUID
+ bewerbBezeichnung : string
+ abteilungBezeichnung : string
}
' #################### Beziehungen ####################
' Eine Startliste besteht aus einer geordneten Liste von Startern (Komposition)
Startliste "1" *-- "1..*" Starter : "enthält geordnet"
' Ein Starter-Eintrag enthält einen Snapshot der ursprünglichen Nennungsdaten
Starter "1" o-- "1" NennungsReferenzVO : "basiert auf"
' Eine Startliste gehört zu genau einer Prüfung/Abteilung
Startliste "1" o-- "1" PruefungsReferenzVO : "für"
' Status-Beziehungen
Startliste "1" -- "1" StartlistenStatusVO
Starter "1" -- "1" StarterStatusVO
}
note top of Startliste
**Aggregate Root: Startliste**
* **Verantwortung:** Dieses Aggregat garantiert die Integrität der Startreihenfolge. Geschäftslogik wie `aendereStartnummer()` oder `zieheStarterZurueck()` wird hier implementiert, um sicherzustellen, dass die Liste konsistent bleibt (z.B. keine doppelten Startnummern).
* **Lebenszyklus:** Eine Startliste wird typischerweise als `VORLAEUFIG` erstellt, kann dann `FINAL` gesetzt und bei Bedarf (z.B. durch Ausfälle) `GEAENDERT` werden.
end note
note right of Starter
**Entität: Starter**
Ein `Starter`-Objekt ist kein Aggregat, da es nicht ohne seine `Startliste` existieren kann. Es repräsentiert eine Zeile auf der Startliste. Es enthält mit der `NennungsReferenzVO` alle Informationen, die für die Anzeige oder den Ausdruck der Liste benötigt werden, ohne dass die ursprüngliche `Nennung` oder andere Services erneut abgefragt werden müssen.
end note
@enduml
@@ -0,0 +1,96 @@
# To-Do-Liste: Ausbaustufen für das ÖTO-Meldestellen-System
**Stand:** 02. Juli 2025
Dies ist eine Übersicht der nächsten logischen Schritte zur Vervollständigung des Systemdesigns, aufbauend auf dem bestehenden DDD-Modell.
---
## Bounded Context: `Abrechnung`
Dieser Context ist entscheidend für alle finanziellen Transaktionen und fehlt bisher komplett.
- [ ] **Datenmodell für Einnahmen entwerfen**
- [ ] Entität `Rechnung` für Nenngelder, Stallgebühren etc. erstellen.
- [ ] Entität `Zahlungseingang` zur Verfolgung von bezahlten Beträgen modellieren.
- [ ] Prozess zur Verknüpfung von Zahlungen mit Nennungen definieren.
- [ ] **Datenmodell für Ausgaben (Preisgelder) entwerfen**
- [ ] Entität `Preisgeldauszahlung` erstellen.
- [ ] Prozess für die Berechnung und Zuordnung von Preisgeldern basierend auf der `Ergebnis`-Entität modellieren.
- [ ] Statusverfolgung für Auszahlungen (z.B. "offen", "ausbezahlt") definieren.
- [ ] **Prozess für die Veranstalter-Abrechnung modellieren**
- [ ] Logik zur Erstellung einer Endabrechnung (Einnahmen vs. Ausgaben) für den Veranstalter entwerfen.
---
## Funktionalität: Mannschaftswertungen
Die aktuelle Modellierung deckt nur Einzelnennungen ab.
- [ ] **Datenmodell für Mannschaften erstellen**
- [ ] Entität `Mannschaft` definieren (Name, Verein, etc.).
- [ ] Entität `Mannschaftsmitglied` als M:N-Beziehung zwischen `Mannschaft` und `Nennung` modellieren.
- [ ] Prozess für die Mannschaftsnennung im `Nennungs_Context` entwerfen.
- [ ] **Modell für Mannschaftsergebnisse definieren**
- [ ] Entität `Mannschaftsergebnis` im `Ergebnis_Context` erstellen.
- [ ] Geschäftsregeln für die Berechnung von Mannschaftsergebnissen festlegen (z.B. Streichergebnisse).
---
## Bounded Context: `Identität & Zugriff`
Ein detailliertes Berechtigungssystem ist für den Betrieb unerlässlich.
- [ ] **Rollenkonzept definieren**
- [ ] Rollen identifizieren (z.B. Meldestelle, Veranstalter, Richter, Zeitnehmer, OEPS-Admin).
- [ ] Rechte pro Rolle granular festlegen (z.B. "darf Ergebnisse eintragen", "darf Turnier anlegen").
- [ ] **Datenmodell für Benutzer und Rechte erstellen**
- [ ] Entität `Benutzer` für den Systemzugang definieren.
- [ ] Entitäten für `Rollen` und `Berechtigungen` erstellen und mit `Benutzer` verknüpfen.
---
## Funktionalität: Detaillierte Zeitplanung (Zeiteinteilung)
Die Erstellung eines exakten Zeitplans ist ein komplexer Prozess.
- [ ] **Ressourcenmodell entwerfen**
- [ ] Entitäten für Veranstaltungs-Ressourcen wie `Reitplatz` oder `Abreiteplatz` erstellen.
- [ ] Modell zur Planung der Verfügbarkeit von Richtern und Funktionären entwickeln.
- [ ] **Planungslogik definieren**
- [ ] Geschäftsregeln zur Berechnung von Prüfungsdauern (basierend auf Starterzahl) festlegen.
- [ ] Logik für die Planung von Pausen, Umbauzeiten und Parallelnutzung von Ressourcen modellieren.
---
## Erweiterung: `Nennungs_Context` (Spezialfälle)
Das OEPS Pflichtenheft beschreibt Spezialfälle, die noch nicht vollständig im Modell abgebildet sind.
- [ ] **Prozess für Pferdetausch modellieren**
- [ ] Methode `tauschePferd()` im `Nennung`-Aggregat entwerfen.
- [ ] [cite_start]Logik zur Abbildung des **T-Satzes** für die Ergebnisdatei definieren. [cite: 209, 215]
- [ ] **Prozess für Nachnennungen modellieren**
- [ ] Regeln für Nachnennungen (z.B. erhöhte Gebühren, Fristen) definieren.
- [ ] [cite_start]Logik zur Abbildung des **N-Satzes** für die Ergebnisdatei entwerfen. [cite: 211, 215]
---
## Funktionalität: Berichtswesen & Dokumentation
Ein System muss diverse Ausgaben generieren können.
- [ ] **Design für Standard-Dokumente erstellen**
- [ ] Layout und Datenanforderungen für druckfertige Startlisten definieren.
- [ ] Layout und Datenanforderungen für offizielle Ergebnislisten definieren.
- [ ] Design für weitere Dokumente wie Boxenschilder oder Richterzettel entwerfen.
- [ ] **Konzept für Berichte entwickeln**
- [ ] Datenanforderungen für Finanzberichte für den Veranstalter spezifizieren.
- [ ] Konzept für statistische Auswertungen (z.B. Teilnehmer pro Prüfung, erfolgreichste Pferde/Reiter) entwickeln.
@@ -0,0 +1,180 @@
@startuml
title "Detailliertes Datenmodell: Veranstaltungs_Context"
!theme vibrant
' Externe Referenzen werden der Übersichtlichkeit halber als vereinfachte Entitäten dargestellt.
package "Externe Referenzen (Andere Kontexte)" {
class Verein_ZNS
class Person_ZNS
class OETORegelReferenz
class Sportfachliche_Stammdaten
}
package "Veranstaltungsplanung" as VeranstaltungsContext {
' #################### Aggregate Root: VeranstaltungsRahmen ####################
class VeranstaltungsRahmen <<(A,orange) Aggregate Root>> {
+ veranstaltungsRahmenId : UUID
--
+ name : string ' z.B. "Reitertage Musterhof Frühling 2025"
+ zeitraum : DatumsbereichVO
+ austragungsort : AdresseVO
' FK zu Service_ZNS_Daten.Verein_ZNS
# hauptveranstalter_verein_nr : VARCHAR(4) <<FK>>
}
' #################### Aggregate Root: Turnier_OEPS ####################
class Turnier_OEPS <<(A,orange) Aggregate Root>> {
' A-Satz, Stelle 2-6
+ oepsTurnierNr : VARCHAR(5) <<PK>>
--
' FK zu VeranstaltungsRahmen
# veranstaltungsRahmenId : UUID <<FK>>
' A-Satz, Stelle 7-31
+ name_ort : VARCHAR(25)
' A-Satz, Stelle 32-47
+ zeitraum : DatumsbereichVO
' A-Satz, Stelle 48-72
+ kategorie_text_turnier : VARCHAR(25)
+ turnierart_sparte : string ' z.B. CDN-A, CSN-B*
' A-Satz (Ergebnis), Stelle 73-75
+ protokoll_version_oeps : VARCHAR(3)
' A-Satz (Ergebnis), Stelle 76-95
+ meldestelle_software_version : VARCHAR(20)
' A-Satz (Ergebnis), Stelle 96-103
+ link_id_turnier : VARCHAR(8)
}
' #################### Aggregate Root: Meisterschaft_Cup_Serie ####################
class Meisterschaft_Cup_Serie <<(A,orange) Aggregate Root>> {
+ mcsId : UUID <<PK>>
--
+ name : string ' z.B. "XYZ Sommercup 2025"
+ typ : string ' Meisterschaft, Cup, Serie
+ jahr : INTEGER
+ sparte : string
# oetoRegelRefId : UUID <<FK>>
}
' #################### Entitäten innerhalb des Turnier_OEPS Aggregats ####################
' Ein Bewerb innerhalb eines Turniers
entity Pruefung_OEPS {
+ pruefungId : UUID <<PK>>
--
' B-Satz, Stelle 61-63 (3-stellig)
+ oepsBewerbNr : VARCHAR(3)
' B-Satz, Stelle 5-39
+ name_text_pruefung : VARCHAR(35)
' B-Satz, Stelle 40-43
+ klasse_text : VARCHAR(4)
' B-Satz, Stelle 44-51
+ kategorie_text_pruefung : VARCHAR(8)
' B-Satz, Stelle 52-59
+ datumPruefung : Date
' Zur Steuerung der spartenspezifischen Logik
+ artDisziplin : DisziplinVO
' B-Satz (Ergebnis), Stelle 64-71
+ link_id_pruefung : VARCHAR(8)
}
' Eine Abteilung eines Bewerbs
entity Pruefung_Abteilung {
+ abteilungId : UUID <<PK>>
--
' B-Satz, Stelle 4
+ oepsAbteilungNr : INTEGER
+ bezeichnung : string ' z.B. "Abt. 1: Junioren"
' B-Satz (Ergebnis), Stelle 52-54
+ anzahl_starter_gemeldet : INTEGER
' B-Satz (Ergebnis), Stelle 55-60 (ohne Komma)
+ geldpreis_summe : DECIMAL
}
' Entität zur Speicherung der konkreten Anforderungen für eine Prüfung/Abteilung
entity PruefungsAnforderungen {
+ anforderungenId: UUID <<PK>>
--
' Kann sich auf eine Pruefung_OEPS oder eine Pruefung_Abteilung beziehen
# bezugs_id: UUID <<FK>>
' FK zu Service_OeTO_Verwaltung.OETORegelReferenz
# oeto_regel_ref_id: UUID <<FK>>
}
entity Anforderung_Lizenz {
# anforderungenId: UUID <<FK>>
# lizenzTypCode: VARCHAR(4) <<FK>>
}
entity Anforderung_Altersklasse {
# anforderungenId: UUID <<FK>>
# altersklasseCode: VARCHAR(4) <<FK>>
}
' #################### Spartenspezifische Erweiterungen ####################
package "Sportfachliche Details für Prüfungen" {
' Alle Spezifika sind 1:1 mit Pruefung_OEPS verbunden
entity DressurPruefungSpezifika {
# pruefungId : UUID <<PK>> <<FK>>
--
' FK zu Service_OeTO_Verwaltung.Sportfachliche_Stammdaten
# aufgabe_stammdatum_id : UUID <<FK>>
+ platz_groesse_code : string ' z.B. 20x40, 20x60
}
entity SpringenPruefungSpezifika {
# pruefungId : UUID <<PK>> <<FK>>
--
' FK zu Service_ZNS_Daten.Person_ZNS
# parcours_designer_person_id : VARCHAR(6) <<FK>>
+ anzahl_hindernisse : INTEGER
+ hoehe_max_cm : INTEGER
+ erlaubte_zeit_sek : INTEGER
' FK zu Service_OeTO_Verwaltung.Sportfachliche_Stammdaten
# wertungs_verfahren_stammdatum_id : UUID <<FK>>
+ anzahl_umlaeufe : INTEGER
+ anzahl_stechen : INTEGER
}
' (Vielseitigkeit und RVK Spezifika hier analog)
}
' #################### Value Objects ####################
class DatumsbereichVO <<VO>> {
+von: Date,
+bis: Date
}
class AdresseVO <<VO>> {
+strasse: string,
+plz: string,
+ort: string
}
class DisziplinVO <<VO>> {
+name: string
}
' #################### Beziehungen ####################
' Aggregat-Hierarchie
VeranstaltungsRahmen "1" -- "1..*" Turnier_OEPS : "beinhaltet"
Turnier_OEPS "1" *-- "1..*" Pruefung_OEPS : "besteht aus"
Pruefung_OEPS "1" *-- "1..*" Pruefung_Abteilung : "unterteilt in"
' Anforderungen zuordnen
Pruefung_OEPS "1" -- "1" PruefungsAnforderungen : "hat"
Pruefung_Abteilung "1" -- "0..1" PruefungsAnforderungen : "hat spezielle"
PruefungsAnforderungen "1" -- "*" Anforderung_Lizenz
PruefungsAnforderungen "1" -- "*" Anforderung_Altersklasse
' Sparten-Spezifika zuordnen (Vererbung/Erweiterung)
Pruefung_OEPS <|-- DressurPruefungSpezifika
Pruefung_OEPS <|-- SpringenPruefungSpezifika
' Meisterschaft/Cup Beziehungen
class MCS_Wertungspruefung
(Meisterschaft_Cup_Serie, MCS_Wertungspruefung) .up. Pruefung_Abteilung
}
' Beziehungen zu externen Kontexten
VeranstaltungsContext.VeranstaltungsRahmen -- Verein_ZNS : "veranstaltet von"
VeranstaltungsContext.SpringenPruefungSpezifika -- Person_ZNS : "Parcoursdesigner"
' Weitere Beziehungen zu OETORegelReferenz, Stammdaten etc.
@enduml
@@ -0,0 +1,134 @@
@startuml
title "Bounded Context: ZNS-Import als Anti-Corruption Layer (ACL)"
!theme vibrant
' Der Bounded Context wird als Paket mit dem Stereotyp <<ACL>> dargestellt
package "ZNS_Import_ACL" <<ACL>> {
' Eine Komponente, die die Übersetzungslogik kapselt
component ZNS_Daten_Uebersetzer [
+ importiere_zns_zip()
+ uebersetze_Personen()
+ uebersetze_Pferde()
+ uebersetze_Vereine()
]
' Die folgenden Entitäten repräsentieren die 1:1 Abbildung der Zeilen aus den ZNS .dat-Dateien.
' Sie sind "anämisch" und enthalten keine Geschäftslogik.
package "Rohdaten-Struktur aus ZNS-Dateien" {
' Basierend auf VEREIN01.DAT
entity ZNS_VEREIN01_dat_Satz {
' VEREIN (Nummer), Stelle 1-4 [cite: 177]
+ verein_nummer : VARCHAR(4)
' VEREINSNAME, Stelle 5-54 [cite: 177]
+ vereinsname : VARCHAR(50)
}
' Basierend auf PFERDE01.DAT
entity ZNS_PFERDE01_dat_Satz {
' SATZNUMMER DES PFERDES, Stelle 202-211 [cite: 164]
+ satznummer_des_pferdes : VARCHAR(10)
' PFERDENAME, Stelle 5-34 [cite: 164]
+ pferdename : VARCHAR(30)
' LEBENSNUMMER, Stelle 35-43 [cite: 164]
+ lebensnummer : VARCHAR(9)
' GEB.JAHR, Stelle 45-48 [cite: 164]
+ geb_jahr : VARCHAR(4)
' ... (weitere Felder gemäß Pflichtenheft S.8)
}
' Basierend auf RICHT01.DAT
entity ZNS_RICHT01_dat_Satz {
' ID, Stelle 1 ('X' oder 'Y') [cite: 154, 162]
+ id_satzart : CHAR(1)
' SATZNUMMER, Stelle 2-7 [cite: 154, 162]
+ satznummer : VARCHAR(6)
' NAME, Stelle 8-82 [cite: 154, 162]
+ name_komplett : VARCHAR(75)
' QUALIFIKATIONEN, Stelle 83-112 [cite: 154, 162]
+ qualifikationen_text : VARCHAR(30)
}
' Basierend auf LIZENZ01.DAT
entity ZNS_LIZENZ01_dat_Satz {
' SATZNUMMER DES REITERS, Stelle 1-6
+ satznummer_des_reiters : VARCHAR(6)
' FAMILIENNAME, Stelle 7-56
+ familienname : VARCHAR(50)
' VORNAME, Stelle 57-81
+ vorname : VARCHAR(25)
' VEREINSNAME, Stelle 84-133
+ vereinsname_text : VARCHAR(50)
' REITERLIZENZ, Stelle 137-140
+ reiterlizenz_code : VARCHAR(4)
' FAHRLIZENZ, Stelle 142-143
+ fahrlizenz_code : VARCHAR(2)
' LIZENZINFO, Stelle 201-210
+ lizenzinfo_text : VARCHAR(10)
' GEBURTSDATUM, Stelle 182-189
+ geburtsdatum_text : VARCHAR(8)
' ... (weitere Felder gemäß Pflichtenheft S.8)
}
}
' Die Pfeile zeigen den Datenfluss: Der Übersetzer konsumiert die Rohdaten-Sätze.
ZNS_Daten_Uebersetzer ..> ZNS_VEREIN01_dat_Satz : "liest"
ZNS_Daten_Uebersetzer ..> ZNS_PFERDE01_dat_Satz : "liest"
ZNS_Daten_Uebersetzer ..> ZNS_RICHT01_dat_Satz : "liest"
ZNS_Daten_Uebersetzer ..> ZNS_LIZENZ01_dat_Satz : "liest"
}
' Außerhalb des ACLs liegt unser sauberes, internes Domänenmodell.
' Der ACL übersetzt die Rohdaten in diese Ziel-Strukturen (oder Events, die diese erzeugen).
package "Internes Domänenmodell (Ziel)" {
' Beispielhaft: das Ziel-Objekt im Personen-Context
class Personenstamm <<Aggregate Root>> {
+ oepsSatzNrPerson : VARCHAR(6)
--
+ name : FamiliennameVO
+ geburtsdatum : Date
+ hauptverein : VereinsReferenz
+ hatLizenz(lizenzTyp) : boolean
}
' Beispielhaft: das Ziel-Objekt im Lizenz-Context
class Lizenznehmer <<Aggregate Root>> {
+ oepsSatzNrPerson : VARCHAR(6)
--
+ lizenzen : List<Lizenz>
+ qualifikationen: List<Qualifikation>
}
}
' Der Übersetzer im ACL erzeugt/aktualisiert die internen Domänenobjekte.
' Dies geschieht oft über Events oder direkte Service-Aufrufe.
ZNS_Daten_Uebersetzer ..> Personenstamm : "erzeugt/aktualisiert"
ZNS_Daten_Uebersetzer ..> Lizenznehmer : "erzeugt/aktualisiert"
note right of ZNS_Import_ACL
**Anti-Corruption Layer (ACL)**
Dieser Bounded Context hat eine einzige
Verantwortlichkeit: Er schützt das
System vor den Details und der
Komplexität der externen ZNS-Schnittstelle.
1. **Einlesen:** Die `.dat`-Dateien werden 1:1 in die
`ZNS_*_dat_Satz`-Entitäten eingelesen.
2. **Übersetzen:** Die Komponente `ZNS_Daten_Uebersetzer`
transformiert diese Rohdaten. Sie löst
Codes auf (z.B. Bundesland), normalisiert
Daten (z.B. kommaseparierte Lizenzen)
und validiert die Daten.
3. **Veröffentlichen:** Das Ergebnis der Übersetzung
wird an die zuständigen internen Bounded
Contexts weitergegeben, z.B. durch das
Auslösen von Domänen-Events wie
`PersonenStammdatenAktualisiert` oder
`NeueLizenzInformationVerfügbar`.
end note
@enduml