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:
+66
-36
@@ -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)
|
||||
}
|
||||
|
||||
+26
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user