Refactor license matrix and tokenizer logic: rename LicenseTable to LizenzTable, replace LicenseMatrixService with LizenzMatrixService, enhance tokenizer with normalized and fallback token handling, improve ZNS import for license extraction, and update related documentation.
Desktop CI — Headless Tests & Build / Compose Desktop — Tests (headless) & Build (push) Waiting to run
Build and Publish Docker Images / build-and-push (., backend/infrastructure/gateway/Dockerfile, api-gateway, api-gateway) (push) Waiting to run
Build and Publish Docker Images / build-and-push (., backend/services/ping/Dockerfile, ping-service, ping-service) (push) Waiting to run
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Waiting to run
Build and Publish Docker Images / build-and-push (., config/docker/keycloak/Dockerfile, keycloak, keycloak) (push) Waiting to run
Desktop CI — Headless Tests & Build / Compose Desktop — Tests (headless) & Build (push) Waiting to run
Build and Publish Docker Images / build-and-push (., backend/infrastructure/gateway/Dockerfile, api-gateway, api-gateway) (push) Waiting to run
Build and Publish Docker Images / build-and-push (., backend/services/ping/Dockerfile, ping-service, ping-service) (push) Waiting to run
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Waiting to run
Build and Publish Docker Images / build-and-push (., config/docker/keycloak/Dockerfile, keycloak, keycloak) (push) Waiting to run
This commit is contained in:
@@ -15,8 +15,25 @@ Versionierung folgt [Semantic Versioning](https://semver.org/lang/de/).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Geändert
|
||||
- Masterdata/Domain: Umbenennungen zur Vereinheitlichung der Terminologie (DE):
|
||||
- `MasterdataLicenseRepository` → `LizenzRepository`
|
||||
- `LicenseMatrixService` → `LizenzMatrixService`
|
||||
- `LicenseMatrixServiceImpl` → `LizenzMatrixServiceImpl`
|
||||
- Test: `LicenseMatrixServiceTest` → `LiznezMatrixServiceTest` (exakt nach Vorgabe)
|
||||
- Infrastructure (Exposed): `LicenseTable` → `LizenzTable`
|
||||
- Docs: Begriff „reit_lizenzen“ → „reiterlizenzen“ in Glossar/UL konsolidiert.
|
||||
|
||||
### Hinzugefügt
|
||||
|
||||
- Masterdata: Automatisches Seeding aller Reiterlizenzen (license_matrix) beim Start des `masterdata-service` via `ReiterlizenzenSeeder` (idempotent; SPRINGEN: LIZENZFREI,R1–R4; DRESSUR: LIZENZFREI,RD1–RD3).
|
||||
|
||||
- **ZNS-Import (LIZENZ01.dat):** Robuster Lizenz-Tokenizer und Normalizer implementiert.
|
||||
- Erkennung: `RD1..RD4`, `R1..R4`, `S1..S4`, `D2..D4`, Kombis `R{n}D{m}`, `R{n}S{k}`, `RDS4` (rechts-/letztes Vorkommen gewinnt).
|
||||
- Normalisierung: `S*→R*`, `D*→RD*`, `RD4→RD3` (bis Enum verfügbar), `R{n}S{k}→Rmax(n,k)`, `R{n}D{m}→R{n}+RD{m}`.
|
||||
- Integration: `ZnsReiterParser` füllt `lizenzen`-Liste (1:n) entsprechend und leitet `lizenzKlasse` bei fehlendem 4‑Spalten‑Code aus Token ab.
|
||||
- QA: Neue Unit-Tests (Tokenizer) für Beispiele `R2S3`, `R2D4`, `RD2` u. a.; alle Parser-Tests grün.
|
||||
|
||||
- **Core:** Modularisierte ZNS-Parser eingeführt (`ZnsVereinParser`, `ZnsReiterParser`, `ZnsPferdParser`, `ZnsFunktionaerParser`) zur Verbesserung der Wartbarkeit und Unterstützung von Einzelimporten.
|
||||
- **Fix:** SQL-Migrationsfehler in `V010` behoben, indem die Umbenennung der Spalte `name` in `verein_name` durch einen idempotenten `DO`-Block abgesichert wurde (behebt "Unable to resolve column 'name'").
|
||||
- **Infrastructure:** Datenbank-Migration `V010` hinzugefügt, um das Schema final mit den `Exposed`-Modellen zu synchronisieren.
|
||||
@@ -44,6 +61,21 @@ Versionierung folgt [Semantic Versioning](https://semver.org/lang/de/).
|
||||
- **Masterdata/API:** Fehlendes Interface-Mapping ergänzt: `RegulationRepository` enthält nun `findAllTurnierklassen()`; `ExposedRegulationRepository` implementiert die Methode und `RegulationController` kompiliert wieder.
|
||||
- **ZNS-Import:** `AltersklassenExposedRepository` korrigiert (richtiger Domain-Typ `AltersklasseDefinition`, Mapping von `SparteE` und Zeitstempeln).
|
||||
|
||||
- **Migration V013:** Idempotent und robust gemacht. Alle `ALTER TABLE ... RENAME`-Operationen laufen nun nur, wenn die Quell-Tabelle existiert (Fix für "Unable to resolve table 'bundesland'/'turnierklasse'").
|
||||
- **Lizenz-Validierung:** `LicenseMatrixServiceImpl` um Cross-Discipline-Mapping R↔RD (ÖTO-Äquivalenzen) erweitert. Damit funktionieren Fälle wie Dressur-Starts mit Spring-Lizenz (R1→RD1, R2→RD2, R3/R4→RD3) bzw. umgekehrt konsistent.
|
||||
|
||||
- **Domain:** Striktere Spartenlizenz-Prüfung in `Reiter.hasLizenzForSparte` implementiert (RD1..RD3 nur DRESSUR; R1..R4 nur SPRINGEN). Behebt Testfehler „isEligible verweigert Start ohne passende Spartenlizenz“ im `LicenseMatrixServiceTest`.
|
||||
|
||||
### Dokumentation
|
||||
- **Masterdata/Docs:** `REITER_LIZENZEN.md` überarbeitet:
|
||||
- Strikte Sparten-Trennung dokumentiert (RD1..RD3 nur Dressur; R1..R4 nur Springen).
|
||||
- Dressur-Tabelle korrigiert (R-Lizenzen entfernen; RD-Pflicht je Klasse).
|
||||
- Validierungslogik ergänzt (2-stufig: Spartenlizenz → Max-Turnierklasse; R↔RD Mapping nur zur Kappung, nicht zur Eligibility).
|
||||
- Vielseitigkeit (CCN/CCI) ergänzt: kumulative Anforderungen (Dressur RD* UND Springen R* je Klasse); Startkartenregel für Einsteiger.
|
||||
- Fahren (CAN/CAI) ergänzt: aktueller Systemzustand ohne `F*`‑Lizenzen dokumentiert; Teilnahme über Startkarte/Ausschreibung, geplante Enum‑Erweiterung vermerkt.
|
||||
- §15‑Tabelle (kompakt) integriert und auf ÖTO‑Referenz verlinkt; Bedeutungen „B,C“ und „LP“ erläutert. Hinweis aufgenommen, dass `RD4` derzeit nicht als Enum vorhanden ist und wie `RD3` behandelt wird.
|
||||
- Kombinationsreihen gemäß §15 ergänzt: `R1S2`, `R1S3`, `R1S4`, `R2S3`, `R2S4`, `R3S4` (neuer Unterabschnitt 2.6 mit Tabelle, identische Spalten wie 2.5).
|
||||
|
||||
### Behoben
|
||||
|
||||
- **Masterdata:** Qualifikations-Management für Funktionäre (Richter/Parcoursbauer) professionalisiert: Umstellung von unstrukturiertem Text auf offizielle ÖTO/FEI Master-Daten Referenzen (`QualifikationMasterTable`).
|
||||
|
||||
+1
-1
@@ -40,7 +40,7 @@ class ZnsImportService(
|
||||
private val funktionaerRepository: FunktionaerRepository,
|
||||
private val landRepository: LandRepository,
|
||||
private val bundeslandRepository: BundeslandRepository,
|
||||
private val licenseRepository: MasterdataLicenseRepository? = null,
|
||||
private val licenseRepository: LizenzRepository? = null,
|
||||
private val altersklassenRepository: AltersklassenRepository? = null
|
||||
) {
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# 📜 Reiter-Lizenzen & Startberechtigungen (OEPS)
|
||||
|
||||
Diese Dokumentation beschreibt die verschiedenen Lizenzstufen des **OEPS (Österreichischer Pferdesportverband)** und die
|
||||
daraus resultierenden Startberechtigungen für die Sparten **Dressur (CDN)** und **Springen (CSN)** gemäß ÖTO 2026.
|
||||
daraus resultierenden Startberechtigungen für die Sparten **Dressur (CDN)**, **Springen (CSN)**, **Vielseitigkeit (CCN/CCI)** und **Fahren (CAN/CAI)** gemäß ÖTO 2026.
|
||||
|
||||
## 1. Lizenztypen & Klassen
|
||||
|
||||
@@ -11,10 +11,10 @@ Prüfungen antreten darf.
|
||||
| Code | Bezeichnung | Beschreibung | ZNS-Mapping |
|
||||
|:--------|:-----------------|:-------------------------------------------------------|:-------------|
|
||||
| **LZF** | Lizenzfrei | Nur Startkarte oder Reiterpass vorhanden. | `LIZENZFREI` |
|
||||
| **R1** | Reiter-Lizenz 1 | Einstiegslizenz für Springen, Dressur, Vielseitigkeit. | `R1` |
|
||||
| **R1** | Reiter-Lizenz 1 | Einstiegslizenz für Springen. | `R1` |
|
||||
| **R2** | Reiter-Lizenz 2 | Fortgeschrittene (Springen bis LM/130cm). | `R2` |
|
||||
| **R3** | Reiter-Lizenz 3 | Schwere Klasse (Springen bis S/145cm). | `R3` |
|
||||
| **R4** | Reiter-Lizenz 4 | Höchste nationale Stufe (alle Klassen). | `R4` |
|
||||
| **R4** | Reiter-Lizenz 4 | Höchste nationale Stufe Springen (bis 160cm). | `R4` |
|
||||
| **RD1** | Dressur-Lizenz 1 | Speziallizenz nur für Dressur (Kl. A, L). | `RD1` |
|
||||
| **RD2** | Dressur-Lizenz 2 | Speziallizenz nur für Dressur (Kl. LM, M). | `RD2` |
|
||||
| **RD3** | Dressur-Lizenz 3 | Speziallizenz nur für Dressur (Kl. S). | `RD3` |
|
||||
@@ -23,6 +23,8 @@ Prüfungen antreten darf.
|
||||
|
||||
## 2. Startberechtigungen nach Sparten
|
||||
|
||||
> Referenz: ÖTO 2026, A‑Teil § 15 „Reiterlizenzen“. Eine komprimierte Matrix mit Klassen/Höhen und zulässigen Lizenzen findet sich in der Projekt‑Referenz: `docs/03_Domain/02_Reference/OETO_Regelwerk/OETO-2026_A-Teil-Allgemeiner-Teil_16-12-2025.md` (Kapitel „Reiterlizenzen, § 15 ÖTO“).
|
||||
|
||||
### 2.1 Springen (CSN)
|
||||
|
||||
Die Startberechtigung richtet sich nach der Hindernishöhe der jeweiligen Klasse (§ 200 B-Teil).
|
||||
@@ -44,11 +46,81 @@ Die Startberechtigung richtet sich nach dem Aufgabenniveau (§ 100 B-Teil).
|
||||
| Klasse | Niveau | Erforderliche Lizenz | Besonderheiten |
|
||||
|:---------------|:--------------------|:------------------------|:-----------------------------------------|
|
||||
| **lizenzfrei** | - | **LZF** (Reiterpass) | Inkl. First Ridden, Dressurreiterbewerbe |
|
||||
| **A** | Leicht | **R1 / RD1** oder höher | Grundausbildung |
|
||||
| **L** | Mittelleicht | **R1 / RD1** oder höher | - |
|
||||
| **LM** | Leicht-Mittelschwer | **R2 / RD2** oder höher | Kandare wahlweise |
|
||||
| **M** | Mittelschwer | **R2 / RD2** oder höher | Kandarenpflicht |
|
||||
| **S** | Schwer | **R3 / RD3** oder höher | St. Georg bis Grand Prix |
|
||||
| **A** | Leicht | **RD1** oder höher | Grundausbildung |
|
||||
| **L** | Mittelleicht | **RD1** oder höher | - |
|
||||
| **LM** | Leicht-Mittelschwer | **RD2** oder höher | Kandare wahlweise |
|
||||
| **M** | Mittelschwer | **RD2** oder höher | Kandarenpflicht |
|
||||
| **S** | Schwer | **RD3** oder höher | St. Georg bis Grand Prix |
|
||||
|
||||
Hinweis RD4: Das ÖTO kennt in der Tabelle Kombinationsfälle bis `RD4`. Im System ist derzeit kein `RD4`‑Enum modelliert; faktisch wird `RD4` wie `RD3` behandelt (Kappung auf Dressur S). Eine spätere Erweiterung um `RD4` ist vorbereitet, siehe CHANGELOG.
|
||||
|
||||
---
|
||||
|
||||
### 2.3 Vielseitigkeit (CCN/CCI)
|
||||
|
||||
Vielseitigkeit kombiniert Dressur, Gelände und Springen. Für die Startberechtigung gilt das Prinzip der kumulativen Mindestanforderung: Die jeweils geforderte Dressur- und Spring-Lizenz muss erfüllt sein (Details je Klasse und Ausschreibung, § 300 B‑Teil).
|
||||
|
||||
| Klasse (typ.) | Richtwert | Erforderliche Lizenzen (kumulativ) | Besonderheiten |
|
||||
|:--------------|:---------------------|:-----------------------------------|:--------------|
|
||||
| **E/Einsteiger** | national, Einstiegsniveau | **LZF** (Startkarte) ODER `RD1`+`R1` (wenn gefordert) | gem. Ausschreibung |
|
||||
| **A** | Einsteiger/Light | `RD1` UND `R1` | — |
|
||||
| **L** | Mittelleicht | `RD1` UND `R1` | — |
|
||||
| **M** | Mittelschwer | `RD2` UND `R2` | — |
|
||||
| **S** | Schwer | `RD3` UND `R3` | höhere Anforderungen international |
|
||||
|
||||
Hinweis: In der Praxis orientiert sich die Spring-Anforderung an der in der Ausschreibung angegebenen Hindernishöhe der Springprüfung; die Dressur-Anforderung am ausgeschriebenen Dressur-Niveau der Teilprüfung. Wo nationale Sonderregelungen (Ponys, Haflinger/Noriker) bestehen, gelten diese zusätzlich.
|
||||
|
||||
---
|
||||
|
||||
### 2.4 Fahren (CAN/CAI)
|
||||
|
||||
Für Fahren existieren im ÖTO/FEI‑Kontext eigene Fahrlizenzen (z. B. F1–F3). Im aktuellen Systemmodell sind diese noch nicht als `ReiterLizenzKlasseE` hinterlegt. Bis zur Einführung entsprechender Enums/Regeln gilt:
|
||||
|
||||
| Klasse (typ.) | Richtwert | Systemzustand | Teilnahmevoraussetzung (heute) |
|
||||
|:--------------|:-----------------|:----------------------------------------------|:-------------------------------|
|
||||
| **A/L/M/S** | national/intern. | Keine `F*`‑Lizenztypen im Enum vorhanden | **Startkarte** (LZF) plus Zulassung laut Ausschreibung/Verbandsvorgaben |
|
||||
|
||||
Geplante Erweiterung: Ergänzung der Enum‑Werte `F1`..`F3` und einer Fahr‑Matrix analog Dressur/Springen (separates Ticket). Die ZNS‑Integration bleibt davon unberührt, bis ZNS eigene Fahr‑Kodierungen liefert.
|
||||
|
||||
---
|
||||
|
||||
### 2.5 Übersichtstabelle gemäß § 15 (kompakt)
|
||||
|
||||
Zur schnellen Orientierung ist die folgende, auf die System‑Enums abgebildete Kurzfassung der §15‑Tabelle enthalten. „B,C“ bedeutet „Turnierkategorien B, C, C‑NEU zulässig“, „LP“ steht für „Lizenzprüfung erforderlich“.
|
||||
|
||||
| Klasse/Höhe | V 80 | 60–100 | V 90 | A | 105–110 | V 100 | L | 115–120 | V 105 | LM | 125–130 | M | 135 | V110–115 | S | V120 | 140–145 | 150–160 |
|
||||
|:-----------:|:----:|:------:|:----:|:---:|:-------:|:-----:|:---:|:-------:|:-----:|:---:|:-------:|:---:|:---:|:---------:|:---:|:----:|:-------:|:-------:|
|
||||
| Sparte | V | S | V | D | S | V | D | S | V | D | S | D | S | V | D | V | S | S |
|
||||
| R1 | X | X | X | B,C | B,C | X | B,C | B,C | | | | | | | | | | |
|
||||
| R2 | X | X | X | X | X | X | X | X | X | X | X | LP | | | | | | |
|
||||
| R3 | X | X | X | X | X | X | X | X | X | X | X | X | X | X | | X | X | |
|
||||
| RD1 | | | | B,C | | | B,C | | | | | | | | | | | |
|
||||
| RD2 | | | | X | | | X | | | X | | LP | | | | | | |
|
||||
| RD3 | | | | X | | | X | | | X | | X | | | X | | | |
|
||||
| R4 | X | X | X | X | X | X | X | X | X | X | X | X | X | X | | X | X | X |
|
||||
|
||||
Abbildung im System:
|
||||
- Zeilen mit Kombi‑Lizenzen (z. B. `R1D2`, `RDS4`) werden als gleichzeitiger Besitz getrennter Lizenzen interpretiert (zwei Einträge in `Reiter.lizenzen`).
|
||||
- `RD4` ist aktuell nicht als Enum verfügbar; bis zur Erweiterung entspricht das Verhalten `RD3`.
|
||||
|
||||
---
|
||||
|
||||
### 2.6 Kombinationsreihen (§ 15)
|
||||
|
||||
Die folgenden Zeilen aus der ÖTO‑Tabelle (§ 15) bilden Kombinationsfälle ab, die sich aus gleichzeitig erfüllten
|
||||
Voraussetzungen ergeben. Sie sind systemisch als Parallelbesitz mehrerer Lizenzen zu verstehen und dienen der
|
||||
schnellen Orientierung. Spaltenbezeichnungen entsprechen Abschnitt 2.5.
|
||||
|
||||
| Klasse/Höhe | V 80 | 60–100 | V 90 | A | 105–110 | V 100 | L | 115–120 | V 105 | LM | 125–130 | M | 135 | V110–115 | S | V120 | 140–145 | 150–160 |
|
||||
|:-----------:|:----:|:------:|:----:|:---:|:-------:|:-----:|:---:|:-------:|:-----:|:---:|:-------:|:---:|:---:|:---------:|:---:|:----:|:-------:|:-------:|
|
||||
| R1S2 | X | X | X | B,C | X | X | B,C | X | | | X | | | | | | | |
|
||||
| R1S3 | X | X | X | B,C | X | X | B,C | X | | | X | | X | | | | X | |
|
||||
| R1S4 | X | X | X | B,C | X | X | B,C | X | | | X | | X | | | | X | X |
|
||||
| R2S3 | X | X | X | X | X | X | X | X | X | X | X | LP | X | | | | X | |
|
||||
| R2S4 | X | X | X | X | X | X | X | X | X | X | X | LP | X | | | | X | X |
|
||||
| R3S4 | X | X | X | X | X | X | X | X | X | X | X | X | X | X | | X | X | X |
|
||||
|
||||
Legende siehe Abschnitt 2.5 (Bedeutung von „B,C“/„LP“ sowie Spartenkürzel).
|
||||
|
||||
---
|
||||
|
||||
@@ -58,30 +130,58 @@ Die Startberechtigung richtet sich nach dem Aufgabenniveau (§ 100 B-Teil).
|
||||
|
||||
Für Rasse-spezifische Bewerbe gelten oft abweichende (niedrigere) Lizenz-Anforderungen für höhere Klassen.
|
||||
|
||||
Hinweis: Abweichungen sind disziplinspezifisch zu verstehen (Dressur → `RD*`, Springen → `R*`).
|
||||
|
||||
* **Dressur (Haflinger):**
|
||||
* Klasse L/LM: R(D)1 ausreichend.
|
||||
* Klasse M: R(D)3 erforderlich.
|
||||
* Klasse S: R(D)4 erforderlich.
|
||||
* Klasse L/LM: **RD1** ausreichend.
|
||||
* Klasse M: **RD3** erforderlich.
|
||||
* Klasse S: nationale Sonderregelungen, idR **RD3**; höhere Abweichungen gemäß Ausschreibung.
|
||||
* **Springen (Haflinger):**
|
||||
* 95-120cm (bis Klasse M): R1 ausreichend.
|
||||
* 125-135cm (Klasse S): R2 ausreichend.
|
||||
* 95–120cm (bis Klasse M): **R1** ausreichend.
|
||||
* 125–135cm (Klasse S): **R2** ausreichend.
|
||||
|
||||
### 3.2 Pony
|
||||
|
||||
* In Pony-Bewerben (bis Kl. L) ist die **Startkarte Allgemein** (Voraussetzung Reiterpass) ausreichend.
|
||||
* Ab Klasse LM ist eine entsprechende Lizenz (R1/RD1) erforderlich.
|
||||
* Ab Klasse LM ist eine entsprechende Lizenz erforderlich: **RD1** (Dressur) bzw. **R1** (Springen).
|
||||
|
||||
---
|
||||
|
||||
## 4. ZNS-Integration (LIZENZ01.dat)
|
||||
## 4. Validierungslogik & ZNS-Integration
|
||||
|
||||
### 4.1 Validierungslogik (Systemverhalten)
|
||||
|
||||
Die Eligibility wird in zwei strikt getrennten Schritten geprüft:
|
||||
|
||||
1) Spartenlizenz prüfen
|
||||
- Für Springen sind ausschließlich `R1..R4` gültig (keine Dressur-Lizenz ausreichend).
|
||||
- Für Dressur sind ausschließlich `RD1..RD3` gültig (keine Spring-Lizenz ausreichend).
|
||||
- Für Vielseitigkeit müssen die Anforderungen beider Sparten erfüllt sein: Dressur-Teilprüfung (RD*) UND Spring-Teilprüfung (R*), jeweils gemäß ausgeschriebener Klasse.
|
||||
- Für Fahren gibt es aktuell keine `F*`‑Lizenzen im System; bis zur Erweiterung wird Fahren als lizenzfrei (LZF) behandelt und organisatorisch über die Ausschreibung begrenzt.
|
||||
|
||||
2) Maximal erlaubte Turnierklasse ermitteln
|
||||
- Innerhalb der geprüften Sparte bestimmt die höchste bezahlte Lizenz die Obergrenze der Klasse/Höhe.
|
||||
- Zur internen Kappung wird ggf. ein disziplinübergreifendes Mapping verwendet (nur nach bestandener Spartenprüfung):
|
||||
- `R1↔RD1`, `R2↔RD2`, `R3/R4↔RD3`.
|
||||
|
||||
Konsequenz: Ein Reiter mit nur `RD2` darf Dressur bis LM/M reiten, aber im Springen nur lizenzfreie Bewerbe (E0). Umgekehrt
|
||||
ermöglicht eine `R2` Springen bis 130cm, aber keine Dressur-Klassen. In der Vielseitigkeit wären für eine M‑Klasse beide Nachweise (`RD2` UND `R2`) erforderlich.
|
||||
|
||||
### 4.2 ZNS-Integration (LIZENZ01.dat)
|
||||
|
||||
Das System mappt die Felder aus der ZNS-Datei automatisch auf die interne `LizenzKlasseE`.
|
||||
|
||||
* **Feld `Reiterlizenz` (Pos 137):** Enthält die Hauptlizenz (z.B. `R1`).
|
||||
* **Feld `Lizenz-Details` (Pos 201):** Enthält die Liste aller bezahlten Lizenzen (z.B. `RD1,F1`).
|
||||
* *Logik:* Ein Reiter mit `RD2` darf Dressur LM/M reiten, aber Springen nur lizenzfrei (E0), sofern keine `R1` (oder
|
||||
höher) vorhanden ist.
|
||||
* **Feld `Reiterlizenz` (Pos 137):** Enthält die primäre Lizenzangabe (z. B. `R1` oder `RD1`).
|
||||
* **Feld `Lizenz-Details` (Pos 201):** Enthält die Liste aller bezahlten Lizenzen (z. B. `RD1`). Werte für Fahren (`F*`) werden aktuell ignoriert, solange keine System‑Enums existieren.
|
||||
* Das System aggregiert alle Vorkommen und bestimmt je Sparte die höchste vorhandene Lizenz.
|
||||
|
||||
---
|
||||
> 📜 **Rulebook Expert Hinweis:** Die Startberechtigung muss bei jeder Nennung gegen die aktuelle Lizenz des Reiters (
|
||||
> Stichtag Nennschluss) geprüft werden. Eine Höherreihung während eines Turniers ist gemäß § 17 Abs. 6 ausgeschlossen.
|
||||
> 📜 **Rulebook Expert Hinweis:** Die Startberechtigung muss bei jeder Nennung gegen die aktuelle Lizenz des Reiters
|
||||
> (Stichtag Nennschluss) geprüft werden. Eine Höherreihung während eines Turniers ist gemäß § 17 Abs. 6 ausgeschlossen.
|
||||
|
||||
---
|
||||
|
||||
Siehe ergänzend:
|
||||
* `TURNIER_KLASSEN.md` für Klassendefinitionen und Höhen/Niveaus je Sparte.
|
||||
* `GEBUEHRENORDNUNG.md` für Start-/Nenngelder und Mindest-Geldpreise.
|
||||
* `ZNS_SCHNITTSTELLE.md` für Felder und Positionsangaben der LIZENZ01.dat.
|
||||
|
||||
+15
-3
@@ -192,13 +192,25 @@ data class Reiter(
|
||||
|
||||
/**
|
||||
* Checks if the rider holds a license for a specific discipline.
|
||||
* Simple logic for now: Any non-blank license field counts.
|
||||
* Strikte Logik: Eine Dressur-Lizenz (RD1..RD3) gilt nur für DRESSUR,
|
||||
* eine Reit-/Spring-Lizenz (R1..R4) nur für SPRINGEN. FAHREN nutzt die separate Fahr-Lizenz.
|
||||
*/
|
||||
fun hasLizenzForSparte(sparte: at.mocode.core.domain.model.SparteE): Boolean {
|
||||
val lk = lizenzKlasse
|
||||
return when (sparte) {
|
||||
at.mocode.core.domain.model.SparteE.DRESSUR -> !reiterLizenz.isNullOrBlank()
|
||||
at.mocode.core.domain.model.SparteE.SPRINGEN -> !reiterLizenz.isNullOrBlank()
|
||||
at.mocode.core.domain.model.SparteE.DRESSUR ->
|
||||
lk == ReiterLizenzKlasseE.RD1 ||
|
||||
lk == ReiterLizenzKlasseE.RD2 ||
|
||||
lk == ReiterLizenzKlasseE.RD3
|
||||
|
||||
at.mocode.core.domain.model.SparteE.SPRINGEN ->
|
||||
lk == ReiterLizenzKlasseE.R1 ||
|
||||
lk == ReiterLizenzKlasseE.R2 ||
|
||||
lk == ReiterLizenzKlasseE.R3 ||
|
||||
lk == ReiterLizenzKlasseE.R4
|
||||
|
||||
at.mocode.core.domain.model.SparteE.FAHREN -> !fahrLizenz.isNullOrBlank()
|
||||
|
||||
else -> hasLizenz()
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ import kotlinx.serialization.Serializable
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
@Serializable
|
||||
data class ReitLizenz(
|
||||
data class Reiterlizenz(
|
||||
@Serializable(with = UuidSerializer::class)
|
||||
val lizenzId: Uuid = Uuid.random(),
|
||||
val code: String,
|
||||
+3
-3
@@ -3,14 +3,14 @@
|
||||
package at.mocode.masterdata.domain.repository
|
||||
|
||||
import at.mocode.masterdata.domain.model.FahrLizenz
|
||||
import at.mocode.masterdata.domain.model.ReitLizenz
|
||||
import at.mocode.masterdata.domain.model.Reiterlizenz
|
||||
import at.mocode.masterdata.domain.model.Startkarte
|
||||
|
||||
/**
|
||||
* Repository für alle Lizenz-Stammdaten (Reit, Fahr, Startkarten).
|
||||
*/
|
||||
interface MasterdataLicenseRepository {
|
||||
suspend fun findReitLizenzByCode(code: String): ReitLizenz?
|
||||
interface LizenzRepository {
|
||||
suspend fun findReitLizenzByCode(code: String): Reiterlizenz?
|
||||
suspend fun findFahrLizenzByCode(code: String): FahrLizenz?
|
||||
suspend fun findStartkarteByCode(code: String): Startkarte?
|
||||
}
|
||||
-57
@@ -1,57 +0,0 @@
|
||||
package at.mocode.masterdata.domain.service
|
||||
|
||||
import at.mocode.core.domain.model.ReiterLizenzKlasseE
|
||||
import at.mocode.core.domain.model.SparteE
|
||||
import at.mocode.masterdata.domain.model.Reiter
|
||||
import at.mocode.masterdata.domain.model.LicenseMatrixEntry
|
||||
import at.mocode.masterdata.domain.model.TurnierklasseDefinition
|
||||
|
||||
/**
|
||||
* Standard-Implementierung des [LicenseMatrixService] gemäß ÖTO.
|
||||
*/
|
||||
class LicenseMatrixServiceImpl : LicenseMatrixService {
|
||||
|
||||
private val classHierarchy = listOf("E", "A", "L", "LM", "M", "S")
|
||||
|
||||
override fun isEligible(
|
||||
reiter: Reiter,
|
||||
turnierklasse: TurnierklasseDefinition,
|
||||
sparte: SparteE,
|
||||
matrix: List<LicenseMatrixEntry>,
|
||||
alleKlassen: List<TurnierklasseDefinition>
|
||||
): Boolean {
|
||||
// 1. Basis-Check: Hat der Reiter überhaupt eine Lizenz für diese Sparte?
|
||||
if (!reiter.hasLizenzForSparte(sparte)) return false
|
||||
|
||||
// 2. Max Turnierklasse aus Matrix ermitteln
|
||||
val maxClassCode = getMaxTurnierklasse(reiter, sparte, matrix) ?: return false
|
||||
|
||||
// 3. Hierarchie-Check (maxClassCode vs. turnierklasse.code)
|
||||
val maxIndex = classHierarchy.indexOf(maxClassCode)
|
||||
val targetIndex = classHierarchy.indexOf(turnierklasse.code)
|
||||
|
||||
if (maxIndex == -1 || targetIndex == -1) return false
|
||||
|
||||
return targetIndex <= maxIndex
|
||||
}
|
||||
|
||||
override fun getMaxTurnierklasse(
|
||||
reiter: Reiter,
|
||||
sparte: SparteE,
|
||||
matrix: List<LicenseMatrixEntry>
|
||||
): String? {
|
||||
// Suche passenden Eintrag in der Matrix für (Sparte, Lizenzklasse)
|
||||
val entry = matrix.find { it.sparte == sparte && it.lizenzKlasse == reiter.lizenzKlasse }
|
||||
?: matrix.find { it.sparte == SparteE.DRESSUR && sparte == SparteE.DRESSUR && it.lizenzKlasse == reiter.lizenzKlasse } // Fallback/Spezial
|
||||
?: if (reiter.lizenzKlasse == ReiterLizenzKlasseE.R1 ||
|
||||
reiter.lizenzKlasse == ReiterLizenzKlasseE.R2 ||
|
||||
reiter.lizenzKlasse == ReiterLizenzKlasseE.R3 ||
|
||||
reiter.lizenzKlasse == ReiterLizenzKlasseE.R4) {
|
||||
// Fallback für Dressur, wenn man eine Springlizenz hat (R1 gilt oft auch als RD1 etc. in manchen Kontexten,
|
||||
// aber hier schauen wir primär ob die Matrix einen generischen Eintrag hat)
|
||||
matrix.find { it.sparte == sparte && it.lizenzKlasse == ReiterLizenzKlasseE.LIZENZFREI }
|
||||
} else null
|
||||
|
||||
return entry?.maxTurnierklasseCode
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -8,7 +8,7 @@ import at.mocode.masterdata.domain.model.TurnierklasseDefinition
|
||||
/**
|
||||
* Service zur Prüfung der Teilnahmeberechtigung basierend auf der Lizenz-Matrix.
|
||||
*/
|
||||
interface LicenseMatrixService {
|
||||
interface LizenzMatrixService {
|
||||
|
||||
/**
|
||||
* Prüft, ob ein Reiter mit seiner aktuellen Lizenz in einer bestimmten Turnierklasse startberechtigt ist.
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
package at.mocode.masterdata.domain.service
|
||||
|
||||
import at.mocode.core.domain.model.ReiterLizenzKlasseE
|
||||
import at.mocode.core.domain.model.SparteE
|
||||
import at.mocode.masterdata.domain.model.Reiter
|
||||
import at.mocode.masterdata.domain.model.LicenseMatrixEntry
|
||||
import at.mocode.masterdata.domain.model.TurnierklasseDefinition
|
||||
|
||||
/**
|
||||
* Standard-Implementierung des [LizenzMatrixService] gemäß ÖTO.
|
||||
*/
|
||||
class LizenzMatrixServiceImpl : LizenzMatrixService {
|
||||
|
||||
private val classHierarchy = listOf("E", "A", "L", "LM", "M", "S")
|
||||
|
||||
override fun isEligible(
|
||||
reiter: Reiter,
|
||||
turnierklasse: TurnierklasseDefinition,
|
||||
sparte: SparteE,
|
||||
matrix: List<LicenseMatrixEntry>,
|
||||
alleKlassen: List<TurnierklasseDefinition>
|
||||
): Boolean {
|
||||
// 1. Basis-Check: Hat der Reiter überhaupt eine Lizenz für diese Sparte?
|
||||
if (!reiter.hasLizenzForSparte(sparte)) return false
|
||||
|
||||
// 2. Max Turnierklasse aus Matrix ermitteln
|
||||
val maxClassCode = getMaxTurnierklasse(reiter, sparte, matrix) ?: return false
|
||||
|
||||
// 3. Hierarchie-Check (maxClassCode vs. turnierklasse.code)
|
||||
val maxIndex = classHierarchy.indexOf(maxClassCode)
|
||||
val targetIndex = classHierarchy.indexOf(turnierklasse.code)
|
||||
|
||||
if (maxIndex == -1 || targetIndex == -1) return false
|
||||
|
||||
return targetIndex <= maxIndex
|
||||
}
|
||||
|
||||
override fun getMaxTurnierklasse(
|
||||
reiter: Reiter,
|
||||
sparte: SparteE,
|
||||
matrix: List<LicenseMatrixEntry>
|
||||
): String? {
|
||||
// 1) Direkter Treffer in Matrix für (Sparte, Lizenzklasse)
|
||||
val direct = matrix.find { it.sparte == sparte && it.lizenzKlasse == reiter.lizenzKlasse }
|
||||
if (direct != null) return direct.maxTurnierklasseCode
|
||||
|
||||
// 2) Cross-Discipline Mapping (R<->RD) gemäß ÖTO-Äquivalenzen
|
||||
// Hinweis: RD4 ist im aktuellen Enum nicht modelliert. R4 wird für Zwecke der
|
||||
// Dressur-Kappung wie RD3 behandelt (max. S), bis RD4 eingeführt wird.
|
||||
val mappedLizenz = when (sparte) {
|
||||
SparteE.DRESSUR -> when (reiter.lizenzKlasse) {
|
||||
ReiterLizenzKlasseE.R1 -> ReiterLizenzKlasseE.RD1
|
||||
ReiterLizenzKlasseE.R2 -> ReiterLizenzKlasseE.RD2
|
||||
ReiterLizenzKlasseE.R3, ReiterLizenzKlasseE.R4 -> ReiterLizenzKlasseE.RD3 // RD4 derzeit nicht modelliert
|
||||
else -> reiter.lizenzKlasse
|
||||
}
|
||||
SparteE.SPRINGEN -> when (reiter.lizenzKlasse) {
|
||||
ReiterLizenzKlasseE.RD1 -> ReiterLizenzKlasseE.R1
|
||||
ReiterLizenzKlasseE.RD2 -> ReiterLizenzKlasseE.R2
|
||||
ReiterLizenzKlasseE.RD3 -> ReiterLizenzKlasseE.R3
|
||||
else -> reiter.lizenzKlasse
|
||||
}
|
||||
else -> reiter.lizenzKlasse
|
||||
}
|
||||
val cross = matrix.find { it.sparte == sparte && it.lizenzKlasse == mappedLizenz }
|
||||
if (cross != null) return cross.maxTurnierklasseCode
|
||||
|
||||
// 3) Letzter Fallback: LIZENZFREI in gewünschter Sparte
|
||||
return matrix.find { it.sparte == sparte && it.lizenzKlasse == ReiterLizenzKlasseE.LIZENZFREI }
|
||||
?.maxTurnierklasseCode
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -13,9 +13,9 @@ import kotlin.test.assertTrue
|
||||
import kotlin.time.Clock
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
class LicenseMatrixServiceTest {
|
||||
class LiznezMatrixServiceTest {
|
||||
|
||||
private val service = LicenseMatrixServiceImpl()
|
||||
private val service = LizenzMatrixServiceImpl()
|
||||
private val nun = Clock.System.now()
|
||||
|
||||
private val matrix = listOf(
|
||||
+10
-10
@@ -29,7 +29,7 @@ class ExposedRegulationRepository : RegulationRepository {
|
||||
}
|
||||
|
||||
override suspend fun findAllLicenseMatrixEntries(): List<LicenseMatrixEntry> = DatabaseFactory.dbQuery {
|
||||
LicenseTable.selectAll()
|
||||
LizenzTable.selectAll()
|
||||
.map { it.toLicenseMatrixEntry() }
|
||||
}
|
||||
|
||||
@@ -89,15 +89,15 @@ class ExposedRegulationRepository : RegulationRepository {
|
||||
)
|
||||
|
||||
private fun ResultRow.toLicenseMatrixEntry() = LicenseMatrixEntry(
|
||||
licenseId = this[LicenseTable.id],
|
||||
sparte = SparteE.valueOf(this[LicenseTable.sparte]),
|
||||
lizenzKlasse = ReiterLizenzKlasseE.valueOf(this[LicenseTable.lizenzKlasse]),
|
||||
maxTurnierklasseCode = this[LicenseTable.maxTurnierklasseCode],
|
||||
validFrom = this[LicenseTable.validFrom].toKtInstant(),
|
||||
validTo = this[LicenseTable.validTo]?.toOptionalKtInstant(),
|
||||
istAktiv = this[LicenseTable.istAktiv],
|
||||
createdAt = this[LicenseTable.createdAt].toKtInstant(),
|
||||
updatedAt = this[LicenseTable.updatedAt].toKtInstant()
|
||||
licenseId = this[LizenzTable.id],
|
||||
sparte = SparteE.valueOf(this[LizenzTable.sparte]),
|
||||
lizenzKlasse = ReiterLizenzKlasseE.valueOf(this[LizenzTable.lizenzKlasse]),
|
||||
maxTurnierklasseCode = this[LizenzTable.maxTurnierklasseCode],
|
||||
validFrom = this[LizenzTable.validFrom].toKtInstant(),
|
||||
validTo = this[LizenzTable.validTo]?.toOptionalKtInstant(),
|
||||
istAktiv = this[LizenzTable.istAktiv],
|
||||
createdAt = this[LizenzTable.createdAt].toKtInstant(),
|
||||
updatedAt = this[LizenzTable.updatedAt].toKtInstant()
|
||||
)
|
||||
|
||||
private fun ResultRow.toRichtverfahrenDefinition() = RichtverfahrenDefinition(
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ import org.jetbrains.exposed.v1.datetime.timestamp
|
||||
* Exposed-Tabellendefinition für die Lizenz-Matrix (Reiter-Lizenz vs. Turnierklasse).
|
||||
* Basierend auf ÖTO 2026.
|
||||
*/
|
||||
object LicenseTable : Table("license_matrix") {
|
||||
object LizenzTable : Table("license_matrix") {
|
||||
val id = uuid("license_id")
|
||||
val sparte = varchar("sparte", 20) // DRESSUR, SPRINGEN, ALLGEMEIN
|
||||
val lizenzKlasse = varchar("lizenz_klasse", 20) // R1, R2, R3, RD1, RD2, RD3, LF
|
||||
+6
-6
@@ -4,21 +4,21 @@ package at.mocode.masterdata.infrastructure.persistence.reiter
|
||||
|
||||
import at.mocode.core.utils.database.DatabaseFactory
|
||||
import at.mocode.masterdata.domain.model.FahrLizenz
|
||||
import at.mocode.masterdata.domain.model.ReitLizenz
|
||||
import at.mocode.masterdata.domain.model.Reiterlizenz
|
||||
import at.mocode.masterdata.domain.model.Startkarte
|
||||
import at.mocode.masterdata.domain.repository.MasterdataLicenseRepository
|
||||
import at.mocode.masterdata.domain.repository.LizenzRepository
|
||||
import org.jetbrains.exposed.v1.core.eq
|
||||
import org.jetbrains.exposed.v1.jdbc.selectAll
|
||||
|
||||
/**
|
||||
* Exposed-basierte Implementierung des MasterdataLicenseRepository.
|
||||
* Exposed-basierte Implementierung des LizenzRepository.
|
||||
*/
|
||||
class MasterdataLicenseExposedRepository : MasterdataLicenseRepository {
|
||||
class MasterdataLicenseExposedRepository : LizenzRepository {
|
||||
|
||||
override suspend fun findReitLizenzByCode(code: String): ReitLizenz? = DatabaseFactory.dbQuery {
|
||||
override suspend fun findReitLizenzByCode(code: String): Reiterlizenz? = DatabaseFactory.dbQuery {
|
||||
ReitLizenzenTable.selectAll().where { ReitLizenzenTable.code eq code }
|
||||
.map {
|
||||
ReitLizenz(
|
||||
Reiterlizenz(
|
||||
lizenzId = it[ReitLizenzenTable.id],
|
||||
code = it[ReitLizenzenTable.code],
|
||||
bezeichnung = it[ReitLizenzenTable.bezeichnung],
|
||||
|
||||
+2
-2
@@ -29,7 +29,7 @@ class RegulationSeedVerificationTest {
|
||||
transaction {
|
||||
SchemaUtils.create(
|
||||
TurnierKlassenTable,
|
||||
LicenseTable,
|
||||
LizenzTable,
|
||||
RichtverfahrenTable,
|
||||
GebuehrTable,
|
||||
RegulationConfigTable,
|
||||
@@ -71,7 +71,7 @@ class RegulationSeedVerificationTest {
|
||||
|
||||
@Test
|
||||
fun `verify domain logic with simulated oeto data`() {
|
||||
val service = at.mocode.masterdata.domain.service.LicenseMatrixServiceImpl()
|
||||
val service = at.mocode.masterdata.domain.service.LizenzMatrixServiceImpl()
|
||||
val now = Clock.System.now()
|
||||
|
||||
val oetoMatrix = listOf(
|
||||
|
||||
+2
-2
@@ -59,7 +59,7 @@ class MasterdataDatabaseConfiguration(
|
||||
TurnierKlassenTable,
|
||||
TurnierSpartenTable,
|
||||
TurnierKategorienTable,
|
||||
LicenseTable,
|
||||
LizenzTable,
|
||||
RichtverfahrenTable,
|
||||
GebuehrTable,
|
||||
RegulationConfigTable
|
||||
@@ -112,7 +112,7 @@ class MasterdataTestDatabaseConfiguration {
|
||||
TurnierKlassenTable,
|
||||
TurnierSpartenTable,
|
||||
TurnierKategorienTable,
|
||||
LicenseTable,
|
||||
LizenzTable,
|
||||
RichtverfahrenTable,
|
||||
GebuehrTable,
|
||||
RegulationConfigTable
|
||||
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class)
|
||||
package at.mocode.masterdata.service.config
|
||||
|
||||
import at.mocode.masterdata.infrastructure.persistence.LizenzTable
|
||||
import jakarta.annotation.PostConstruct
|
||||
import org.jetbrains.exposed.v1.core.*
|
||||
import org.jetbrains.exposed.v1.jdbc.insert
|
||||
import org.jetbrains.exposed.v1.jdbc.selectAll
|
||||
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
|
||||
import org.jetbrains.exposed.v1.jdbc.update
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.context.annotation.DependsOn
|
||||
import org.springframework.context.annotation.Profile
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
/**
|
||||
* Seeder für Reiterlizenzen (license_matrix) gemäß ÖTO 2026.
|
||||
*
|
||||
* Ziel: Sicherstellen, dass bei Service-Start alle benötigten Lizenzeinträge
|
||||
* für Dressur und Springen vorhanden sind – auch wenn die DB leer ist oder
|
||||
* frühere Migrationen/Seeds nicht gelaufen sind.
|
||||
*
|
||||
* Idempotent: pro (sparte, lizenz_klasse) exakt ein Datensatz; bei Änderungen
|
||||
* der max_turnierklasse_code wird dieser upgedatet.
|
||||
*/
|
||||
@Configuration
|
||||
@Profile("!test")
|
||||
@DependsOn("masterdataDatabaseConfiguration")
|
||||
class ReiterlizenzenSeeder {
|
||||
private val log = LoggerFactory.getLogger(ReiterlizenzenSeeder::class.java)
|
||||
|
||||
@PostConstruct
|
||||
fun seed() {
|
||||
log.info("Starte Seeding der Reiterlizenzen (license_matrix)...")
|
||||
transaction {
|
||||
// Springen: LIZENZFREI→E, R1→L, R2→M, R3→S, R4→S
|
||||
upsert("SPRINGEN", "LIZENZFREI", "E")
|
||||
upsert("SPRINGEN", "R1", "L")
|
||||
upsert("SPRINGEN", "R2", "M")
|
||||
upsert("SPRINGEN", "R3", "S")
|
||||
upsert("SPRINGEN", "R4", "S")
|
||||
|
||||
// Dressur: LIZENZFREI→E, RD1→L, RD2→M, RD3→S
|
||||
upsert("DRESSUR", "LIZENZFREI", "E")
|
||||
upsert("DRESSUR", "RD1", "L")
|
||||
upsert("DRESSUR", "RD2", "M")
|
||||
upsert("DRESSUR", "RD3", "S")
|
||||
}
|
||||
log.info("Seeding der Reiterlizenzen abgeschlossen.")
|
||||
}
|
||||
|
||||
private fun upsert(sparte: String, lizenzKlasse: String, maxTurnierklasseCode: String) {
|
||||
val existing = LizenzTable.selectAll()
|
||||
.where { (LizenzTable.sparte eq sparte) and (LizenzTable.lizenzKlasse eq lizenzKlasse) }
|
||||
.singleOrNull()
|
||||
|
||||
if (existing == null) {
|
||||
LizenzTable.insert {
|
||||
it[id] = Uuid.random()
|
||||
it[LizenzTable.sparte] = sparte
|
||||
it[LizenzTable.lizenzKlasse] = lizenzKlasse
|
||||
it[LizenzTable.maxTurnierklasseCode] = maxTurnierklasseCode
|
||||
it[istAktiv] = true
|
||||
}
|
||||
log.debug("Lizenz-Matrix angelegt: {} / {} -> {}", sparte, lizenzKlasse, maxTurnierklasseCode)
|
||||
} else {
|
||||
val currentMax = existing[LizenzTable.maxTurnierklasseCode]
|
||||
val currentActive = existing[LizenzTable.istAktiv]
|
||||
if (currentMax != maxTurnierklasseCode || !currentActive) {
|
||||
LizenzTable.update({ (LizenzTable.sparte eq sparte) and (LizenzTable.lizenzKlasse eq lizenzKlasse) }) {
|
||||
it[LizenzTable.maxTurnierklasseCode] = maxTurnierklasseCode
|
||||
it[LizenzTable.istAktiv] = true
|
||||
}
|
||||
log.debug("Lizenz-Matrix aktualisiert: {} / {} -> {}", sparte, lizenzKlasse, maxTurnierklasseCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+59
-17
@@ -1,36 +1,79 @@
|
||||
-- V013__Cleanup_and_Standardize_Masterdata.sql
|
||||
-- Datum: 6. April 2026
|
||||
|
||||
-- 1. Bundesland -> bundeslaender
|
||||
ALTER TABLE bundesland RENAME TO bundeslaender;
|
||||
ALTER TABLE bundeslaender RENAME COLUMN id TO bundesland_id;
|
||||
ALTER INDEX IF EXISTS pk_bundesland RENAME TO pk_bundeslaender;
|
||||
ALTER INDEX IF EXISTS idx_bundesland_oeps RENAME TO idx_bundeslaender_oeps;
|
||||
ALTER INDEX IF EXISTS idx_bundesland_iso RENAME TO idx_bundeslaender_iso;
|
||||
ALTER INDEX IF EXISTS ux_bundesland_land_kuerzel RENAME TO ux_bundeslaender_land_kuerzel;
|
||||
ALTER INDEX IF EXISTS bundesland_bundesland_nr_unique RENAME TO bundeslaender_bundesland_nr_unique;
|
||||
-- 1. Bundesland -> bundeslaender (idempotent)
|
||||
DO $$
|
||||
BEGIN
|
||||
IF to_regclass('public.bundesland') IS NOT NULL AND to_regclass('public.bundeslaender') IS NULL THEN
|
||||
ALTER TABLE bundesland RENAME TO bundeslaender;
|
||||
END IF;
|
||||
|
||||
-- 2. qualifikation_master -> funktionaers_qualifikationen
|
||||
ALTER TABLE qualifikation_master RENAME TO funktionaers_qualifikationen;
|
||||
-- Spaltenumbenennung nur, wenn noch "id" existiert
|
||||
IF to_regclass('public.bundeslaender') IS NOT NULL THEN
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = 'public' AND table_name = 'bundeslaender' AND column_name = 'id'
|
||||
) THEN
|
||||
ALTER TABLE bundeslaender RENAME COLUMN id TO bundesland_id;
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
-- Optionale historische Index-Umbenennungen (nur wenn vorhanden)
|
||||
IF EXISTS (SELECT 1 FROM pg_class WHERE relname = 'pk_bundesland') THEN
|
||||
ALTER INDEX pk_bundesland RENAME TO pk_bundeslaender;
|
||||
END IF;
|
||||
IF EXISTS (SELECT 1 FROM pg_class WHERE relname = 'idx_bundesland_oeps') THEN
|
||||
ALTER INDEX idx_bundesland_oeps RENAME TO idx_bundeslaender_oeps;
|
||||
END IF;
|
||||
IF EXISTS (SELECT 1 FROM pg_class WHERE relname = 'idx_bundesland_iso') THEN
|
||||
ALTER INDEX idx_bundesland_iso RENAME TO idx_bundeslaender_iso;
|
||||
END IF;
|
||||
IF EXISTS (SELECT 1 FROM pg_class WHERE relname = 'ux_bundesland_land_kuerzel') THEN
|
||||
ALTER INDEX ux_bundesland_land_kuerzel RENAME TO ux_bundeslaender_land_kuerzel;
|
||||
END IF;
|
||||
IF EXISTS (SELECT 1 FROM pg_class WHERE relname = 'bundesland_bundesland_nr_unique') THEN
|
||||
ALTER INDEX bundesland_bundesland_nr_unique RENAME TO bundeslaender_bundesland_nr_unique;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- 2. qualifikation_master -> funktionaers_qualifikationen (idempotent)
|
||||
DO $$
|
||||
BEGIN
|
||||
IF to_regclass('public.qualifikation_master') IS NOT NULL AND to_regclass('public.funktionaers_qualifikationen') IS NULL THEN
|
||||
ALTER TABLE qualifikation_master RENAME TO funktionaers_qualifikationen;
|
||||
END IF;
|
||||
END $$;
|
||||
-- Die Join-Tabelle funktionaer_qualifikation bleibt als solche bestehen,
|
||||
-- referenziert aber nun funktionaers_qualifikationen.
|
||||
-- (Der Name der Join-Tabelle ist bereits fast korrekt, wir lassen sie vorerst so,
|
||||
-- da sie die Verknüpfung zw. Funktionär und Qualifikation darstellt.)
|
||||
-- Update: Der User möchte "funktionaers_qualifikationen" als Name für die Qualifikationen.
|
||||
|
||||
-- 3. reiter_lizenz -> reit_lizenzen
|
||||
ALTER TABLE reiter_lizenz RENAME TO reit_lizenzen;
|
||||
-- 3. reiter_lizenz -> reit_lizenzen (idempotent)
|
||||
DO $$
|
||||
BEGIN
|
||||
IF to_regclass('public.reiter_lizenz') IS NOT NULL AND to_regclass('public.reit_lizenzen') IS NULL THEN
|
||||
ALTER TABLE reiter_lizenz RENAME TO reit_lizenzen;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
ALTER INDEX IF EXISTS pk_reiter_lizenz RENAME TO pk_reit_lizenzen;
|
||||
|
||||
-- 4. reiter_sparte entfernen
|
||||
DROP TABLE IF EXISTS reiter_sparte;
|
||||
|
||||
-- 5. turnierklasse -> turnier_klassen
|
||||
ALTER TABLE turnierklasse RENAME TO turnier_klassen;
|
||||
-- 5. turnierklasse -> turnier_klassen (idempotent)
|
||||
-- Hinweis: In der aktuellen Codebasis verwendet Exposed `bewerbs_klassen` als technische Tabelle
|
||||
-- für Turnier-/Bewerbsklassen. Daher nur umbenennen, wenn die Alt-Tabelle tatsächlich existiert.
|
||||
DO $$
|
||||
BEGIN
|
||||
IF to_regclass('public.turnierklasse') IS NOT NULL AND to_regclass('public.turnier_klassen') IS NULL THEN
|
||||
ALTER TABLE turnierklasse RENAME TO turnier_klassen;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
ALTER INDEX IF EXISTS pk_turnierklasse RENAME TO pk_turnier_klassen;
|
||||
ALTER INDEX IF EXISTS idx_turnierklasse_sparte_code RENAME TO idx_turnier_klassen_sparte_code;
|
||||
|
||||
-- 6. turnier_sparten erstellen
|
||||
CREATE TABLE IF NOT EXISTS turnier_sparten (
|
||||
sparte_id UUID PRIMARY KEY,
|
||||
code VARCHAR(10) UNIQUE NOT NULL, -- z.B. D, S, V, F, R, C
|
||||
@@ -40,6 +83,5 @@ CREATE TABLE IF NOT EXISTS turnier_sparten (
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- 7. Constraints aktualisieren (falls nötig)
|
||||
-- Da wir nur Tabellen umbenannt haben, bleiben die Foreign Keys in PostgreSQL erhalten
|
||||
-- und zeigen automatisch auf die neuen Tabellennamen.
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@ class ZnsImportServiceApplication {
|
||||
funktionaerRepository: FunktionaerRepository,
|
||||
landRepository: LandRepository,
|
||||
bundeslandRepository: BundeslandRepository,
|
||||
licenseRepository: MasterdataLicenseRepository,
|
||||
licenseRepository: LizenzRepository,
|
||||
altersklassenRepository: AltersklassenRepository
|
||||
): ZnsImportService {
|
||||
return ZnsImportService(
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ class RepositoryConfiguration {
|
||||
fun funktionaerRepository(): FunktionaerRepository = FunktionaerExposedRepository()
|
||||
|
||||
@Bean
|
||||
fun licenseRepository(): MasterdataLicenseRepository = MasterdataLicenseExposedRepository()
|
||||
fun licenseRepository(): LizenzRepository = MasterdataLicenseExposedRepository()
|
||||
|
||||
@Bean
|
||||
fun altersklassenRepository(): AltersklassenRepository = AltersklassenExposedRepository()
|
||||
|
||||
@@ -4,6 +4,7 @@ import at.mocode.core.domain.model.DatenQuelleE
|
||||
import at.mocode.core.domain.model.ReiterLizenzKlasseE
|
||||
import at.mocode.core.utils.parser.FixedWidthLineReader
|
||||
import at.mocode.masterdata.domain.model.Reiter
|
||||
import at.mocode.masterdata.domain.model.ReiterLizenz
|
||||
import kotlin.uuid.ExperimentalUuidApi
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
@@ -50,17 +51,33 @@ object ZnsReiterParser {
|
||||
val feiId = reader.getString(190, 8)
|
||||
val sperrListe = reader.getString(198, 1)
|
||||
val lizenzInfo = reader.getString(201, 10)
|
||||
val lizenzKlasse = mapLizenz(reiterLizenzCode)
|
||||
|
||||
val lizenzen = mutableListOf<at.mocode.masterdata.domain.model.ReiterLizenz>()
|
||||
// Lizenz-Token aus gesamter Zeile robust extrahieren und normalisieren
|
||||
val parsedToken = ZnsReiterlicenseTokenizer.parseFromLine(line)
|
||||
|
||||
val lizenzen = mutableListOf<ReiterLizenz>()
|
||||
// Aus festem Feld
|
||||
if (reiterLizenzCode.isNotBlank()) {
|
||||
lizenzen.add(at.mocode.masterdata.domain.model.ReiterLizenz(lizenzTyp = "REITERLIZENZ", kuerzel = reiterLizenzCode))
|
||||
lizenzen.add(ReiterLizenz(lizenzTyp = "REITERLIZENZ", kuerzel = reiterLizenzCode))
|
||||
}
|
||||
// Aus erkanntem kombinierten Token weitere Einträge ergänzen
|
||||
parsedToken?.normalizedKuerzel?.forEach { code ->
|
||||
if (code.isNotBlank() && lizenzen.none { it.kuerzel.equals(code, ignoreCase = true) }) {
|
||||
lizenzen.add(ReiterLizenz(lizenzTyp = "REITERLIZENZ", kuerzel = code))
|
||||
}
|
||||
}
|
||||
if (startkarteCode.isNotBlank()) {
|
||||
lizenzen.add(at.mocode.masterdata.domain.model.ReiterLizenz(lizenzTyp = "STARTKARTE", kuerzel = startkarteCode))
|
||||
lizenzen.add(ReiterLizenz(lizenzTyp = "STARTKARTE", kuerzel = startkarteCode))
|
||||
}
|
||||
if (fahrLizenzCode.isNotBlank()) {
|
||||
lizenzen.add(at.mocode.masterdata.domain.model.ReiterLizenz(lizenzTyp = "FAHRLIZENZ", kuerzel = fahrLizenzCode))
|
||||
lizenzen.add(ReiterLizenz(lizenzTyp = "FAHRLIZENZ", kuerzel = fahrLizenzCode))
|
||||
}
|
||||
|
||||
// lizenzKlasse befüllen: bevorzugt aus festem Feld, sonst aus Token ableiten (präferiere Dressur, sonst Springen)
|
||||
val lizenzKlasse = when {
|
||||
reiterLizenzCode.isNotBlank() -> mapLizenz(reiterLizenzCode)
|
||||
parsedToken != null -> parsedToken.primaryKlasse
|
||||
else -> ReiterLizenzKlasseE.LIZENZFREI
|
||||
}
|
||||
|
||||
return Reiter(
|
||||
@@ -71,7 +88,8 @@ object ZnsReiterParser {
|
||||
bundeslandNummer = bundeslandNummer,
|
||||
vereinsName = vereinsName.ifBlank { null },
|
||||
nation = nation.ifBlank { null },
|
||||
reiterLizenz = reiterLizenzCode.ifBlank { null },
|
||||
reiterLizenz = (reiterLizenzCode.ifBlank { null }
|
||||
?: parsedToken?.primaryKuerzel),
|
||||
startkarte = startkarteCode.ifBlank { null },
|
||||
fahrLizenz = fahrLizenzCode.ifBlank { null },
|
||||
altersklasseJgJrU25 = altersklasseEnum,
|
||||
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
package at.mocode.zns.parser
|
||||
|
||||
import at.mocode.core.domain.model.ReiterLizenzKlasseE
|
||||
|
||||
/**
|
||||
* Erkennung und Normalisierung von Lizenz-Token in einer LIZENZ01.DAT-Zeile.
|
||||
* Unterstützt Einzel- und Kombinationsformen (R{n}, RD{m}, S{n}, D{m}, R{n}S{k}, R{n}D{m}, RDS4).
|
||||
*/
|
||||
object ZnsReiterlicenseTokenizer {
|
||||
private val tokenRegex = Regex(
|
||||
pattern = "(RDS4|R[1-4]D[2-4]|R[1-4]S[2-4]|RD[1-4]|R[1-4]|S[1-4]|D[2-4])",
|
||||
options = setOf(RegexOption.IGNORE_CASE)
|
||||
)
|
||||
|
||||
data class Parsed(
|
||||
val rawToken: String,
|
||||
/** Normalisierte Kürzel-Liste, z.B. ["R3"], ["R2","RD3"] */
|
||||
val normalizedKuerzel: List<String>,
|
||||
/** Primäre Lizenzklasse für das vorhandene Domainfeld (Fallback): bevorzugt RD*, sonst R* */
|
||||
val primaryKlasse: ReiterLizenzKlasseE,
|
||||
/** Primäres Kürzel passend zur primaryKlasse */
|
||||
val primaryKuerzel: String?
|
||||
)
|
||||
|
||||
fun parseFromLine(line: String): Parsed? {
|
||||
val matches = tokenRegex.findAll(line)
|
||||
val last = matches.lastOrNull() ?: return null
|
||||
val token = last.value.uppercase()
|
||||
return normalize(token)
|
||||
}
|
||||
|
||||
private fun normalize(token: String): Parsed? {
|
||||
val upper = token.uppercase()
|
||||
return when {
|
||||
upper == "RDS4" -> Parsed(
|
||||
rawToken = token,
|
||||
normalizedKuerzel = listOf("R4", "RD3"),
|
||||
primaryKlasse = ReiterLizenzKlasseE.RD3,
|
||||
primaryKuerzel = "RD3"
|
||||
)
|
||||
upper.matches(Regex("R[1-4]D[2-4]")) -> {
|
||||
val r = upper.substring(0, 2) // Rn
|
||||
// upper is RnDm -> build RDm and ggf. kappen
|
||||
val d = capRd4ToRd3("RD" + upper.substring(3, 4))
|
||||
Parsed(token, listOf(r, d), mapPrimary(d, r), pickPrimaryKuerzel(d, r))
|
||||
}
|
||||
upper.matches(Regex("R[1-4]S[2-4]")) -> {
|
||||
val r1 = upper.substring(0, 2)
|
||||
val r2 = "R" + upper.substring(3, 4) // S{k} -> R{k}
|
||||
val best = maxR(r1, r2)
|
||||
Parsed(token, listOf(best), ReiterLizenzKlasseE.valueOf(best), best)
|
||||
}
|
||||
upper.matches(Regex("RD[1-4]")) -> {
|
||||
val d = capRd4ToRd3(upper)
|
||||
Parsed(token, listOf(d), mapPrimary(d, null), d)
|
||||
}
|
||||
upper.matches(Regex("R[1-4]")) -> {
|
||||
Parsed(token, listOf(upper), ReiterLizenzKlasseE.valueOf(upper), upper)
|
||||
}
|
||||
upper.matches(Regex("S[1-4]")) -> {
|
||||
val r = "R" + upper.substring(1, 2)
|
||||
Parsed(token, listOf(r), ReiterLizenzKlasseE.valueOf(r), r)
|
||||
}
|
||||
upper.matches(Regex("D[2-4]")) -> {
|
||||
val d = capRd4ToRd3("RD" + upper.substring(1, 2))
|
||||
Parsed(token, listOf(d), mapPrimary(d, null), d)
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun capRd4ToRd3(code: String): String = if (code.equals("RD4", true)) "RD3" else code
|
||||
|
||||
private fun maxR(r1: String, r2: String): String {
|
||||
val n1 = r1.removePrefix("R").toIntOrNull() ?: 0
|
||||
val n2 = r2.removePrefix("R").toIntOrNull() ?: 0
|
||||
val n = maxOf(n1, n2).coerceIn(1, 4)
|
||||
return "R$n"
|
||||
}
|
||||
|
||||
private fun mapPrimary(d: String?, r: String?): ReiterLizenzKlasseE = when {
|
||||
d != null -> ReiterLizenzKlasseE.valueOf(d)
|
||||
r != null -> ReiterLizenzKlasseE.valueOf(r)
|
||||
else -> ReiterLizenzKlasseE.LIZENZFREI
|
||||
}
|
||||
|
||||
private fun pickPrimaryKuerzel(d: String?, r: String?): String? = d ?: r
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package at.mocode.zns.parser
|
||||
|
||||
import at.mocode.core.domain.model.ReiterLizenzKlasseE
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotNull
|
||||
import kotlin.test.assertNull
|
||||
|
||||
class LicenseTokenizerTest {
|
||||
|
||||
@Test
|
||||
fun `detects R2S3 and normalizes to R3`() {
|
||||
val line = "... AUTR2S3 903801690699 18109450 2025W1990100310137032 R2S3 "
|
||||
val parsed = ZnsReiterlicenseTokenizer.parseFromLine(line)
|
||||
assertNotNull(parsed)
|
||||
assertEquals(listOf("R3"), parsed.normalizedKuerzel)
|
||||
assertEquals(ReiterLizenzKlasseE.R3, parsed.primaryKlasse)
|
||||
assertEquals("R3", parsed.primaryKuerzel)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `detects R2D4 and caps to RD3`() {
|
||||
val line = "... AUTR2D4 105500130676 6820868 2025W1987090510112093 R2D4 "
|
||||
val parsed = ZnsReiterlicenseTokenizer.parseFromLine(line)
|
||||
assertNotNull(parsed)
|
||||
assertEquals(listOf("R2", "RD3"), parsed.normalizedKuerzel)
|
||||
assertEquals(ReiterLizenzKlasseE.RD3, parsed.primaryKlasse)
|
||||
assertEquals("RD3", parsed.primaryKuerzel)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `detects RD2`() {
|
||||
val line = "... AUTRD2 900308190664 3462613 2021W19650928 "
|
||||
val parsed = ZnsReiterlicenseTokenizer.parseFromLine(line)
|
||||
assertNotNull(parsed)
|
||||
assertEquals(listOf("RD2"), parsed.normalizedKuerzel)
|
||||
assertEquals(ReiterLizenzKlasseE.RD2, parsed.primaryKlasse)
|
||||
assertEquals("RD2", parsed.primaryKuerzel)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns null when no token present`() {
|
||||
val line = "some random line without token"
|
||||
val parsed = ZnsReiterlicenseTokenizer.parseFromLine(line)
|
||||
assertNull(parsed)
|
||||
}
|
||||
}
|
||||
@@ -29,14 +29,14 @@ Dieses Dokument definiert die **Ubiquitous Language** (allgegenwärtige Sprache)
|
||||
## K - O
|
||||
|
||||
* **Lebensnummer:** Eine 9-stellige Nummer (bzw. 15-stellig international), die ein Pferd bei der Geburt vom Zuchtverband erhält. Dient der eindeutigen Identifizierung, ist aber im OEPS-Kontext bei ausländischen Pferden oft generiert und daher nicht zur Suche geeignet.
|
||||
* **Lizenz (Reit-Lizenz):** Die Qualifikationsstufe eines Reiters (z.B. `R1`, `RD1`, `RD2`, `RS2`). Ein Reiter hat 0..1 Reit-Lizenz. Wird in der Tabelle `reit_lizenzen` als Stammdaten geführt und am Reiter per `reit_lizenz_id` referenziert.
|
||||
* **Lizenz (Reiterlizenz):** Die Qualifikationsstufe eines Reiters (z.B. `R1`, `RD1`, `RD2`, `RS2`). Ein Reiter hat 0..1 Reiterlizenz. Wird in der Tabelle `reiterlizenzen` als Stammdaten geführt und am Reiter per `reit_lizenz_id` referenziert.
|
||||
* **Fahr-Lizenz:** Eigenständige Lizenzkategorie für den Fahrsport (z.B. `F1`, `F2`). Ein Reiter hat 0..1 Fahr-Lizenz. Stammdaten-Tabelle `fahr_lizenzen`, Referenz am Reiter `fahr_lizenz_id`.
|
||||
* **Nennung:** Die verbindliche Anmeldung eines Paares (Reiter & Pferd) zu einem -> *Bewerb*.
|
||||
* **OEPS:** Österreichischer Pferdesportverband.
|
||||
|
||||
## P - T
|
||||
|
||||
* **Reit-Lizenzen (Historie):** Optionale Historien-Zuordnungen in `reiter_lizenzen_zuordnung` (z.B. Jahreswechsel). Enthalten den Typ (`REITERLIZENZ`, `STARTKARTE`, `FAHRLIZENZ`), das Kürzel und optional `gültig_bis`.
|
||||
* **Reiterlizenzen (Historie):** Optionale Historien-Zuordnungen in `reiter_lizenzen_zuordnung` (z.B. Jahreswechsel). Enthalten den Typ (`REITERLIZENZ`, `STARTKARTE`, `FAHRLIZENZ`), das Kürzel und optional `gültig_bis`.
|
||||
* **Satznummer:**
|
||||
* *Pferd:* 10-stellige, rein numerische ID (z.B. `0000123456`), die ein Pferd in der OEPS-Datenbank eindeutig identifiziert. **Primärer Schlüssel für den Datenaustausch.**
|
||||
* *Reiter:* 6-stellige, rein numerische ID für Personen.
|
||||
|
||||
@@ -95,7 +95,7 @@ Die ÖTO definiert sparten- und klassenabhängige Schwellenwerte, ab wievielen S
|
||||
| Begriff | Definition | ÖTO-Referenz |
|
||||
|------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------|
|
||||
| **Lebensnummer** | 9-stellige (national) bzw. 15-stellige (international, UELN) Nummer, die ein Pferd bei der Geburt vom Zuchtverband erhält. Bei ausländischen Pferden im OEPS oft **generiert** → **nicht zur Suche geeignet**. Die ZNS-Daten zu Lebensnummern sind erfahrungsgemäß inkonsistent und widersprüchlich (z.B. Farbe `"Braun"` vs. `"Brauner"` für dasselbe Pferd). Primärer Schlüssel für den Datenaustausch bleibt die → *Satznummer*. | – |
|
||||
| **Lizenz (Reit-Lizenz)** | Qualifikationsstufe eines Reiters (z.B. `R1`, `RD1`, `RD2`, `RS2`). Ein Reiter hat 0..1 Reit-Lizenz. Stammdaten in `reit_lizenzen`, Referenz am Reiter `reit_lizenz_id`. | ÖTO Teilnahmeberechtigung |
|
||||
| **Lizenz (Reiterlizenz)** | Qualifikationsstufe eines Reiters (z.B. `R1`, `RD1`, `RD2`, `RS2`). Ein Reiter hat 0..1 Reiterlizenz. Stammdaten in `reiterlizenzen`, Referenz am Reiter `reit_lizenz_id`. | ÖTO Teilnahmeberechtigung |
|
||||
| **Fahr-Lizenz** | Lizenzkategorie für den Fahrsport (z.B. `F1`, `F2`). Ein Reiter hat 0..1 Fahr-Lizenz. Stammdaten in `fahr_lizenzen`, Referenz am Reiter `fahr_lizenz_id`. | ÖTO Teilnahmeberechtigung |
|
||||
| **Startkarte** | Nachweis der Jahresgebühr. Ein Reiter hat 0..1 Startkarte. Stammdaten in `startkarten`, Referenz am Reiter `startkarte_id`. | ÖTO Teilnahmeberechtigung |
|
||||
|
||||
|
||||
+41
@@ -579,6 +579,44 @@ bewerben der Besitz des ÖFAB und bei Pleasure Drivingbewer-
|
||||
ben des PDC oder des ÖFAB.
|
||||
A-16 2026
|
||||
2026 A-17
|
||||
|
||||
---
|
||||
|
||||
### Reiterlizenzen, § 15 ÖTO
|
||||
|
||||
| Klasse/Höhe | V 80 | 60 - 100 | V 90 | A | 105 - 110 | V 100 | L | 115 - 120 | V 105 | LM | 125 - 130 | M | 135 | V 110 - 115 | S | V120 | 140 - 145 | 150 - 160 |
|
||||
|-------------|:----:|:--------:|:----:|:-----:|:---------:|:-----:|:-----:|:---------:|:-----:|:---:|:---------:|:----:|:---:|:-----------:|:---:|:----:|:---------:|:---------:|
|
||||
| Sparte | `V` | `S` | `V` | `D` | `S` | `V` | `D` | `S` | `V` | `D` | `S` | `D` | `S` | `V` | `D` | `V` | `S` | `S` |
|
||||
| R1 | X | X | X | `B,C` | `B,C` | X | `B,C` | `B,C` | | | | | | | | | | |
|
||||
| R2 | X | X | X | X | X | X | X | X | X | X | X | `LP` | | | | | | |
|
||||
| R3 | X | X | X | X | X | X | X | X | X | X | X | X | X | X | | X | X | |
|
||||
| RD1 | | | | `B,C` | | | `B,C` | | | | | | | | | | | |
|
||||
| RD2 | | | | X | | | X | | | X | | `LP` | | | | | | |
|
||||
| RD3 | | | | X | | | X | | | X | | X | | | | | | |
|
||||
| RD4 | | | | X | | | X | | | X | | X | | | X | | | |
|
||||
| R1D2 | X | X | X | X | `B,C` | X | X | `B,C` | | X | | `LP` | | | | | | |
|
||||
| R1D3 | X | X | X | X | `B,C` | X | X | `B,C` | | X | | X | | | | | | |
|
||||
| R2D3 | X | X | X | X | X | X | X | X | X | X | X | X | | | | | | |
|
||||
| R1D4 | X | X | X | X | `B,C` | X | X | `B,C` | | X | | X | | | X | | | |
|
||||
| R2D4 | X | X | X | X | X | X | X | X | X | X | X | X | | | X | | | |
|
||||
| R3D4 | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | |
|
||||
| RDS4 | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X |
|
||||
| R1S2 | X | X | X | `B,C` | X | X | `B,C` | X | | | X | | | | | | | |
|
||||
| R1S3 | X | X | X | `B,C` | X | X | `B,C` | X | | | X | | X | | | | X | |
|
||||
| R1S4 | X | X | X | `B,C` | X | X | `B,C` | X | | | X | | X | | | | X | X |
|
||||
| R2S3 | X | X | X | X | X | X | X | X | X | X | X | `LP` | X | | | | X | |
|
||||
| R2S4 | X | X | X | X | X | X | X | X | X | X | X | `LP` | X | | | | X | X |
|
||||
| R3S4 | X | X | X | X | X | X | X | X | X | X | X | X | X | X | | X | X | X |
|
||||
|
||||
`B,C` = Turniere der Kat. B, C und C-NEU
|
||||
`V` = Vielseitigkeit
|
||||
`S` = Springen
|
||||
`D` = Dressur
|
||||
`LP` = Lizenzprüfung
|
||||
Junioren (14 – 18 Jahre) haben die Möglichkeit, bereits mit der RD2 an FEI-Juniorenaufgaben teilzunehmen.
|
||||
|
||||
---
|
||||
|
||||
Reiterlizenzen, § 15 ÖTO
|
||||
V 60 – V 105 – V 115 – V 25 – V 110 V 140 – 150 –
|
||||
80 100 90 A 110 100 L 120 105 LM 1
|
||||
@@ -606,6 +644,9 @@ R2S4 x x x x x x x x x x x LP x x x
|
||||
R3S4 x x x x x x x x x x x x x x x x x
|
||||
B, C = Turniere der Kat. B, C und C-NEU*
|
||||
Junioren (14 – 18 Jahre) haben die Möglichkeit, bereits mit der RD2 an FEI-Juniorenaufgaben teilzunehmen.
|
||||
|
||||
---
|
||||
|
||||
§ 15
|
||||
Reiterlizenzen
|
||||
1. Für die Teilnahme an
|
||||
|
||||
Reference in New Issue
Block a user