feat(masterdata): introduce Regulation domain with API, persistence, and metrics integration

- Added `RegulationRepository` and its `Exposed` implementation for persistence.
- Implemented REST endpoints for regulations (`/rules`) in `RegulationController`, including support for tournament classes, license matrix, guidelines, fees, and configuration retrieval.
- Integrated OpenAPI documentation for `/rules` endpoints with Swagger UI in `masterdataApiModule`.
- Enabled Micrometer-based metrics for Prometheus in the API layer.
- Updated Gradle dependencies to include OpenAPI, Swagger, and Micrometer libraries.
- Registered `RegulationRepository` and `RegulationController` in `MasterdataConfiguration`.
- Improved database access patterns and reduced repetitive validation logic across domain services.
- Added unit and application tests for `RegulationController` to verify API behavior and repository interactions.
- Updated the service's `ROADMAP.md` to mark API v1 endpoints and observability as complete.

Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
This commit is contained in:
2026-03-30 15:38:13 +02:00
parent d8c9d11adb
commit 2f17778df6
29 changed files with 591 additions and 105 deletions
@@ -18,12 +18,12 @@ import kotlin.uuid.Uuid
*
* @property altersklasseId Eindeutiger interner Identifikator für diese Altersklassendefinition (UUID).
* @property altersklasseCode Ein eindeutiges Kürzel oder Code für die Altersklasse
* (z.B. "JGD_U16", "JUN_U18", "YR_U21", "AK", "PONY_U14"). Dient als fachlicher Schlüssel.
* (z. B. "JGD_U16", "JUN_U18", "YR_U21", "AK", "PONY_U14"). Dient als fachlicher Schlüssel.
* @property bezeichnung Die offizielle oder allgemein verständliche Bezeichnung der Altersklasse.
* @property minAlter Das Mindestalter (Jahre, inklusive) für diese Altersklasse. `null`, wenn es keine Untergrenze gibt.
* @property maxAlter Das Höchstalter (Jahre, inklusive) für diese Altersklasse. `null`, wenn es keine Obergrenze gibt.
* @property stichtagRegelText Eine Beschreibung der Regel für den Stichtag zur Altersberechnung
* (z.B. "31.12. des laufenden Kalenderjahres", "Geburtstag im laufenden Jahr").
* (z. B. "31.12. des laufenden Kalenderjahres", "Geburtstag im laufenden Jahr").
* @property sparteFilter Optionale Angabe, ob diese Altersklassendefinition nur für eine spezifische Sparte gilt.
* @property geschlechtFilter Optionaler Filter für das Geschlecht ('M', 'W'), falls die Altersklasse geschlechtsspezifisch ist.
* `null` bedeutet für alle Geschlechter gültig.
@@ -38,7 +38,7 @@ data class AltersklasseDefinition(
@Serializable(with = UuidSerializer::class)
val altersklasseId: Uuid = Uuid.random(), // Interner Primärschlüssel
var altersklasseCode: String, // Fachlicher PK, z.B. "JGD_U16"
var altersklasseCode: String, // Fachlicher PK, z. B. "JGD_U16"
var bezeichnung: String,
var minAlter: Int? = null,
var maxAlter: Int? = null,
@@ -16,11 +16,11 @@ import kotlin.uuid.Uuid
* @property bundeslandId Eindeutiger interner Identifikator für dieses Bundesland (UUID).
* @property landId Fremdschlüssel zur `LandDefinition`, dem dieses Bundesland angehört.
* @property oepsCode Der 2-stellige numerische OEPS-Code für österreichische Bundesländer
* (z.B. "01" für Wien, "02" für Niederösterreich). Sollte eindeutig sein für Land "Österreich".
* (z. B. "01" für Wien, "02" für Niederösterreich). Sollte eindeutig sein für Land "Österreich".
* @property iso3166_2_Code Optionaler offizieller ISO 3166-2 Code für das Bundesland
* (z.B. "AT-1" für Burgenland, "DE-BY" für Bayern).
* (z. B. "AT-1" für Burgenland, "DE-BY" für Bayern).
* @property name Der offizielle Name des Bundeslandes.
* @property kuerzel Ein gängiges Kürzel für das Bundesland (z.B. "NÖ", "W", "STMK").
* @property kuerzel Ein gängiges Kürzel für das Bundesland (z. B. "NÖ", "W", "STMK").
* @property wappenUrl Optionaler URL-Pfad zu einer Bilddatei des Bundeslandwappens.
* @property istAktiv Gibt an, ob dieses Bundesland aktuell im System ausgewählt/verwendet werden kann.
* @property sortierReihenfolge Optionale Zahl zur Steuerung der Sortierreihenfolge in Auswahllisten.
@@ -35,10 +35,10 @@ data class BundeslandDefinition(
@Serializable(with = UuidSerializer::class)
var landId: Uuid, // FK zu LandDefinition.landId
var oepsCode: String?, // z.B. "01", "02", ... für Österreich; eindeutig pro landId = Österreich
var iso3166_2_Code: String?, // z.B. "AT-1", "DE-BY"; Eindeutig global oder pro Land?
var name: String, // z.B. "Niederösterreich", "Bayern"
var kuerzel: String? = null, // z.B. "NÖ", "BY"
var oepsCode: String?, // z. B. "01", "02", ... für Österreich; eindeutig pro landId = Österreich
var iso3166_2_Code: String?, // z. B. "AT-1", "DE-BY"; Eindeutig global oder pro Land?
var name: String, // z. B. "Niederösterreich", "Bayern"
var kuerzel: String? = null, // z. B. "NÖ", "BY"
var wappenUrl: String? = null,
var istAktiv: Boolean = true,
var sortierReihenfolge: Int? = null,
@@ -18,7 +18,7 @@ import kotlin.uuid.Uuid
* Domain-Modell für einen Funktionär im actor-context.
*
* Repräsentiert eine Person mit einer definierten Rolle bei Turnieren (Richter, TBA,
* Parcoursbauer, etc.). Die Qualifikation wird gegen `RICHT01.DAT` aus dem ZNS geprüft.
* Parcoursbauer etc.). Die Qualifikation wird gegen `RICHT01.DAT` aus dem ZNS geprüft.
*
* Aggregate Root des `officials`-Bounded Context.
*
@@ -0,0 +1,18 @@
@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class)
package at.mocode.masterdata.domain.repository
import at.mocode.masterdata.domain.model.*
/**
* Repository für alle Regel-bezogenen Daten (Regulation-as-Data).
*/
interface RegulationRepository {
suspend fun findAllTurnierklassen(): List<TurnierklasseDefinition>
suspend fun findAllLicenseMatrixEntries(): List<LicenseMatrixEntry>
suspend fun findAllRichtverfahren(): List<RichtverfahrenDefinition>
suspend fun findAllGebuehren(): List<GebuehrDefinition>
suspend fun findAllRegulationConfigs(): List<RegulationConfig>
suspend fun findActiveRegulationConfigs(): List<RegulationConfig>
}
@@ -11,7 +11,7 @@ import kotlin.uuid.Uuid
* Repository-Interface für DomReiter (Reiter) Domain-Operationen.
*
* Definiert den Vertrag für Datenzugriffs-Operationen ohne Abhängigkeit
* von konkreten Implementierungsdetails (Datenbank, etc.).
* von konkreten Implementierungsdetails (Datenbank etc.).
*/
interface ReiterRepository {