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:
+33
@@ -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
|
||||
)
|
||||
+34
@@ -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
|
||||
)
|
||||
+32
@@ -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
|
||||
)
|
||||
+34
@@ -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
|
||||
)
|
||||
+35
@@ -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
|
||||
)
|
||||
+53
@@ -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
|
||||
}
|
||||
+73
@@ -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
|
||||
}
|
||||
}
|
||||
+44
@@ -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?
|
||||
}
|
||||
+48
@@ -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
|
||||
}
|
||||
}
|
||||
+110
@@ -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))
|
||||
}
|
||||
}
|
||||
+113
@@ -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)
|
||||
}
|
||||
}
|
||||
+128
@@ -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))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user