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
@@ -19,10 +19,10 @@ import java.util.zip.ZipInputStream
* Domänenobjekte über die jeweiligen Repositories (Upsert-Logik).
*
* Die Verarbeitungsreihenfolge ist fix:
* 1. VEREIN01.DAT → DomVerein (via VereinRepository)
* 2. LIZENZ01.DAT → DomReiter (via ReiterRepository)
* 3. PFERDE01.DAT → DomPferd (via HorseRepository)
* 4. RICHT01.DAT → DomFunktionaer (via FunktionaerRepository)
* 1. VEREIN01.DAT → Verein (via VereinRepository)
* 2. LIZENZ01.DAT → Reiter (via ReiterRepository)
* 3. PFERDE01.DAT → Pferd (via HorseRepository)
* 4. RICHT01.DAT → Funktionaer (via FunktionaerRepository)
*
* Dieser Service hat **keine** Spring-Abhängigkeit und kann daher sowohl
* im Backend (REST-Upload) als auch in der Compose Desktop App (Offline-Import)
@@ -57,18 +57,19 @@ class ZnsImportService(
ZipInputStream(zipInputStream).use { zip ->
var entry = zip.nextEntry
while (entry != null) {
val entryPath = entry.name.uppercase()
val fileName = entryPath.substringAfterLast("/")
val fileName = entry.name.uppercase().substringAfterLast("/")
if (fileName in setOf(FILE_VEREIN, FILE_LIZENZ, FILE_PFERDE, FILE_RICHT)) {
// Read all bytes from the current entry
val bytes = zip.readBytes()
// Convert to string using the correct charset and split into lines
val content = bytes.toString(CP850)
val outputStream = java.io.ByteArrayOutputStream()
val buffer = ByteArray(4096)
var len: Int
while (zip.read(buffer).also { len = it } > 0) {
outputStream.write(buffer, 0, len)
}
val content = outputStream.toString(CP850)
val lines = content.split(Regex("\\r?\\n|\\r")).filter { it.isNotBlank() }
dateien[fileName] = lines
}
zip.closeEntry()
entry = zip.nextEntry
}
}
@@ -83,6 +84,8 @@ class ZnsImportService(
*/
suspend fun importiereZip(zipInputStream: InputStream): ZnsImportResult {
val dateien = extrahiereDateien(zipInputStream)
// println("[DEBUG_LOG] Gefundene Dateien: ${dateien.keys}")
// dateien.forEach { (name, lines) -> println("[DEBUG_LOG] Datei $name hat ${lines.size} Zeilen") }
val fehler = mutableListOf<String>()
val warnungen = mutableListOf<String>()
@@ -126,13 +129,11 @@ class ZnsImportService(
} else {
vereinRepository.save(
vorhanden.copy(
name = verein.name,
kurzname = verein.kurzname,
vereinName = verein.vereinName,
bundesland = verein.bundesland,
ort = verein.ort,
plz = verein.plz,
strasse = verein.strasse,
oepsRegionNummer = verein.oepsRegionNummer,
istAktiv = verein.istAktiv,
datenQuelle = verein.datenQuelle
).withUpdatedTimestamp()
@@ -207,9 +208,12 @@ class ZnsImportService(
val pferd = ZnsLegacyParsers.parsePferd(zeile) ?: return@forEachIndexed
if (pferd.pferdeName.isBlank()) return@forEachIndexed
// Match primarily by satznummer, then by lebensnummer, then by kopfnummer+name
// Match primarily by satznummer, then by lebensnummer
val vorhanden = pferd.satznummer?.takeIf { it.isNotBlank() }?.let { horseRepository.findBySatznummer(it) }
?: pferd.lebensnummer?.takeIf { it.isNotBlank() }?.let { horseRepository.findByLebensnummer(it) }
?: (if (pferd.pferdeName.isNotBlank()) horseRepository.findByName(pferd.pferdeName, 100).find {
it.geburtsjahr == pferd.geburtsjahr && it.kopfnummer == pferd.kopfnummer
} else null)
if (vorhanden == null) {
horseRepository.save(pferd)
@@ -253,7 +257,7 @@ class ZnsImportService(
zeilen.forEachIndexed { index, zeile ->
runCatching {
val funktionaer = ZnsLegacyParsers.parseFunktionaer(zeile) ?: return@forEachIndexed
val satzID = funktionaer.satzID
val satzID = funktionaer.satzId
val satzNummer = funktionaer.satzNummer
val vorhanden = funktionaerRepository.findBySatz(satzID, satzNummer)
if (vorhanden == null) {
@@ -262,8 +266,6 @@ class ZnsImportService(
} else {
funktionaerRepository.save(
vorhanden.copy(
satzID = satzID,
satzNummer = satzNummer,
name = funktionaer.name,
qualifikationen = funktionaer.qualifikationen,
istAktiv = funktionaer.istAktiv,
@@ -1,9 +1,9 @@
package at.mocode.zns.importer
import at.mocode.masterdata.domain.model.DomFunktionaer
import at.mocode.masterdata.domain.model.DomPferd
import at.mocode.masterdata.domain.model.DomReiter
import at.mocode.masterdata.domain.model.DomVerein
import at.mocode.masterdata.domain.model.Funktionaer
import at.mocode.masterdata.domain.model.Pferd
import at.mocode.masterdata.domain.model.Reiter
import at.mocode.masterdata.domain.model.Verein
import at.mocode.masterdata.domain.repository.FunktionaerRepository
import at.mocode.masterdata.domain.repository.HorseRepository
import at.mocode.masterdata.domain.repository.ReiterRepository
@@ -113,29 +113,29 @@ class ZnsImportServiceTest {
val zip = buildZip("VEREIN01.DAT" to vereinZeile())
coEvery { vereinRepository.findByVereinsNummer(any()) } returns null
coEvery { vereinRepository.save(any()) } answers { firstArg<DomVerein>() }
coEvery { vereinRepository.save(any()) } answers { firstArg<Verein>() }
val result = service.importiereZip(zip)
assertThat(result.vereineImportiert).isEqualTo(1)
assertThat(result.vereineAktualisiert).isEqualTo(0)
assertThat(result.fehler).isEmpty()
coVerify(exactly = 1) { vereinRepository.save(any<DomVerein>()) }
coVerify(exactly = 1) { vereinRepository.save(any<Verein>()) }
}
@Test
fun `importiereZip - bestehende Vereine werden aktualisiert`() = runTest {
val zip = buildZip("VEREIN01.DAT" to vereinZeile(name = "Neuer Name"))
val vorhanden = DomVerein(vereinsNummer = "0001", name = "Alter Name")
val vorhanden = Verein(vereinsNummer = "0001", vereinName = "Alter Name")
coEvery { vereinRepository.findByVereinsNummer("0001") } returns vorhanden
coEvery { vereinRepository.save(any()) } answers { firstArg<DomVerein>() }
coEvery { vereinRepository.save(any()) } answers { firstArg<Verein>() }
val result = service.importiereZip(zip)
assertThat(result.vereineImportiert).isEqualTo(0)
assertThat(result.vereineAktualisiert).isEqualTo(1)
coVerify(exactly = 1) { vereinRepository.save(match { it.name == "Neuer Name" }) }
coVerify(exactly = 1) { vereinRepository.save(match { it.vereinName == "Neuer Name" }) }
}
@Test
@@ -143,14 +143,14 @@ class ZnsImportServiceTest {
val zip = buildZip("LIZENZ01.DAT" to lizenzZeile())
coEvery { reiterRepository.findBySatznummer(any()) } returns null
coEvery { reiterRepository.save(any()) } answers { firstArg<DomReiter>() }
coEvery { reiterRepository.save(any()) } answers { firstArg<Reiter>() }
val result = service.importiereZip(zip)
assertThat(result.reiterImportiert).isEqualTo(1)
assertThat(result.reiterAktualisiert).isEqualTo(0)
assertThat(result.fehler).isEmpty()
coVerify(exactly = 1) { reiterRepository.save(any<DomReiter>()) }
coVerify(exactly = 1) { reiterRepository.save(any<Reiter>()) }
}
@Test
@@ -159,14 +159,34 @@ class ZnsImportServiceTest {
coEvery { horseRepository.findBySatznummer(any()) } returns null
coEvery { horseRepository.findByLebensnummer(any()) } returns null
coEvery { horseRepository.save(any()) } answers { firstArg<DomPferd>() }
coEvery { horseRepository.findByName(any(), any()) } returns emptyList()
coEvery { horseRepository.save(any()) } answers { firstArg<Pferd>() }
val result = service.importiereZip(zip)
assertThat(result.pferdeImportiert).isEqualTo(1)
assertThat(result.pferdeAktualisiert).isEqualTo(0)
assertThat(result.fehler).isEmpty()
coVerify(exactly = 1) { horseRepository.save(any<DomPferd>()) }
coVerify(exactly = 1) { horseRepository.save(any<Pferd>()) }
}
@Test
fun `importiereZip - Pferde werden ueber Kopfnummer und Geburtsjahr aktualisiert, wenn Satznummer fehlt`() = runTest {
// Zeile ohne Satznummer (zu kurz)
val zeile = "0001Blitz "
val zip = buildZip("PFERDE01.DAT" to zeile)
val existing = Pferd(pferdeName = "Blitz", kopfnummer = "0001", geschlecht = at.mocode.core.domain.model.PferdeGeschlechtE.UNBEKANNT)
coEvery { horseRepository.findBySatznummer(any()) } returns null
coEvery { horseRepository.findByLebensnummer(any()) } returns null
coEvery { horseRepository.findByName("Blitz", 100) } returns listOf(existing)
coEvery { horseRepository.save(any()) } answers { firstArg<Pferd>() }
val result = service.importiereZip(zip)
assertThat(result.pferdeAktualisiert).isEqualTo(1)
coVerify(exactly = 1) { horseRepository.save(match { it.pferdeName == "Blitz" && it.pferdId == existing.pferdId }) }
}
@Test
@@ -174,14 +194,14 @@ class ZnsImportServiceTest {
val zip = buildZip("RICHT01.DAT" to funktionaerZeile())
coEvery { funktionaerRepository.findBySatz(any(), any()) } returns null
coEvery { funktionaerRepository.save(any()) } answers { firstArg<DomFunktionaer>() }
coEvery { funktionaerRepository.save(any()) } answers { firstArg<Funktionaer>() }
val result = service.importiereZip(zip)
assertThat(result.richterImportiert).isEqualTo(1)
assertThat(result.richterAktualisiert).isEqualTo(0)
assertThat(result.fehler).isEmpty()
coVerify(exactly = 1) { funktionaerRepository.save(any<DomFunktionaer>()) }
coVerify(exactly = 1) { funktionaerRepository.save(any<Funktionaer>()) }
}
@Test
@@ -192,13 +212,13 @@ class ZnsImportServiceTest {
)
coEvery { funktionaerRepository.findBySatz(any(), any()) } returns null
coEvery { funktionaerRepository.save(any()) } answers { firstArg<DomFunktionaer>() }
coEvery { funktionaerRepository.save(any()) } answers { firstArg<Funktionaer>() }
val result = service.importiereZip(zip)
assertThat(result.richterImportiert).isEqualTo(4)
assertThat(result.fehler).isEmpty()
coVerify(exactly = 4) { funktionaerRepository.save(any<DomFunktionaer>()) }
coVerify(exactly = 4) { funktionaerRepository.save(any<Funktionaer>()) }
}
@Test
@@ -211,14 +231,14 @@ class ZnsImportServiceTest {
)
coEvery { vereinRepository.findByVereinsNummer(any()) } returns null
coEvery { vereinRepository.save(any()) } answers { firstArg<DomVerein>() }
coEvery { vereinRepository.save(any()) } answers { firstArg<Verein>() }
coEvery { reiterRepository.findBySatznummer(any()) } returns null
coEvery { reiterRepository.save(any()) } answers { firstArg<DomReiter>() }
coEvery { reiterRepository.save(any()) } answers { firstArg<Reiter>() }
coEvery { horseRepository.findBySatznummer(any()) } returns null
coEvery { horseRepository.findByLebensnummer(any()) } returns null
coEvery { horseRepository.save(any()) } answers { firstArg<DomPferd>() }
coEvery { horseRepository.save(any()) } answers { firstArg<Pferd>() }
coEvery { funktionaerRepository.findBySatz(any(), any()) } returns null
coEvery { funktionaerRepository.save(any()) } answers { firstArg<DomFunktionaer>() }
coEvery { funktionaerRepository.save(any()) } answers { firstArg<Funktionaer>() }
val result = service.importiereZip(zip)