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