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:
parent
e8757c5c32
commit
d8c9d11adb
|
|
@ -132,9 +132,9 @@
|
|||
## Nächste konkrete Schritte (2‑Wochen Sprint‑Plan)
|
||||
|
||||
1. [x] ADRs für Importer‑Einbettung, Rule‑Versionierung, API-Schichten abschließen (🏗️)
|
||||
2. Exposed-Tabellen vervollständigen und in `SchemaUtils.create`/Migrationen registrieren (👷)
|
||||
3. UseCases: Altersklasse, Lizenz‑Matrix, Abteilungs‑Regeln inkl. Unit‑Tests (👷🧐)
|
||||
4. ZNS‑Importer an Repositories anbinden, Idempotenz-Checks ergänzen, Mini‑ZNS Testlauf (👷🧐)
|
||||
2. [x] Exposed-Tabellen vervollständigen und in `SchemaUtils.create`/Migrationen registrieren (👷)
|
||||
3. [x] UseCases: Altersklasse, Lizenz‑Matrix, Abteilungs‑Regeln inkl. Unit‑Tests (👷🧐)
|
||||
4. [ ] ZNS‑Importer an Repositories anbinden, Idempotenz-Checks ergänzen, Mini‑ZNS Testlauf (👷🧐) *
|
||||
5. API v1 Endpunkte + OpenAPI, Contract‑Tests (👷🧐)
|
||||
6. Observability-Grundlagen (Metriken + Dashboards) (🐧)
|
||||
7. Curator: Docs aktualisieren, Runbooks und Changelogs pflegen (🧹)
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
@ -20,7 +20,7 @@ import kotlin.uuid.Uuid
|
|||
*/
|
||||
class ExposedReiterRepository : ReiterRepository {
|
||||
|
||||
private fun rowToDomReiter(row: ResultRow): DomReiter {
|
||||
private fun rowToDomReiter(row: ResultRow, sparten: List<SparteE> = emptyList()): DomReiter {
|
||||
return DomReiter(
|
||||
reiterId = row[ReiterTable.id],
|
||||
personId = row[ReiterTable.personId],
|
||||
|
|
@ -30,6 +30,7 @@ class ExposedReiterRepository : ReiterRepository {
|
|||
geburtsdatum = row[ReiterTable.geburtsdatum],
|
||||
lizenzNummer = row[ReiterTable.lizenzNummer],
|
||||
lizenzKlasse = LizenzKlasseE.valueOf(row[ReiterTable.lizenzKlasse]),
|
||||
lizenzSparten = sparten,
|
||||
startkartAktiv = row[ReiterTable.startkartAktiv],
|
||||
startkartSaison = row[ReiterTable.startkartSaison],
|
||||
feiId = row[ReiterTable.feiId],
|
||||
|
|
@ -44,21 +45,32 @@ class ExposedReiterRepository : ReiterRepository {
|
|||
)
|
||||
}
|
||||
|
||||
private fun getSpartenForReiter(reiterId: Uuid): List<SparteE> {
|
||||
return ReiterSparteTable.selectAll().where { ReiterSparteTable.reiterId eq reiterId }
|
||||
.map { SparteE.valueOf(it[ReiterSparteTable.sparte]) }
|
||||
}
|
||||
|
||||
override suspend fun findById(id: Uuid): DomReiter? = DatabaseFactory.dbQuery {
|
||||
ReiterTable.selectAll().where { ReiterTable.id eq id }
|
||||
.map(::rowToDomReiter)
|
||||
.map { rowToDomReiter(it, getSpartenForReiter(id)) }
|
||||
.singleOrNull()
|
||||
}
|
||||
|
||||
override suspend fun findBySatznummer(satznummer: String): DomReiter? = DatabaseFactory.dbQuery {
|
||||
ReiterTable.selectAll().where { ReiterTable.satznummer eq satznummer }
|
||||
.map(::rowToDomReiter)
|
||||
.map { row ->
|
||||
val id = row[ReiterTable.id]
|
||||
rowToDomReiter(row, getSpartenForReiter(id))
|
||||
}
|
||||
.singleOrNull()
|
||||
}
|
||||
|
||||
override suspend fun findByFeiId(feiId: String): DomReiter? = DatabaseFactory.dbQuery {
|
||||
ReiterTable.selectAll().where { ReiterTable.feiId eq feiId }
|
||||
.map(::rowToDomReiter)
|
||||
.map { row ->
|
||||
val id = row[ReiterTable.id]
|
||||
rowToDomReiter(row, getSpartenForReiter(id))
|
||||
}
|
||||
.singleOrNull()
|
||||
}
|
||||
|
||||
|
|
@ -66,7 +78,10 @@ class ExposedReiterRepository : ReiterRepository {
|
|||
val pattern = "%$searchTerm%"
|
||||
ReiterTable.selectAll().where { (ReiterTable.nachname like pattern) or (ReiterTable.vorname like pattern) }
|
||||
.limit(limit)
|
||||
.map(::rowToDomReiter)
|
||||
.map { row ->
|
||||
val id = row[ReiterTable.id]
|
||||
rowToDomReiter(row, getSpartenForReiter(id))
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun findByVereinsNummer(vereinsNummer: String, activeOnly: Boolean): List<DomReiter> =
|
||||
|
|
@ -75,7 +90,10 @@ class ExposedReiterRepository : ReiterRepository {
|
|||
if (activeOnly) {
|
||||
query.andWhere { ReiterTable.istAktiv eq true }
|
||||
}
|
||||
query.map(::rowToDomReiter)
|
||||
query.map { row ->
|
||||
val id = row[ReiterTable.id]
|
||||
rowToDomReiter(row, getSpartenForReiter(id))
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun findByLizenzKlasse(lizenzKlasse: LizenzKlasseE, activeOnly: Boolean): List<DomReiter> =
|
||||
|
|
@ -84,14 +102,22 @@ class ExposedReiterRepository : ReiterRepository {
|
|||
if (activeOnly) {
|
||||
query.andWhere { ReiterTable.istAktiv eq true }
|
||||
}
|
||||
query.map(::rowToDomReiter)
|
||||
query.map { row ->
|
||||
val id = row[ReiterTable.id]
|
||||
rowToDomReiter(row, getSpartenForReiter(id))
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun findBySparte(sparte: SparteE, activeOnly: Boolean): List<DomReiter> = DatabaseFactory.dbQuery {
|
||||
// Da wir in ReiterTable keinen sparteFilter haben, müssen wir ggf. über eine andere Tabelle gehen
|
||||
// oder die Logik anpassen. Fürs erste geben wir eine leere Liste zurück oder suchen nach Name in Lizenz?
|
||||
// TODO: Implementierung prüfen, falls Sparten-Lizenzierung in eigener Tabelle liegt.
|
||||
emptyList()
|
||||
val query = (ReiterTable innerJoin ReiterSparteTable)
|
||||
.selectAll().where { ReiterSparteTable.sparte eq sparte.name }
|
||||
if (activeOnly) {
|
||||
query.andWhere { ReiterTable.istAktiv eq true }
|
||||
}
|
||||
query.map { row ->
|
||||
val id = row[ReiterTable.id]
|
||||
rowToDomReiter(row, getSpartenForReiter(id))
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun findGastreiter(activeOnly: Boolean): List<DomReiter> = DatabaseFactory.dbQuery {
|
||||
|
|
@ -99,19 +125,28 @@ class ExposedReiterRepository : ReiterRepository {
|
|||
if (activeOnly) {
|
||||
query.andWhere { ReiterTable.istAktiv eq true }
|
||||
}
|
||||
query.map(::rowToDomReiter)
|
||||
query.map { row ->
|
||||
val id = row[ReiterTable.id]
|
||||
rowToDomReiter(row, getSpartenForReiter(id))
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun findAllActive(limit: Int, offset: Int): List<DomReiter> = DatabaseFactory.dbQuery {
|
||||
ReiterTable.selectAll().where { ReiterTable.istAktiv eq true }
|
||||
.limit(limit).offset(offset.toLong())
|
||||
.map(::rowToDomReiter)
|
||||
.map { row ->
|
||||
val id = row[ReiterTable.id]
|
||||
rowToDomReiter(row, getSpartenForReiter(id))
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun findAll(limit: Int, offset: Int): List<DomReiter> = DatabaseFactory.dbQuery {
|
||||
ReiterTable.selectAll()
|
||||
.limit(limit).offset(offset.toLong())
|
||||
.map(::rowToDomReiter)
|
||||
.map { row ->
|
||||
val id = row[ReiterTable.id]
|
||||
rowToDomReiter(row, getSpartenForReiter(id))
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun save(reiter: DomReiter): DomReiter = DatabaseFactory.dbQuery {
|
||||
|
|
@ -136,7 +171,6 @@ class ExposedReiterRepository : ReiterRepository {
|
|||
it[datenQuelle] = reiter.datenQuelle.name
|
||||
it[updatedAt] = reiter.updatedAt
|
||||
}
|
||||
reiter
|
||||
} else {
|
||||
ReiterTable.insert {
|
||||
it[id] = reiter.reiterId
|
||||
|
|
@ -159,8 +193,19 @@ class ExposedReiterRepository : ReiterRepository {
|
|||
it[createdAt] = reiter.createdAt
|
||||
it[updatedAt] = reiter.updatedAt
|
||||
}
|
||||
reiter
|
||||
}
|
||||
|
||||
// Sparten aktualisieren
|
||||
ReiterSparteTable.deleteWhere { ReiterSparteTable.reiterId eq reiter.reiterId }
|
||||
reiter.lizenzSparten.forEach { sparte ->
|
||||
ReiterSparteTable.insert {
|
||||
it[ReiterSparteTable.id] = Uuid.random()
|
||||
it[ReiterSparteTable.reiterId] = reiter.reiterId
|
||||
it[ReiterSparteTable.sparte] = sparte.name
|
||||
}
|
||||
}
|
||||
|
||||
reiter
|
||||
}
|
||||
|
||||
override suspend fun delete(id: Uuid): Boolean = DatabaseFactory.dbQuery {
|
||||
|
|
@ -177,30 +222,15 @@ class ExposedReiterRepository : ReiterRepository {
|
|||
|
||||
override suspend fun upsertBySatznummer(reiter: DomReiter): DomReiter = DatabaseFactory.dbQuery {
|
||||
val existing = ReiterTable.selectAll().where { ReiterTable.satznummer eq reiter.satznummer }
|
||||
.map(::rowToDomReiter)
|
||||
.map { row ->
|
||||
val id = row[ReiterTable.id]
|
||||
rowToDomReiter(row, getSpartenForReiter(id))
|
||||
}
|
||||
.singleOrNull()
|
||||
|
||||
if (existing != null) {
|
||||
val toUpdate = reiter.copy(reiterId = existing.reiterId)
|
||||
ReiterTable.update({ ReiterTable.id eq existing.reiterId }) {
|
||||
it[personId] = toUpdate.personId
|
||||
it[nachname] = toUpdate.nachname
|
||||
it[vorname] = toUpdate.vorname
|
||||
it[geburtsdatum] = toUpdate.geburtsdatum
|
||||
it[lizenzNummer] = toUpdate.lizenzNummer
|
||||
it[lizenzKlasse] = toUpdate.lizenzKlasse.name
|
||||
it[startkartAktiv] = toUpdate.startkartAktiv
|
||||
it[startkartSaison] = toUpdate.startkartSaison
|
||||
it[feiId] = toUpdate.feiId
|
||||
it[nation] = toUpdate.nation
|
||||
it[vereinsNummer] = toUpdate.vereinsNummer
|
||||
it[vereinsName] = toUpdate.vereinsName
|
||||
it[istGastreiter] = toUpdate.istGastreiter
|
||||
it[istAktiv] = toUpdate.istAktiv
|
||||
it[datenQuelle] = toUpdate.datenQuelle.name
|
||||
it[updatedAt] = toUpdate.updatedAt
|
||||
}
|
||||
toUpdate
|
||||
save(toUpdate)
|
||||
} else {
|
||||
save(reiter)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class)
|
||||
|
||||
package at.mocode.masterdata.infrastructure.persistence
|
||||
|
||||
import org.jetbrains.exposed.v1.core.Table
|
||||
import org.jetbrains.exposed.v1.datetime.CurrentTimestamp
|
||||
import org.jetbrains.exposed.v1.datetime.timestamp
|
||||
|
||||
/**
|
||||
* Exposed-Tabellendefinition für die Spartenberechtigung eines Reiters.
|
||||
* Verknüpft einen Reiter mit den Sparten (DRESSUR, SPRINGEN), für die er lizenziert ist.
|
||||
*/
|
||||
object ReiterSparteTable : Table("reiter_sparte") {
|
||||
val id = uuid("id")
|
||||
val reiterId = uuid("reiter_id").references(ReiterTable.id)
|
||||
val sparte = varchar("sparte", 20) // DRESSUR, SPRINGEN
|
||||
|
||||
val createdAt = timestamp("created_at").defaultExpression(CurrentTimestamp)
|
||||
val updatedAt = timestamp("updated_at").defaultExpression(CurrentTimestamp)
|
||||
|
||||
override val primaryKey = PrimaryKey(id)
|
||||
|
||||
init {
|
||||
uniqueIndex("ux_reiter_sparte", reiterId, sparte)
|
||||
}
|
||||
}
|
||||
|
|
@ -42,7 +42,8 @@ class MasterdataDatabaseConfiguration {
|
|||
LicenseTable,
|
||||
RichtverfahrenTable,
|
||||
GebuehrTable,
|
||||
RegulationConfigTable
|
||||
RegulationConfigTable,
|
||||
ReiterSparteTable
|
||||
)
|
||||
log.info("Masterdata database schema initialized successfully")
|
||||
}
|
||||
|
|
@ -87,7 +88,8 @@ class MasterdataTestDatabaseConfiguration {
|
|||
LicenseTable,
|
||||
RichtverfahrenTable,
|
||||
GebuehrTable,
|
||||
RegulationConfigTable
|
||||
RegulationConfigTable,
|
||||
ReiterSparteTable
|
||||
)
|
||||
log.info("Test masterdata database schema initialized successfully")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
-- V007: Cleanup Initial Tables and Add ReiterSparte Table
|
||||
-- Harmonisierung: Löschen der veralteten dom_person / dom_verein Tabellen aus V1
|
||||
-- Hinzufügen der Zwischentabelle für Reiter-Sparten
|
||||
|
||||
-- Löschen der alten Tabellen (Daten wurden bereits in V006 in die neuen Tabellen migriert bzw. werden neu importiert)
|
||||
-- Vorsicht: Da dies ein Greenfield-Projekt ist und der Fokus auf V26 liegt, ist ein sauberer Schnitt hier erlaubt.
|
||||
DROP TABLE IF EXISTS dom_person CASCADE;
|
||||
DROP TABLE IF EXISTS dom_verein CASCADE;
|
||||
|
||||
-- Erstellung der Reiter-Sparten Tabelle
|
||||
CREATE TABLE IF NOT EXISTS reiter_sparte
|
||||
(
|
||||
id
|
||||
UUID
|
||||
PRIMARY
|
||||
KEY,
|
||||
reiter_id
|
||||
UUID
|
||||
NOT
|
||||
NULL
|
||||
REFERENCES
|
||||
reiter
|
||||
(
|
||||
reiter_id
|
||||
) ON DELETE CASCADE,
|
||||
sparte VARCHAR
|
||||
(
|
||||
20
|
||||
) NOT NULL,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP
|
||||
WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX ux_reiter_sparte ON reiter_sparte (reiter_id, sparte);
|
||||
Loading…
Reference in New Issue
Block a user