Refactor domain models (DomFunktionaer, DomReiter, DomPferd) for ZNS alignment: update properties, streamline validation logic, and enhance parser to support new format. Adjust controllers and repository methods accordingly.
This commit is contained in:
+74
-36
@@ -49,6 +49,32 @@ class ZnsImportService(
|
||||
private const val FILE_RICHT = "RICHT01.DAT"
|
||||
}
|
||||
|
||||
/**
|
||||
* Extrahiert die relevanten Dateien aus dem ZIP-Archiv.
|
||||
*/
|
||||
fun extrahiereDateien(zipInputStream: InputStream): Map<String, List<String>> {
|
||||
val dateien = mutableMapOf<String, List<String>>()
|
||||
ZipInputStream(zipInputStream).use { zip ->
|
||||
var entry = zip.nextEntry
|
||||
while (entry != null) {
|
||||
val entryPath = entry.name.uppercase()
|
||||
val fileName = entryPath.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 lines = content.split(Regex("\\r?\\n|\\r")).filter { it.isNotBlank() }
|
||||
dateien[fileName] = lines
|
||||
}
|
||||
zip.closeEntry()
|
||||
entry = zip.nextEntry
|
||||
}
|
||||
}
|
||||
return dateien
|
||||
}
|
||||
|
||||
/**
|
||||
* Importiert eine ZNS-ZIP-Datei aus einem [InputStream].
|
||||
*
|
||||
@@ -56,18 +82,7 @@ class ZnsImportService(
|
||||
* @return [ZnsImportResult] mit Statistiken und eventuellen Fehlern.
|
||||
*/
|
||||
suspend fun importiereZip(zipInputStream: InputStream): ZnsImportResult {
|
||||
val dateien = mutableMapOf<String, List<String>>()
|
||||
ZipInputStream(zipInputStream).use { zip ->
|
||||
var entry = zip.nextEntry
|
||||
while (entry != null) {
|
||||
val name = entry.name.uppercase().substringAfterLast("/")
|
||||
if (name in setOf(FILE_VEREIN, FILE_LIZENZ, FILE_PFERDE, FILE_RICHT)) {
|
||||
dateien[name] = zip.readBytes().toString(CP850).lines()
|
||||
}
|
||||
zip.closeEntry()
|
||||
entry = zip.nextEntry
|
||||
}
|
||||
}
|
||||
val dateien = extrahiereDateien(zipInputStream)
|
||||
|
||||
val fehler = mutableListOf<String>()
|
||||
val warnungen = mutableListOf<String>()
|
||||
@@ -75,7 +90,7 @@ class ZnsImportService(
|
||||
val (vereineNeu, vereineUpd) = importiereVereine(dateien[FILE_VEREIN] ?: emptyList(), fehler)
|
||||
val (reiterNeu, reiterUpd) = importiereReiter(dateien[FILE_LIZENZ] ?: emptyList(), fehler, warnungen)
|
||||
val (pferdeNeu, pferdeUpd) = importierePferde(dateien[FILE_PFERDE] ?: emptyList(), fehler)
|
||||
val (richterNeu, richterUpd) = importiereRichter(dateien[FILE_RICHT] ?: emptyList(), fehler, warnungen)
|
||||
val (richterNeu, richterUpd) = importiereFunktionaere(dateien[FILE_RICHT] ?: emptyList(), fehler, warnungen)
|
||||
|
||||
return ZnsImportResult(
|
||||
vereineImportiert = vereineNeu,
|
||||
@@ -95,7 +110,7 @@ class ZnsImportService(
|
||||
// Private Hilfsmethoden
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private suspend fun importiereVereine(
|
||||
suspend fun importiereVereine(
|
||||
zeilen: List<String>,
|
||||
fehler: MutableList<String>
|
||||
): Pair<Int, Int> {
|
||||
@@ -131,7 +146,7 @@ class ZnsImportService(
|
||||
return Pair(neu, aktualisiert)
|
||||
}
|
||||
|
||||
private suspend fun importiereReiter(
|
||||
suspend fun importiereReiter(
|
||||
zeilen: List<String>,
|
||||
fehler: MutableList<String>,
|
||||
warnungen: MutableList<String>
|
||||
@@ -150,8 +165,23 @@ class ZnsImportService(
|
||||
vorhanden.copy(
|
||||
vorname = reiter.vorname,
|
||||
nachname = reiter.nachname,
|
||||
bundeslandNummer = reiter.bundeslandNummer,
|
||||
vereinsName = reiter.vereinsName,
|
||||
nation = reiter.nation,
|
||||
reiterLizenz = reiter.reiterLizenz,
|
||||
startkarte = reiter.startkarte,
|
||||
fahrLizenz = reiter.fahrLizenz,
|
||||
altersklasseJgJrU25 = reiter.altersklasseJgJrU25,
|
||||
altersklasseY = reiter.altersklasseY,
|
||||
mitgliedsNummer = reiter.mitgliedsNummer,
|
||||
telefonNummer = reiter.telefonNummer,
|
||||
kader = reiter.kader,
|
||||
lastPayYear = reiter.lastPayYear,
|
||||
geschlecht = reiter.geschlecht,
|
||||
geburtsdatum = reiter.geburtsdatum,
|
||||
feiId = reiter.feiId,
|
||||
sperrListe = reiter.sperrListe,
|
||||
lizenzInfo = reiter.lizenzInfo,
|
||||
lizenzKlasse = reiter.lizenzKlasse,
|
||||
istAktiv = reiter.istAktiv,
|
||||
datenQuelle = reiter.datenQuelle
|
||||
@@ -166,7 +196,7 @@ class ZnsImportService(
|
||||
return Pair(neu, aktualisiert)
|
||||
}
|
||||
|
||||
private suspend fun importierePferde(
|
||||
suspend fun importierePferde(
|
||||
zeilen: List<String>,
|
||||
fehler: MutableList<String>
|
||||
): Pair<Int, Int> {
|
||||
@@ -175,9 +205,12 @@ class ZnsImportService(
|
||||
zeilen.forEachIndexed { index, zeile ->
|
||||
runCatching {
|
||||
val pferd = ZnsLegacyParsers.parsePferd(zeile) ?: return@forEachIndexed
|
||||
val vorhanden = pferd.lebensnummer
|
||||
?.takeIf { it.isNotBlank() }
|
||||
?.let { horseRepository.findByLebensnummer(it) }
|
||||
if (pferd.pferdeName.isBlank()) return@forEachIndexed
|
||||
|
||||
// Match primarily by satznummer, then by lebensnummer, then by kopfnummer+name
|
||||
val vorhanden = pferd.satznummer?.takeIf { it.isNotBlank() }?.let { horseRepository.findBySatznummer(it) }
|
||||
?: pferd.lebensnummer?.takeIf { it.isNotBlank() }?.let { horseRepository.findByLebensnummer(it) }
|
||||
|
||||
if (vorhanden == null) {
|
||||
horseRepository.save(pferd)
|
||||
neu++
|
||||
@@ -186,10 +219,17 @@ class ZnsImportService(
|
||||
vorhanden.copy(
|
||||
pferdeName = pferd.pferdeName,
|
||||
geschlecht = pferd.geschlecht,
|
||||
geburtsdatum = pferd.geburtsdatum,
|
||||
rasse = pferd.rasse,
|
||||
geburtsjahr = pferd.geburtsjahr,
|
||||
farbe = pferd.farbe,
|
||||
abstammung = pferd.abstammung,
|
||||
vereinNummer = pferd.vereinNummer,
|
||||
lastPayYear = pferd.lastPayYear,
|
||||
verantwortlichePersonId = pferd.verantwortlichePersonId,
|
||||
lebensnummer = pferd.lebensnummer,
|
||||
oepsNummer = pferd.oepsNummer,
|
||||
kopfnummer = pferd.kopfnummer,
|
||||
satznummer = pferd.satznummer,
|
||||
vater = pferd.vater,
|
||||
feiPass = pferd.feiPass,
|
||||
istAktiv = pferd.istAktiv,
|
||||
datenQuelle = pferd.datenQuelle
|
||||
).withUpdatedTimestamp()
|
||||
@@ -203,7 +243,7 @@ class ZnsImportService(
|
||||
return Pair(neu, aktualisiert)
|
||||
}
|
||||
|
||||
private suspend fun importiereRichter(
|
||||
suspend fun importiereFunktionaere(
|
||||
zeilen: List<String>,
|
||||
fehler: MutableList<String>,
|
||||
warnungen: MutableList<String>
|
||||
@@ -212,24 +252,22 @@ class ZnsImportService(
|
||||
var aktualisiert = 0
|
||||
zeilen.forEachIndexed { index, zeile ->
|
||||
runCatching {
|
||||
val richter = ZnsLegacyParsers.parseRichter(zeile) ?: return@forEachIndexed
|
||||
val richterNummer = richter.richterNummer ?: run {
|
||||
warnungen.add("$FILE_RICHT Zeile ${index + 1}: Keine RichterNummer – übersprungen.")
|
||||
return@forEachIndexed
|
||||
}
|
||||
val vorhanden = funktionaerRepository.findByRichterNummer(richterNummer)
|
||||
val funktionaer = ZnsLegacyParsers.parseFunktionaer(zeile) ?: return@forEachIndexed
|
||||
val satzID = funktionaer.satzID
|
||||
val satzNummer = funktionaer.satzNummer
|
||||
val vorhanden = funktionaerRepository.findBySatz(satzID, satzNummer)
|
||||
if (vorhanden == null) {
|
||||
funktionaerRepository.save(richter)
|
||||
funktionaerRepository.save(funktionaer)
|
||||
neu++
|
||||
} else {
|
||||
funktionaerRepository.save(
|
||||
vorhanden.copy(
|
||||
vorname = richter.vorname,
|
||||
nachname = richter.nachname,
|
||||
vereinsNummer = richter.vereinsNummer,
|
||||
richterNummer = richter.richterNummer,
|
||||
istAktiv = richter.istAktiv,
|
||||
datenQuelle = richter.datenQuelle
|
||||
satzID = satzID,
|
||||
satzNummer = satzNummer,
|
||||
name = funktionaer.name,
|
||||
qualifikationen = funktionaer.qualifikationen,
|
||||
istAktiv = funktionaer.istAktiv,
|
||||
datenQuelle = funktionaer.datenQuelle
|
||||
).withUpdatedTimestamp()
|
||||
)
|
||||
aktualisiert++
|
||||
|
||||
+35
-13
@@ -74,7 +74,8 @@ class ZnsImportServiceTest {
|
||||
return satznummer.padEnd(6) +
|
||||
nachname.padEnd(50) +
|
||||
vorname.padEnd(25) +
|
||||
" ".repeat(200) // Rest auffüllen
|
||||
"01" + // 82-83 Bundesland
|
||||
" ".repeat(250) // Rest auffüllen
|
||||
}
|
||||
|
||||
/** Erzeugt eine gültige PFERDE01.DAT-Zeile (mind. 211 Zeichen). */
|
||||
@@ -88,17 +89,19 @@ class ZnsImportServiceTest {
|
||||
lebensnummer.padEnd(9) +
|
||||
"W" + // Geschlecht: Wallach
|
||||
"2015" + // Geburtsjahr
|
||||
" ".repeat(157) // Auffüllen bis Stelle 201
|
||||
return base + "SAT0000001".padEnd(10) // Satznummer ab Stelle 202
|
||||
" ".repeat(157) // Auffüllen bis Stelle 201 (1 bis 201 = 201 Zeichen)
|
||||
return base + "1234567890".padEnd(10) // Satznummer ab Stelle 202
|
||||
}
|
||||
|
||||
/** Erzeugt eine gültige RICHT01.DAT-Zeile (mind. 83 Zeichen). */
|
||||
private fun richterZeile(
|
||||
satznummer: String = "R00001",
|
||||
name: String = "Huber, Anna"
|
||||
private fun funktionaerZeile(
|
||||
typ: String = "X",
|
||||
satznummer: String = "123456",
|
||||
name: String = "Huber, Anna",
|
||||
qualifikationen: String = "GA"
|
||||
): String {
|
||||
// Stelle 1: Typ, 2-7: Satznummer (6), 8-82: Name (75)
|
||||
return "R" + satznummer.padEnd(6) + name.padEnd(75)
|
||||
// Stelle 1: Typ (X=Richter, Y=Parcoursbauer), 2-7: Satznummer (6), 8-82: Name (75), 83-112: Quali (30)
|
||||
return typ + satznummer.padEnd(6) + name.padEnd(75) + qualifikationen.padEnd(30)
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
@@ -154,6 +157,7 @@ class ZnsImportServiceTest {
|
||||
fun `importiereZip - neue Pferde werden gespeichert`() = runTest {
|
||||
val zip = buildZip("PFERDE01.DAT" to pferdeZeile())
|
||||
|
||||
coEvery { horseRepository.findBySatznummer(any()) } returns null
|
||||
coEvery { horseRepository.findByLebensnummer(any()) } returns null
|
||||
coEvery { horseRepository.save(any()) } answers { firstArg<DomPferd>() }
|
||||
|
||||
@@ -166,10 +170,10 @@ class ZnsImportServiceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `importiereZip - neue Richter werden gespeichert`() = runTest {
|
||||
val zip = buildZip("RICHT01.DAT" to richterZeile())
|
||||
fun `importiereZip - neue Funktionaere werden gespeichert`() = runTest {
|
||||
val zip = buildZip("RICHT01.DAT" to funktionaerZeile())
|
||||
|
||||
coEvery { funktionaerRepository.findByRichterNummer(any()) } returns null
|
||||
coEvery { funktionaerRepository.findBySatz(any(), any()) } returns null
|
||||
coEvery { funktionaerRepository.save(any()) } answers { firstArg<DomFunktionaer>() }
|
||||
|
||||
val result = service.importiereZip(zip)
|
||||
@@ -180,22 +184,40 @@ class ZnsImportServiceTest {
|
||||
coVerify(exactly = 1) { funktionaerRepository.save(any<DomFunktionaer>()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `importiereZip - Richter und Parcoursbauer mit Mac-Zeilenumbruch werden importiert`() = runTest {
|
||||
// Nur \r als Umbruch
|
||||
val zip = buildZip(
|
||||
"RICHT01.DAT" to "X139552Mc Mullen Elizabeth DIOR\rX014346Schubert Renate DM,DPF,GAR-SP,SPF,SS*\rX001416Lechner-Gebhard Jeannette DPF,DSGP\rY135894Helmreich Marilena GA\r"
|
||||
)
|
||||
|
||||
coEvery { funktionaerRepository.findBySatz(any(), any()) } returns null
|
||||
coEvery { funktionaerRepository.save(any()) } answers { firstArg<DomFunktionaer>() }
|
||||
|
||||
val result = service.importiereZip(zip)
|
||||
|
||||
assertThat(result.richterImportiert).isEqualTo(4)
|
||||
assertThat(result.fehler).isEmpty()
|
||||
coVerify(exactly = 4) { funktionaerRepository.save(any<DomFunktionaer>()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `importiereZip - vollstaendiger Import aller vier Dateien`() = runTest {
|
||||
val zip = buildZip(
|
||||
"VEREIN01.DAT" to vereinZeile(),
|
||||
"LIZENZ01.DAT" to lizenzZeile(),
|
||||
"PFERDE01.DAT" to pferdeZeile(),
|
||||
"RICHT01.DAT" to richterZeile()
|
||||
"RICHT01.DAT" to funktionaerZeile()
|
||||
)
|
||||
|
||||
coEvery { vereinRepository.findByVereinsNummer(any()) } returns null
|
||||
coEvery { vereinRepository.save(any()) } answers { firstArg<DomVerein>() }
|
||||
coEvery { reiterRepository.findBySatznummer(any()) } returns null
|
||||
coEvery { reiterRepository.save(any()) } answers { firstArg<DomReiter>() }
|
||||
coEvery { horseRepository.findBySatznummer(any()) } returns null
|
||||
coEvery { horseRepository.findByLebensnummer(any()) } returns null
|
||||
coEvery { horseRepository.save(any()) } answers { firstArg<DomPferd>() }
|
||||
coEvery { funktionaerRepository.findByRichterNummer(any()) } returns null
|
||||
coEvery { funktionaerRepository.findBySatz(any(), any()) } returns null
|
||||
coEvery { funktionaerRepository.save(any()) } answers { firstArg<DomFunktionaer>() }
|
||||
|
||||
val result = service.importiereZip(zip)
|
||||
|
||||
Reference in New Issue
Block a user