Introduce modular ZNS parsers (ZnsVereinParser, ZnsReiterParser, ZnsPferdParser, ZnsFunktionaerParser), replace legacy parsers, and update the import service/test suites to support streamlined parsing and isolated imports. Deprecate ZnsLegacyParsers. Add comprehensive tests (ZnsParserTest) and revise extraction logic for ZNS data.
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
package at.mocode.zns.parser
|
||||
|
||||
import at.mocode.core.domain.model.DatenQuelleE
|
||||
import at.mocode.core.utils.parser.FixedWidthLineReader
|
||||
import at.mocode.masterdata.domain.model.Funktionaer
|
||||
import kotlin.uuid.ExperimentalUuidApi
|
||||
|
||||
/**
|
||||
* Spezialisierter Parser für RICHT01.DAT.
|
||||
*/
|
||||
@OptIn(ExperimentalUuidApi::class)
|
||||
object ZnsFunktionaerParser {
|
||||
|
||||
fun parse(line: String): Funktionaer? {
|
||||
if (line.isBlank() || line.length < 8) return null
|
||||
|
||||
val reader = FixedWidthLineReader(line)
|
||||
val satzID = reader.getString(1, 1).uppercase()
|
||||
if (satzID != "X" && satzID != "Y") return null
|
||||
|
||||
val satzNummer = reader.getIntOrNull(2, 6)
|
||||
if (satzNummer == null) return null
|
||||
|
||||
// Name begins directly after the satzNummer (position 8)
|
||||
// Name is 75 characters long, starting at 8 (8 to 82)
|
||||
val name = reader.getString(8, 75).trim()
|
||||
// Qualifikation starts at 83
|
||||
val qualifikationenRaw = reader.getString(83, 30).trim()
|
||||
val qualifikationen = qualifikationenRaw.split(",")
|
||||
.map { it.trim() }
|
||||
.filter { it.isNotBlank() }
|
||||
|
||||
return Funktionaer(
|
||||
personId = null,
|
||||
satzId = satzID,
|
||||
satzNummer = satzNummer,
|
||||
name = name.ifBlank { null },
|
||||
qualifikationen = qualifikationen,
|
||||
datenQuelle = DatenQuelleE.IMPORT_ZNS
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -14,9 +14,9 @@ import kotlin.uuid.Uuid
|
||||
/**
|
||||
* Parsers for the legacy ZNS .dat files (Fixed-Width format, CP850 encoded).
|
||||
*
|
||||
* This module is independent of backend infrastructure so it can be used
|
||||
* by both the backend API and the Compose Desktop app (Offline-First).
|
||||
* @deprecated Use specialized parsers instead (ZnsVereinParser, ZnsReiterParser, etc.)
|
||||
*/
|
||||
@Deprecated("Use specialized parsers instead")
|
||||
@OptIn(ExperimentalUuidApi::class)
|
||||
object ZnsLegacyParsers {
|
||||
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
package at.mocode.zns.parser
|
||||
|
||||
import at.mocode.core.domain.model.DatenQuelleE
|
||||
import at.mocode.core.domain.model.PferdeGeschlechtE
|
||||
import at.mocode.core.utils.parser.FixedWidthLineReader
|
||||
import at.mocode.masterdata.domain.model.Pferd
|
||||
import kotlin.uuid.ExperimentalUuidApi
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
/**
|
||||
* Spezialisierter Parser für PFERDE01.DAT.
|
||||
*/
|
||||
@OptIn(ExperimentalUuidApi::class)
|
||||
object ZnsPferdParser {
|
||||
|
||||
fun parse(line: String): Pferd? {
|
||||
if (line.isBlank() || line.trim().length < 4) return null
|
||||
|
||||
val reader = FixedWidthLineReader(line)
|
||||
val kopfnummer = reader.getString(1, 4)
|
||||
val name = reader.getString(5, 30)
|
||||
|
||||
// We need at least a name to identify a horse record
|
||||
if (name.isBlank()) return null
|
||||
|
||||
val lebensnummer = reader.getString(35, 9)
|
||||
val geschlechtChar = reader.getString(44, 1)
|
||||
val geschlecht = mapGeschlecht(geschlechtChar)
|
||||
val geburtsjahr = reader.getIntOrNull(45, 4)
|
||||
val farbe = reader.getString(49, 15)
|
||||
val abstammung = reader.getString(64, 15)
|
||||
val vereinNummer = reader.getIntOrNull(79, 4)
|
||||
val lastPayYear = reader.getIntOrNull(83, 4)
|
||||
val verantwortlichePersonId = reader.getString(87, 75)
|
||||
val vaterName = reader.getString(162, 30)
|
||||
val feiPass = reader.getString(192, 10)
|
||||
val satznummer = reader.getString(202, 10)
|
||||
|
||||
return Pferd(
|
||||
personId = Uuid.random(),
|
||||
pferdeName = name,
|
||||
geschlecht = geschlecht,
|
||||
geburtsjahr = geburtsjahr,
|
||||
lebensnummer = lebensnummer.ifBlank { null },
|
||||
kopfnummer = kopfnummer.ifBlank { null },
|
||||
satznummer = satznummer.ifBlank { null },
|
||||
farbe = farbe.ifBlank { null },
|
||||
abstammung = abstammung.ifBlank { null },
|
||||
vereinNummer = vereinNummer,
|
||||
lastPayYear = lastPayYear,
|
||||
verantwortlichePersonId = verantwortlichePersonId.ifBlank { null },
|
||||
vater = vaterName.ifBlank { null },
|
||||
feiPass = feiPass.ifBlank { null },
|
||||
datenQuelle = DatenQuelleE.IMPORT_ZNS
|
||||
)
|
||||
}
|
||||
|
||||
private fun mapGeschlecht(geschlecht: String): PferdeGeschlechtE {
|
||||
return when (geschlecht.uppercase()) {
|
||||
"W" -> PferdeGeschlechtE.WALLACH
|
||||
"S" -> PferdeGeschlechtE.STUTE
|
||||
"H" -> PferdeGeschlechtE.HENGST
|
||||
else -> PferdeGeschlechtE.UNBEKANNT
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package at.mocode.zns.parser
|
||||
|
||||
import at.mocode.core.domain.model.DatenQuelleE
|
||||
import at.mocode.core.domain.model.ReiterLizenzKlasseE
|
||||
import at.mocode.core.utils.parser.FixedWidthLineReader
|
||||
import at.mocode.masterdata.domain.model.Reiter
|
||||
import kotlin.uuid.ExperimentalUuidApi
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
/**
|
||||
* Spezialisierter Parser für LIZENZ01.DAT.
|
||||
*/
|
||||
@OptIn(ExperimentalUuidApi::class)
|
||||
object ZnsReiterParser {
|
||||
|
||||
fun parse(line: String): Reiter? {
|
||||
if (line.isBlank() || line.length < 57) return null
|
||||
|
||||
val reader = FixedWidthLineReader(line)
|
||||
|
||||
val satznummer = reader.getString(1, 6)
|
||||
if (satznummer.isBlank()) return null
|
||||
|
||||
val nachname = reader.getString(7, 50)
|
||||
val vorname = reader.getString(57, 25)
|
||||
val bundeslandNummer = reader.getIntOrNull(82, 2)
|
||||
val vereinsName = reader.getString(84, 50)
|
||||
val nation = reader.getString(134, 3)
|
||||
val reiterLizenz = reader.getString(137, 4)
|
||||
// Ab Stelle 137 weicht die Realität der ZNS.zip von der Spec 2.4 ab
|
||||
// Die Realität (Aichinger Ewald) zeigt:
|
||||
// 134-136: AUT
|
||||
// 137-140: R2
|
||||
// 147-158: 206607000676 (Mitgliedsnummer 8 Stellen ab 147?)
|
||||
// 160-166: 4825910 (Telefonnummer?)
|
||||
// 177-180: 2023 (LastPayYear)
|
||||
// 181: M (Geschlecht)
|
||||
// 182-189: 19571010 (Geburtsdatum)
|
||||
val startkarte = reader.getString(141, 1)
|
||||
val fahrLizenz = reader.getString(142, 2)
|
||||
val altersklasseJgJrU25 = reader.getString(144, 2)
|
||||
val altersklasseY = reader.getString(146, 1)
|
||||
val mitgliedsNummerStr = reader.getString(147, 8)
|
||||
val mitgliedsNummer = mitgliedsNummerStr.toIntOrNull()
|
||||
val telefonNummer = reader.getString(155, 22).trim()
|
||||
val kader = reader.getString(177, 1)
|
||||
val lastPayYear = reader.getIntOrNull(177, 4)
|
||||
val geschlecht = reader.getString(181, 1)
|
||||
val geburtsdatum = reader.getLocalDateOrNull(182, 8)
|
||||
val feiId = reader.getString(190, 8)
|
||||
val sperrListe = reader.getString(198, 1)
|
||||
val lizenzInfo = reader.getString(201, 10)
|
||||
val lizenzKlasse = mapLizenz(reiterLizenz)
|
||||
|
||||
return Reiter(
|
||||
personId = Uuid.random(),
|
||||
satznummer = satznummer,
|
||||
nachname = nachname,
|
||||
vorname = vorname,
|
||||
bundeslandNummer = bundeslandNummer,
|
||||
vereinsName = vereinsName.ifBlank { null },
|
||||
nation = nation.ifBlank { null },
|
||||
reiterLizenz = reiterLizenz.ifBlank { null },
|
||||
startkarte = startkarte.ifBlank { null },
|
||||
fahrLizenz = fahrLizenz.ifBlank { null },
|
||||
altersklasseJgJrU25 = altersklasseJgJrU25.ifBlank { null },
|
||||
altersklasseY = altersklasseY.ifBlank { null },
|
||||
mitgliedsNummer = mitgliedsNummer,
|
||||
telefonNummer = telefonNummer.ifBlank { null },
|
||||
kader = kader.ifBlank { null },
|
||||
lastPayYear = lastPayYear,
|
||||
geschlecht = geschlecht.ifBlank { null },
|
||||
geburtsdatum = geburtsdatum,
|
||||
feiId = feiId.ifBlank { null },
|
||||
sperrListe = sperrListe.ifBlank { null },
|
||||
lizenzInfo = lizenzInfo.ifBlank { null },
|
||||
lizenzKlasse = lizenzKlasse,
|
||||
datenQuelle = DatenQuelleE.IMPORT_ZNS
|
||||
)
|
||||
}
|
||||
|
||||
private fun mapLizenz(lizenz: String): ReiterLizenzKlasseE {
|
||||
return when (lizenz.uppercase()) {
|
||||
"R1" -> ReiterLizenzKlasseE.R1
|
||||
"R2" -> ReiterLizenzKlasseE.R2
|
||||
"R3" -> ReiterLizenzKlasseE.R3
|
||||
"RD1" -> ReiterLizenzKlasseE.RD1
|
||||
"RD2" -> ReiterLizenzKlasseE.RD2
|
||||
"RD3" -> ReiterLizenzKlasseE.RD3
|
||||
else -> ReiterLizenzKlasseE.LIZENZFREI
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package at.mocode.zns.parser
|
||||
|
||||
import at.mocode.core.domain.model.DatenQuelleE
|
||||
import at.mocode.core.utils.parser.FixedWidthLineReader
|
||||
import at.mocode.masterdata.domain.model.Verein
|
||||
import kotlin.uuid.ExperimentalUuidApi
|
||||
|
||||
/**
|
||||
* Spezialisierter Parser für VEREIN01.DAT.
|
||||
*/
|
||||
@OptIn(ExperimentalUuidApi::class)
|
||||
object ZnsVereinParser {
|
||||
|
||||
fun parse(line: String): Verein? {
|
||||
if (line.isBlank() || line.length < 5) return null
|
||||
|
||||
val reader = FixedWidthLineReader(line)
|
||||
|
||||
val vereinsNummer = reader.getString(1, 4)
|
||||
val vereinsName = reader.getString(5, 50)
|
||||
|
||||
if (vereinsNummer.isBlank() || vereinsName.isBlank()) return null
|
||||
|
||||
return Verein(
|
||||
vereinsNummer = vereinsNummer,
|
||||
vereinName = vereinsName,
|
||||
datenQuelle = DatenQuelleE.IMPORT_ZNS
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package at.mocode.zns.parser
|
||||
|
||||
import at.mocode.core.utils.parser.FixedWidthLineReader
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotNull
|
||||
import kotlin.uuid.ExperimentalUuidApi
|
||||
|
||||
@OptIn(ExperimentalUuidApi::class)
|
||||
class ZnsParserTest {
|
||||
|
||||
@Test
|
||||
fun `parseVerein should extract VEREIN01 correctly`() {
|
||||
val line = "0001REIT- U. FAHRCLUB ALTHOF "
|
||||
val verein = ZnsVereinParser.parse(line)
|
||||
|
||||
assertNotNull(verein)
|
||||
assertEquals("0001", verein.vereinsNummer)
|
||||
assertEquals("REIT- U. FAHRCLUB ALTHOF", verein.vereinName)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `parseLizenz should extract Hämmerle Ute correctly`() {
|
||||
val line = "100236Hämmerle Ute 09Reitervereinigung Eichenhof Dornbirn AUTRD2 900308190664 3462613 2021W19650928 "
|
||||
val reiter = ZnsReiterParser.parse(line)
|
||||
|
||||
assertNotNull(reiter)
|
||||
assertEquals("100236", reiter.satznummer)
|
||||
assertEquals("Hämmerle", reiter.nachname)
|
||||
assertEquals("Ute", reiter.vorname)
|
||||
assertEquals(9, reiter.bundeslandNummer)
|
||||
assertEquals("Reitervereinigung Eichenhof Dornbirn", reiter.vereinsName)
|
||||
assertEquals("AUT", reiter.nation)
|
||||
assertEquals("RD2", reiter.reiterLizenz)
|
||||
// 90030819 ist die Mitgliedsnummer ab Pos 147
|
||||
assertEquals(90030819, reiter.mitgliedsNummer)
|
||||
assertEquals("2021", reiter.lastPayYear.toString())
|
||||
assertEquals("W", reiter.geschlecht)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `parseLizenz should extract Neuwirth Petra correctly`() {
|
||||
val line = "100562Neuwirth Petra 02Reit- und Fahrverein Schlosshof AUT P 283501510664 4090200 2025W19840323 Para "
|
||||
val reiter = ZnsReiterParser.parse(line)
|
||||
|
||||
assertNotNull(reiter)
|
||||
assertEquals("100562", reiter.satznummer)
|
||||
assertEquals("Neuwirth", reiter.nachname)
|
||||
assertEquals("Petra", reiter.vorname)
|
||||
assertEquals(2, reiter.bundeslandNummer)
|
||||
assertEquals("Reit- und Fahrverein Schlosshof", reiter.vereinsName)
|
||||
assertEquals("AUT", reiter.nation)
|
||||
assertEquals(28350151, reiter.mitgliedsNummer)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `parseLizenz should extract Thuniot Tamara correctly`() {
|
||||
val line = "101592Thuniot Tamara 02Voltigiergruppe Club 43 AUT V 229701830650 6018700 2025W1987010610044492 V "
|
||||
val reiter = ZnsReiterParser.parse(line)
|
||||
|
||||
assertNotNull(reiter)
|
||||
assertEquals("101592", reiter.satznummer)
|
||||
assertEquals("Thuniot", reiter.nachname)
|
||||
assertEquals("Tamara", reiter.vorname)
|
||||
assertEquals(22970183, reiter.mitgliedsNummer)
|
||||
assertEquals("10044492", reiter.feiId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `parsePferd should extract PFERDE01 correctly`() {
|
||||
val line = "0001Zarin 13 200632349S2004Fuchsschimmel 0123 1234Verantwortliche Person Vater Name FEI-PASS-10001ABC "
|
||||
val pferd = ZnsPferdParser.parse(line)
|
||||
|
||||
assertNotNull(pferd)
|
||||
assertEquals("0001", pferd.kopfnummer)
|
||||
assertEquals("Zarin 13", pferd.pferdeName)
|
||||
assertEquals("200632349", pferd.lebensnummer)
|
||||
assertEquals(2004, pferd.geburtsjahr)
|
||||
assertEquals("Fuchsschimmel", pferd.farbe)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `parseFunktionaer should extract RICHT01 correctly for Richter`() {
|
||||
// 1(X) + 6(001061) + 75(Name) + 30(Quali) = 112 characters
|
||||
val line = "X001061Stöglehner Otto DPF, DSGP, SS* "
|
||||
val funktionaer = ZnsFunktionaerParser.parse(line)
|
||||
|
||||
assertNotNull(funktionaer)
|
||||
assertEquals("X", funktionaer.satzId)
|
||||
assertEquals(1061, funktionaer.satzNummer)
|
||||
assertEquals("Stöglehner Otto", funktionaer.name)
|
||||
assertEquals(listOf("DPF", "DSGP", "SS*"), funktionaer.qualifikationen)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user