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.
Some checks are pending
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
Some checks are pending
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:
parent
b7fa2d26a9
commit
7bf89c58d3
32
CHANGELOG.md
32
CHANGELOG.md
|
|
@ -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`).
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,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?
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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(
|
||||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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],
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ class ZnsImportServiceApplication {
|
|||
funktionaerRepository: FunktionaerRepository,
|
||||
landRepository: LandRepository,
|
||||
bundeslandRepository: BundeslandRepository,
|
||||
licenseRepository: MasterdataLicenseRepository,
|
||||
licenseRepository: LizenzRepository,
|
||||
altersklassenRepository: AltersklassenRepository
|
||||
): ZnsImportService {
|
||||
return ZnsImportService(
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 |
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user