feat(masterdata): add controllers, services, and repositories for Reiter, Horse, and Verein domains

- Introduced entities `ReiterController`, `HorseController`, and `VereinController`, with associated REST routes.
- Implemented upsert functionality for `Reiter`, `Horse`, and `Verein` repositories.
- Added services for `Altersklasse` calculations and integrated them into the domain layer.
- Updated database schema to include `ReiterTable`, `HorseTable`, `VereinTable`, and `FunktionaerTable`.
- Refactored `masterdataApiModule` to register new domain controllers.
- Adjusted Ktor server and Spring configurations to support new domains.

Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
This commit is contained in:
2026-03-30 13:16:55 +02:00
parent c576bbd6af
commit 0c870ba2e3
15 changed files with 533 additions and 33 deletions
@@ -2,8 +2,8 @@
package at.mocode.masterdata.domain.repository
import at.mocode.masterdata.domain.model.DomPferd
import at.mocode.core.domain.model.PferdeGeschlechtE
import at.mocode.masterdata.domain.model.DomPferd
import kotlin.uuid.Uuid
/**
@@ -245,4 +245,10 @@ interface HorseRepository {
* @return The count of FEI registered horses
*/
suspend fun countFeiRegistered(activeOnly: Boolean = true): Long
/**
* Speichert ein Pferd basierend auf der Lebensnummer (Upsert).
* Wenn ein Pferd mit der Lebensnummer existiert, wird es aktualisiert, ansonsten neu angelegt.
*/
suspend fun upsertByLebensnummer(horse: DomPferd): DomPferd
}
@@ -86,4 +86,10 @@ interface ReiterRepository {
* Prüft ob ein Reiter mit der gegebenen Satznummer bereits existiert.
*/
suspend fun existsBySatznummer(satznummer: String): Boolean
/**
* Speichert einen Reiter basierend auf der Satznummer (Upsert).
* Wenn ein Reiter mit der Satznummer existiert, wird er aktualisiert, ansonsten neu angelegt.
*/
suspend fun upsertBySatznummer(reiter: DomReiter): DomReiter
}
@@ -69,4 +69,10 @@ interface VereinRepository {
* Prüft ob ein Verein mit der gegebenen Vereinsnummer bereits existiert.
*/
suspend fun existsByVereinsNummer(vereinsNummer: String): Boolean
/**
* Speichert einen Verein basierend auf der Vereinsnummer (Upsert).
* Wenn ein Verein mit der Nummer existiert, wird er aktualisiert, ansonsten neu angelegt.
*/
suspend fun upsertByVereinsNummer(verein: DomVerein): DomVerein
}
@@ -0,0 +1,38 @@
package at.mocode.masterdata.domain.service
import at.mocode.core.domain.model.SparteE
import at.mocode.masterdata.domain.model.AltersklasseDefinition
import at.mocode.masterdata.domain.model.DomReiter
import kotlinx.datetime.LocalDate
/**
* Service zur Berechnung und Ermittlung von Altersklassen gemäß ÖTO.
*/
interface AltersklasseRechner {
/**
* Ermittelt das Alter einer Person für ein bestimmtes Jahr gemäß der ÖTO-Stichtagsregel
* (Alter am 31.12. des laufenden Kalenderjahres).
*
* @param geburtsdatum Das Geburtsdatum der Person.
* @param referenzJahr Das Kalenderjahr, für das das Alter berechnet werden soll.
* @return Das Alter in Jahren.
*/
fun berechneOetoAlter(geburtsdatum: LocalDate, referenzJahr: Int): Int
/**
* Ermittelt alle zutreffenden Altersklassen für einen Reiter in einem bestimmten Jahr und einer Sparte.
*
* @param reiter Der Reiter, für den die Altersklasse ermittelt werden soll.
* @param referenzJahr Das Kalenderjahr des Turniers.
* @param sparte Die Sparte des Bewerbs (optional).
* @param verfügbareDefinitionen Die Liste der im System definierten Altersklassen.
* @return Eine Liste der zutreffenden Altersklassen-Definitionen.
*/
fun ermittleAltersklassen(
reiter: DomReiter,
referenzJahr: Int,
sparte: SparteE? = null,
verfügbareDefinitionen: List<AltersklasseDefinition>
): List<AltersklasseDefinition>
}
@@ -0,0 +1,43 @@
package at.mocode.masterdata.domain.service
import at.mocode.core.domain.model.SparteE
import at.mocode.masterdata.domain.model.AltersklasseDefinition
import at.mocode.masterdata.domain.model.DomReiter
import kotlinx.datetime.LocalDate
/**
* Standard-Implementierung des [AltersklasseRechner] gemäß ÖTO.
*/
class AltersklasseRechnerImpl : AltersklasseRechner {
override fun berechneOetoAlter(geburtsdatum: LocalDate, referenzJahr: Int): Int {
// Gemäß ÖTO: Stichtag für alle Altersklassen ist der 31. Dezember des laufenden Kalenderjahres.
// Das bedeutet einfach: ReferenzJahr - GeburtsJahr.
return referenzJahr - geburtsdatum.year
}
override fun ermittleAltersklassen(
reiter: DomReiter,
referenzJahr: Int,
sparte: SparteE?,
verfügbareDefinitionen: List<AltersklasseDefinition>
): List<AltersklasseDefinition> {
val geburtsdatum = reiter.geburtsdatum ?: return emptyList()
val alter = berechneOetoAlter(geburtsdatum, referenzJahr)
return verfügbareDefinitionen.filter { def ->
if (!def.istAktiv) return@filter false
// Sparte prüfen (falls in der Definition eine Sparte vorgegeben ist)
if (def.sparteFilter != null && sparte != null && def.sparteFilter != sparte) {
return@filter false
}
// Alter prüfen
val minMatch = def.minAlter == null || alter >= def.minAlter!!
val maxMatch = def.maxAlter == null || alter <= def.maxAlter!!
minMatch && maxMatch
}
}
}