Refactor domain models (DomFunktionaer, DomReiter, DomPferd) to align with ZNS conventions: simplify naming, update properties, and enhance parser logic. Adjust related controllers, repository methods, and tests. Update MASTER_ROADMAP with changes to domain models.

This commit is contained in:
2026-04-06 00:00:20 +02:00
parent 1e5fa3d053
commit f50d4deb16
57 changed files with 811 additions and 532 deletions
+13
View File
@@ -40,6 +40,19 @@ Versionierung folgt [Semantic Versioning](https://semver.org/lang/de/).
---
## [1.0.1-SNAPSHOT] — 2026-04-05
### Geändert
- **Refactoring:** `DomVerein` zu `Verein`, `DomReiter` zu `Reiter`, `DomPferd` zu `Pferd` und `DomFunktionaer` zu `Funktionaer` umbenannt (Domain, Infrastructure, API, Core).
- **Domain:** `personId` ist nun optional (`nullable`) bei `Verein`, `Reiter`, `Pferd` und `Funktionaer`, um ZNS-Initialimporte zu unterstützen.
- **Infrastructure:** `VereinTable`, `ReiterTable`, `HorseTable` und `FunktionaerTable` synchronisiert; `personId` ist nun optional.
- **API:** `VereinController`, `ReiterController`, `HorseController` und `FunktionaerController` (DTOs/Requests) an die neuen Modelle angepasst.
- **Doku:** `Ubiquitous_Language.md` und `MASTER_ROADMAP.md` an das neue Namensschema (`Reiter`, `Pferd`, `Funktionaer`) angepasst.
### Behoben
- **ZNS-Import:** Kompatibilitätsprobleme in `ZnsLegacyParsers` und `ZnsImportService` nach Domain-Refactorings behoben (UUID-Person-Referenzen und Enum-Synchronisation).
- **Domain:** Felder `kurzname` und `oepsRegionNummer` bei `Verein` entfernt (nicht in VEREIN01.DAT vorhanden).
## [1.0.0-SNAPSHOT] — 2026-04-03
### Hinzugefügt
@@ -19,10 +19,10 @@ import java.util.zip.ZipInputStream
* Domänenobjekte über die jeweiligen Repositories (Upsert-Logik).
*
* Die Verarbeitungsreihenfolge ist fix:
* 1. VEREIN01.DAT → DomVerein (via VereinRepository)
* 2. LIZENZ01.DAT → DomReiter (via ReiterRepository)
* 3. PFERDE01.DAT → DomPferd (via HorseRepository)
* 4. RICHT01.DAT → DomFunktionaer (via FunktionaerRepository)
* 1. VEREIN01.DAT → Verein (via VereinRepository)
* 2. LIZENZ01.DAT → Reiter (via ReiterRepository)
* 3. PFERDE01.DAT → Pferd (via HorseRepository)
* 4. RICHT01.DAT → Funktionaer (via FunktionaerRepository)
*
* Dieser Service hat **keine** Spring-Abhängigkeit und kann daher sowohl
* im Backend (REST-Upload) als auch in der Compose Desktop App (Offline-Import)
@@ -57,18 +57,19 @@ class ZnsImportService(
ZipInputStream(zipInputStream).use { zip ->
var entry = zip.nextEntry
while (entry != null) {
val entryPath = entry.name.uppercase()
val fileName = entryPath.substringAfterLast("/")
val fileName = entry.name.uppercase().substringAfterLast("/")
if (fileName in setOf(FILE_VEREIN, FILE_LIZENZ, FILE_PFERDE, FILE_RICHT)) {
// Read all bytes from the current entry
val bytes = zip.readBytes()
// Convert to string using the correct charset and split into lines
val content = bytes.toString(CP850)
val outputStream = java.io.ByteArrayOutputStream()
val buffer = ByteArray(4096)
var len: Int
while (zip.read(buffer).also { len = it } > 0) {
outputStream.write(buffer, 0, len)
}
val content = outputStream.toString(CP850)
val lines = content.split(Regex("\\r?\\n|\\r")).filter { it.isNotBlank() }
dateien[fileName] = lines
}
zip.closeEntry()
entry = zip.nextEntry
}
}
@@ -83,6 +84,8 @@ class ZnsImportService(
*/
suspend fun importiereZip(zipInputStream: InputStream): ZnsImportResult {
val dateien = extrahiereDateien(zipInputStream)
// println("[DEBUG_LOG] Gefundene Dateien: ${dateien.keys}")
// dateien.forEach { (name, lines) -> println("[DEBUG_LOG] Datei $name hat ${lines.size} Zeilen") }
val fehler = mutableListOf<String>()
val warnungen = mutableListOf<String>()
@@ -126,13 +129,11 @@ class ZnsImportService(
} else {
vereinRepository.save(
vorhanden.copy(
name = verein.name,
kurzname = verein.kurzname,
vereinName = verein.vereinName,
bundesland = verein.bundesland,
ort = verein.ort,
plz = verein.plz,
strasse = verein.strasse,
oepsRegionNummer = verein.oepsRegionNummer,
istAktiv = verein.istAktiv,
datenQuelle = verein.datenQuelle
).withUpdatedTimestamp()
@@ -207,9 +208,12 @@ class ZnsImportService(
val pferd = ZnsLegacyParsers.parsePferd(zeile) ?: return@forEachIndexed
if (pferd.pferdeName.isBlank()) return@forEachIndexed
// Match primarily by satznummer, then by lebensnummer, then by kopfnummer+name
// Match primarily by satznummer, then by lebensnummer
val vorhanden = pferd.satznummer?.takeIf { it.isNotBlank() }?.let { horseRepository.findBySatznummer(it) }
?: pferd.lebensnummer?.takeIf { it.isNotBlank() }?.let { horseRepository.findByLebensnummer(it) }
?: (if (pferd.pferdeName.isNotBlank()) horseRepository.findByName(pferd.pferdeName, 100).find {
it.geburtsjahr == pferd.geburtsjahr && it.kopfnummer == pferd.kopfnummer
} else null)
if (vorhanden == null) {
horseRepository.save(pferd)
@@ -253,7 +257,7 @@ class ZnsImportService(
zeilen.forEachIndexed { index, zeile ->
runCatching {
val funktionaer = ZnsLegacyParsers.parseFunktionaer(zeile) ?: return@forEachIndexed
val satzID = funktionaer.satzID
val satzID = funktionaer.satzId
val satzNummer = funktionaer.satzNummer
val vorhanden = funktionaerRepository.findBySatz(satzID, satzNummer)
if (vorhanden == null) {
@@ -262,8 +266,6 @@ class ZnsImportService(
} else {
funktionaerRepository.save(
vorhanden.copy(
satzID = satzID,
satzNummer = satzNummer,
name = funktionaer.name,
qualifikationen = funktionaer.qualifikationen,
istAktiv = funktionaer.istAktiv,
@@ -1,9 +1,9 @@
package at.mocode.zns.importer
import at.mocode.masterdata.domain.model.DomFunktionaer
import at.mocode.masterdata.domain.model.DomPferd
import at.mocode.masterdata.domain.model.DomReiter
import at.mocode.masterdata.domain.model.DomVerein
import at.mocode.masterdata.domain.model.Funktionaer
import at.mocode.masterdata.domain.model.Pferd
import at.mocode.masterdata.domain.model.Reiter
import at.mocode.masterdata.domain.model.Verein
import at.mocode.masterdata.domain.repository.FunktionaerRepository
import at.mocode.masterdata.domain.repository.HorseRepository
import at.mocode.masterdata.domain.repository.ReiterRepository
@@ -113,29 +113,29 @@ class ZnsImportServiceTest {
val zip = buildZip("VEREIN01.DAT" to vereinZeile())
coEvery { vereinRepository.findByVereinsNummer(any()) } returns null
coEvery { vereinRepository.save(any()) } answers { firstArg<DomVerein>() }
coEvery { vereinRepository.save(any()) } answers { firstArg<Verein>() }
val result = service.importiereZip(zip)
assertThat(result.vereineImportiert).isEqualTo(1)
assertThat(result.vereineAktualisiert).isEqualTo(0)
assertThat(result.fehler).isEmpty()
coVerify(exactly = 1) { vereinRepository.save(any<DomVerein>()) }
coVerify(exactly = 1) { vereinRepository.save(any<Verein>()) }
}
@Test
fun `importiereZip - bestehende Vereine werden aktualisiert`() = runTest {
val zip = buildZip("VEREIN01.DAT" to vereinZeile(name = "Neuer Name"))
val vorhanden = DomVerein(vereinsNummer = "0001", name = "Alter Name")
val vorhanden = Verein(vereinsNummer = "0001", vereinName = "Alter Name")
coEvery { vereinRepository.findByVereinsNummer("0001") } returns vorhanden
coEvery { vereinRepository.save(any()) } answers { firstArg<DomVerein>() }
coEvery { vereinRepository.save(any()) } answers { firstArg<Verein>() }
val result = service.importiereZip(zip)
assertThat(result.vereineImportiert).isEqualTo(0)
assertThat(result.vereineAktualisiert).isEqualTo(1)
coVerify(exactly = 1) { vereinRepository.save(match { it.name == "Neuer Name" }) }
coVerify(exactly = 1) { vereinRepository.save(match { it.vereinName == "Neuer Name" }) }
}
@Test
@@ -143,14 +143,14 @@ class ZnsImportServiceTest {
val zip = buildZip("LIZENZ01.DAT" to lizenzZeile())
coEvery { reiterRepository.findBySatznummer(any()) } returns null
coEvery { reiterRepository.save(any()) } answers { firstArg<DomReiter>() }
coEvery { reiterRepository.save(any()) } answers { firstArg<Reiter>() }
val result = service.importiereZip(zip)
assertThat(result.reiterImportiert).isEqualTo(1)
assertThat(result.reiterAktualisiert).isEqualTo(0)
assertThat(result.fehler).isEmpty()
coVerify(exactly = 1) { reiterRepository.save(any<DomReiter>()) }
coVerify(exactly = 1) { reiterRepository.save(any<Reiter>()) }
}
@Test
@@ -159,14 +159,34 @@ class ZnsImportServiceTest {
coEvery { horseRepository.findBySatznummer(any()) } returns null
coEvery { horseRepository.findByLebensnummer(any()) } returns null
coEvery { horseRepository.save(any()) } answers { firstArg<DomPferd>() }
coEvery { horseRepository.findByName(any(), any()) } returns emptyList()
coEvery { horseRepository.save(any()) } answers { firstArg<Pferd>() }
val result = service.importiereZip(zip)
assertThat(result.pferdeImportiert).isEqualTo(1)
assertThat(result.pferdeAktualisiert).isEqualTo(0)
assertThat(result.fehler).isEmpty()
coVerify(exactly = 1) { horseRepository.save(any<DomPferd>()) }
coVerify(exactly = 1) { horseRepository.save(any<Pferd>()) }
}
@Test
fun `importiereZip - Pferde werden ueber Kopfnummer und Geburtsjahr aktualisiert, wenn Satznummer fehlt`() = runTest {
// Zeile ohne Satznummer (zu kurz)
val zeile = "0001Blitz "
val zip = buildZip("PFERDE01.DAT" to zeile)
val existing = Pferd(pferdeName = "Blitz", kopfnummer = "0001", geschlecht = at.mocode.core.domain.model.PferdeGeschlechtE.UNBEKANNT)
coEvery { horseRepository.findBySatznummer(any()) } returns null
coEvery { horseRepository.findByLebensnummer(any()) } returns null
coEvery { horseRepository.findByName("Blitz", 100) } returns listOf(existing)
coEvery { horseRepository.save(any()) } answers { firstArg<Pferd>() }
val result = service.importiereZip(zip)
assertThat(result.pferdeAktualisiert).isEqualTo(1)
coVerify(exactly = 1) { horseRepository.save(match { it.pferdeName == "Blitz" && it.pferdId == existing.pferdId }) }
}
@Test
@@ -174,14 +194,14 @@ class ZnsImportServiceTest {
val zip = buildZip("RICHT01.DAT" to funktionaerZeile())
coEvery { funktionaerRepository.findBySatz(any(), any()) } returns null
coEvery { funktionaerRepository.save(any()) } answers { firstArg<DomFunktionaer>() }
coEvery { funktionaerRepository.save(any()) } answers { firstArg<Funktionaer>() }
val result = service.importiereZip(zip)
assertThat(result.richterImportiert).isEqualTo(1)
assertThat(result.richterAktualisiert).isEqualTo(0)
assertThat(result.fehler).isEmpty()
coVerify(exactly = 1) { funktionaerRepository.save(any<DomFunktionaer>()) }
coVerify(exactly = 1) { funktionaerRepository.save(any<Funktionaer>()) }
}
@Test
@@ -192,13 +212,13 @@ class ZnsImportServiceTest {
)
coEvery { funktionaerRepository.findBySatz(any(), any()) } returns null
coEvery { funktionaerRepository.save(any()) } answers { firstArg<DomFunktionaer>() }
coEvery { funktionaerRepository.save(any()) } answers { firstArg<Funktionaer>() }
val result = service.importiereZip(zip)
assertThat(result.richterImportiert).isEqualTo(4)
assertThat(result.fehler).isEmpty()
coVerify(exactly = 4) { funktionaerRepository.save(any<DomFunktionaer>()) }
coVerify(exactly = 4) { funktionaerRepository.save(any<Funktionaer>()) }
}
@Test
@@ -211,14 +231,14 @@ class ZnsImportServiceTest {
)
coEvery { vereinRepository.findByVereinsNummer(any()) } returns null
coEvery { vereinRepository.save(any()) } answers { firstArg<DomVerein>() }
coEvery { vereinRepository.save(any()) } answers { firstArg<Verein>() }
coEvery { reiterRepository.findBySatznummer(any()) } returns null
coEvery { reiterRepository.save(any()) } answers { firstArg<DomReiter>() }
coEvery { reiterRepository.save(any()) } answers { firstArg<Reiter>() }
coEvery { horseRepository.findBySatznummer(any()) } returns null
coEvery { horseRepository.findByLebensnummer(any()) } returns null
coEvery { horseRepository.save(any()) } answers { firstArg<DomPferd>() }
coEvery { horseRepository.save(any()) } answers { firstArg<Pferd>() }
coEvery { funktionaerRepository.findBySatz(any(), any()) } returns null
coEvery { funktionaerRepository.save(any()) } answers { firstArg<DomFunktionaer>() }
coEvery { funktionaerRepository.save(any()) } answers { firstArg<Funktionaer>() }
val result = service.importiereZip(zip)
@@ -2,7 +2,7 @@
package at.mocode.entries.api
import at.mocode.core.domain.model.NennungsStatusE
import at.mocode.core.domain.model.NennStatusE
import at.mocode.core.domain.model.StartwunschE
import at.mocode.core.domain.serialization.UuidSerializer
import kotlinx.serialization.Serializable
@@ -13,28 +13,28 @@ import kotlin.uuid.Uuid
*/
@Serializable
data class NennungDetailDto(
@Serializable(with = UuidSerializer::class)
@Serializable(with = UuidSerializer::class)
val nennungId: Uuid,
@Serializable(with = UuidSerializer::class)
@Serializable(with = UuidSerializer::class)
val abteilungId: Uuid,
@Serializable(with = UuidSerializer::class)
@Serializable(with = UuidSerializer::class)
val bewerbId: Uuid,
@Serializable(with = UuidSerializer::class)
@Serializable(with = UuidSerializer::class)
val turnierId: Uuid,
@Serializable(with = UuidSerializer::class)
@Serializable(with = UuidSerializer::class)
val reiterId: Uuid,
@Serializable(with = UuidSerializer::class)
@Serializable(with = UuidSerializer::class)
val pferdId: Uuid,
@Serializable(with = UuidSerializer::class)
@Serializable(with = UuidSerializer::class)
val zahlerId: Uuid? = null,
val status: NennungsStatusE,
val startwunsch: StartwunschE,
val istNachnennung: Boolean,
val nachnenngebuehrErlassen: Boolean,
val isNachnenngebuehrFaellig: Boolean,
val bemerkungen: String? = null,
val createdAt: String,
val updatedAt: String
val status: NennStatusE,
val startwunsch: StartwunschE,
val istNachnennung: Boolean,
val nachnenngebuehrErlassen: Boolean,
val isNachnenngebuehrFaellig: Boolean,
val bemerkungen: String? = null,
val createdAt: String,
val updatedAt: String
)
/**
@@ -42,21 +42,21 @@ data class NennungDetailDto(
*/
@Serializable
data class NennungSummaryDto(
@Serializable(with = UuidSerializer::class)
@Serializable(with = UuidSerializer::class)
val nennungId: Uuid,
@Serializable(with = UuidSerializer::class)
@Serializable(with = UuidSerializer::class)
val turnierId: Uuid,
@Serializable(with = UuidSerializer::class)
@Serializable(with = UuidSerializer::class)
val bewerbId: Uuid,
@Serializable(with = UuidSerializer::class)
@Serializable(with = UuidSerializer::class)
val abteilungId: Uuid,
@Serializable(with = UuidSerializer::class)
@Serializable(with = UuidSerializer::class)
val reiterId: Uuid,
@Serializable(with = UuidSerializer::class)
@Serializable(with = UuidSerializer::class)
val pferdId: Uuid,
val status: NennungsStatusE,
val istNachnennung: Boolean,
val createdAt: String
val status: NennStatusE,
val istNachnennung: Boolean,
val createdAt: String
)
/**
@@ -86,8 +86,8 @@ data class NennungEinreichenRequest(
*/
@Serializable
data class NennungStatusAendernRequest(
val neuerStatus: NennungsStatusE,
val bemerkungen: String? = null
val neuerStatus: NennStatusE,
val bemerkungen: String? = null
)
/**
@@ -2,7 +2,7 @@
package at.mocode.entries.domain.model
import at.mocode.core.domain.model.NennungsStatusE
import at.mocode.core.domain.model.NennStatusE
import at.mocode.core.domain.model.StartwunschE
import at.mocode.core.domain.serialization.InstantSerializer
import at.mocode.core.domain.serialization.UuidSerializer
@@ -28,8 +28,8 @@ import kotlin.uuid.Uuid
* @property abteilungId Reference to the Abteilung (smallest entry unit).
* @property bewerbId Reference to the parent Bewerb (for display/reporting).
* @property turnierId Reference to the Turnier.
* @property reiterId Reference to the DomReiter (actor-context).
* @property pferdId Reference to the DomPferd (actor-context).
* @property reiterId Reference to the Reiter (actor-context).
* @property pferdId Reference to the Pferd (actor-context).
* @property zahlerId Reference to the payer (may differ from rider, e.g. club pays).
* @property status Current status of this entry.
* @property startwunsch Rider's preferred starting position (vorne/hinten).
@@ -63,7 +63,7 @@ data class DomNennung(
val zahlerId: Uuid? = null,
// Entry Details
val status: NennungsStatusE = NennungsStatusE.EINGEGANGEN,
val status: NennStatusE = NennStatusE.EINGEGANGEN,
val startwunsch: StartwunschE = StartwunschE.KEIN_WUNSCH,
// Late Entry (Nachnennung)
@@ -83,8 +83,8 @@ data class DomNennung(
* Checks if this entry is still active (not withdrawn or cancelled).
*/
fun isAktiv(): Boolean = status !in listOf(
NennungsStatusE.ZURUECKGEZOGEN,
NennungsStatusE.NICHT_ANGETRETEN
NennStatusE.ZURUECKGEZOGEN,
NennStatusE.NICHT_ANGETRETEN
)
/**
@@ -2,7 +2,7 @@
package at.mocode.entries.domain.repository
import at.mocode.core.domain.model.NennungsStatusE
import at.mocode.core.domain.model.NennStatusE
import at.mocode.entries.domain.model.DomNennung
import kotlin.uuid.Uuid
@@ -52,7 +52,7 @@ interface NennungRepository {
/**
* Sucht alle Nennungen mit einem bestimmten Status.
*/
suspend fun findByStatus(status: NennungsStatusE): List<DomNennung>
suspend fun findByStatus(status: NennStatusE): List<DomNennung>
/**
* Sucht alle Nachnennungen für einen Bewerb.
@@ -84,5 +84,5 @@ interface NennungRepository {
/**
* Zählt alle Nennungen für ein Turnier mit einem bestimmten Status.
*/
suspend fun countByTurnierIdAndStatus(turnierId: Uuid, status: NennungsStatusE): Long
suspend fun countByTurnierIdAndStatus(turnierId: Uuid, status: NennStatusE): Long
}
@@ -3,12 +3,12 @@
package at.mocode.entries.domain.service
import at.mocode.core.domain.model.AbteilungsTeilungsTypE
import at.mocode.core.domain.model.LizenzKlasseE
import at.mocode.core.domain.model.ReiterLizenzKlasseE
import at.mocode.core.domain.model.PruefungsTypE
import at.mocode.core.domain.model.SparteE
import at.mocode.entries.domain.model.DomAbteilung
import at.mocode.entries.domain.model.DomBewerb
import at.mocode.masterdata.domain.model.DomReiter
import at.mocode.masterdata.domain.model.Reiter
/**
* Service für die Anwendung der Abteilungs-Regeln gemäß ÖTO § 39.
@@ -37,14 +37,14 @@ class AbteilungsRegelService {
fun bestimmeAbteilung(
bewerb: DomBewerb,
abteilungen: List<DomAbteilung>,
reiter: DomReiter
reiter: Reiter
): DomAbteilung? {
if (abteilungen.isEmpty()) return null
if (abteilungen.size == 1) return abteilungen.first()
return when (bewerb.teilungsTyp) {
AbteilungsTeilungsTypE.NACH_LIZENZ -> {
val istLizenzfrei = reiter.lizenzKlasse == LizenzKlasseE.LIZENZFREI
val istLizenzfrei = reiter.lizenzKlasse == ReiterLizenzKlasseE.LIZENZFREI
if (istLizenzfrei) {
// Suche Abteilung für Lizenzfreie (oft Abt. 1 oder explizit benannt)
abteilungen.find { it.bezeichnung?.contains("frei", ignoreCase = true) == true }
@@ -61,7 +61,7 @@ class AbteilungsRegelService {
AbteilungsTeilungsTypE.STRUKTURELL -> {
// Bei strukturellen Teilungen (z.B. Caprilli oder CSN-C-NEU)
if (bewerb.pruefungsTyp == PruefungsTypE.CAPRILLI) {
val istLizenzfrei = reiter.lizenzKlasse == LizenzKlasseE.LIZENZFREI
val istLizenzfrei = reiter.lizenzKlasse == ReiterLizenzKlasseE.LIZENZFREI
if (istLizenzfrei) abteilungen.find { it.abteilungsNummer == 1 }
else abteilungen.find { it.abteilungsNummer == 2 }
} else if (bewerb.sparte == SparteE.SPRINGEN && bewerb.hoeheCm != null) {
@@ -70,7 +70,7 @@ class AbteilungsRegelService {
// ≥ 100 cm: nur R1+ erlaubt → alle in Abt. 1 (R1/R2+)
val hoehe = bewerb.hoeheCm!!
if (hoehe <= 95) {
val istLizenzfrei = reiter.lizenzKlasse == LizenzKlasseE.LIZENZFREI
val istLizenzfrei = reiter.lizenzKlasse == ReiterLizenzKlasseE.LIZENZFREI
if (istLizenzfrei) {
abteilungen.find { it.bezeichnung?.contains("ohne", ignoreCase = true) == true }
?: abteilungen.minByOrNull { it.abteilungsNummer }
@@ -80,7 +80,7 @@ class AbteilungsRegelService {
}
} else {
// ≥ 100 cm: Pflicht-Teilung R1 / R2+
val istR1 = reiter.lizenzKlasse == LizenzKlasseE.R1
val istR1 = reiter.lizenzKlasse == ReiterLizenzKlasseE.R1
if (istR1) {
abteilungen.find { it.bezeichnung?.contains("R1", ignoreCase = false) == true }
?: abteilungen.minByOrNull { it.abteilungsNummer }
@@ -5,7 +5,7 @@ package at.mocode.entries.domain.service
import at.mocode.core.domain.model.*
import at.mocode.entries.domain.model.DomAbteilung
import at.mocode.entries.domain.model.DomBewerb
import at.mocode.masterdata.domain.model.DomReiter
import at.mocode.masterdata.domain.model.Reiter
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.uuid.Uuid
@@ -30,7 +30,7 @@ class AbteilungsRegelServiceTest {
val abt1 = createAbteilung(bewerb.bewerbId, 1, "lizenzfrei")
val abt2 = createAbteilung(bewerb.bewerbId, 2, "R1 und hoeher")
val reiter = createReiter(lizenzKlasse = LizenzKlasseE.LIZENZFREI)
val reiter = createReiter(lizenzKlasse = ReiterLizenzKlasseE.LIZENZFREI)
val result = service.bestimmeAbteilung(bewerb, listOf(abt1, abt2), reiter)
@@ -43,7 +43,7 @@ class AbteilungsRegelServiceTest {
val abt1 = createAbteilung(bewerb.bewerbId, 1, "lizenzfrei")
val abt2 = createAbteilung(bewerb.bewerbId, 2, "Lizenz")
val reiter = createReiter(lizenzKlasse = LizenzKlasseE.R1)
val reiter = createReiter(lizenzKlasse = ReiterLizenzKlasseE.R1)
val result = service.bestimmeAbteilung(bewerb, listOf(abt1, abt2), reiter)
@@ -68,7 +68,7 @@ class AbteilungsRegelServiceTest {
val bewerb = createBewerb(teilungsTyp = AbteilungsTeilungsTypE.STRUKTURELL, hoeheCm = 95)
val abtOhne = createAbteilung(bewerb.bewerbId, 1, "ohne Lizenz")
val abtMit = createAbteilung(bewerb.bewerbId, 2, "mit Lizenz")
val reiter = createReiter(LizenzKlasseE.LIZENZFREI)
val reiter = createReiter(ReiterLizenzKlasseE.LIZENZFREI)
val result = service.bestimmeAbteilung(bewerb, listOf(abtOhne, abtMit), reiter)
@@ -80,7 +80,7 @@ class AbteilungsRegelServiceTest {
val bewerb = createBewerb(teilungsTyp = AbteilungsTeilungsTypE.STRUKTURELL, hoeheCm = 95)
val abtOhne = createAbteilung(bewerb.bewerbId, 1, "ohne Lizenz")
val abtMit = createAbteilung(bewerb.bewerbId, 2, "mit Lizenz")
val reiter = createReiter(LizenzKlasseE.R1)
val reiter = createReiter(ReiterLizenzKlasseE.R1)
val result = service.bestimmeAbteilung(bewerb, listOf(abtOhne, abtMit), reiter)
@@ -93,7 +93,7 @@ class AbteilungsRegelServiceTest {
val abtOhne = createAbteilung(bewerb.bewerbId, 1, "ohne Lizenz")
val abtMit = createAbteilung(bewerb.bewerbId, 2, "mit Lizenz")
val result = service.bestimmeAbteilung(bewerb, listOf(abtOhne, abtMit), createReiter(LizenzKlasseE.LIZENZFREI))
val result = service.bestimmeAbteilung(bewerb, listOf(abtOhne, abtMit), createReiter(ReiterLizenzKlasseE.LIZENZFREI))
assertEquals(abtOhne.abteilungId, result?.abteilungId)
}
@@ -105,7 +105,7 @@ class AbteilungsRegelServiceTest {
val bewerb = createBewerb(teilungsTyp = AbteilungsTeilungsTypE.STRUKTURELL, hoeheCm = 100)
val abtR1 = createAbteilung(bewerb.bewerbId, 1, "R1")
val abtR2 = createAbteilung(bewerb.bewerbId, 2, "R2+")
val reiter = createReiter(LizenzKlasseE.R1)
val reiter = createReiter(ReiterLizenzKlasseE.R1)
val result = service.bestimmeAbteilung(bewerb, listOf(abtR1, abtR2), reiter)
@@ -117,7 +117,7 @@ class AbteilungsRegelServiceTest {
val bewerb = createBewerb(teilungsTyp = AbteilungsTeilungsTypE.STRUKTURELL, hoeheCm = 100)
val abtR1 = createAbteilung(bewerb.bewerbId, 1, "R1")
val abtR2 = createAbteilung(bewerb.bewerbId, 2, "R2+")
val reiter = createReiter(LizenzKlasseE.R2)
val reiter = createReiter(ReiterLizenzKlasseE.R2)
val result = service.bestimmeAbteilung(bewerb, listOf(abtR1, abtR2), reiter)
@@ -130,7 +130,7 @@ class AbteilungsRegelServiceTest {
val abtR1 = createAbteilung(bewerb.bewerbId, 1, "R1")
val abtR2 = createAbteilung(bewerb.bewerbId, 2, "R2+")
val result = service.bestimmeAbteilung(bewerb, listOf(abtR1, abtR2), createReiter(LizenzKlasseE.R1))
val result = service.bestimmeAbteilung(bewerb, listOf(abtR1, abtR2), createReiter(ReiterLizenzKlasseE.R1))
assertEquals(abtR1.abteilungId, result?.abteilungId)
}
@@ -191,7 +191,7 @@ class AbteilungsRegelServiceTest {
val abt1 = createAbteilung(bewerb.bewerbId, 1, "ohne Lizenz")
val abt2 = createAbteilung(bewerb.bewerbId, 2, "mit Lizenz")
val result = service.bestimmeAbteilung(bewerb, listOf(abt1, abt2), createReiter(LizenzKlasseE.LIZENZFREI))
val result = service.bestimmeAbteilung(bewerb, listOf(abt1, abt2), createReiter(ReiterLizenzKlasseE.LIZENZFREI))
assertEquals(abt1.abteilungId, result?.abteilungId)
}
@@ -202,7 +202,7 @@ class AbteilungsRegelServiceTest {
val abt1 = createAbteilung(bewerb.bewerbId, 1, "ohne Lizenz")
val abt2 = createAbteilung(bewerb.bewerbId, 2, "mit Lizenz")
val result = service.bestimmeAbteilung(bewerb, listOf(abt1, abt2), createReiter(LizenzKlasseE.R1))
val result = service.bestimmeAbteilung(bewerb, listOf(abt1, abt2), createReiter(ReiterLizenzKlasseE.R1))
assertEquals(abt2.abteilungId, result?.abteilungId)
}
@@ -236,7 +236,7 @@ class AbteilungsRegelServiceTest {
starterAnzahl = starterAnzahl
)
private fun createReiter(lizenzKlasse: LizenzKlasseE = LizenzKlasseE.LIZENZFREI) = DomReiter(
private fun createReiter(lizenzKlasse: ReiterLizenzKlasseE = ReiterLizenzKlasseE.LIZENZFREI) = Reiter(
personId = Uuid.random(),
satznummer = "123456",
nachname = "Mustermann",
@@ -2,7 +2,7 @@
package at.mocode.entries.service.persistence
import at.mocode.core.domain.model.NennungsStatusE
import at.mocode.core.domain.model.NennStatusE
import at.mocode.core.domain.model.StartwunschE
import at.mocode.entries.domain.model.DomNennung
import at.mocode.entries.domain.repository.NennungRepository
@@ -32,7 +32,7 @@ class NennungRepositoryImpl : NennungRepository {
reiterId = row[NennungTable.reiterId].toKotlinUuid(),
pferdId = row[NennungTable.pferdId].toKotlinUuid(),
zahlerId = row[NennungTable.zahlerId]?.toKotlinUuid(),
status = NennungsStatusE.valueOf(row[NennungTable.status]),
status = NennStatusE.valueOf(row[NennungTable.status]),
startwunsch = StartwunschE.valueOf(row[NennungTable.startwunsch]),
istNachnennung = row[NennungTable.istNachnennung],
nachnenngebuehrErlassen = row[NennungTable.nachnenngebuehrErlassen],
@@ -79,7 +79,7 @@ class NennungRepositoryImpl : NennungRepository {
}.map(::rowToNennung)
}
override suspend fun findByStatus(status: NennungsStatusE): List<DomNennung> = tenantTransaction {
override suspend fun findByStatus(status: NennStatusE): List<DomNennung> = tenantTransaction {
NennungTable.selectAll().where { NennungTable.status eq status.name }
.map(::rowToNennung)
}
@@ -145,7 +145,7 @@ class NennungRepositoryImpl : NennungRepository {
NennungTable.selectAll().where { NennungTable.abteilungId eq abteilungId.toJavaUuid() }.count()
}
override suspend fun countByTurnierIdAndStatus(turnierId: Uuid, status: NennungsStatusE): Long = tenantTransaction {
override suspend fun countByTurnierIdAndStatus(turnierId: Uuid, status: NennStatusE): Long = tenantTransaction {
NennungTable.selectAll().where {
(NennungTable.turnierId eq turnierId.toJavaUuid()) and
(NennungTable.status eq status.name)
@@ -2,7 +2,7 @@
package at.mocode.entries.service.usecase
import at.mocode.core.domain.model.NennungsStatusE
import at.mocode.core.domain.model.NennStatusE
import at.mocode.entries.api.*
import at.mocode.entries.domain.model.DomNennung
import at.mocode.entries.domain.model.DomNennungsTransfer
@@ -101,7 +101,7 @@ class NennungUseCases(
val nennung = nennungRepository.findById(nennungId)
?: throw NoSuchElementException("Nennung nicht gefunden: $nennungId")
val updated = nennung.copy(status = NennungsStatusE.ZURUECKGEZOGEN).withUpdatedTimestamp()
val updated = nennung.copy(status = NennStatusE.ZURUECKGEZOGEN).withUpdatedTimestamp()
val saved = nennungRepository.save(updated)
log.info("Nennung zurückgezogen: nennungId={}", nennungId)
return saved.toDetailDto()
@@ -131,7 +131,7 @@ class NennungUseCases(
}
// 1. Ursprungs-Nennung schließen
val geschlosseneNennung = ursprung.copy(status = NennungsStatusE.TRANSFERIERT).withUpdatedTimestamp()
val geschlosseneNennung = ursprung.copy(status = NennStatusE.TRANSFERIERT).withUpdatedTimestamp()
nennungRepository.save(geschlosseneNennung)
// 2. Neue Nennung anlegen
@@ -151,7 +151,7 @@ private fun DomNennung.Companion.random(now: kotlin.time.Instant): DomNennung {
reiterId = Uuid.random(),
pferdId = Uuid.random(),
zahlerId = null,
status = at.mocode.core.domain.model.NennungsStatusE.EINGEGANGEN,
status = at.mocode.core.domain.model.NennStatusE.EINGEGANGEN,
startwunsch = at.mocode.core.domain.model.StartwunschE.VORNE,
istNachnennung = false,
nachnenngebuehrErlassen = false,
@@ -3,7 +3,7 @@
package at.mocode.masterdata.api.rest
import at.mocode.core.domain.serialization.InstantSerializer
import at.mocode.masterdata.domain.model.DomFunktionaer
import at.mocode.masterdata.domain.model.Funktionaer
import at.mocode.masterdata.domain.repository.FunktionaerRepository
import io.ktor.http.*
import io.ktor.server.request.*
@@ -96,8 +96,8 @@ class FunktionaerController(private val funktionaerRepository: FunktionaerReposi
*/
post {
val req = call.receive<FunktionaerCreateRequest>()
val domFunktionaer = DomFunktionaer(
satzID = req.satzID,
val domFunktionaer = Funktionaer(
satzId = req.satzID,
satzNummer = req.satzNummer,
name = req.name,
qualifikationen = req.qualifikationen,
@@ -139,9 +139,9 @@ class FunktionaerController(private val funktionaerRepository: FunktionaerReposi
private fun parseUuid(value: String?): Uuid? = value?.let { runCatching { Uuid.parse(it) }.getOrNull() }
private fun DomFunktionaer.toDto() = FunktionaerDto(
private fun Funktionaer.toDto() = FunktionaerDto(
funktionaerId = funktionaerId.toString(),
satzID = satzID,
satzID = satzId,
satzNummer = satzNummer,
name = name,
qualifikationen = qualifikationen,
@@ -4,7 +4,7 @@ package at.mocode.masterdata.api.rest
import at.mocode.core.domain.model.PferdeGeschlechtE
import at.mocode.core.domain.serialization.InstantSerializer
import at.mocode.masterdata.domain.model.DomPferd
import at.mocode.masterdata.domain.model.Pferd
import at.mocode.masterdata.domain.repository.HorseRepository
import io.ktor.http.*
import io.ktor.server.request.*
@@ -110,7 +110,7 @@ class HorseController(private val horseRepository: HorseRepository) {
val req = call.receive<HorseCreateRequest>()
val geschlecht = runCatching { PferdeGeschlechtE.valueOf(req.geschlecht) }.getOrNull()
?: return@post call.respond(HttpStatusCode.BadRequest, "Ungültiges Geschlecht: ${req.geschlecht}")
val domPferd = DomPferd(
val pferd = Pferd(
kopfnummer = req.kopfnummer,
pferdeName = req.pferdeName,
lebensnummer = req.lebensnummer,
@@ -120,7 +120,7 @@ class HorseController(private val horseRepository: HorseRepository) {
satznummer = req.satznummer,
istAktiv = req.istAktiv
)
val saved = horseRepository.save(domPferd)
val saved = horseRepository.save(pferd)
call.respond(HttpStatusCode.Created, saved.toDto())
}
@@ -161,7 +161,7 @@ class HorseController(private val horseRepository: HorseRepository) {
private fun parseUuid(value: String?): Uuid? = value?.let { runCatching { Uuid.parse(it) }.getOrNull() }
private fun DomPferd.toDto() = HorseDto(
private fun Pferd.toDto() = HorseDto(
pferdId = pferdId.toString(),
kopfnummer = kopfnummer,
pferdeName = pferdeName,
@@ -2,10 +2,10 @@
package at.mocode.masterdata.api.rest
import at.mocode.core.domain.model.LizenzKlasseE
import at.mocode.core.domain.model.ReiterLizenzKlasseE
import at.mocode.core.domain.serialization.InstantSerializer
import at.mocode.core.domain.serialization.LocalDateSerializer
import at.mocode.masterdata.domain.model.DomReiter
import at.mocode.masterdata.domain.model.Reiter
import at.mocode.masterdata.domain.repository.ReiterRepository
import io.ktor.http.*
import io.ktor.server.request.*
@@ -132,9 +132,9 @@ class ReiterController(private val reiterRepository: ReiterRepository) {
*/
post {
val req = call.receive<ReiterCreateRequest>()
val lizenzKlasse = runCatching { LizenzKlasseE.valueOf(req.lizenzKlasse) }.getOrNull()
val lizenzKlasse = runCatching { ReiterLizenzKlasseE.valueOf(req.lizenzKlasse) }.getOrNull()
?: return@post call.respond(HttpStatusCode.BadRequest, "Ungültige lizenzKlasse: ${req.lizenzKlasse}")
val domReiter = DomReiter(
val domReiter = Reiter(
personId = Uuid.random(),
satznummer = req.satznummer,
nachname = req.nachname,
@@ -165,7 +165,7 @@ class ReiterController(private val reiterRepository: ReiterRepository) {
val existing = reiterRepository.findById(id) ?: return@put call.respond(HttpStatusCode.NotFound)
val req = call.receive<ReiterUpdateRequest>()
val lizenzKlasse = req.lizenzKlasse?.let {
runCatching { LizenzKlasseE.valueOf(it) }.getOrNull()
runCatching { ReiterLizenzKlasseE.valueOf(it) }.getOrNull()
?: return@put call.respond(HttpStatusCode.BadRequest, "Ungültige lizenzKlasse: $it")
} ?: existing.lizenzKlasse
val updated = existing.copy(
@@ -202,7 +202,7 @@ class ReiterController(private val reiterRepository: ReiterRepository) {
private fun parseUuid(value: String?): Uuid? = value?.let { runCatching { Uuid.parse(it) }.getOrNull() }
private fun DomReiter.toDto() = ReiterDto(
private fun Reiter.toDto() = ReiterDto(
reiterId = reiterId.toString(),
satznummer = satznummer,
nachname = nachname,
@@ -3,7 +3,7 @@
package at.mocode.masterdata.api.rest
import at.mocode.core.domain.serialization.InstantSerializer
import at.mocode.masterdata.domain.model.DomVerein
import at.mocode.masterdata.domain.model.Verein
import at.mocode.masterdata.domain.repository.VereinRepository
import io.ktor.http.*
import io.ktor.server.request.*
@@ -24,7 +24,6 @@ class VereinController(private val vereinRepository: VereinRepository) {
val vereinId: String,
val vereinsNummer: String,
val name: String,
val kurzname: String? = null,
val bundesland: String? = null,
val ort: String? = null,
val plz: String? = null,
@@ -32,9 +31,9 @@ class VereinController(private val vereinRepository: VereinRepository) {
val email: String? = null,
val telefon: String? = null,
val website: String? = null,
val oepsRegionNummer: String? = null,
val istVeranstalter: Boolean,
val istAktiv: Boolean,
val imageUrl: String? = null,
val bemerkungen: String? = null,
@Serializable(with = InstantSerializer::class)
val updatedAt: Instant
@@ -44,7 +43,6 @@ class VereinController(private val vereinRepository: VereinRepository) {
data class VereinCreateRequest(
val vereinsNummer: String,
val name: String,
val kurzname: String? = null,
val bundesland: String? = null,
val ort: String? = null,
val plz: String? = null,
@@ -52,16 +50,15 @@ class VereinController(private val vereinRepository: VereinRepository) {
val email: String? = null,
val telefon: String? = null,
val website: String? = null,
val oepsRegionNummer: String? = null,
val istVeranstalter: Boolean = false,
val istAktiv: Boolean = true,
val imageUrl: String? = null,
val bemerkungen: String? = null
)
@Serializable
data class VereinUpdateRequest(
val name: String? = null,
val kurzname: String? = null,
val bundesland: String? = null,
val ort: String? = null,
val plz: String? = null,
@@ -69,9 +66,9 @@ class VereinController(private val vereinRepository: VereinRepository) {
val email: String? = null,
val telefon: String? = null,
val website: String? = null,
val oepsRegionNummer: String? = null,
val istVeranstalter: Boolean? = null,
val istAktiv: Boolean? = null,
val imageUrl: String? = null,
val bemerkungen: String? = null
)
@@ -95,7 +92,7 @@ class VereinController(private val vereinRepository: VereinRepository) {
}
/**
* GET /verein/search?q=... Sucht Vereine nach Name oder Kurzname.
* GET /verein/search?q=... Sucht Vereine nach Name.
*/
get("/search") {
val query = call.request.queryParameters["q"] ?: ""
@@ -126,10 +123,9 @@ class VereinController(private val vereinRepository: VereinRepository) {
*/
post {
val req = call.receive<VereinCreateRequest>()
val domVerein = DomVerein(
val domVerein = Verein(
vereinsNummer = req.vereinsNummer,
name = req.name,
kurzname = req.kurzname,
vereinName = req.name,
bundesland = req.bundesland,
ort = req.ort,
plz = req.plz,
@@ -137,9 +133,9 @@ class VereinController(private val vereinRepository: VereinRepository) {
email = req.email,
telefon = req.telefon,
website = req.website,
oepsRegionNummer = req.oepsRegionNummer,
istVeranstalter = req.istVeranstalter,
istAktiv = req.istAktiv,
imageUrl = req.imageUrl,
bemerkungen = req.bemerkungen
)
val saved = vereinRepository.save(domVerein)
@@ -154,8 +150,7 @@ class VereinController(private val vereinRepository: VereinRepository) {
val existing = vereinRepository.findById(id) ?: return@put call.respond(HttpStatusCode.NotFound)
val req = call.receive<VereinUpdateRequest>()
val updated = existing.copy(
name = req.name ?: existing.name,
kurzname = req.kurzname ?: existing.kurzname,
vereinName = req.name ?: existing.vereinName,
bundesland = req.bundesland ?: existing.bundesland,
ort = req.ort ?: existing.ort,
plz = req.plz ?: existing.plz,
@@ -163,9 +158,9 @@ class VereinController(private val vereinRepository: VereinRepository) {
email = req.email ?: existing.email,
telefon = req.telefon ?: existing.telefon,
website = req.website ?: existing.website,
oepsRegionNummer = req.oepsRegionNummer ?: existing.oepsRegionNummer,
istVeranstalter = req.istVeranstalter ?: existing.istVeranstalter,
istAktiv = req.istAktiv ?: existing.istAktiv,
imageUrl = req.imageUrl ?: existing.imageUrl,
bemerkungen = req.bemerkungen ?: existing.bemerkungen
)
val saved = vereinRepository.save(updated)
@@ -185,11 +180,10 @@ class VereinController(private val vereinRepository: VereinRepository) {
private fun parseUuid(value: String?): Uuid? = value?.let { runCatching { Uuid.parse(it) }.getOrNull() }
private fun DomVerein.toDto() = VereinDto(
private fun Verein.toDto() = VereinDto(
vereinId = vereinId.toString(),
vereinsNummer = vereinsNummer,
name = name,
kurzname = kurzname,
name = vereinName,
bundesland = bundesland,
ort = ort,
plz = plz,
@@ -197,9 +191,9 @@ class VereinController(private val vereinRepository: VereinRepository) {
email = email,
telefon = telefon,
website = website,
oepsRegionNummer = oepsRegionNummer,
istVeranstalter = istVeranstalter,
istAktiv = istAktiv,
imageUrl = imageUrl,
bemerkungen = bemerkungen,
updatedAt = updatedAt
)
@@ -18,7 +18,7 @@ import kotlin.uuid.Uuid
* aus dem ZNS geprüft.
*
* @property funktionaerId Eindeutige interne ID (UUID).
* @property satzID Typ des Satzes (X = Richter, Y = Parcoursbauer). Aus ZNS (RICHT01.DAT / PARCO01.DAT).
* @property satzId Typ des Satzes (X = Richter, Y = Parcoursbauer). Aus ZNS (RICHT01.DAT / PARCO01.DAT).
* @property satzNummer Satznummer (6-stellig). Aus ZNS (RICHT01.DAT / PARCO01.DAT).
* @property name Vollständiger Name (Nachname, Vorname). Aus ZNS (RICHT01.DAT / PARCO01.DAT).
* @property qualifikation Qualifikationen (getrennt durch `,`). Aus ZNS (RICHT01.DAT / PARCO01.DAT).
@@ -29,24 +29,42 @@ import kotlin.uuid.Uuid
* @property updatedAt Letzter Änderungszeitpunkt.
*/
@Serializable
data class DomFunktionaer(
data class Funktionaer(
@Serializable(with = UuidSerializer::class)
val funktionaerId: Uuid = Uuid.random(),
val satzID: String,
// Reference to base person
@Serializable(with = UuidSerializer::class)
val personId: Uuid? = null,
// === ZNS.zip RICHT01.DAT === ANFANG ===
// Alphanumerisch (1) WERT "X" = RICHTER, "Y" = PARCOURSBAUER
var satzId: String,
// Numerisch (6) FORMAT: 000000
val satzNummer: Int,
// Alphanumerisch (75)
var name: String? = null, // Nachname, Vorname
// Alphanumerisch (30+)
var qualifikationen: List<String> = emptyList(), // Liste der Qualifikations-Kürzel
// var vorname: String,
// var nachname: String,
// var geburtsdatum: LocalDate? = null,
// val richterNummer: String? = null,
// var rollen: Set<FunktionaerRolleE> = emptySet(),
// var richterQualifikation: RichterQualifikationE? = null,
// var qualifiziertFuerSparten: Set<SparteE> = emptySet(),
// var email: String? = null,
// var telefon: String? = null,
// var vereinsNummer: String? = null,
// === ZNS.zip RICHT01.DAT === ENDE ===
// Kontakt
var imageUrl: String? = null,
var email: String? = null,
var telefon: String? = null,
var website: String? = null,
// Adresse
var strasse: String? = null,
var hausnummer: String? = null,
var ort: String? = null,
var plz: String? = null,
var bundesland: String? = null,
// Status & Verwaltung
var istAktiv: Boolean = true,
@@ -73,12 +91,12 @@ data class DomFunktionaer(
/**
* Prüft, ob der Funktionär als Richter qualifiziert ist.
*/
fun istRichter(): Boolean = satzID.uppercase() == "X"
fun istRichter(): Boolean = satzId.uppercase() == "X"
/**
* Prüft, ob der Funktionär als Parcoursbauer qualifiziert ist.
*/
fun istParcoursbauer(): Boolean = satzID.uppercase() == "Y"
fun istParcoursbauer(): Boolean = satzId.uppercase() == "Y"
/**
* Validiert die Pflichtfelder für den Turniereinsatz.
@@ -97,5 +115,5 @@ data class DomFunktionaer(
/**
* Erstellt eine Kopie mit aktualisiertem Zeitstempel.
*/
fun withUpdatedTimestamp(): DomFunktionaer = this.copy(updatedAt = Clock.System.now())
fun withUpdatedTimestamp(): Funktionaer = this.copy(updatedAt = Clock.System.now())
}
@@ -2,7 +2,7 @@
package at.mocode.masterdata.domain.model
import at.mocode.core.domain.model.LizenzKlasseE
import at.mocode.core.domain.model.ReiterLizenzKlasseE
import at.mocode.core.domain.model.SparteE
import at.mocode.core.domain.serialization.InstantSerializer
import at.mocode.core.domain.serialization.UuidSerializer
@@ -15,20 +15,20 @@ import kotlin.uuid.Uuid
*/
@Serializable
data class LicenseMatrixEntry(
@Serializable(with = UuidSerializer::class)
@Serializable(with = UuidSerializer::class)
val licenseId: Uuid = Uuid.random(),
val sparte: SparteE,
val lizenzKlasse: LizenzKlasseE,
val maxTurnierklasseCode: String, // E, A, L, LM, M, S
val sparte: SparteE,
val lizenzKlasse: ReiterLizenzKlasseE,
val maxTurnierklasseCode: String, // E, A, L, LM, M, S
@Serializable(with = InstantSerializer::class)
@Serializable(with = InstantSerializer::class)
val validFrom: Instant,
@Serializable(with = InstantSerializer::class)
@Serializable(with = InstantSerializer::class)
val validTo: Instant? = null,
val istAktiv: Boolean = true,
@Serializable(with = InstantSerializer::class)
val istAktiv: Boolean = true,
@Serializable(with = InstantSerializer::class)
val createdAt: Instant,
@Serializable(with = InstantSerializer::class)
@Serializable(with = InstantSerializer::class)
val updatedAt: Instant
)
@@ -4,6 +4,7 @@ package at.mocode.masterdata.domain.model
import at.mocode.core.domain.model.DatenQuelleE
import at.mocode.core.domain.model.PferdeGeschlechtE
import at.mocode.core.domain.serialization.InstantSerializer
import at.mocode.core.domain.serialization.UuidSerializer
import kotlinx.datetime.todayIn
import kotlinx.serialization.Serializable
@@ -39,42 +40,67 @@ import kotlin.uuid.Uuid
* @property updatedAt Timestamp when this record was last updated.
*/
@Serializable
data class DomPferd(
data class Pferd(
@Serializable(with = UuidSerializer::class)
val pferdId: Uuid = Uuid.random(),
// PFERDE01.DAT Information
// Reference to base person
@Serializable(with = UuidSerializer::class)
val personId: Uuid? = null,
// === ZNS.zip PFERDE01.DAT === ANFANG ===
// Alphanumerisch (4)
var kopfnummer: String? = null,
// Alphanumerisch (30)
var pferdeName: String,
// Alphanumerisch (9) FORMAT: 000000000
var lebensnummer: String? = null,
// Alphanumerisch (1)
var geschlecht: PferdeGeschlechtE,
// Numerisch (4) FORMAT: 0000
var geburtsjahr: Int? = null,
// Alphanumerisch (15)
var farbe: String? = null,
// Alphanumerisch (15)
var abstammung: String? = null,
// Numerisch (4) FORMAT: 0000
var vereinNummer: Int? = null,
// Numerisch (4) FORMAT: 0000
var lastPayYear: Int? = null,
// Alphanumerisch (75) Standard: BLANK
var verantwortlichePersonId: String? = null,
// Alphanumerisch (30) Standard: BLANK
var vater: String? = null,
// Alphanumerisch (10) Standard: BLANK
var feiPass: String? = null,
// Alphanumerisch (10) FORMAT: 0000000000
var satznummer: String? = null,
// var geburtsdatum: LocalDate? = null,
// var rasse: String? = null,
// @Serializable(with = UuidSerializer::class)
// var besitzerId: Uuid? = null,
// var zuechterName: String? = null,
// var zuchtbuchNummer: String? = null,
// var chipNummer: String? = null,
// var passNummer: String? = null,
// var oepsNummer: String? = null,
// var mutterName: String? = null,
// var mutterVaterName: String? = null,
// var stockmass: Int? = null, // Height in cm
// === ZNS.zip PFERDE01.DAT === ENDE ===
var istAktiv: Boolean = true,
// Status & Verwaltung
val istAktiv: Boolean = true,
var bemerkungen: String? = null,
var datenQuelle: DatenQuelleE = DatenQuelleE.MANUELL,
var createdAt: Instant = Clock.System.now(),
val datenQuelle: DatenQuelleE = DatenQuelleE.IMPORT_ZNS,
// Audit
@Serializable(with = InstantSerializer::class)
val createdAt: Instant = Clock.System.now(),
@Serializable(with = InstantSerializer::class)
var updatedAt: Instant = Clock.System.now()
) {
/**
@@ -141,7 +167,7 @@ data class DomPferd(
/**
* Creates a copy of this horse with an updated timestamp.
*/
fun withUpdatedTimestamp(): DomPferd {
fun withUpdatedTimestamp(): Pferd {
return this.copy(updatedAt = Clock.System.now())
}
}
@@ -3,8 +3,7 @@
package at.mocode.masterdata.domain.model
import at.mocode.core.domain.model.DatenQuelleE
import at.mocode.core.domain.model.LizenzKlasseE
import at.mocode.core.domain.model.SparteE
import at.mocode.core.domain.model.ReiterLizenzKlasseE
import at.mocode.core.domain.serialization.InstantSerializer
import at.mocode.core.domain.serialization.LocalDateSerializer
import at.mocode.core.domain.serialization.UuidSerializer
@@ -51,42 +50,101 @@ import kotlin.uuid.Uuid
* @property updatedAt Timestamp when this record was last updated.
*/
@Serializable
data class DomReiter(
data class Reiter(
@Serializable(with = UuidSerializer::class)
val reiterId: Uuid = Uuid.random(),
// Reference to base person
@Serializable(with = UuidSerializer::class)
val personId: Uuid,
val personId: Uuid? = null,
// ZNS Identification
// === ZNS.zip LIZENZ01.DAT === ANFANG ===
// Alphanumerisch (6) FORMAT: 000000
var satznummer: String?,
// Alphanumerisch (50)
var nachname: String,
// Alphanumerisch (25)
var vorname: String,
// Numerisch (2) FORMAT: 99
var bundeslandNummer: Int? = null,
// Alphanumerisch (50)
var vereinsName: String? = null,
// Alphanumerisch (3)
var nation: String? = null,
// Alphanumerisch (4) Keine Lizenz: BLANK
var reiterLizenz: String? = null,
// Alphanumerisch (1) Keine Startkarte: BLANK
var startkarte: String? = null,
// Alphanumerisch (2) Keine Fahrlizenz: BLANK
var fahrLizenz: String? = null,
// Alphanumerisch (2) WERTE: Standard: BLANK, JG=JUGENDLICHER, JR=JUNIOR, 25=U25
var altersklasseJgJrU25: String? = null,
// Alphanumerisch (1) WERTE: Standard: BLANK Y=JUNGER-REITER
var altersklasseY: String? = null,
// Numerisch (8) FORMAT: 00000000
var mitgliedsNummer: Int? = null,
// Alphanumerisch (21) Standard: BLANK
var telefonNummer: String? = null,
// Alphanumerisch (1) Standard: BLANK
var kader: String? = null,
/** Numerisch (4). Letztes Jahr, in dem die Lizenz bezahlt wurde. */
var lastPayYear: Int? = null,
/** Bestimmte Lizenzklasse (z.B. R1, R2, ...). */
var lizenzKlasse: ReiterLizenzKlasseE = ReiterLizenzKlasseE.LIZENZFREI,
// Alphanumerisch (1) WERTE: M=MÄNNLICH, W=WEIBLICH
var geschlecht: String? = null,
// Datum (8) FORMAT: YYYYMMDD
@Serializable(with = LocalDateSerializer::class)
var geburtsdatum: LocalDate? = null,
// Alphanumerisch (10) Standard: BLANK
var feiId: String? = null,
// Alphanumerisch (1) Werte: BLANK = nicht auf Sperrliste, S=auf Sperrliste, dort nachsehen!
var sperrListe: String? = null,
// Alphanumerisch (10) Standard: BLANK
var lizenzInfo: String? = null,
var lizenzKlasse: LizenzKlasseE = LizenzKlasseE.LIZENZFREI,
// === ZNS.zip LIZENZ01.DAT === ENDE ===
// Kontakt
var imageUrl: String? = null,
var email: String? = null,
var telefon: String? = null,
var website: String? = null,
// Adresse
var strasse: String? = null,
var hausnummer: String? = null,
var plz: String? = null,
var ort: String? = null,
var bundesland: String? = null,
// Status & Verwaltung
val istAktiv: Boolean = true,
var bemerkungen: String? = null,
val datenQuelle: DatenQuelleE = DatenQuelleE.IMPORT_ZNS,
// Audit
@Serializable(with = InstantSerializer::class)
val createdAt: Instant = Clock.System.now(),
@Serializable(with = InstantSerializer::class)
@@ -112,19 +170,19 @@ data class DomReiter(
fun hasLizenz(): Boolean = !reiterLizenz.isNullOrBlank()
/**
* Checks if the rider has a license for a specific sparte.
* Checks if the rider holds a license for a specific discipline.
* Simple logic for now: Any non-blank license field counts.
*/
fun hasLizenzForSparte(sparte: SparteE): Boolean {
// If we have a license class, check if it's applicable for the sparte
if (lizenzKlasse == LizenzKlasseE.LIZENZFREI) return false
fun hasLizenzForSparte(sparte: at.mocode.core.domain.model.SparteE): Boolean {
return when (sparte) {
SparteE.DRESSUR -> true // Everyone with a license can do dressage (simplified)
SparteE.SPRINGEN -> !listOf(LizenzKlasseE.RD1, LizenzKlasseE.RD2, LizenzKlasseE.RD3).contains(lizenzKlasse)
else -> true
at.mocode.core.domain.model.SparteE.DRESSUR -> !reiterLizenz.isNullOrBlank()
at.mocode.core.domain.model.SparteE.SPRINGEN -> !reiterLizenz.isNullOrBlank()
at.mocode.core.domain.model.SparteE.FAHREN -> !fahrLizenz.isNullOrBlank()
else -> hasLizenz()
}
}
/**
* Validates the rider for competition entry.
* Returns a list of warning messages (never hard errors TBA has final say).
@@ -146,5 +204,5 @@ data class DomReiter(
/**
* Creates a copy of this rider with an updated timestamp.
*/
fun withUpdatedTimestamp(): DomReiter = this.copy(updatedAt = Clock.System.now())
fun withUpdatedTimestamp(): Reiter = this.copy(updatedAt = Clock.System.now())
}
@@ -21,8 +21,7 @@ import kotlin.uuid.Uuid
*
* @property vereinId Eindeutige interne ID (UUID).
* @property vereinsNummer ÖPS-Vereinsnummer aus ZNS (VEREIN01.dat), 4-stellig. Primärschlüssel für ZNS-Datenaustausch.
* @property name Offizieller Vereinsname.
* @property kurzname Kurzbezeichnung des Vereins (optional).
* @property vereinName Offizieller Vereinsname.
* @property bundesland Bundesland, in dem der Verein ansässig ist.
* @property ort Ort / Stadt des Vereinssitzes.
* @property plz Postleitzahl.
@@ -30,7 +29,6 @@ import kotlin.uuid.Uuid
* @property email Offizielle E-Mail-Adresse des Vereins.
* @property telefon Telefonnummer des Vereins.
* @property website Website-URL des Vereins.
* @property oepsRegionNummer Regionsnummer beim OEPS (Landesverband).
* @property istVeranstalter Ob der Verein als Veranstalter von Turnieren zugelassen ist.
* @property istAktiv Ob der Verein aktuell aktiv ist.
* @property bemerkungen Interne Notizen.
@@ -39,35 +37,40 @@ import kotlin.uuid.Uuid
* @property updatedAt Letzter Änderungszeitpunkt.
*/
@Serializable
data class DomVerein(
data class Verein(
@Serializable(with = UuidSerializer::class)
val vereinId: Uuid = Uuid.random(),
// Identifikation
// Reference to base person
@Serializable(with = UuidSerializer::class)
val personId: Uuid? = null,
// === ZNS.zip VEREIN01.DAT === ANFANG ===
/** Numerisch (4) FORMAT: 0000. Primärschlüssel für ZNS-Datenaustausch. */
val vereinsNummer: String,
// Stammdaten
var name: String,
var kurzname: String? = null,
/** Alphanumerisch (50). Offizieller Vereinsname. */
var vereinName: String,
// Adresse
var bundesland: String? = null,
var ort: String? = null,
var plz: String? = null,
var strasse: String? = null,
// === ZNS.zip VEREIN01.DAT === ENDE ===
// Kontakt
var imageUrl: String? = null,
var email: String? = null,
var telefon: String? = null,
var website: String? = null,
// OEPS-Verwaltung
var oepsRegionNummer: String? = null,
var istVeranstalter: Boolean = false,
// Adresse
var strasse: String? = null,
var hausnummer: String? = null,
var plz: String? = null,
var ort: String? = null,
var bundesland: String? = null,
// Status & Verwaltung
var istAktiv: Boolean = true,
var logoUrl: String? = null,
var istVeranstalter: Boolean = false,
var bemerkungen: String? = null,
var datenQuelle: DatenQuelleE = DatenQuelleE.IMPORT_ZNS,
@@ -80,7 +83,7 @@ data class DomVerein(
/**
* Gibt den Anzeigenamen zurück Kurzname bevorzugt, sonst vollständiger Name.
*/
fun getDisplayName(): String = kurzname ?: name
fun getDisplayName(): String = vereinName
/**
* Gibt den vollständigen Anzeigenamen mit Vereinsnummer zurück.
@@ -91,13 +94,13 @@ data class DomVerein(
* Prüft, ob vollständige Adressdaten vorhanden sind.
*/
fun hasCompleteAddress(): Boolean =
!ort.isNullOrBlank() && !plz.isNullOrBlank() && !strasse.isNullOrBlank()
!strasse.isNullOrBlank() && !plz.isNullOrBlank() && !ort.isNullOrBlank()
/**
* Validiert den Verein für den Einsatz als Veranstalter.
* Gibt Warnungen zurück (kein harter Fehler Override-Event möglich).
*/
fun validateFuerVeranstaltung(): List<String> {
fun validateVeranstaltung(): List<String> {
val warnings = mutableListOf<String>()
if (!istAktiv) {
@@ -118,5 +121,5 @@ data class DomVerein(
/**
* Erstellt eine Kopie mit aktualisiertem Zeitstempel.
*/
fun withUpdatedTimestamp(): DomVerein = this.copy(updatedAt = Clock.System.now())
fun withUpdatedTimestamp(): Verein = this.copy(updatedAt = Clock.System.now())
}
@@ -2,11 +2,11 @@
package at.mocode.masterdata.domain.repository
import at.mocode.masterdata.domain.model.DomFunktionaer
import at.mocode.masterdata.domain.model.Funktionaer
import kotlin.uuid.Uuid
/**
* Repository-Interface für DomFunktionaer (Funktionär) Domain-Operationen.
* Repository-Interface für Funktionaer (Funktionär) Domain-Operationen.
*
* Definiert den Vertrag für Datenzugriffs-Operationen ohne Abhängigkeit
* von konkreten Implementierungsdetails (Datenbank, etc.).
@@ -16,22 +16,22 @@ interface FunktionaerRepository {
/**
* Sucht einen Funktionär anhand seiner eindeutigen ID.
*/
suspend fun findById(id: Uuid): DomFunktionaer?
suspend fun findById(id: Uuid): Funktionaer?
/**
* Sucht einen Funktionär anhand seiner Satz-ID und Satznummer.
*/
suspend fun findBySatz(satzID: String, satzNummer: Int): DomFunktionaer?
suspend fun findBySatz(satzID: String, satzNummer: Int): Funktionaer?
/**
* Gibt alle Funktionäre zurück (paginiert).
*/
suspend fun findAll(limit: Int = 100, offset: Int = 0): List<DomFunktionaer>
suspend fun findAll(limit: Int = 100, offset: Int = 0): List<Funktionaer>
/**
* Speichert einen Funktionär (Insert oder Update).
*/
suspend fun save(funktionaer: DomFunktionaer): DomFunktionaer
suspend fun save(funktionaer: Funktionaer): Funktionaer
/**
* Löscht einen Funktionär anhand seiner ID.
@@ -3,11 +3,11 @@
package at.mocode.masterdata.domain.repository
import at.mocode.core.domain.model.PferdeGeschlechtE
import at.mocode.masterdata.domain.model.DomPferd
import at.mocode.masterdata.domain.model.Pferd
import kotlin.uuid.Uuid
/**
* Repository interface for DomPferd (Horse) domain operations.
* Repository interface for Pferd (Horse) domain operations.
*
* This interface defines the contract for horse data access operations
* without depending on specific implementation details (database, etc.).
@@ -22,7 +22,7 @@ interface HorseRepository {
* @param id The unique identifier of the horse
* @return The horse if found, null otherwise
*/
suspend fun findById(id: Uuid): DomPferd?
suspend fun findById(id: Uuid): Pferd?
/**
* Finds a horse by its life number (Lebensnummer).
@@ -30,7 +30,7 @@ interface HorseRepository {
* @param lebensnummer The life number to search for
* @return The horse if found, null otherwise
*/
suspend fun findByLebensnummer(lebensnummer: String): DomPferd?
suspend fun findByLebensnummer(lebensnummer: String): Pferd?
/**
* Finds a horse by its chip number.
@@ -38,7 +38,7 @@ interface HorseRepository {
* @param chipNummer The chip number to search for
* @return The horse if found, null otherwise
*/
suspend fun findByChipNummer(chipNummer: String): DomPferd?
suspend fun findByChipNummer(chipNummer: String): Pferd?
/**
* Finds a horse by its passport number.
@@ -46,7 +46,7 @@ interface HorseRepository {
* @param passNummer The passport number to search for
* @return The horse if found, null otherwise
*/
suspend fun findByPassNummer(passNummer: String): DomPferd?
suspend fun findByPassNummer(passNummer: String): Pferd?
/**
* Finds a horse by its OEPS number.
@@ -54,7 +54,7 @@ interface HorseRepository {
* @param oepsNummer The OEPS number to search for
* @return The horse if found, null otherwise
*/
suspend fun findByOepsNummer(oepsNummer: String): DomPferd?
suspend fun findByOepsNummer(oepsNummer: String): Pferd?
/**
* Finds a horse by its FEI number.
@@ -62,7 +62,7 @@ interface HorseRepository {
* @param feiNummer The FEI number to search for
* @return The horse if found, null otherwise
*/
suspend fun findByFeiNummer(feiNummer: String): DomPferd?
suspend fun findByFeiNummer(feiNummer: String): Pferd?
/**
* Finds horses by name (partial match).
@@ -71,7 +71,7 @@ interface HorseRepository {
* @param limit Maximum number of results to return
* @return List of matching horses
*/
suspend fun findByName(searchTerm: String, limit: Int = 50): List<DomPferd>
suspend fun findByName(searchTerm: String, limit: Int = 50): List<Pferd>
/**
* Finds all horses owned by a specific person.
@@ -80,7 +80,7 @@ interface HorseRepository {
* @param activeOnly Whether to return only active horses
* @return List of horses owned by the person
*/
suspend fun findByOwnerId(ownerId: Uuid, activeOnly: Boolean = true): List<DomPferd>
suspend fun findByOwnerId(ownerId: Uuid, activeOnly: Boolean = true): List<Pferd>
/**
* Finds all horses for which a person is responsible.
@@ -89,7 +89,7 @@ interface HorseRepository {
* @param activeOnly Whether to return only active horses
* @return List of horses for which the person is responsible
*/
suspend fun findByResponsiblePersonId(responsiblePersonId: Uuid, activeOnly: Boolean = true): List<DomPferd>
suspend fun findByResponsiblePersonId(responsiblePersonId: Uuid, activeOnly: Boolean = true): List<Pferd>
/**
* Finds horses by gender.
@@ -103,7 +103,7 @@ interface HorseRepository {
geschlecht: PferdeGeschlechtE,
activeOnly: Boolean = true,
limit: Int = 100
): List<DomPferd>
): List<Pferd>
/**
* Finds horses by breed.
@@ -113,7 +113,7 @@ interface HorseRepository {
* @param limit Maximum number of results to return
* @return List of horses of the specified breed
*/
suspend fun findByRasse(rasse: String, activeOnly: Boolean = true, limit: Int = 100): List<DomPferd>
suspend fun findByRasse(rasse: String, activeOnly: Boolean = true, limit: Int = 100): List<Pferd>
/**
* Finds horses by birth year.
@@ -122,7 +122,7 @@ interface HorseRepository {
* @param activeOnly Whether to return only active horses
* @return List of horses born in the specified year
*/
suspend fun findByBirthYear(birthYear: Int, activeOnly: Boolean = true): List<DomPferd>
suspend fun findByBirthYear(birthYear: Int, activeOnly: Boolean = true): List<Pferd>
/**
* Finds horses by birth year range.
@@ -132,7 +132,7 @@ interface HorseRepository {
* @param activeOnly Whether to return only active horses
* @return List of horses born within the specified year range
*/
suspend fun findByBirthYearRange(fromYear: Int, toYear: Int, activeOnly: Boolean = true): List<DomPferd>
suspend fun findByBirthYearRange(fromYear: Int, toYear: Int, activeOnly: Boolean = true): List<Pferd>
/**
* Finds all active horses.
@@ -140,7 +140,7 @@ interface HorseRepository {
* @param limit Maximum number of results to return
* @return List of active horses
*/
suspend fun findAllActive(limit: Int = 1000): List<DomPferd>
suspend fun findAllActive(limit: Int = 1000): List<Pferd>
/**
* Finds horses with OEPS registration.
@@ -148,7 +148,7 @@ interface HorseRepository {
* @param activeOnly Whether to return only active horses
* @return List of OEPS registered horses
*/
suspend fun findOepsRegistered(activeOnly: Boolean = true): List<DomPferd>
suspend fun findOepsRegistered(activeOnly: Boolean = true): List<Pferd>
/**
* Finds horses with FEI registration.
@@ -156,7 +156,7 @@ interface HorseRepository {
* @param activeOnly Whether to return only active horses
* @return List of FEI registered horses
*/
suspend fun findFeiRegistered(activeOnly: Boolean = true): List<DomPferd>
suspend fun findFeiRegistered(activeOnly: Boolean = true): List<Pferd>
/**
* Saves a horse (create or update).
@@ -164,7 +164,7 @@ interface HorseRepository {
* @param horse The horse to save
* @return The saved horse with updated timestamps
*/
suspend fun save(horse: DomPferd): DomPferd
suspend fun save(horse: Pferd): Pferd
/**
* Deletes a horse by ID.
@@ -252,7 +252,7 @@ interface HorseRepository {
* @param kopfnummer The head number to search for
* @return The list of horses found
*/
suspend fun findByKopfnummer(kopfnummer: String): List<DomPferd>
suspend fun findByKopfnummer(kopfnummer: String): List<Pferd>
/**
* Finds a horse by its ZNS satznummer.
@@ -260,17 +260,17 @@ interface HorseRepository {
* @param satznummer The ZNS satznummer to search for
* @return The horse if found, null otherwise
*/
suspend fun findBySatznummer(satznummer: String): DomPferd?
suspend fun findBySatznummer(satznummer: String): Pferd?
/**
* Speichert ein Pferd basierend auf der ZNS satznummer (Upsert).
* Wenn ein Pferd mit der satznummer existiert, wird es aktualisiert, ansonsten neu angelegt.
*/
suspend fun upsertBySatznummer(horse: DomPferd): DomPferd
suspend fun upsertBySatznummer(horse: Pferd): Pferd
/**
* Speichert ein Pferd basierend auf der Lebensnummer (Upsert).
* Wenn ein Pferd mit der Lebensnummer existiert, wird es aktualisiert, ansonsten neu angelegt.
*/
suspend fun upsertByLebensnummer(horse: DomPferd): DomPferd
suspend fun upsertByLebensnummer(horse: Pferd): Pferd
}
@@ -2,11 +2,11 @@
package at.mocode.masterdata.domain.repository
import at.mocode.masterdata.domain.model.DomReiter
import at.mocode.masterdata.domain.model.Reiter
import kotlin.uuid.Uuid
/**
* Repository-Interface für DomReiter (Reiter) Domain-Operationen.
* Repository-Interface für Reiter (Reiter) Domain-Operationen.
*
* Definiert den Vertrag für Datenzugriffs-Operationen ohne Abhängigkeit
* von konkreten Implementierungsdetails (Datenbank etc.).
@@ -16,22 +16,22 @@ interface ReiterRepository {
/**
* Sucht einen Reiter anhand seiner eindeutigen ID.
*/
suspend fun findById(id: Uuid): DomReiter?
suspend fun findById(id: Uuid): Reiter?
/**
* Sucht einen Reiter anhand seiner Satznummer (OEPS-Mitgliedsnummer).
*/
suspend fun findBySatznummer(satznummer: String?): DomReiter?
suspend fun findBySatznummer(satznummer: String?): Reiter?
/**
* Gibt alle Reiter zurück (paginiert).
*/
suspend fun findAll(limit: Int = 100, offset: Int = 0): List<DomReiter>
suspend fun findAll(limit: Int = 100, offset: Int = 0): List<Reiter>
/**
* Speichert einen Reiter (Insert oder Update).
*/
suspend fun save(reiter: DomReiter): DomReiter
suspend fun save(reiter: Reiter): Reiter
/**
* Löscht einen Reiter anhand seiner ID.
@@ -54,5 +54,5 @@ interface ReiterRepository {
* Speichert einen Reiter basierend auf der Satznummer (Upsert).
* Wenn ein Reiter mit der Satznummer existiert, wird er aktualisiert, ansonsten neu angelegt.
*/
suspend fun upsertBySatznummer(reiter: DomReiter): DomReiter
suspend fun upsertBySatznummer(reiter: Reiter): Reiter
}
@@ -2,11 +2,11 @@
package at.mocode.masterdata.domain.repository
import at.mocode.masterdata.domain.model.DomVerein
import at.mocode.masterdata.domain.model.Verein
import kotlin.uuid.Uuid
/**
* Repository-Interface für DomVerein (Verein) Domain-Operationen.
* Repository-Interface für Verein (Verein) Domain-Operationen.
*
* Definiert den Vertrag für Datenzugriffs-Operationen ohne Abhängigkeit
* von konkreten Implementierungsdetails (Datenbank etc.).
@@ -16,42 +16,42 @@ interface VereinRepository {
/**
* Sucht einen Verein anhand seiner eindeutigen ID.
*/
suspend fun findById(id: Uuid): DomVerein?
suspend fun findById(id: Uuid): Verein?
/**
* Sucht einen Verein anhand seiner OEPS-Vereinsnummer.
*/
suspend fun findByVereinsNummer(vereinsNummer: String): DomVerein?
suspend fun findByVereinsNummer(vereinsNummer: String): Verein?
/**
* Sucht Vereine anhand des Namens (Teilübereinstimmung).
*/
suspend fun findByName(searchTerm: String, limit: Int = 50): List<DomVerein>
suspend fun findByName(searchTerm: String, limit: Int = 50): List<Verein>
/**
* Sucht alle Vereine eines Bundeslandes.
*/
suspend fun findByBundesland(bundesland: String, activeOnly: Boolean = true): List<DomVerein>
suspend fun findByBundesland(bundesland: String, activeOnly: Boolean = true): List<Verein>
/**
* Sucht alle Vereine, die als Veranstalter markiert sind.
*/
suspend fun findVeranstalter(activeOnly: Boolean = true): List<DomVerein>
suspend fun findVeranstalter(activeOnly: Boolean = true): List<Verein>
/**
* Gibt alle aktiven Vereine zurück (paginiert).
*/
suspend fun findAllActive(limit: Int = 100, offset: Int = 0): List<DomVerein>
suspend fun findAllActive(limit: Int = 100, offset: Int = 0): List<Verein>
/**
* Gibt alle Vereine zurück (paginiert).
*/
suspend fun findAll(limit: Int = 100, offset: Int = 0): List<DomVerein>
suspend fun findAll(limit: Int = 100, offset: Int = 0): List<Verein>
/**
* Speichert einen Verein (Insert oder Update).
*/
suspend fun save(verein: DomVerein): DomVerein
suspend fun save(verein: Verein): Verein
/**
* Löscht einen Verein anhand seiner ID.
@@ -74,5 +74,5 @@ interface VereinRepository {
* Speichert einen Verein basierend auf der Vereinsnummer (Upsert).
* Wenn ein Verein mit der Nummer existiert, wird er aktualisiert, ansonsten neu angelegt.
*/
suspend fun upsertByVereinsNummer(verein: DomVerein): DomVerein
suspend fun upsertByVereinsNummer(verein: Verein): Verein
}
@@ -1,7 +1,7 @@
package at.mocode.masterdata.domain.service
import at.mocode.masterdata.domain.model.DomPferd
import at.mocode.masterdata.domain.model.DomReiter
import at.mocode.masterdata.domain.model.Pferd
import at.mocode.masterdata.domain.model.Reiter
/**
* Service zur Prüfung von Abteilungs-Regeln gemäß ÖTO § 39.
@@ -23,8 +23,8 @@ interface AbteilungsRegelService {
* @return Die Abteilungsnummer (1, 2, 3), in die der Teilnehmer fällt.
*/
fun ermittleAbteilungStrukturell(
reiter: DomReiter,
pferd: DomPferd,
reiter: Reiter,
pferd: Pferd,
turnierklasseCode: String,
sparte: at.mocode.core.domain.model.SparteE,
istCNeu: Boolean = false,
@@ -1,9 +1,9 @@
package at.mocode.masterdata.domain.service
import at.mocode.core.domain.model.LizenzKlasseE
import at.mocode.core.domain.model.ReiterLizenzKlasseE
import at.mocode.core.domain.model.SparteE
import at.mocode.masterdata.domain.model.DomPferd
import at.mocode.masterdata.domain.model.DomReiter
import at.mocode.masterdata.domain.model.Pferd
import at.mocode.masterdata.domain.model.Reiter
/**
* Standard-Implementierung des [AbteilungsRegelService] gemäß ÖTO § 39.
@@ -11,8 +11,8 @@ import at.mocode.masterdata.domain.model.DomReiter
class AbteilungsRegelServiceImpl : AbteilungsRegelService {
override fun ermittleAbteilungStrukturell(
reiter: DomReiter,
pferd: DomPferd,
reiter: Reiter,
pferd: Pferd,
turnierklasseCode: String,
sparte: SparteE,
istCNeu: Boolean,
@@ -24,13 +24,13 @@ class AbteilungsRegelServiceImpl : AbteilungsRegelService {
if (istCNeu && sparte == SparteE.SPRINGEN) {
if (hoehe != null && hoehe <= 95) {
return when (reiter.lizenzKlasse) {
LizenzKlasseE.LIZENZFREI -> 1
LizenzKlasseE.R1, LizenzKlasseE.RD1 -> 2
ReiterLizenzKlasseE.LIZENZFREI -> 1
ReiterLizenzKlasseE.R1, ReiterLizenzKlasseE.RD1 -> 2
else -> 3 // R2+
}
} else if (hoehe != null && hoehe >= 100) {
return when (reiter.lizenzKlasse) {
LizenzKlasseE.R1, LizenzKlasseE.RD1 -> 1
ReiterLizenzKlasseE.R1, ReiterLizenzKlasseE.RD1 -> 1
else -> 2 // R2+
}
}
@@ -39,7 +39,7 @@ class AbteilungsRegelServiceImpl : AbteilungsRegelService {
// 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
ReiterLizenzKlasseE.R1, ReiterLizenzKlasseE.RD1 -> 1 // Abt. 1: R1
else -> 2 // Abt. 2+: R2 und höher
}
}
@@ -2,7 +2,7 @@ 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 at.mocode.masterdata.domain.model.Reiter
import kotlinx.datetime.LocalDate
/**
@@ -30,9 +30,9 @@ interface AltersklasseRechner {
* @return Eine Liste der zutreffenden Altersklassen-Definitionen.
*/
fun ermittleAltersklassen(
reiter: DomReiter,
referenzJahr: Int,
sparte: SparteE? = null,
verfügbareDefinitionen: List<AltersklasseDefinition>
reiter: Reiter,
referenzJahr: Int,
sparte: SparteE? = null,
verfügbareDefinitionen: List<AltersklasseDefinition>
): List<AltersklasseDefinition>
}
@@ -2,7 +2,7 @@ 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 at.mocode.masterdata.domain.model.Reiter
import kotlinx.datetime.LocalDate
/**
@@ -17,10 +17,10 @@ class AltersklasseRechnerImpl : AltersklasseRechner {
}
override fun ermittleAltersklassen(
reiter: DomReiter,
referenzJahr: Int,
sparte: SparteE?,
verfügbareDefinitionen: List<AltersklasseDefinition>
reiter: Reiter,
referenzJahr: Int,
sparte: SparteE?,
verfügbareDefinitionen: List<AltersklasseDefinition>
): List<AltersklasseDefinition> {
val geburtsdatum = reiter.geburtsdatum ?: return emptyList()
val alter = berechneOetoAlter(geburtsdatum, referenzJahr)
@@ -1,7 +1,7 @@
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.Reiter
import at.mocode.masterdata.domain.model.LicenseMatrixEntry
import at.mocode.masterdata.domain.model.TurnierklasseDefinition
@@ -21,7 +21,7 @@ interface LicenseMatrixService {
* @return true, wenn der Reiter startberechtigt ist, sonst false.
*/
fun isEligible(
reiter: DomReiter,
reiter: Reiter,
turnierklasse: TurnierklasseDefinition,
sparte: SparteE,
matrix: List<LicenseMatrixEntry>,
@@ -37,7 +37,7 @@ interface LicenseMatrixService {
* @return Der Code der maximal erlaubten Turnierklasse oder null, wenn keine Regel gefunden wurde.
*/
fun getMaxTurnierklasse(
reiter: DomReiter,
reiter: Reiter,
sparte: SparteE,
matrix: List<LicenseMatrixEntry>
): String?
@@ -1,7 +1,7 @@
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.Reiter
import at.mocode.masterdata.domain.model.LicenseMatrixEntry
import at.mocode.masterdata.domain.model.TurnierklasseDefinition
@@ -13,7 +13,7 @@ class LicenseMatrixServiceImpl : LicenseMatrixService {
private val classHierarchy = listOf("E", "A", "L", "LM", "M", "S")
override fun isEligible(
reiter: DomReiter,
reiter: Reiter,
turnierklasse: TurnierklasseDefinition,
sparte: SparteE,
matrix: List<LicenseMatrixEntry>,
@@ -35,7 +35,7 @@ class LicenseMatrixServiceImpl : LicenseMatrixService {
}
override fun getMaxTurnierklasse(
reiter: DomReiter,
reiter: Reiter,
sparte: SparteE,
matrix: List<LicenseMatrixEntry>
): String? {
@@ -2,11 +2,11 @@
package at.mocode.masterdata.domain.service
import at.mocode.core.domain.model.LizenzKlasseE
import at.mocode.core.domain.model.ReiterLizenzKlasseE
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 at.mocode.masterdata.domain.model.Pferd
import at.mocode.masterdata.domain.model.Reiter
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
@@ -17,24 +17,24 @@ class AbteilungsRegelServiceTest {
private val service = AbteilungsRegelServiceImpl()
private val standardPferd = DomPferd(pferdeName = "Testpferd", geschlecht = PferdeGeschlechtE.WALLACH)
private val standardPferd = Pferd(pferdeName = "Testpferd", geschlecht = PferdeGeschlechtE.WALLACH)
private val dummyPersonId = Uuid.random()
@Test
fun `ermittleAbteilungStrukturell teilt Klassen A und L nach R1`() {
val r1Reiter = DomReiter(
val r1Reiter = Reiter(
personId = dummyPersonId,
satznummer = "1",
nachname = "R1",
vorname = "R1",
lizenzKlasse = LizenzKlasseE.R1
lizenzKlasse = ReiterLizenzKlasseE.R1
)
val r2Reiter = DomReiter(
val r2Reiter = Reiter(
personId = dummyPersonId,
satznummer = "2",
nachname = "R2",
vorname = "R2",
lizenzKlasse = LizenzKlasseE.R2
lizenzKlasse = ReiterLizenzKlasseE.R2
)
assertEquals(1, service.ermittleAbteilungStrukturell(r1Reiter, standardPferd, "A", SparteE.SPRINGEN))
@@ -46,26 +46,26 @@ class AbteilungsRegelServiceTest {
@Test
fun `ermittleAbteilungStrukturell berücksichtigt C-NEU Regeln`() {
val lfReiter = DomReiter(
val lfReiter = Reiter(
personId = dummyPersonId,
satznummer = "0",
nachname = "LF",
vorname = "LF",
lizenzKlasse = LizenzKlasseE.LIZENZFREI
lizenzKlasse = ReiterLizenzKlasseE.LIZENZFREI
)
val r1Reiter = DomReiter(
val r1Reiter = Reiter(
personId = dummyPersonId,
satznummer = "1",
nachname = "R1",
vorname = "R1",
lizenzKlasse = LizenzKlasseE.R1
lizenzKlasse = ReiterLizenzKlasseE.R1
)
val r2Reiter = DomReiter(
val r2Reiter = Reiter(
personId = dummyPersonId,
satznummer = "2",
nachname = "R2",
vorname = "R2",
lizenzKlasse = LizenzKlasseE.R2
lizenzKlasse = ReiterLizenzKlasseE.R2
)
// Bis 95cm
@@ -4,7 +4,7 @@ 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 at.mocode.masterdata.domain.model.Reiter
import kotlinx.datetime.LocalDate
import kotlin.test.Test
import kotlin.test.assertEquals
@@ -30,7 +30,7 @@ class AltersklasseRechnerTest {
@Test
fun `ermittleAltersklassen findet passende Definitionen`() {
val reiter = DomReiter(
val reiter = Reiter(
personId = Uuid.random(),
satznummer = "123456",
nachname = "Mustermann",
@@ -73,7 +73,7 @@ class AltersklasseRechnerTest {
@Test
fun `ermittleAltersklassen beruecksichtigt SpartenFilter`() {
val reiter = DomReiter(
val reiter = Reiter(
personId = Uuid.random(),
satznummer = "123456",
nachname = "Mustermann",
@@ -2,9 +2,9 @@
package at.mocode.masterdata.domain.service
import at.mocode.core.domain.model.LizenzKlasseE
import at.mocode.core.domain.model.ReiterLizenzKlasseE
import at.mocode.core.domain.model.SparteE
import at.mocode.masterdata.domain.model.DomReiter
import at.mocode.masterdata.domain.model.Reiter
import at.mocode.masterdata.domain.model.LicenseMatrixEntry
import at.mocode.masterdata.domain.model.TurnierklasseDefinition
import kotlin.test.Test
@@ -21,7 +21,7 @@ class LicenseMatrixServiceTest {
private val matrix = listOf(
LicenseMatrixEntry(
sparte = SparteE.SPRINGEN,
lizenzKlasse = LizenzKlasseE.R1,
lizenzKlasse = ReiterLizenzKlasseE.R1,
maxTurnierklasseCode = "L",
validFrom = nun,
createdAt = nun,
@@ -29,7 +29,7 @@ class LicenseMatrixServiceTest {
),
LicenseMatrixEntry(
sparte = SparteE.SPRINGEN,
lizenzKlasse = LizenzKlasseE.R2,
lizenzKlasse = ReiterLizenzKlasseE.R2,
maxTurnierklasseCode = "M",
validFrom = nun,
createdAt = nun,
@@ -37,7 +37,7 @@ class LicenseMatrixServiceTest {
),
LicenseMatrixEntry(
sparte = SparteE.DRESSUR,
lizenzKlasse = LizenzKlasseE.RD1,
lizenzKlasse = ReiterLizenzKlasseE.RD1,
maxTurnierklasseCode = "L",
validFrom = nun,
createdAt = nun,
@@ -90,12 +90,12 @@ class LicenseMatrixServiceTest {
@Test
fun `isEligible erlaubt Starts bis zum Limit`() {
val r1Reiter = DomReiter(
val r1Reiter = Reiter(
personId = Uuid.random(),
satznummer = "1",
nachname = "R1",
vorname = "Reiter",
lizenzKlasse = LizenzKlasseE.R1
lizenzKlasse = ReiterLizenzKlasseE.R1
)
val klasseA = turnierklassen.find { it.code == "A" }!!
@@ -109,12 +109,12 @@ class LicenseMatrixServiceTest {
@Test
fun `isEligible verweigert Start ohne passende Spartenlizenz`() {
val rd1Reiter = DomReiter(
val rd1Reiter = Reiter(
personId = Uuid.random(),
satznummer = "2",
nachname = "RD1",
vorname = "Reiter",
lizenzKlasse = LizenzKlasseE.RD1
lizenzKlasse = ReiterLizenzKlasseE.RD1
)
val klasseA = turnierklassen.find { it.code == "A" }!!
@@ -2,7 +2,7 @@
package at.mocode.masterdata.infrastructure.persistence
import at.mocode.core.domain.model.LizenzKlasseE
import at.mocode.core.domain.model.ReiterLizenzKlasseE
import at.mocode.core.domain.model.SparteE
import at.mocode.core.utils.database.DatabaseFactory
import at.mocode.masterdata.domain.model.*
@@ -72,7 +72,7 @@ class ExposedRegulationRepository : RegulationRepository {
private fun ResultRow.toLicenseMatrixEntry() = LicenseMatrixEntry(
licenseId = this[LicenseTable.id],
sparte = SparteE.valueOf(this[LicenseTable.sparte]),
lizenzKlasse = LizenzKlasseE.valueOf(this[LicenseTable.lizenzKlasse]),
lizenzKlasse = ReiterLizenzKlasseE.valueOf(this[LicenseTable.lizenzKlasse]),
maxTurnierklasseCode = this[LicenseTable.maxTurnierklasseCode],
validFrom = this[LicenseTable.validFrom].toKtInstant(),
validTo = this[LicenseTable.validTo]?.toOptionalKtInstant(),
@@ -2,6 +2,7 @@
package at.mocode.masterdata.infrastructure.persistence
import at.mocode.masterdata.infrastructure.persistence.reiter.ReiterTable
import org.jetbrains.exposed.v1.core.Table
import org.jetbrains.exposed.v1.datetime.CurrentTimestamp
import org.jetbrains.exposed.v1.datetime.timestamp
@@ -2,6 +2,8 @@
package at.mocode.masterdata.infrastructure.persistence
import at.mocode.masterdata.infrastructure.persistence.reiter.ReiterTable
import at.mocode.masterdata.infrastructure.persistence.verein.VereinTable
import org.jetbrains.exposed.v1.core.Table
import org.jetbrains.exposed.v1.datetime.CurrentTimestamp
import org.jetbrains.exposed.v1.datetime.timestamp
@@ -1,10 +1,9 @@
@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class)
package at.mocode.masterdata.infrastructure.persistence
package at.mocode.masterdata.infrastructure.persistence.funktionaer
import at.mocode.core.domain.model.DatenQuelleE
import at.mocode.core.utils.database.DatabaseFactory
import at.mocode.masterdata.domain.model.DomFunktionaer
import at.mocode.masterdata.domain.model.Funktionaer
import at.mocode.masterdata.domain.repository.FunktionaerRepository
import org.jetbrains.exposed.v1.core.ResultRow
import org.jetbrains.exposed.v1.core.and
@@ -19,24 +18,25 @@ import kotlin.uuid.Uuid
/**
* Exposed-basierte Implementierung des Funktionaer-Repositorys.
*/
class ExposedFunktionaerRepository : FunktionaerRepository {
class FunktionaerExposedRepository : FunktionaerRepository {
private fun rowToDomFunktionaer(row: ResultRow, qualifikationen: List<String> = emptyList()): DomFunktionaer {
return DomFunktionaer(
funktionaerId = row[FunktionaerTable.id],
satzID = row[FunktionaerTable.satzID] ?: "X",
satzNummer = row[FunktionaerTable.satzNummer] ?: 0,
name = row[FunktionaerTable.name],
qualifikationen = qualifikationen,
istAktiv = row[FunktionaerTable.istAktiv],
bemerkungen = row[FunktionaerTable.bemerkungen],
datenQuelle = DatenQuelleE.valueOf(row[FunktionaerTable.datenQuelle]),
createdAt = row[FunktionaerTable.createdAt],
updatedAt = row[FunktionaerTable.updatedAt]
private fun rowToDomFunktionaer(row: ResultRow, qualifikationen: List<String> = emptyList()): Funktionaer {
return Funktionaer(
funktionaerId = row[FunktionaerTable.id],
personId = row[FunktionaerTable.personId],
satzId = row[FunktionaerTable.satzId] ?: "X",
satzNummer = row[FunktionaerTable.satzNummer] ?: 0,
name = row[FunktionaerTable.name],
qualifikationen = qualifikationen,
istAktiv = row[FunktionaerTable.istAktiv],
bemerkungen = row[FunktionaerTable.bemerkungen],
datenQuelle = DatenQuelleE.valueOf(row[FunktionaerTable.datenQuelle]),
createdAt = row[FunktionaerTable.createdAt],
updatedAt = row[FunktionaerTable.updatedAt]
)
}
override suspend fun findById(id: Uuid): DomFunktionaer? = DatabaseFactory.dbQuery {
override suspend fun findById(id: Uuid): Funktionaer? = DatabaseFactory.dbQuery {
val qualifikationen = FunktionaerQualifikationTable
.selectAll().where { FunktionaerQualifikationTable.funktionaerId eq id }
.map { it[FunktionaerQualifikationTable.qualifikation] }
@@ -46,9 +46,9 @@ class ExposedFunktionaerRepository : FunktionaerRepository {
.singleOrNull()
}
override suspend fun findBySatz(satzID: String, satzNummer: Int): DomFunktionaer? = DatabaseFactory.dbQuery {
override suspend fun findBySatz(satzID: String, satzNummer: Int): Funktionaer? = DatabaseFactory.dbQuery {
val row = FunktionaerTable.selectAll()
.where { (FunktionaerTable.satzID eq satzID) and (FunktionaerTable.satzNummer eq satzNummer) }
.where { (FunktionaerTable.satzId eq satzID) and (FunktionaerTable.satzNummer eq satzNummer) }
.singleOrNull() ?: return@dbQuery null
val qualifikationen = FunktionaerQualifikationTable
@@ -58,7 +58,7 @@ class ExposedFunktionaerRepository : FunktionaerRepository {
rowToDomFunktionaer(row, qualifikationen)
}
override suspend fun findAll(limit: Int, offset: Int): List<DomFunktionaer> = DatabaseFactory.dbQuery {
override suspend fun findAll(limit: Int, offset: Int): List<Funktionaer> = DatabaseFactory.dbQuery {
val funktionaere = FunktionaerTable.selectAll()
.limit(limit).offset(offset.toLong())
.toList()
@@ -73,11 +73,12 @@ class ExposedFunktionaerRepository : FunktionaerRepository {
}
}
override suspend fun save(funktionaer: DomFunktionaer): DomFunktionaer = DatabaseFactory.dbQuery {
override suspend fun save(funktionaer: Funktionaer): Funktionaer = DatabaseFactory.dbQuery {
val exists = FunktionaerTable.selectAll().where { FunktionaerTable.id eq funktionaer.funktionaerId }.any()
if (exists) {
FunktionaerTable.update({ FunktionaerTable.id eq funktionaer.funktionaerId }) {
it[satzID] = funktionaer.satzID
it[personId] = funktionaer.personId
it[satzId] = funktionaer.satzId
it[satzNummer] = funktionaer.satzNummer
it[name] = funktionaer.name
it[istAktiv] = funktionaer.istAktiv
@@ -88,7 +89,8 @@ class ExposedFunktionaerRepository : FunktionaerRepository {
} else {
FunktionaerTable.insert {
it[id] = funktionaer.funktionaerId
it[satzID] = funktionaer.satzID
it[personId] = funktionaer.personId
it[satzId] = funktionaer.satzId
it[satzNummer] = funktionaer.satzNummer
it[name] = funktionaer.name
it[istAktiv] = funktionaer.istAktiv
@@ -121,7 +123,7 @@ class ExposedFunktionaerRepository : FunktionaerRepository {
override suspend fun existsBySatz(satzID: String, satzNummer: Int): Boolean = DatabaseFactory.dbQuery {
FunktionaerTable.selectAll()
.where { (FunktionaerTable.satzID eq satzID) and (FunktionaerTable.satzNummer eq satzNummer) }
.where { (FunktionaerTable.satzId eq satzID) and (FunktionaerTable.satzNummer eq satzNummer) }
.any()
}
}
@@ -1,10 +1,11 @@
@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class)
@file:OptIn(ExperimentalUuidApi::class)
package at.mocode.masterdata.infrastructure.persistence
package at.mocode.masterdata.infrastructure.persistence.funktionaer
import org.jetbrains.exposed.v1.core.Table
import org.jetbrains.exposed.v1.datetime.CurrentTimestamp
import org.jetbrains.exposed.v1.datetime.timestamp
import kotlin.uuid.ExperimentalUuidApi
/**
@@ -12,19 +13,42 @@ import org.jetbrains.exposed.v1.datetime.timestamp
*/
object FunktionaerTable : Table("funktionaer") {
val id = uuid("funktionaer_id")
val satzID = varchar("satz_id", 1).nullable()
val personId = uuid("person_id").nullable()
// === ZNS.zip RICHT01.DAT === ANFANG ===
val satzId = varchar("satz_id", 1)
val satzNummer = integer("satz_nummer").nullable()
val name = varchar("name", 200).nullable()
// === ZNS.zip RICHT01.DAT === ENDE ===
// Kontakt
val imageUrl = varchar("image_url", 255).nullable()
val email = varchar("email", 200).nullable()
val telefon = varchar("telefon", 50).nullable()
val website = varchar("website", 255).nullable()
// Adresse
val strasse = varchar("strasse", 200).nullable()
val hausnummer = varchar("hausnummer", 10).nullable()
val plz = varchar("plz", 10).nullable()
val ort = varchar("ort", 100).nullable()
val bundesland = varchar("bundesland", 100).nullable()
// Status & Verwaltung
val istAktiv = bool("ist_aktiv").default(true)
val bemerkungen = text("bemerkungen").nullable()
val datenQuelle = varchar("daten_quelle", 50)
// Audit
val createdAt = timestamp("created_at").defaultExpression(CurrentTimestamp)
val updatedAt = timestamp("updated_at").defaultExpression(CurrentTimestamp)
override val primaryKey = PrimaryKey(id)
init {
index("idx_funktionaer_satz", isUnique = true, satzID, satzNummer)
index("idx_funktionaer_satz", isUnique = true, satzId, satzNummer)
}
}
@@ -1,11 +1,11 @@
@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class)
@file:OptIn(ExperimentalUuidApi::class)
package at.mocode.masterdata.infrastructure.persistence
package at.mocode.masterdata.infrastructure.persistence.pferd
import at.mocode.core.domain.model.DatenQuelleE
import at.mocode.core.domain.model.PferdeGeschlechtE
import at.mocode.core.utils.database.DatabaseFactory
import at.mocode.masterdata.domain.model.DomPferd
import at.mocode.masterdata.domain.model.Pferd
import at.mocode.masterdata.domain.repository.HorseRepository
import org.jetbrains.exposed.v1.core.ResultRow
import org.jetbrains.exposed.v1.core.eq
@@ -14,12 +14,13 @@ import org.jetbrains.exposed.v1.jdbc.deleteWhere
import org.jetbrains.exposed.v1.jdbc.insert
import org.jetbrains.exposed.v1.jdbc.selectAll
import org.jetbrains.exposed.v1.jdbc.update
import kotlin.uuid.ExperimentalUuidApi
import kotlin.uuid.Uuid
class HorseRepositoryImpl : HorseRepository {
class HorseExposedRepository : HorseRepository {
private fun rowToDomPferd(row: ResultRow): DomPferd {
return DomPferd(
private fun rowToPferd(row: ResultRow): Pferd {
return Pferd(
pferdId = row[HorseTable.id],
kopfnummer = row[HorseTable.kopfnummer],
pferdeName = row[HorseTable.pferdeName],
@@ -42,43 +43,43 @@ class HorseRepositoryImpl : HorseRepository {
)
}
override suspend fun findById(id: Uuid): DomPferd? = DatabaseFactory.dbQuery {
override suspend fun findById(id: Uuid): Pferd? = DatabaseFactory.dbQuery {
HorseTable.selectAll().where { HorseTable.id eq id }
.map(::rowToDomPferd)
.map(::rowToPferd)
.singleOrNull()
}
override suspend fun findByLebensnummer(lebensnummer: String): DomPferd? = DatabaseFactory.dbQuery {
override suspend fun findByLebensnummer(lebensnummer: String): Pferd? = DatabaseFactory.dbQuery {
HorseTable.selectAll().where { HorseTable.lebensnummer eq lebensnummer }
.map(::rowToDomPferd)
.map(::rowToPferd)
.singleOrNull()
}
override suspend fun findBySatznummer(satznummer: String): DomPferd? = DatabaseFactory.dbQuery {
override suspend fun findBySatznummer(satznummer: String): Pferd? = DatabaseFactory.dbQuery {
HorseTable.selectAll().where { HorseTable.satznummer eq satznummer }
.map(::rowToDomPferd)
.map(::rowToPferd)
.singleOrNull()
}
override suspend fun findByKopfnummer(kopfnummer: String): List<DomPferd> = DatabaseFactory.dbQuery {
override suspend fun findByKopfnummer(kopfnummer: String): List<Pferd> = DatabaseFactory.dbQuery {
HorseTable.selectAll().where { HorseTable.kopfnummer eq kopfnummer }
.map(::rowToDomPferd)
.map(::rowToPferd)
}
override suspend fun findByName(searchTerm: String, limit: Int): List<DomPferd> = DatabaseFactory.dbQuery {
override suspend fun findByName(searchTerm: String, limit: Int): List<Pferd> = DatabaseFactory.dbQuery {
val pattern = "%$searchTerm%"
HorseTable.selectAll().where { HorseTable.pferdeName like pattern }
.limit(limit)
.map(::rowToDomPferd)
.map(::rowToPferd)
}
override suspend fun findAllActive(limit: Int): List<DomPferd> = DatabaseFactory.dbQuery {
override suspend fun findAllActive(limit: Int): List<Pferd> = DatabaseFactory.dbQuery {
HorseTable.selectAll().where { HorseTable.istAktiv eq true }
.limit(limit)
.map(::rowToDomPferd)
.map(::rowToPferd)
}
override suspend fun save(horse: DomPferd): DomPferd = DatabaseFactory.dbQuery {
override suspend fun save(horse: Pferd): Pferd = DatabaseFactory.dbQuery {
val exists = HorseTable.selectAll().where { HorseTable.id eq horse.pferdId }.any()
if (exists) {
HorseTable.update({ HorseTable.id eq horse.pferdId }) {
@@ -139,11 +140,11 @@ class HorseRepositoryImpl : HorseRepository {
HorseTable.selectAll().where { HorseTable.istAktiv eq true }.count()
}
override suspend fun upsertByLebensnummer(horse: DomPferd): DomPferd = DatabaseFactory.dbQuery {
override suspend fun upsertByLebensnummer(horse: Pferd): Pferd = DatabaseFactory.dbQuery {
val lebensnummer = horse.lebensnummer ?: return@dbQuery save(horse)
val existing = HorseTable.selectAll().where { HorseTable.lebensnummer eq lebensnummer }
.map(::rowToDomPferd)
.map(::rowToPferd)
.singleOrNull()
if (existing != null) {
@@ -173,11 +174,11 @@ class HorseRepositoryImpl : HorseRepository {
}
}
override suspend fun upsertBySatznummer(horse: DomPferd): DomPferd = DatabaseFactory.dbQuery {
override suspend fun upsertBySatznummer(horse: Pferd): Pferd = DatabaseFactory.dbQuery {
val satznummer = horse.satznummer ?: return@dbQuery save(horse)
val existing = HorseTable.selectAll().where { HorseTable.satznummer eq satznummer }
.map(::rowToDomPferd)
.map(::rowToPferd)
.singleOrNull()
if (existing != null) {
@@ -207,28 +208,28 @@ class HorseRepositoryImpl : HorseRepository {
}
}
// Not implemented or needed based on current requirements/DomPferd state
override suspend fun findByChipNummer(chipNummer: String): DomPferd? = null
override suspend fun findByPassNummer(passNummer: String): DomPferd? = null
override suspend fun findByOepsNummer(oepsNummer: String): DomPferd? = null
override suspend fun findByFeiNummer(feiNummer: String): DomPferd? = null
override suspend fun findByOwnerId(ownerId: Uuid, activeOnly: Boolean): List<DomPferd> = emptyList()
override suspend fun findByResponsiblePersonId(responsiblePersonId: Uuid, activeOnly: Boolean): List<DomPferd> =
// Not implemented or needed based on current requirements/Pferd state
override suspend fun findByChipNummer(chipNummer: String): Pferd? = null
override suspend fun findByPassNummer(passNummer: String): Pferd? = null
override suspend fun findByOepsNummer(oepsNummer: String): Pferd? = null
override suspend fun findByFeiNummer(feiNummer: String): Pferd? = null
override suspend fun findByOwnerId(ownerId: Uuid, activeOnly: Boolean): List<Pferd> = emptyList()
override suspend fun findByResponsiblePersonId(responsiblePersonId: Uuid, activeOnly: Boolean): List<Pferd> =
emptyList()
override suspend fun findByGeschlecht(
geschlecht: PferdeGeschlechtE,
activeOnly: Boolean,
limit: Int
): List<DomPferd> = emptyList()
): List<Pferd> = emptyList()
override suspend fun findByRasse(rasse: String, activeOnly: Boolean, limit: Int): List<DomPferd> = emptyList()
override suspend fun findByBirthYear(birthYear: Int, activeOnly: Boolean): List<DomPferd> = emptyList()
override suspend fun findByBirthYearRange(fromYear: Int, toYear: Int, activeOnly: Boolean): List<DomPferd> =
override suspend fun findByRasse(rasse: String, activeOnly: Boolean, limit: Int): List<Pferd> = emptyList()
override suspend fun findByBirthYear(birthYear: Int, activeOnly: Boolean): List<Pferd> = emptyList()
override suspend fun findByBirthYearRange(fromYear: Int, toYear: Int, activeOnly: Boolean): List<Pferd> =
emptyList()
override suspend fun findOepsRegistered(activeOnly: Boolean): List<DomPferd> = emptyList()
override suspend fun findFeiRegistered(activeOnly: Boolean): List<DomPferd> = emptyList()
override suspend fun findOepsRegistered(activeOnly: Boolean): List<Pferd> = emptyList()
override suspend fun findFeiRegistered(activeOnly: Boolean): List<Pferd> = emptyList()
override suspend fun existsByChipNummer(chipNummer: String): Boolean = false
override suspend fun existsByPassNummer(passNummer: String): Boolean = false
override suspend fun existsByOepsNummer(oepsNummer: String): Boolean = false
@@ -1,16 +1,21 @@
@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class)
@file:OptIn(ExperimentalUuidApi::class)
package at.mocode.masterdata.infrastructure.persistence
package at.mocode.masterdata.infrastructure.persistence.pferd
import org.jetbrains.exposed.v1.core.Table
import org.jetbrains.exposed.v1.datetime.CurrentTimestamp
import org.jetbrains.exposed.v1.datetime.timestamp
import kotlin.uuid.ExperimentalUuidApi
/**
* Exposed-Tabellendefinition für die Pferd-Entität basierend auf PFERDE01.DAT.
*/
object HorseTable : Table("horse") {
val id = uuid("horse_id")
val personId = uuid("person_id").nullable()
// === ZNS.zip PFERDE01.DAT === ANFANG ===
val kopfnummer = varchar("kopfnummer", 4).nullable().index()
val pferdeName = varchar("pferde_name", 200).index()
val lebensnummer = varchar("lebensnummer", 50).nullable().index()
@@ -25,9 +30,14 @@ object HorseTable : Table("horse") {
val feiPass = varchar("fei_pass", 50).nullable()
val satznummer = varchar("satznummer", 10).nullable()
// === ZNS.zip PFERDE01.DAT === ENDE ===
// Status & Verwaltung
val istAktiv = bool("ist_aktiv").default(true)
val bemerkungen = text("bemerkungen").nullable()
val datenQuelle = varchar("daten_quelle", 50)
// Audit
val createdAt = timestamp("created_at").defaultExpression(CurrentTimestamp)
val updatedAt = timestamp("updated_at").defaultExpression(CurrentTimestamp)
@@ -1,24 +1,25 @@
@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class)
@file:OptIn(ExperimentalUuidApi::class)
package at.mocode.masterdata.infrastructure.persistence
package at.mocode.masterdata.infrastructure.persistence.reiter
import at.mocode.core.domain.model.DatenQuelleE
import at.mocode.core.domain.model.LizenzKlasseE
import at.mocode.core.domain.model.ReiterLizenzKlasseE
import at.mocode.core.utils.database.DatabaseFactory
import at.mocode.masterdata.domain.model.DomReiter
import at.mocode.masterdata.domain.model.Reiter
import at.mocode.masterdata.domain.repository.ReiterRepository
import org.jetbrains.exposed.v1.core.ResultRow
import org.jetbrains.exposed.v1.core.eq
import org.jetbrains.exposed.v1.jdbc.*
import kotlin.uuid.ExperimentalUuidApi
import kotlin.uuid.Uuid
/**
* Exposed-basierte Implementierung des Reiter-Repositorys.
*/
class ExposedReiterRepository : ReiterRepository {
class ReiterExposedRepository : ReiterRepository {
private fun rowToDomReiter(row: ResultRow): DomReiter {
return DomReiter(
private fun rowToDomReiter(row: ResultRow): Reiter {
return Reiter(
reiterId = row[ReiterTable.id],
personId = row[ReiterTable.personId],
satznummer = row[ReiterTable.satznummer],
@@ -41,7 +42,7 @@ class ExposedReiterRepository : ReiterRepository {
feiId = row[ReiterTable.feiId],
sperrListe = row[ReiterTable.sperrListe],
lizenzInfo = row[ReiterTable.lizenzInfo],
lizenzKlasse = LizenzKlasseE.valueOf(row[ReiterTable.lizenzKlasse]),
lizenzKlasse = ReiterLizenzKlasseE.valueOf(row[ReiterTable.lizenzKlasse]),
istAktiv = row[ReiterTable.istAktiv],
bemerkungen = row[ReiterTable.bemerkungen],
datenQuelle = DatenQuelleE.valueOf(row[ReiterTable.datenQuelle]),
@@ -50,25 +51,25 @@ class ExposedReiterRepository : ReiterRepository {
)
}
override suspend fun findById(id: Uuid): DomReiter? = DatabaseFactory.dbQuery {
override suspend fun findById(id: Uuid): Reiter? = DatabaseFactory.dbQuery {
ReiterTable.selectAll().where { ReiterTable.id eq id }
.map { rowToDomReiter(it) }
.singleOrNull()
}
override suspend fun findBySatznummer(satznummer: String?): DomReiter? = DatabaseFactory.dbQuery {
override suspend fun findBySatznummer(satznummer: String?): Reiter? = DatabaseFactory.dbQuery {
ReiterTable.selectAll().where { ReiterTable.satznummer eq satznummer }
.map { row -> rowToDomReiter(row) }
.singleOrNull()
}
override suspend fun findAll(limit: Int, offset: Int): List<DomReiter> = DatabaseFactory.dbQuery {
override suspend fun findAll(limit: Int, offset: Int): List<Reiter> = DatabaseFactory.dbQuery {
ReiterTable.selectAll()
.limit(limit).offset(offset.toLong())
.map { row -> rowToDomReiter(row) }
}
override suspend fun save(reiter: DomReiter): DomReiter = DatabaseFactory.dbQuery {
override suspend fun save(reiter: Reiter): Reiter = DatabaseFactory.dbQuery {
val exists = ReiterTable.selectAll().where { ReiterTable.id eq reiter.reiterId }.any()
if (exists) {
ReiterTable.update({ ReiterTable.id eq reiter.reiterId }) {
@@ -147,7 +148,7 @@ class ExposedReiterRepository : ReiterRepository {
ReiterTable.selectAll().where { ReiterTable.satznummer eq satznummer }.any()
}
override suspend fun upsertBySatznummer(reiter: DomReiter): DomReiter = DatabaseFactory.dbQuery {
override suspend fun upsertBySatznummer(reiter: Reiter): Reiter = DatabaseFactory.dbQuery {
val existing = findBySatznummer(reiter.satznummer)
if (existing != null) {
@@ -1,19 +1,22 @@
@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class)
@file:OptIn(ExperimentalUuidApi::class)
package at.mocode.masterdata.infrastructure.persistence
package at.mocode.masterdata.infrastructure.persistence.reiter
import at.mocode.core.domain.model.LizenzKlasseE
import org.jetbrains.exposed.v1.core.Table
import org.jetbrains.exposed.v1.datetime.CurrentTimestamp
import org.jetbrains.exposed.v1.datetime.date
import org.jetbrains.exposed.v1.datetime.timestamp
import kotlin.uuid.ExperimentalUuidApi
/**
* Exposed-Tabellendefinition für die Reiter-Entität.
*/
object ReiterTable : Table("reiter") {
val id = uuid("reiter_id")
val personId = uuid("person_id")
val personId = uuid("person_id").nullable()
// === ZNS.zip LITENZ01.DAT === ANFANG ===
val satznummer = varchar("satznummer", 10).nullable()
val nachname = varchar("nachname", 100)
val vorname = varchar("vorname", 100)
@@ -34,11 +37,28 @@ object ReiterTable : Table("reiter") {
val feiId = varchar("fei_id", 20).nullable()
val sperrListe = varchar("sperr_liste", 50).nullable()
val lizenzInfo = varchar("lizenz_info", 100).nullable()
val lizenzKlasse = varchar("lizenz_klasse", 50).default(LizenzKlasseE.LIZENZFREI.name)
// === ZNS.zip LITENZ01.DAT === ENDE ===
// Kontakt
val imageUrl = varchar("image_url", 255).nullable()
val email = varchar("email", 200).nullable()
val telefon = varchar("telefon", 50).nullable()
val website = varchar("website", 255).nullable()
// Adresse
val strasse = varchar("strasse", 200).nullable()
val hausnummer = varchar("hausnummer", 10).nullable()
val plz = varchar("plz", 10).nullable()
val ort = varchar("ort", 100).nullable()
val bundesland = varchar("bundesland", 100).nullable()
// Status & Verwaltung
val istAktiv = bool("ist_aktiv").default(true)
val bemerkungen = text("bemerkungen").nullable()
val datenQuelle = varchar("daten_quelle", 50)
// Audit
val createdAt = timestamp("created_at").defaultExpression(CurrentTimestamp)
val updatedAt = timestamp("updated_at").defaultExpression(CurrentTimestamp)
@@ -1,102 +1,113 @@
@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class)
@file:OptIn(ExperimentalUuidApi::class)
package at.mocode.masterdata.infrastructure.persistence
package at.mocode.masterdata.infrastructure.persistence.verein
import at.mocode.core.domain.model.DatenQuelleE
import at.mocode.core.utils.database.DatabaseFactory
import at.mocode.masterdata.domain.model.DomVerein
import at.mocode.masterdata.domain.model.Verein
import at.mocode.masterdata.domain.repository.VereinRepository
import org.jetbrains.exposed.v1.core.ResultRow
import org.jetbrains.exposed.v1.core.eq
import org.jetbrains.exposed.v1.core.like
import org.jetbrains.exposed.v1.core.or
import org.jetbrains.exposed.v1.jdbc.*
import kotlin.uuid.ExperimentalUuidApi
import kotlin.uuid.Uuid
/**
* Exposed-basierte Implementierung des Verein-Repositorys.
*/
class ExposedVereinRepository : VereinRepository {
class VereinExposedRepository : VereinRepository {
private fun rowToDomVerein(row: ResultRow): DomVerein {
return DomVerein(
private fun rowToVereinDomain(row: ResultRow): Verein {
return Verein(
vereinId = row[VereinTable.id],
personId = row[VereinTable.personId],
// === ZNS.zip VEREIN01.DAT === ANFANG ===
vereinsNummer = row[VereinTable.vereinsNummer],
name = row[VereinTable.name],
kurzname = row[VereinTable.kurzname],
bundesland = row[VereinTable.bundesland],
ort = row[VereinTable.ort],
plz = row[VereinTable.plz],
strasse = row[VereinTable.strasse],
vereinName = row[VereinTable.vereinName],
// === ZNS.zip VEREIN01.DAT === ENDE ===
// Kontakt
imageUrl = row[VereinTable.imageUrl],
email = row[VereinTable.email],
telefon = row[VereinTable.telefon],
website = row[VereinTable.website],
oepsRegionNummer = row[VereinTable.oepsRegionNummer],
istVeranstalter = row[VereinTable.istVeranstalter],
// Adresse
strasse = row[VereinTable.strasse],
hausnummer = row[VereinTable.hausnummer],
plz = row[VereinTable.plz],
ort = row[VereinTable.ort],
bundesland = row[VereinTable.bundesland],
// Status & Verwaltung
istAktiv = row[VereinTable.istAktiv],
logoUrl = row[VereinTable.logoUrl],
istVeranstalter = row[VereinTable.istVeranstalter],
bemerkungen = row[VereinTable.bemerkungen],
datenQuelle = DatenQuelleE.valueOf(row[VereinTable.datenQuelle]),
// Audit
createdAt = row[VereinTable.createdAt],
updatedAt = row[VereinTable.updatedAt]
)
}
override suspend fun findById(id: Uuid): DomVerein? = DatabaseFactory.dbQuery {
override suspend fun findById(id: Uuid): Verein? = DatabaseFactory.dbQuery {
VereinTable.selectAll().where { VereinTable.id eq id }
.map(::rowToDomVerein)
.map(::rowToVereinDomain)
.singleOrNull()
}
override suspend fun findByVereinsNummer(vereinsNummer: String): DomVerein? = DatabaseFactory.dbQuery {
override suspend fun findByVereinsNummer(vereinsNummer: String): Verein? = DatabaseFactory.dbQuery {
VereinTable.selectAll().where { VereinTable.vereinsNummer eq vereinsNummer }
.map(::rowToDomVerein)
.map(::rowToVereinDomain)
.singleOrNull()
}
override suspend fun findByName(searchTerm: String, limit: Int): List<DomVerein> = DatabaseFactory.dbQuery {
override suspend fun findByName(searchTerm: String, limit: Int): List<Verein> = DatabaseFactory.dbQuery {
val pattern = "%$searchTerm%"
VereinTable.selectAll().where { (VereinTable.name like pattern) or (VereinTable.kurzname like pattern) }
VereinTable.selectAll().where { VereinTable.vereinName like pattern }
.limit(limit)
.map(::rowToDomVerein)
.map(::rowToVereinDomain)
}
override suspend fun findByBundesland(bundesland: String, activeOnly: Boolean): List<DomVerein> =
override suspend fun findByBundesland(bundesland: String, activeOnly: Boolean): List<Verein> =
DatabaseFactory.dbQuery {
val query = VereinTable.selectAll().where { VereinTable.bundesland eq bundesland }
if (activeOnly) {
query.andWhere { VereinTable.istAktiv eq true }
}
query.map(::rowToDomVerein)
query.map(::rowToVereinDomain)
}
override suspend fun findVeranstalter(activeOnly: Boolean): List<DomVerein> = DatabaseFactory.dbQuery {
override suspend fun findVeranstalter(activeOnly: Boolean): List<Verein> = DatabaseFactory.dbQuery {
val query = VereinTable.selectAll().where { VereinTable.istVeranstalter eq true }
if (activeOnly) {
query.andWhere { VereinTable.istAktiv eq true }
}
query.map(::rowToDomVerein)
query.map(::rowToVereinDomain)
}
override suspend fun findAllActive(limit: Int, offset: Int): List<DomVerein> = DatabaseFactory.dbQuery {
override suspend fun findAllActive(limit: Int, offset: Int): List<Verein> = DatabaseFactory.dbQuery {
VereinTable.selectAll().where { VereinTable.istAktiv eq true }
.limit(limit).offset(offset.toLong())
.map(::rowToDomVerein)
.map(::rowToVereinDomain)
}
override suspend fun findAll(limit: Int, offset: Int): List<DomVerein> = DatabaseFactory.dbQuery {
override suspend fun findAll(limit: Int, offset: Int): List<Verein> = DatabaseFactory.dbQuery {
VereinTable.selectAll()
.limit(limit).offset(offset.toLong())
.map(::rowToDomVerein)
.map(::rowToVereinDomain)
}
override suspend fun save(verein: DomVerein): DomVerein = DatabaseFactory.dbQuery {
override suspend fun save(verein: Verein): Verein = DatabaseFactory.dbQuery {
val exists = VereinTable.selectAll().where { VereinTable.id eq verein.vereinId }.any()
if (exists) {
VereinTable.update({ VereinTable.id eq verein.vereinId }) {
it[vereinsNummer] = verein.vereinsNummer
it[name] = verein.name
it[kurzname] = verein.kurzname
it[vereinName] = verein.vereinName
it[bundesland] = verein.bundesland
it[ort] = verein.ort
it[plz] = verein.plz
@@ -104,10 +115,9 @@ class ExposedVereinRepository : VereinRepository {
it[email] = verein.email
it[telefon] = verein.telefon
it[website] = verein.website
it[oepsRegionNummer] = verein.oepsRegionNummer
it[istVeranstalter] = verein.istVeranstalter
it[istAktiv] = verein.istAktiv
it[logoUrl] = verein.logoUrl
it[imageUrl] = verein.imageUrl
it[bemerkungen] = verein.bemerkungen
it[datenQuelle] = verein.datenQuelle.name
it[updatedAt] = verein.updatedAt
@@ -116,9 +126,9 @@ class ExposedVereinRepository : VereinRepository {
} else {
VereinTable.insert {
it[id] = verein.vereinId
it[personId] = verein.personId
it[vereinsNummer] = verein.vereinsNummer
it[name] = verein.name
it[kurzname] = verein.kurzname
it[vereinName] = verein.vereinName
it[bundesland] = verein.bundesland
it[ort] = verein.ort
it[plz] = verein.plz
@@ -126,10 +136,9 @@ class ExposedVereinRepository : VereinRepository {
it[email] = verein.email
it[telefon] = verein.telefon
it[website] = verein.website
it[oepsRegionNummer] = verein.oepsRegionNummer
it[istVeranstalter] = verein.istVeranstalter
it[istAktiv] = verein.istAktiv
it[logoUrl] = verein.logoUrl
it[imageUrl] = verein.imageUrl
it[bemerkungen] = verein.bemerkungen
it[datenQuelle] = verein.datenQuelle.name
it[createdAt] = verein.createdAt
@@ -151,17 +160,16 @@ class ExposedVereinRepository : VereinRepository {
VereinTable.selectAll().where { VereinTable.vereinsNummer eq vereinsNummer }.any()
}
override suspend fun upsertByVereinsNummer(verein: DomVerein): DomVerein = DatabaseFactory.dbQuery {
override suspend fun upsertByVereinsNummer(verein: Verein): Verein = DatabaseFactory.dbQuery {
val existing = VereinTable.selectAll().where { VereinTable.vereinsNummer eq verein.vereinsNummer }
.map(::rowToDomVerein)
.map(::rowToVereinDomain)
.singleOrNull()
if (existing != null) {
val toUpdate = verein.copy(vereinId = existing.vereinId)
VereinTable.update({ VereinTable.id eq existing.vereinId }) {
it[vereinsNummer] = toUpdate.vereinsNummer
it[name] = toUpdate.name
it[kurzname] = toUpdate.kurzname
it[vereinName] = toUpdate.vereinName
it[bundesland] = toUpdate.bundesland
it[ort] = toUpdate.ort
it[plz] = toUpdate.plz
@@ -169,10 +177,9 @@ class ExposedVereinRepository : VereinRepository {
it[email] = toUpdate.email
it[telefon] = toUpdate.telefon
it[website] = toUpdate.website
it[oepsRegionNummer] = toUpdate.oepsRegionNummer
it[istVeranstalter] = toUpdate.istVeranstalter
it[istAktiv] = toUpdate.istAktiv
it[logoUrl] = toUpdate.logoUrl
it[imageUrl] = toUpdate.imageUrl
it[bemerkungen] = toUpdate.bemerkungen
it[datenQuelle] = toUpdate.datenQuelle.name
it[updatedAt] = toUpdate.updatedAt
@@ -1,32 +1,47 @@
@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class)
@file:OptIn(ExperimentalUuidApi::class)
package at.mocode.masterdata.infrastructure.persistence
package at.mocode.masterdata.infrastructure.persistence.verein
import org.jetbrains.exposed.v1.core.Table
import org.jetbrains.exposed.v1.datetime.CurrentTimestamp
import org.jetbrains.exposed.v1.datetime.timestamp
import kotlin.uuid.ExperimentalUuidApi
/**
* Exposed-Tabellendefinition für die Verein-Entität.
*/
object VereinTable : Table("verein") {
val id = uuid("verein_id")
val personId = uuid("person_id").nullable()
// === ZNS.zip VEREIN01.DAT === ANFANG ===
val vereinsNummer = varchar("vereins_nummer", 10).uniqueIndex()
val name = varchar("name", 200)
val kurzname = varchar("kurzname", 100).nullable()
val bundesland = varchar("bundesland", 100).nullable()
val ort = varchar("ort", 100).nullable()
val plz = varchar("plz", 10).nullable()
val strasse = varchar("strasse", 200).nullable()
val vereinName = varchar("verein_name", 200)
// === ZNS.zip VEREIN01.DAT === ENDE ===
// Kontakt
val imageUrl = varchar("image_url", 255).nullable()
val email = varchar("email", 200).nullable()
val telefon = varchar("telefon", 50).nullable()
val website = varchar("website", 255).nullable()
val oepsRegionNummer = varchar("oeps_region_nummer", 10).nullable()
val istVeranstalter = bool("ist_veranstalter").default(false)
// Adresse
val strasse = varchar("strasse", 200).nullable()
val hausnummer = varchar("hausnummer", 10).nullable()
val plz = varchar("plz", 10).nullable()
val ort = varchar("ort", 100).nullable()
val bundesland = varchar("bundesland", 100).nullable()
// Status & Verwaltung
val istAktiv = bool("ist_aktiv").default(true)
val logoUrl = varchar("logo_url", 255).nullable()
val istVeranstalter = bool("ist_veranstalter").default(false)
val bemerkungen = text("bemerkungen").nullable()
val datenQuelle = varchar("daten_quelle", 50)
// Audit
val createdAt = timestamp("created_at").defaultExpression(CurrentTimestamp)
val updatedAt = timestamp("updated_at").defaultExpression(CurrentTimestamp)
@@ -2,7 +2,7 @@
package at.mocode.masterdata.infrastructure.persistence
import at.mocode.core.domain.model.LizenzKlasseE
import at.mocode.core.domain.model.ReiterLizenzKlasseE
import at.mocode.core.domain.model.SparteE
import at.mocode.masterdata.domain.model.LicenseMatrixEntry
import at.mocode.masterdata.domain.model.TurnierklasseDefinition
@@ -77,7 +77,7 @@ class RegulationSeedVerificationTest {
val oetoMatrix = listOf(
LicenseMatrixEntry(
sparte = SparteE.SPRINGEN,
lizenzKlasse = LizenzKlasseE.R1,
lizenzKlasse = ReiterLizenzKlasseE.R1,
maxTurnierklasseCode = "L",
validFrom = now,
createdAt = now,
@@ -85,7 +85,7 @@ class RegulationSeedVerificationTest {
),
LicenseMatrixEntry(
sparte = SparteE.SPRINGEN,
lizenzKlasse = LizenzKlasseE.R2,
lizenzKlasse = ReiterLizenzKlasseE.R2,
maxTurnierklasseCode = "M",
validFrom = now,
createdAt = now,
@@ -93,12 +93,12 @@ class RegulationSeedVerificationTest {
)
)
val r1Reiter = at.mocode.masterdata.domain.model.DomReiter(
val r1Reiter = at.mocode.masterdata.domain.model.Reiter(
personId = Uuid.random(),
satznummer = "123456",
nachname = "Müller",
vorname = "Hans",
lizenzKlasse = LizenzKlasseE.R1
lizenzKlasse = ReiterLizenzKlasseE.R1
)
val klasseL = TurnierklasseDefinition(
@@ -4,6 +4,10 @@ import at.mocode.masterdata.api.rest.*
import at.mocode.masterdata.application.usecase.*
import at.mocode.masterdata.domain.repository.*
import at.mocode.masterdata.infrastructure.persistence.*
import at.mocode.masterdata.infrastructure.persistence.funktionaer.FunktionaerExposedRepository
import at.mocode.masterdata.infrastructure.persistence.pferd.HorseExposedRepository
import at.mocode.masterdata.infrastructure.persistence.reiter.ReiterExposedRepository
import at.mocode.masterdata.infrastructure.persistence.verein.VereinExposedRepository
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Profile
@@ -40,22 +44,22 @@ class MasterdataConfiguration {
@Bean
fun reiterRepository(): ReiterRepository {
return ExposedReiterRepository()
return ReiterExposedRepository()
}
@Bean
fun vereinRepository(): VereinRepository {
return ExposedVereinRepository()
return VereinExposedRepository()
}
@Bean
fun horseRepository(): HorseRepository {
return HorseRepositoryImpl()
return HorseExposedRepository()
}
@Bean
fun funktionaerRepository(): FunktionaerRepository {
return ExposedFunktionaerRepository()
return FunktionaerExposedRepository()
}
@Bean
@@ -2,6 +2,10 @@ package at.mocode.masterdata.service.config
import at.mocode.masterdata.infrastructure.persistence.*
import at.mocode.masterdata.infrastructure.persistence.funktionaer.FunktionaerTable
import at.mocode.masterdata.infrastructure.persistence.pferd.HorseTable
import at.mocode.masterdata.infrastructure.persistence.reiter.ReiterTable
import at.mocode.masterdata.infrastructure.persistence.verein.VereinTable
import jakarta.annotation.PostConstruct
import jakarta.annotation.PreDestroy
import org.jetbrains.exposed.v1.jdbc.SchemaUtils
@@ -1,6 +1,6 @@
-- V008: Seed OETO 2026 Data (Turnierklassen, Lizenz-Matrix, Altersklassen)
-- Basierend auf ÖTO 2026
-- Lizenz-Keys entsprechen LizenzKlasseE-Enum (core-domain): LIZENZFREI, R1, R2, R3, R4, RD1, RD2, RD3
-- Lizenz-Keys entsprechen ReiterLizenzKlasseE-Enum (core-domain): LIZENZFREI, R1, R2, R3, R4, RD1, RD2, RD3
-- HINWEIS: RD4 existiert NICHT im Enum nur RD1/RD2/RD3 sind gültige Dressur-Lizenzen (ÖTO 2026)
-- 1. Turnierklassen (Springen & Dressur)
@@ -1,6 +1,6 @@
-- V009: Regulation-as-Data Höhen-Lizenz-Matrix (Springen) & Mindestalter-Pferd-Matrix
-- Basierend auf ÖTO 2026 (§ 231 Springen, § 103 Dressur) und FEI GR Art. 136
-- Lizenz-Keys entsprechen LizenzKlasseE-Enum (core-domain): LIZENZFREI, R1, R2, R3, R4, RD1, RD2, RD3
-- Lizenz-Keys entsprechen ReiterLizenzKlasseE-Enum (core-domain): LIZENZFREI, R1, R2, R3, R4, RD1, RD2, RD3
-- Status: DRAFT wird auf STABLE angehoben nach Fachfreigabe durch ÖTO-Fachreferat
-- ─────────────────────────────────────────────────────────────────────────────
@@ -1,7 +1,10 @@
package at.mocode.zns.import.service.config
import at.mocode.masterdata.domain.repository.*
import at.mocode.masterdata.infrastructure.persistence.*
import at.mocode.masterdata.infrastructure.persistence.funktionaer.FunktionaerExposedRepository
import at.mocode.masterdata.infrastructure.persistence.pferd.HorseExposedRepository
import at.mocode.masterdata.infrastructure.persistence.reiter.ReiterExposedRepository
import at.mocode.masterdata.infrastructure.persistence.verein.VereinExposedRepository
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
@@ -9,14 +12,14 @@ import org.springframework.context.annotation.Configuration
class RepositoryConfiguration {
@Bean
fun vereinRepository(): VereinRepository = ExposedVereinRepository()
fun vereinRepository(): VereinRepository = VereinExposedRepository()
@Bean
fun reiterRepository(): ReiterRepository = ExposedReiterRepository()
fun reiterRepository(): ReiterRepository = ReiterExposedRepository()
@Bean
fun horseRepository(): HorseRepository = HorseRepositoryImpl()
fun horseRepository(): HorseRepository = HorseExposedRepository()
@Bean
fun funktionaerRepository(): FunktionaerRepository = ExposedFunktionaerRepository()
fun funktionaerRepository(): FunktionaerRepository = FunktionaerExposedRepository()
}
@@ -1,9 +1,9 @@
package at.mocode.zns.import.service.config
import at.mocode.masterdata.infrastructure.persistence.FunktionaerTable
import at.mocode.masterdata.infrastructure.persistence.HorseTable
import at.mocode.masterdata.infrastructure.persistence.ReiterTable
import at.mocode.masterdata.infrastructure.persistence.VereinTable
import at.mocode.masterdata.infrastructure.persistence.funktionaer.FunktionaerTable
import at.mocode.masterdata.infrastructure.persistence.pferd.HorseTable
import at.mocode.masterdata.infrastructure.persistence.reiter.ReiterTable
import at.mocode.masterdata.infrastructure.persistence.verein.VereinTable
import jakarta.annotation.PostConstruct
import org.jetbrains.exposed.v1.jdbc.Database
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
@@ -159,7 +159,7 @@ enum class VeranstaltungsTypE {
* Lizenzklasse eines Reiters gemäß ÖTO Teilnahmeberechtigung.
*/
@Serializable
enum class LizenzKlasseE {
enum class ReiterLizenzKlasseE {
/** Lizenzfrei keine Lizenz erforderlich */
LIZENZFREI,
@@ -174,6 +174,7 @@ enum class LizenzKlasseE {
/** Reiter-Lizenz Klasse 4 */
R4,
/** Dressur-Reiter Klasse 1 */
RD1,
@@ -183,21 +184,35 @@ enum class LizenzKlasseE {
/** Dressur-Reiter Klasse 3 */
RD3,
/** Jugend/Nachwuchs */
JN,
}
/** Junioren */
/**
* Altersklasse eines Reiters gemäß ÖTO Teilnahmeberechtigung.
*/
@Serializable
enum class ReiterAltersKlasseE {
// JUGEND/JUNIOR/U25
/** Jugend */
JG,
/** Young Rider */
YR
/** Junioren */
JR,
/** Junge-Reiter */
Y,
/** U25 */
U25,
}
/**
* Status einer Nennung im registration-context.
*/
@Serializable
enum class NennungsStatusE {
enum class NennStatusE {
/** Nennung eingegangen, noch nicht bestätigt */
EINGEGANGEN,
@@ -1,13 +1,13 @@
package at.mocode.zns.parser
import at.mocode.core.domain.model.DatenQuelleE
import at.mocode.core.domain.model.LizenzKlasseE
import at.mocode.core.domain.model.ReiterLizenzKlasseE
import at.mocode.core.domain.model.PferdeGeschlechtE
import at.mocode.core.utils.parser.FixedWidthLineReader
import at.mocode.masterdata.domain.model.DomFunktionaer
import at.mocode.masterdata.domain.model.DomPferd
import at.mocode.masterdata.domain.model.DomReiter
import at.mocode.masterdata.domain.model.DomVerein
import at.mocode.masterdata.domain.model.Funktionaer
import at.mocode.masterdata.domain.model.Pferd
import at.mocode.masterdata.domain.model.Reiter
import at.mocode.masterdata.domain.model.Verein
import kotlin.uuid.ExperimentalUuidApi
import kotlin.uuid.Uuid
@@ -23,7 +23,7 @@ object ZnsLegacyParsers {
/**
* Parses a line from VEREIN01.DAT.
*/
fun parseVerein(line: String): DomVerein? {
fun parseVerein(line: String): Verein? {
if (line.isBlank() || line.length < 5) return null
val reader = FixedWidthLineReader(line)
@@ -33,9 +33,9 @@ object ZnsLegacyParsers {
if (vereinsNummer.isBlank() || vereinsName.isBlank()) return null
return DomVerein(
return Verein(
vereinsNummer = vereinsNummer,
name = vereinsName,
vereinName = vereinsName,
datenQuelle = DatenQuelleE.IMPORT_ZNS
)
}
@@ -43,7 +43,7 @@ object ZnsLegacyParsers {
/**
* Parses a line from LIZENZ01.DAT.
*/
fun parseLizenz(line: String): DomReiter? {
fun parseLizenz(line: String): Reiter? {
if (line.isBlank() || line.length < 57) return null
val reader = FixedWidthLineReader(line)
@@ -79,8 +79,9 @@ object ZnsLegacyParsers {
val feiId = reader.getString(190, 8)
val sperrListe = reader.getString(198, 1)
val lizenzInfo = reader.getString(201, 10)
val lizenzKlasse = mapLizenz(reiterLizenz)
return DomReiter(
return Reiter(
personId = Uuid.random(),
satznummer = satznummer,
nachname = nachname,
@@ -102,7 +103,7 @@ object ZnsLegacyParsers {
feiId = feiId.ifBlank { null },
sperrListe = sperrListe.ifBlank { null },
lizenzInfo = lizenzInfo.ifBlank { null },
lizenzKlasse = mapLizenz(reiterLizenz),
lizenzKlasse = lizenzKlasse,
datenQuelle = DatenQuelleE.IMPORT_ZNS
)
}
@@ -110,12 +111,16 @@ object ZnsLegacyParsers {
/**
* Parses a line from PFERDE01.DAT.
*/
fun parsePferd(line: String): DomPferd? {
if (line.isBlank() || line.trim().length < 40) return null
fun parsePferd(line: String): Pferd? {
if (line.isBlank() || line.trim().length < 4) return null
val reader = FixedWidthLineReader(line)
val kopfnummer = reader.getString(1, 4)
val name = reader.getString(5, 30)
// We need at least a name to identify a horse record
if (name.isBlank()) return null
val lebensnummer = reader.getString(35, 9)
val geschlechtChar = reader.getString(44, 1)
val geschlecht = mapGeschlecht(geschlechtChar)
@@ -128,16 +133,15 @@ object ZnsLegacyParsers {
val vaterName = reader.getString(162, 30)
val feiPass = reader.getString(192, 10)
val satznummer = reader.getString(202, 10)
// Some lines might not have a satznummer, but we need at least a name to identify it
if (satznummer.isBlank() && name.isBlank()) return null
return DomPferd(
return Pferd(
personId = Uuid.random(),
pferdeName = name,
geschlecht = geschlecht,
geburtsjahr = geburtsjahr,
lebensnummer = lebensnummer.ifBlank { null },
kopfnummer = kopfnummer.ifBlank { null },
satznummer = satznummer,
satznummer = satznummer.ifBlank { null },
farbe = farbe.ifBlank { null },
abstammung = abstammung.ifBlank { null },
vereinNummer = vereinNummer,
@@ -152,7 +156,7 @@ object ZnsLegacyParsers {
/**
* Parses a line from RICHT01.DAT (Richter oder Parcoursbauer).
*/
fun parseFunktionaer(line: String): DomFunktionaer? {
fun parseFunktionaer(line: String): Funktionaer? {
if (line.isBlank() || line.length < 8) return null
val reader = FixedWidthLineReader(line)
@@ -171,8 +175,9 @@ object ZnsLegacyParsers {
.map { it.trim() }
.filter { it.isNotBlank() }
return DomFunktionaer(
satzID = satzID,
return Funktionaer(
personId = null,
satzId = satzID,
satzNummer = satzNummer,
name = name.ifBlank { null },
qualifikationen = qualifikationen,
@@ -180,18 +185,15 @@ object ZnsLegacyParsers {
)
}
private fun mapLizenz(lizenz: String): LizenzKlasseE {
private fun mapLizenz(lizenz: String): ReiterLizenzKlasseE {
return when (lizenz.uppercase()) {
"R1" -> LizenzKlasseE.R1
"R2" -> LizenzKlasseE.R2
"R3" -> LizenzKlasseE.R3
"RD1" -> LizenzKlasseE.RD1
"RD2" -> LizenzKlasseE.RD2
"RD3" -> LizenzKlasseE.RD3
"JN" -> LizenzKlasseE.JN
"JG" -> LizenzKlasseE.JG
"YR" -> LizenzKlasseE.YR
else -> LizenzKlasseE.LIZENZFREI
"R1" -> ReiterLizenzKlasseE.R1
"R2" -> ReiterLizenzKlasseE.R2
"R3" -> ReiterLizenzKlasseE.R3
"RD1" -> ReiterLizenzKlasseE.RD1
"RD2" -> ReiterLizenzKlasseE.RD2
"RD3" -> ReiterLizenzKlasseE.RD3
else -> ReiterLizenzKlasseE.LIZENZFREI
}
}
@@ -14,7 +14,7 @@ class ZnsLegacyParsersTest {
assertNotNull(result)
assertEquals("1234", result.vereinsNummer)
assertEquals("Reitverein Test", result.name)
assertEquals("Reitverein Test", result.vereinName)
}
@Test
@@ -82,7 +82,7 @@ class ZnsLegacyParsersTest {
val result = ZnsLegacyParsers.parseFunktionaer(line)
assertNotNull(result)
assertEquals("X", result.satzID)
assertEquals("X", result.satzId)
assertEquals(10128, result.satzNummer)
assertEquals("Zitterbart Rainer", result.name)
assertEquals(listOf("PI-A"), result.qualifikationen)
@@ -94,7 +94,7 @@ class ZnsLegacyParsersTest {
val line1 = "X139552Mc Mullen Elizabeth DIOR"
val result1 = ZnsLegacyParsers.parseFunktionaer(line1)
assertNotNull(result1)
assertEquals("X", result1.satzID)
assertEquals("X", result1.satzId)
assertEquals(139552, result1.satzNummer)
assertEquals("Mc Mullen Elizabeth", result1.name)
assertEquals(listOf("DIOR"), result1.qualifikationen)
@@ -111,10 +111,28 @@ class ZnsLegacyParsersTest {
val line3 = "Y002211Salusek Andreas Christian P3,PL2"
val result3 = ZnsLegacyParsers.parseFunktionaer(line3)
assertNotNull(result3)
assertEquals("Y", result3.satzID)
assertEquals("Y", result3.satzId)
assertEquals(2211, result3.satzNummer)
assertEquals("Salusek Andreas Christian", result3.name)
assertEquals(listOf("P3", "PL2"), result3.qualifikationen)
// X001061Kager Franz DPF,DSGP,GAR-SP,GAR-VS,SPF
val line4 = "X001061Kager Franz DPF,DSGP,GAR-SP,GAR-VS,SPF"
val result4 = ZnsLegacyParsers.parseFunktionaer(line4)
assertNotNull(result4)
assertEquals("X", result4.satzId)
assertEquals(1061, result4.satzNummer)
assertEquals("Kager Franz", result4.name)
assertEquals(listOf("DPF", "DSGP", "GAR-SP", "GAR-VS", "SPF"), result4.qualifikationen)
// X001112Keiblinger Brigitta DPF,DSGP,SPF,SS,VS,VSILEV1"
val line5 = "X001112Keiblinger Brigitta DPF,DSGP,SPF,SS,VS,VSILEV1"
val result5 = ZnsLegacyParsers.parseFunktionaer(line5)
assertNotNull(result5)
assertEquals("X", result5.satzId)
assertEquals(1112, result5.satzNummer)
assertEquals("Keiblinger Brigitta", result5.name)
assertEquals(listOf("DPF", "DSGP", "SPF", "SS", "VS", "VSILEV1"), result5.qualifikationen)
}
@Test
@@ -145,6 +163,20 @@ class ZnsLegacyParsersTest {
assertEquals("5637401268", result.satznummer)
}
@Test
fun `parsePferd should extract shortened PFERDE01 correctly`() {
// A line that ends after the name
val line = "1234Fuchur"
val result = ZnsLegacyParsers.parsePferd(line)
assertNotNull(result)
assertEquals("1234", result.kopfnummer)
assertEquals("Fuchur", result.pferdeName)
assertEquals(null, result.satznummer)
assertEquals(null, result.lebensnummer)
assertEquals(PferdeGeschlechtE.UNBEKANNT, result.geschlecht)
}
@Test
fun `parseLizenz should extract real LIZENZ01 correctly for Ebner Sarah`() {
// Real example from user:
+2 -2
View File
@@ -23,7 +23,7 @@ Vollständige Self-Hosted Infrastruktur (Gitea, Pangolin, Zora). Datensouveräni
* **CI/CD:** ✅ Gitea Actions mit ARM64-Runner (VM 102) aktiv. Docker-Publish Pipeline grün.
* **Code-Basis:** ✅ Backend (Java 25 / Spring Boot / Kotlin), Frontend (KMP/Compose Desktop).
* **Domain-Design:** ✅ 6 Bounded Contexts (SCS-Architektur) definiert. Ubiquitous Language erstellt.
* **Domain-Modelle:**`DomReiter`, `DomNennung`, `DomNennungsTransfer`, `DomPferd`, `DomFunktionaer`, `DomVerein`,
* **Domain-Modelle:**`Reiter`, `DomNennung`, `DomNennungsTransfer`, `Pferd`, `Funktionaer`, `Verein`,
`DomBewerb`, `DomAbteilung`, `DomStartliste`, `DomVeranstaltung`, `DomTurnier`, `DomAusschreibung` implementiert.
Enums ÖTO-konform.
* **Dokumentation:** ✅ Konsolidiert. ÖTO-Regelwerk-Referenzen (Abteilungs-Schwellenwerte) dokumentiert.
@@ -124,7 +124,7 @@ und über definierte Schnittstellen kommunizieren.
#### 👷 Agent: Backend Developer
* [x] **`actor-context`:** Domain-Modelle für `DomPferd`, `DomFunktionaer`, `DomVerein` implementiert.
* [x] **`actor-context`:** Domain-Modelle für `Pferd`, `Funktionaer`, `Verein` implementiert.
* [x] **`registration-context`:** `DomBewerb`, `DomAbteilung`, `DomStartliste` implementiert.
* [x] **`event-management-context`:** `DomVeranstaltung`, `DomTurnier`, `DomAusschreibung` implementiert.
* [x] **Persistenz:** Repository-Interfaces und erste DB-Migrationen (Flyway/Liquibase).
+4 -3
View File
@@ -7,9 +7,10 @@ last_update: 2026-03-25
# Roadmap: ZNS-Importer (MVP)
🧹 **[Curator]** | 25. März 2026
10. **🏗️ [Lead Architect]** | 5. April 2026
**Kontext:**
Der ZNS-Importer wurde für die Verarbeitung der Datei `RICHT01.dat` optimiert. Es wurde klargestellt, dass Richter (SatzID 'X') und Parcoursbauer (SatzID 'Y') gemeinsam in dieser Datei geliefert werden. Eine separate `PARCO01.dat` existiert nicht.
Um den `registration-context` und `actor-context` unter realistischen Bedingungen testen zu können, benötigen wir echte
Stammdaten (Reiter, Pferde, Vereine, Funktionäre). Diese Daten werden vom ÖPS (Österreichischer Pferdesportverband) in
Form einer `ZNS.zip` bereitgestellt.
@@ -29,7 +30,7 @@ gesteuert wird und die Daten persistent im Backend (`actor-context`) ablegt.
1. `VEREIN01.dat` (Vereine)
2. `LIZENZ01.dat` (Reiter/Lizenzen)
3. `PFERDE01.dat` (Pferde - benötigt Vereins-ID)
4. `RICHT01.dat` (Richter/Parcoursbauer)
4. `RICHT01.dat` (Richter & Parcoursbauer kombiniert in einer Datei)
---
@@ -50,7 +51,7 @@ gesteuert wird und die Daten persistent im Backend (`actor-context`) ablegt.
* ZIP-Entpackung in-memory implementiert (`ZnsImportService`).
* [x] **Legacy-Parser (CP850 Fixed-Width):**
* `ZnsLegacyParsers` in `core:zns-parser` (KMP-Modul) implementiert.
* Alle 4 Dateitypen (VEREIN01, LIZENZ01, PFERDE01, RICHT01) bytegenau gemappt. 4 Unit-Tests grün.
* Alle 4 Dateitypen (VEREIN01, LIZENZ01, PFERDE01, RICHT01) bytegenau gemappt. RICHT01 verarbeitet Richter ('X') und Parcoursbauer ('Y'). ✅
### Phase 2: Domain-Mapping & Persistenz (👷 Backend Developer)
@@ -2,7 +2,7 @@
type: Reference
status: ACTIVE
owner: Lead Architect & ÖTO/FEI Rulebook Expert
last_update: 2026-04-02
last_update: 2026-04-05
sources:
- ÖTO 2026, Abschnitt A I, § 2 & § 3 & § 4
- Domain Workshop 2026-03-17
@@ -71,7 +71,7 @@ Die ÖTO definiert sparten- und klassenabhängige Schwellenwerte, ab wievielen S
| Begriff | Definition | ÖTO-Referenz |
|----------------|------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------|
| **FEI-ID** | Eindeutige Identifikationsnummer der Internationalen Reiterlichen Vereinigung (FEI) für Reiter und Pferde. | FEI General Regulations |
| **Funktionär** | Person mit einer definierten Rolle bei einem Turnier (Richter, Parcoursbauer, TBA, ...). Qualifikation wird gegen `RICHT01.DAT` geprüft. | ÖTO Funktionärs-Qualifikation |
| **Funktionär** | Person mit einer definierten Rolle bei einem Turnier (Richter, Parcoursbauer, TBA, ...). Qualifikation wird gegen `RICHT01.DAT` geprüft. Im Code als Entität **`Funktionaer`** abgebildet. | ÖTO Funktionärs-Qualifikation |
### G
@@ -136,6 +136,7 @@ Die ÖTO definiert sparten- und klassenabhängige Schwellenwerte, ab wievielen S
| Begriff | Definition | ÖTO-Referenz |
|------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------|
| **Reiter** | Eine Person, die an einem Bewerb teilnimmt. Spezialisierung einer Person mit pferdesportlichen Attributen (Lizenz, Startkarte). Im Code als Entität **`Reiter`** abgebildet. Datenquelle primär `LIZENZ01.DAT`. | ÖTO § 2 Abs. 11 |
| **Satznummer** | *Pferd:* 10-stellige, rein numerische ID (`0000123456`). **Primärer Schlüssel für den Datenaustausch.** *Reiter:* 6-stellige, rein numerische ID. | ZNS-Schnittstelle |
| **Serie** | Synonym für → *Cup*. Übergeordneter Wettbewerb, der Ergebnisse aus Bewerben bei **mindestens zwei Turnieren** aggregiert. Besitzt ein **eigenes Reglement** (siehe Abschnitt 4). | ÖTO § 2 Abs. 8 |
| **Sparte** | Die unterschiedlichen Arten von Turnieren oder Bewerben (z.B. Dressur = CDN, Springen = CSN). Bestimmt das anzuwendende Richtverfahren und das Regelwerk. | ÖTO § 2 Abs. 9, § 3 Abs. 2 |
@@ -162,7 +163,7 @@ Die ÖTO definiert sparten- und klassenabhängige Schwellenwerte, ab wievielen S
| Begriff | Definition | ÖTO-Referenz |
|-------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------|
| **Veranstaltung** | In unserer Software: Der Oberbegriff für jede Art von pferdesportlicher Veranstaltung, die von einem Verein durchgeführt wird. Erhält eine **intern vergebene ID**. Entspricht dem ÖTO-Oberbegriff „Pferdesportliche Veranstaltung" (§ 2 Abs. 1). Kann vom Typ Turnier, Reitertreffen, Sonderprüfung, PS&S oder Turnierartig sein. | ÖTO § 2 Abs. 1 |
| **Veranstalter** | OEPS-Mitgliedsverein (über LFV angeschlossen), der eine Veranstaltung ausrichtet. Besitzt eine **Vereinsnummer**. | ÖTO § 2 Abs. 12 |
| **Veranstalter** | OEPS-Mitgliedsverein (über LFV angeschlossen), der eine Veranstaltung ausrichtet. Besitzt eine **Vereinsnummer**. Im Code als Entität **`Verein`** abgebildet. | ÖTO § 2 Abs. 12 |
| **VeranstaltungsKassa** | Kassen- und Abrechnungsführer auf Ebene der → *Veranstaltung*. Konsolidiert alle Zahlungen, Belege und Rückgelder über mehrere → *Turniere* derselben Veranstaltung. Dient als zentrale Sammelkasse; kann Zahlvorgänge turnierübergreifend splitten und konsolidieren. | Billing Context |
### Z
@@ -181,7 +182,7 @@ Die ÖTO definiert sparten- und klassenabhängige Schwellenwerte, ab wievielen S
| Veranstaltung, Turnier, Ausschreibung, Veranstalter | `event-management-context` |
| Bewerb, Abteilung, Startliste, Ergebnis, Richtverfahren | `competition-context` |
| Nennung, Nennungs-Transfer, Startwunsch, ZNS-Import | `registration-context` |
| Reiter, Pferd, Lizenz, Funktionär, Kopfnummer, Satznummer | `actor-context` |
| Reiter, Pferd, Lizenz, Funktionär, Kopfnummer, Satznummer, Verein | `actor-context` |
| Nenngeld, Startgeld, Konto, Transaktion, Sportförderbeitrag | `billing-context` |
| Cup, Serie, Meisterschaft, Reglement, Endklassement | `series-context` *(Phase 2+)* |
| Login, Rolle, Berechtigung | `identity-context` |
@@ -234,4 +235,4 @@ Ein Reglement definiert typischerweise:
---
*Erstellt: 2026-03-24 | Autoren: Lead Architect, ÖTO/FEI Rulebook Expert, Curator*
*Basiert auf: ÖTO 2026 § 2, § 3, § 4 | Domain Workshop 2026-03-17 | Session 2026-03-24*
*Basiert auf: ÖTO 2026 § 2, § 3, § 4 | Domain Workshop 2026-03-17 | Session 2026-03-24 | Update: 2026-04-05 (Verein-Renaming & Bereinigung)*