feat(masterdata): introduce Reiter-Sparte persistence, services, and validations

- Added `ReiterSparteTable` to manage rider-discipline associations.
- Introduced services and tests for `LicenseMatrix`, `Altersklasse`, and `AbteilungsRegel` with domain logic and validations for ÖTO compliance.
- Enhanced `ExposedReiterRepository` to save and query `Reiter` disciplines efficiently.
- Implemented database migration script `V007__Cleanup_Initial_Tables_and_Add_Sparte.sql`.
- Updated `MasterdataDatabaseConfiguration` to include `ReiterSparteTable` in the schema initialization.
- Expanded test coverage with new cases for eligibility checks, age group determinations, and splitting regulations.

Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
This commit is contained in:
2026-03-30 14:47:11 +02:00
parent e8757c5c32
commit d8c9d11adb
17 changed files with 871 additions and 41 deletions
@@ -0,0 +1,33 @@
@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class)
package at.mocode.masterdata.domain.model
import at.mocode.core.domain.serialization.InstantSerializer
import at.mocode.core.domain.serialization.UuidSerializer
import kotlinx.serialization.Serializable
import kotlin.time.Instant
import kotlin.uuid.Uuid
/**
* Domänenmodell für Gebühren gemäß ÖTO oder Veranstaltervorgabe.
*/
@Serializable
data class GebuehrDefinition(
@Serializable(with = UuidSerializer::class)
val gebuehrId: Uuid = Uuid.random(),
val bezeichnung: String,
val typ: String, // NENNUNG, STARTGELD, BOX, STALLGELD, SONSTIGES
val betrag: Double,
val waehrung: String = "EUR",
@Serializable(with = InstantSerializer::class)
val validFrom: Instant,
@Serializable(with = InstantSerializer::class)
val validTo: Instant? = null,
val istAktiv: Boolean = true,
@Serializable(with = InstantSerializer::class)
val createdAt: Instant,
@Serializable(with = InstantSerializer::class)
val updatedAt: Instant
)
@@ -0,0 +1,34 @@
@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class)
package at.mocode.masterdata.domain.model
import at.mocode.core.domain.model.LizenzKlasseE
import at.mocode.core.domain.model.SparteE
import at.mocode.core.domain.serialization.InstantSerializer
import at.mocode.core.domain.serialization.UuidSerializer
import kotlinx.serialization.Serializable
import kotlin.time.Instant
import kotlin.uuid.Uuid
/**
* Domänenmodell für die Lizenz-Matrix (Reiter-Lizenz vs. maximal erlaubte Turnierklasse).
*/
@Serializable
data class LicenseMatrixEntry(
@Serializable(with = UuidSerializer::class)
val licenseId: Uuid = Uuid.random(),
val sparte: SparteE,
val lizenzKlasse: LizenzKlasseE,
val maxTurnierklasseCode: String, // E, A, L, LM, M, S
@Serializable(with = InstantSerializer::class)
val validFrom: Instant,
@Serializable(with = InstantSerializer::class)
val validTo: Instant? = null,
val istAktiv: Boolean = true,
@Serializable(with = InstantSerializer::class)
val createdAt: Instant,
@Serializable(with = InstantSerializer::class)
val updatedAt: Instant
)
@@ -0,0 +1,32 @@
@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class)
package at.mocode.masterdata.domain.model
import at.mocode.core.domain.serialization.InstantSerializer
import at.mocode.core.domain.serialization.UuidSerializer
import kotlinx.serialization.Serializable
import kotlin.time.Instant
import kotlin.uuid.Uuid
/**
* Domänenmodell für allgemeine Regelkonfigurationen.
*/
@Serializable
data class RegulationConfig(
@Serializable(with = UuidSerializer::class)
val configId: Uuid = Uuid.random(),
val key: String,
val value: String,
val beschreibung: String? = null,
@Serializable(with = InstantSerializer::class)
val validFrom: Instant,
@Serializable(with = InstantSerializer::class)
val validTo: Instant? = null,
val istAktiv: Boolean = true,
@Serializable(with = InstantSerializer::class)
val createdAt: Instant,
@Serializable(with = InstantSerializer::class)
val updatedAt: Instant
)
@@ -0,0 +1,34 @@
@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class)
package at.mocode.masterdata.domain.model
import at.mocode.core.domain.model.SparteE
import at.mocode.core.domain.serialization.InstantSerializer
import at.mocode.core.domain.serialization.UuidSerializer
import kotlinx.serialization.Serializable
import kotlin.time.Instant
import kotlin.uuid.Uuid
/**
* Domänenmodell für Richtverfahren gemäß ÖTO.
*/
@Serializable
data class RichtverfahrenDefinition(
@Serializable(with = UuidSerializer::class)
val richtverfahrenId: Uuid = Uuid.random(),
val sparte: SparteE,
val code: String, // A1, A2, AM5, RV_A, RV_B
val bezeichnung: String,
val beschreibung: String? = null,
@Serializable(with = InstantSerializer::class)
val validFrom: Instant,
@Serializable(with = InstantSerializer::class)
val validTo: Instant? = null,
val istAktiv: Boolean = true,
@Serializable(with = InstantSerializer::class)
val createdAt: Instant,
@Serializable(with = InstantSerializer::class)
val updatedAt: Instant
)
@@ -0,0 +1,35 @@
@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class)
package at.mocode.masterdata.domain.model
import at.mocode.core.domain.model.SparteE
import at.mocode.core.domain.serialization.InstantSerializer
import at.mocode.core.domain.serialization.UuidSerializer
import kotlinx.serialization.Serializable
import kotlin.time.Instant
import kotlin.uuid.Uuid
/**
* Domänenmodell für eine Turnierklasse gemäß ÖTO.
*/
@Serializable
data class TurnierklasseDefinition(
@Serializable(with = UuidSerializer::class)
val turnierklasseId: Uuid = Uuid.random(),
val sparte: SparteE,
val code: String, // E, A, L, LM, M, S
val bezeichnung: String,
val maxHoehe: Int? = null, // in cm (Springen)
val aufgabenNiveau: String? = null, // (Dressur)
@Serializable(with = InstantSerializer::class)
val validFrom: Instant,
@Serializable(with = InstantSerializer::class)
val validTo: Instant? = null,
val istAktiv: Boolean = true,
@Serializable(with = InstantSerializer::class)
val createdAt: Instant,
@Serializable(with = InstantSerializer::class)
val updatedAt: Instant
)
@@ -0,0 +1,53 @@
package at.mocode.masterdata.domain.service
import at.mocode.masterdata.domain.model.DomPferd
import at.mocode.masterdata.domain.model.DomReiter
/**
* Service zur Prüfung von Abteilungs-Regeln gemäß ÖTO § 39.
*/
interface AbteilungsRegelService {
/**
* Prüft, ob eine strukturelle Teilung (unabhängig von der Starterzahl) erforderlich ist.
* Gemäß § 39 A-Teil:
* - Klassen A & L: Trennung nach R1/RD1 (Abt. 1) und höher (Abt. 2+).
* - CSN-C-NEU (bis 95cm): Abt. 1 (lizenzfrei), Abt. 2 (R1), Abt. 3 (R2+).
*
* @param reiter Der Reiter.
* @param pferd Das Pferd.
* @param turnierklasseCode Der Code der Turnierklasse (E, A, L, ...).
* @param sparte Die Sparte (DRESSUR, SPRINGEN).
* @param istCNeu Ob es sich um ein C-NEU Turnier handelt.
* @param hoehe Bei Springen: Die Hindernishöhe in cm.
* @return Die Abteilungsnummer (1, 2, 3), in die der Teilnehmer fällt.
*/
fun ermittleAbteilungStrukturell(
reiter: DomReiter,
pferd: DomPferd,
turnierklasseCode: String,
sparte: at.mocode.core.domain.model.SparteE,
istCNeu: Boolean = false,
hoehe: Int? = null
): Int
/**
* Prüft, ob eine kapazitive Teilung (aufgrund der Starterzahl) erforderlich ist.
* Gemäß § 39 A-Teil:
* - Standard-Springen: > 80 Starter.
* - Stil- & Springpferdeprüfungen: > 30 Starter.
* - Dressur: > 30 Starter (Empfehlung/Warnung).
*
* @param starterAnzahl Aktuelle Anzahl der Nennungen/Starter.
* @param turnierklasseCode Der Code der Turnierklasse.
* @param sparte Die Sparte.
* @param istStilOderJungpferdePruefung Ob es sich um eine Stil- oder Jungpferdeprüfung handelt.
* @return true, wenn eine Teilung MUSS oder SOLLTE (Warnung).
*/
fun istTeilungErforderlich(
starterAnzahl: Int,
turnierklasseCode: String,
sparte: at.mocode.core.domain.model.SparteE,
istStilOderJungpferdePruefung: Boolean = false
): Boolean
}
@@ -0,0 +1,73 @@
package at.mocode.masterdata.domain.service
import at.mocode.core.domain.model.LizenzKlasseE
import at.mocode.core.domain.model.SparteE
import at.mocode.masterdata.domain.model.DomPferd
import at.mocode.masterdata.domain.model.DomReiter
/**
* Standard-Implementierung des [AbteilungsRegelService] gemäß ÖTO § 39.
*/
class AbteilungsRegelServiceImpl : AbteilungsRegelService {
override fun ermittleAbteilungStrukturell(
reiter: DomReiter,
pferd: DomPferd,
turnierklasseCode: String,
sparte: SparteE,
istCNeu: Boolean,
hoehe: Int?
): Int {
// Gemäß § 39 A-Teil / 3.1 Strukturelle Teilung
// Fall 1: CSN-C-NEU (Spezialregeln)
if (istCNeu && sparte == SparteE.SPRINGEN) {
if (hoehe != null && hoehe <= 95) {
return when (reiter.lizenzKlasse) {
LizenzKlasseE.LIZENZFREI -> 1
LizenzKlasseE.R1, LizenzKlasseE.RD1 -> 2
else -> 3 // R2+
}
} else if (hoehe != null && hoehe >= 100) {
return when (reiter.lizenzKlasse) {
LizenzKlasseE.R1, LizenzKlasseE.RD1 -> 1
else -> 2 // R2+
}
}
}
// Fall 2: Klassen A & L (Standardregelung § 39 Abs. 1)
if (turnierklasseCode == "A" || turnierklasseCode == "L") {
return when (reiter.lizenzKlasse) {
LizenzKlasseE.R1, LizenzKlasseE.RD1 -> 1 // Abt. 1: R1
else -> 2 // Abt. 2+: R2 und höher
}
}
// Default: Keine strukturelle Teilung (Abt. 1)
return 1
}
override fun istTeilungErforderlich(
starterAnzahl: Int,
turnierklasseCode: String,
sparte: SparteE,
istStilOderJungpferdePruefung: Boolean
): Boolean {
// Gemäß § 39 A-Teil / 3.2 Kapazitive Teilung
if (sparte == SparteE.SPRINGEN) {
return if (istStilOderJungpferdePruefung) {
starterAnzahl > 30 // MUSS
} else {
starterAnzahl > 80 // MUSS
}
}
if (sparte == SparteE.DRESSUR) {
return starterAnzahl > 30 // KANN (System gibt Warnung)
}
return false
}
}
@@ -0,0 +1,44 @@
package at.mocode.masterdata.domain.service
import at.mocode.core.domain.model.SparteE
import at.mocode.masterdata.domain.model.DomReiter
import at.mocode.masterdata.domain.model.LicenseMatrixEntry
import at.mocode.masterdata.domain.model.TurnierklasseDefinition
/**
* Service zur Prüfung der Teilnahmeberechtigung basierend auf der Lizenz-Matrix.
*/
interface LicenseMatrixService {
/**
* Prüft, ob ein Reiter mit seiner aktuellen Lizenz in einer bestimmten Turnierklasse startberechtigt ist.
*
* @param reiter Der Reiter, dessen Berechtigung geprüft werden soll.
* @param turnierklasse Die Turnierklasse (E, A, L, LM, M, S), in der gestartet werden soll.
* @param sparte Die Sparte des Bewerbs.
* @param matrix Die aktuelle Lizenz-Matrix (Regulation-as-Data).
* @param alleKlassen Alle verfügbaren Turnierklassen-Definitionen zur Code-Validierung.
* @return true, wenn der Reiter startberechtigt ist, sonst false.
*/
fun isEligible(
reiter: DomReiter,
turnierklasse: TurnierklasseDefinition,
sparte: SparteE,
matrix: List<LicenseMatrixEntry>,
alleKlassen: List<TurnierklasseDefinition>
): Boolean
/**
* Ermittelt die maximal erlaubte Turnierklasse für einen Reiter in einer Sparte.
*
* @param reiter Der Reiter.
* @param sparte Die Sparte.
* @param matrix Die aktuelle Lizenz-Matrix.
* @return Der Code der maximal erlaubten Turnierklasse oder null, wenn keine Regel gefunden wurde.
*/
fun getMaxTurnierklasse(
reiter: DomReiter,
sparte: SparteE,
matrix: List<LicenseMatrixEntry>
): String?
}
@@ -0,0 +1,48 @@
package at.mocode.masterdata.domain.service
import at.mocode.core.domain.model.SparteE
import at.mocode.masterdata.domain.model.DomReiter
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: DomReiter,
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: DomReiter,
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
return entry?.maxTurnierklasseCode
}
}
@@ -0,0 +1,110 @@
@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class)
package at.mocode.masterdata.domain.service
import at.mocode.core.domain.model.LizenzKlasseE
import at.mocode.core.domain.model.PferdeGeschlechtE
import at.mocode.core.domain.model.SparteE
import at.mocode.masterdata.domain.model.DomPferd
import at.mocode.masterdata.domain.model.DomReiter
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
import kotlin.uuid.Uuid
class AbteilungsRegelServiceTest {
private val service = AbteilungsRegelServiceImpl()
private val standardPferd = DomPferd(pferdeName = "Testpferd", geschlecht = PferdeGeschlechtE.WALLACH)
private val dummyPersonId = Uuid.random()
@Test
fun `ermittleAbteilungStrukturell teilt Klassen A und L nach R1`() {
val r1Reiter = DomReiter(
personId = dummyPersonId,
satznummer = "1",
nachname = "R1",
vorname = "R1",
lizenzKlasse = LizenzKlasseE.R1
)
val r2Reiter = DomReiter(
personId = dummyPersonId,
satznummer = "2",
nachname = "R2",
vorname = "R2",
lizenzKlasse = LizenzKlasseE.R2
)
assertEquals(1, service.ermittleAbteilungStrukturell(r1Reiter, standardPferd, "A", SparteE.SPRINGEN))
assertEquals(2, service.ermittleAbteilungStrukturell(r2Reiter, standardPferd, "A", SparteE.SPRINGEN))
assertEquals(1, service.ermittleAbteilungStrukturell(r1Reiter, standardPferd, "L", SparteE.SPRINGEN))
assertEquals(2, service.ermittleAbteilungStrukturell(r2Reiter, standardPferd, "L", SparteE.SPRINGEN))
}
@Test
fun `ermittleAbteilungStrukturell berücksichtigt C-NEU Regeln`() {
val lfReiter = DomReiter(
personId = dummyPersonId,
satznummer = "0",
nachname = "LF",
vorname = "LF",
lizenzKlasse = LizenzKlasseE.LIZENZFREI
)
val r1Reiter = DomReiter(
personId = dummyPersonId,
satznummer = "1",
nachname = "R1",
vorname = "R1",
lizenzKlasse = LizenzKlasseE.R1
)
val r2Reiter = DomReiter(
personId = dummyPersonId,
satznummer = "2",
nachname = "R2",
vorname = "R2",
lizenzKlasse = LizenzKlasseE.R2
)
// Bis 95cm
assertEquals(
1,
service.ermittleAbteilungStrukturell(lfReiter, standardPferd, "E", SparteE.SPRINGEN, istCNeu = true, hoehe = 90)
)
assertEquals(
2,
service.ermittleAbteilungStrukturell(r1Reiter, standardPferd, "E", SparteE.SPRINGEN, istCNeu = true, hoehe = 90)
)
assertEquals(
3,
service.ermittleAbteilungStrukturell(r2Reiter, standardPferd, "E", SparteE.SPRINGEN, istCNeu = true, hoehe = 90)
)
// Ab 100cm
assertEquals(
1,
service.ermittleAbteilungStrukturell(r1Reiter, standardPferd, "A", SparteE.SPRINGEN, istCNeu = true, hoehe = 100)
)
assertEquals(
2,
service.ermittleAbteilungStrukturell(r2Reiter, standardPferd, "A", SparteE.SPRINGEN, istCNeu = true, hoehe = 100)
)
}
@Test
fun `istTeilungErforderlich prüft Starterzahlen`() {
// Springen Standard
assertFalse(service.istTeilungErforderlich(80, "A", SparteE.SPRINGEN))
assertTrue(service.istTeilungErforderlich(81, "A", SparteE.SPRINGEN))
// Springen Stil / Jungpferde
assertFalse(service.istTeilungErforderlich(30, "A", SparteE.SPRINGEN, istStilOderJungpferdePruefung = true))
assertTrue(service.istTeilungErforderlich(31, "A", SparteE.SPRINGEN, istStilOderJungpferdePruefung = true))
// Dressur
assertFalse(service.istTeilungErforderlich(30, "A", SparteE.DRESSUR))
assertTrue(service.istTeilungErforderlich(31, "A", SparteE.DRESSUR))
}
}
@@ -0,0 +1,113 @@
@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class)
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
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
import kotlin.time.Clock
import kotlin.uuid.Uuid
class AltersklasseRechnerTest {
private val rechner = AltersklasseRechnerImpl()
@Test
fun `berechneOetoAlter berechnet korrektes Alter am 31_12_`() {
val geb = LocalDate(2010, 5, 15)
assertEquals(16, rechner.berechneOetoAlter(geb, 2026))
val gebSilvester = LocalDate(2010, 12, 31)
assertEquals(16, rechner.berechneOetoAlter(gebSilvester, 2026))
val gebNeujahr = LocalDate(2011, 1, 1)
assertEquals(15, rechner.berechneOetoAlter(gebNeujahr, 2026))
}
@Test
fun `ermittleAltersklassen findet passende Definitionen`() {
val reiter = DomReiter(
personId = Uuid.random(),
satznummer = "123456",
nachname = "Mustermann",
vorname = "Max",
geburtsdatum = LocalDate(2010, 1, 1) // 16 Jahre in 2026
)
val nun = Clock.System.now()
val definitionen = listOf(
AltersklasseDefinition(
altersklasseCode = "JG",
bezeichnung = "Jugend",
minAlter = 8,
maxAlter = 15,
createdAt = nun,
updatedAt = nun
),
AltersklasseDefinition(
altersklasseCode = "JN",
bezeichnung = "Junioren",
minAlter = 16,
maxAlter = 18,
createdAt = nun,
updatedAt = nun
),
AltersklasseDefinition(
altersklasseCode = "AK",
bezeichnung = "Allg. Klasse",
minAlter = 19,
createdAt = nun,
updatedAt = nun
)
)
val ergebnis = rechner.ermittleAltersklassen(reiter, 2026, SparteE.SPRINGEN, definitionen)
assertEquals(1, ergebnis.size)
assertEquals("JN", ergebnis[0].altersklasseCode)
}
@Test
fun `ermittleAltersklassen berücksichtigt SpartenFilter`() {
val reiter = DomReiter(
personId = Uuid.random(),
satznummer = "123456",
nachname = "Mustermann",
vorname = "Max",
geburtsdatum = LocalDate(2013, 1, 1) // 13 Jahre in 2026
)
val nun = Clock.System.now()
val definitionen = listOf(
AltersklasseDefinition(
altersklasseCode = "CH_D",
bezeichnung = "Children Dressur",
minAlter = 12,
maxAlter = 14,
sparteFilter = SparteE.DRESSUR,
createdAt = nun,
updatedAt = nun
),
AltersklasseDefinition(
altersklasseCode = "JG",
bezeichnung = "Jugend",
minAlter = 8,
maxAlter = 15,
createdAt = nun,
updatedAt = nun
)
)
val ergebnisDressur = rechner.ermittleAltersklassen(reiter, 2026, SparteE.DRESSUR, definitionen)
assertEquals(2, ergebnisDressur.size)
assertTrue(ergebnisDressur.any { it.altersklasseCode == "CH_D" })
val ergebnisSpringen = rechner.ermittleAltersklassen(reiter, 2026, SparteE.SPRINGEN, definitionen)
assertEquals(1, ergebnisSpringen.size)
assertEquals("JG", ergebnisSpringen[0].altersklasseCode)
}
}
@@ -0,0 +1,128 @@
@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class)
package at.mocode.masterdata.domain.service
import at.mocode.core.domain.model.LizenzKlasseE
import at.mocode.core.domain.model.SparteE
import at.mocode.masterdata.domain.model.DomReiter
import at.mocode.masterdata.domain.model.LicenseMatrixEntry
import at.mocode.masterdata.domain.model.TurnierklasseDefinition
import kotlin.test.Test
import kotlin.test.assertFalse
import kotlin.test.assertTrue
import kotlin.time.Clock
import kotlin.uuid.Uuid
class LicenseMatrixServiceTest {
private val service = LicenseMatrixServiceImpl()
private val nun = Clock.System.now()
private val matrix = listOf(
LicenseMatrixEntry(
sparte = SparteE.SPRINGEN,
lizenzKlasse = LizenzKlasseE.R1,
maxTurnierklasseCode = "L",
validFrom = nun,
createdAt = nun,
updatedAt = nun
),
LicenseMatrixEntry(
sparte = SparteE.SPRINGEN,
lizenzKlasse = LizenzKlasseE.R2,
maxTurnierklasseCode = "M",
validFrom = nun,
createdAt = nun,
updatedAt = nun
),
LicenseMatrixEntry(
sparte = SparteE.DRESSUR,
lizenzKlasse = LizenzKlasseE.RD1,
maxTurnierklasseCode = "L",
validFrom = nun,
createdAt = nun,
updatedAt = nun
)
)
private val turnierklassen = listOf(
TurnierklasseDefinition(
sparte = SparteE.SPRINGEN,
code = "E",
bezeichnung = "E",
validFrom = nun,
createdAt = nun,
updatedAt = nun
),
TurnierklasseDefinition(
sparte = SparteE.SPRINGEN,
code = "A",
bezeichnung = "A",
validFrom = nun,
createdAt = nun,
updatedAt = nun
),
TurnierklasseDefinition(
sparte = SparteE.SPRINGEN,
code = "L",
bezeichnung = "L",
validFrom = nun,
createdAt = nun,
updatedAt = nun
),
TurnierklasseDefinition(
sparte = SparteE.SPRINGEN,
code = "LM",
bezeichnung = "LM",
validFrom = nun,
createdAt = nun,
updatedAt = nun
),
TurnierklasseDefinition(
sparte = SparteE.SPRINGEN,
code = "M",
bezeichnung = "M",
validFrom = nun,
createdAt = nun,
updatedAt = nun
)
)
@Test
fun `isEligible erlaubt Starts bis zum Limit`() {
val r1Reiter = DomReiter(
personId = Uuid.random(),
satznummer = "1",
nachname = "R1",
vorname = "Reiter",
lizenzKlasse = LizenzKlasseE.R1,
lizenzSparten = listOf(SparteE.SPRINGEN),
startkartAktiv = true
)
val klasseA = turnierklassen.find { it.code == "A" }!!
val klasseL = turnierklassen.find { it.code == "L" }!!
val klasseM = turnierklassen.find { it.code == "M" }!!
assertTrue(service.isEligible(r1Reiter, klasseA, SparteE.SPRINGEN, matrix, turnierklassen))
assertTrue(service.isEligible(r1Reiter, klasseL, SparteE.SPRINGEN, matrix, turnierklassen))
assertFalse(service.isEligible(r1Reiter, klasseM, SparteE.SPRINGEN, matrix, turnierklassen))
}
@Test
fun `isEligible verweigert Start ohne passende Spartenlizenz`() {
val rd1Reiter = DomReiter(
personId = Uuid.random(),
satznummer = "2",
nachname = "RD1",
vorname = "Reiter",
lizenzKlasse = LizenzKlasseE.RD1,
lizenzSparten = listOf(SparteE.DRESSUR), // Nur Dressur
startkartAktiv = true
)
val klasseA = turnierklassen.find { it.code == "A" }!!
assertFalse(service.isEligible(rd1Reiter, klasseA, SparteE.SPRINGEN, matrix, turnierklassen))
}
}