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:
@@ -1,5 +1,7 @@
|
||||
package at.mocode.core.utils.parser
|
||||
|
||||
import kotlinx.datetime.LocalDate
|
||||
|
||||
/**
|
||||
* A simple utility to parse fixed-width strings based on 1-based start positions and lengths.
|
||||
* This is particularly useful for parsing legacy data formats like the OePS ZNS formats.
|
||||
@@ -36,4 +38,26 @@ class FixedWidthLineReader(private val line: String) {
|
||||
val str = getString(start1Based, length)
|
||||
return str.toIntOrNull()
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a string and parses it as a LocalDate (format YYYYMMDD).
|
||||
* Returns null if the field is empty or cannot be parsed.
|
||||
*/
|
||||
fun getLocalDateOrNull(start1Based: Int, length: Int): LocalDate? {
|
||||
val str = getString(start1Based, length)
|
||||
if (str.length != 8) return null
|
||||
|
||||
val year = str.substring(0, 4).toIntOrNull()
|
||||
val month = str.substring(4, 6).toIntOrNull()
|
||||
val day = str.substring(6, 8).toIntOrNull()
|
||||
|
||||
if (year == null || month == null || day == null) return null
|
||||
if (month !in 1..12 || day !in 1..31) return null
|
||||
|
||||
return try {
|
||||
LocalDate(year, month, day)
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
package at.mocode.zns.parser
|
||||
|
||||
import at.mocode.masterdata.domain.model.DomVerein
|
||||
import at.mocode.masterdata.domain.model.DomReiter
|
||||
import at.mocode.masterdata.domain.model.DomPferd
|
||||
import at.mocode.masterdata.domain.model.DomFunktionaer
|
||||
import at.mocode.core.domain.model.DatenQuelleE
|
||||
import at.mocode.core.domain.model.LizenzKlasseE
|
||||
import at.mocode.core.domain.model.PferdeGeschlechtE
|
||||
import at.mocode.core.utils.parser.FixedWidthLineReader
|
||||
import kotlinx.datetime.LocalDate
|
||||
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 kotlin.uuid.ExperimentalUuidApi
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
@@ -54,24 +53,56 @@ object ZnsLegacyParsers {
|
||||
|
||||
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 lizenzString = reader.getString(137, 4)
|
||||
val lizenz = mapLizenz(lizenzString)
|
||||
|
||||
val sperrlisteFlag = reader.getString(200, 1)
|
||||
val gesperrt = sperrlisteFlag == "S"
|
||||
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 mitgliedsNummer = reader.getIntOrNull(147, 8)
|
||||
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)
|
||||
|
||||
return DomReiter(
|
||||
personId = Uuid.random(),
|
||||
satznummer = satznummer,
|
||||
nachname = nachname,
|
||||
vorname = vorname,
|
||||
bundeslandNummer = bundeslandNummer,
|
||||
vereinsName = vereinsName.ifBlank { null },
|
||||
nation = nation.ifBlank { null },
|
||||
lizenzKlasse = lizenz,
|
||||
istAktiv = !gesperrt,
|
||||
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 = mapLizenz(reiterLizenz),
|
||||
datenQuelle = DatenQuelleE.IMPORT_ZNS
|
||||
)
|
||||
}
|
||||
@@ -80,52 +111,71 @@ object ZnsLegacyParsers {
|
||||
* Parses a line from PFERDE01.DAT.
|
||||
*/
|
||||
fun parsePferd(line: String): DomPferd? {
|
||||
if (line.isBlank() || line.length < 202) return null
|
||||
if (line.isBlank() || line.trim().length < 40) return null
|
||||
|
||||
val reader = FixedWidthLineReader(line)
|
||||
|
||||
val satznummer = reader.getString(202, 10)
|
||||
if (satznummer.isBlank()) return null
|
||||
|
||||
val name = reader.getString(5, 30)
|
||||
val kopfnummer = reader.getString(1, 4)
|
||||
val name = reader.getString(5, 30)
|
||||
val lebensnummer = reader.getString(35, 9)
|
||||
|
||||
val geschlechtChar = reader.getString(44, 1)
|
||||
val geschlecht = mapGeschlecht(geschlechtChar)
|
||||
|
||||
val geburtsjahr = reader.getIntOrNull(45, 4)
|
||||
val geburtsdatum = geburtsjahr?.let { LocalDate(it, 1, 1) }
|
||||
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)
|
||||
// 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(
|
||||
pferdeName = name,
|
||||
geschlecht = geschlecht,
|
||||
geburtsdatum = geburtsdatum,
|
||||
geburtsjahr = geburtsjahr,
|
||||
lebensnummer = lebensnummer.ifBlank { null },
|
||||
kopfnummer = kopfnummer.ifBlank { null },
|
||||
satznummer = satznummer,
|
||||
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
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a line from RICHT01.DAT.
|
||||
* Parses a line from RICHT01.DAT (Richter oder Parcoursbauer).
|
||||
*/
|
||||
fun parseRichter(line: String): DomFunktionaer? {
|
||||
fun parseFunktionaer(line: String): DomFunktionaer? {
|
||||
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.getString(2, 6)
|
||||
if (satznummer.isBlank()) return null
|
||||
val satzNummer = reader.getIntOrNull(2, 6)
|
||||
if (satzNummer == null) return null
|
||||
|
||||
val fullName = reader.getString(8, 75)
|
||||
val parts = fullName.split(",").map { it.trim() }
|
||||
val nachname = parts.getOrNull(0) ?: fullName
|
||||
val vorname = parts.getOrNull(1) ?: ""
|
||||
// Name begins directly after the satzNummer (position 8)
|
||||
val name = reader.getString(8, 75).trim()
|
||||
// Qualifikation is much later, probably at 83?
|
||||
// Wait, name is 75 chars, so 8 + 75 = 83.
|
||||
val qualifikationenRaw = reader.getString(83, 30).trim()
|
||||
val qualifikationen = qualifikationenRaw.split(",")
|
||||
.map { it.trim() }
|
||||
.filter { it.isNotBlank() }
|
||||
|
||||
return DomFunktionaer(
|
||||
richterNummer = satznummer,
|
||||
nachname = nachname,
|
||||
vorname = vorname,
|
||||
satzID = satzID,
|
||||
satzNummer = satzNummer,
|
||||
name = name.ifBlank { null },
|
||||
qualifikationen = qualifikationen,
|
||||
datenQuelle = DatenQuelleE.IMPORT_ZNS
|
||||
)
|
||||
}
|
||||
|
||||
+153
-22
@@ -21,27 +21,35 @@ class ZnsLegacyParsersTest {
|
||||
@Test
|
||||
fun `parseLizenz should extract LIZENZ01 correctly`() {
|
||||
val sb = StringBuilder()
|
||||
sb.append("123456")
|
||||
sb.append("Mustermann ")
|
||||
sb.append("Max ")
|
||||
sb.append("01")
|
||||
sb.append("Reitverein Wien ")
|
||||
sb.append("AUT")
|
||||
sb.append("R1 ")
|
||||
|
||||
while (sb.length < 199) {
|
||||
sb.append(" ")
|
||||
}
|
||||
sb.append("S")
|
||||
sb.append("123456") // 1-6
|
||||
sb.append("Mustermann ") // 7-56
|
||||
sb.append("Max ") // 57-81
|
||||
sb.append("01") // 82-83
|
||||
sb.append("Reitverein Wien ") // 84-133
|
||||
sb.append("AUT") // 134-136
|
||||
sb.append("R1 ") // 137-140
|
||||
sb.append(" ") // 141-146 (leer)
|
||||
sb.append("00000001") // 147-154 (mitgliedsNummer)
|
||||
sb.append("0676 12345678 ") // 155-176 (telefonNummer length 22)
|
||||
sb.append("2026") // 177-180 (lastPayYear)
|
||||
sb.append("M") // 181 (geschlecht)
|
||||
sb.append("19800101") // 182-189 (geburtsdatum)
|
||||
sb.append("1000000001") // 190-199 (feiId length 10)
|
||||
sb.append("S") // 200 (sperrListe)
|
||||
sb.append("INFO1 ") // 201-210 (lizenzInfo)
|
||||
|
||||
val result = ZnsLegacyParsers.parseLizenz(sb.toString())
|
||||
assertNotNull(result)
|
||||
assertEquals("123456", result.satznummer)
|
||||
assertEquals("Mustermann", result.nachname)
|
||||
assertEquals("Max", result.vorname)
|
||||
assertEquals(1, result.bundeslandNummer)
|
||||
assertEquals("Reitverein Wien", result.vereinsName)
|
||||
assertEquals(LizenzKlasseE.R1, result.lizenzKlasse)
|
||||
assertEquals(false, result.istAktiv)
|
||||
assertEquals("AUT", result.nation)
|
||||
assertEquals("R1", result.reiterLizenz)
|
||||
assertEquals(2026, result.lastPayYear)
|
||||
assertEquals("M", result.geschlecht)
|
||||
assertEquals("1980-01-01", result.geburtsdatum.toString())
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -60,21 +68,144 @@ class ZnsLegacyParsersTest {
|
||||
|
||||
val result = ZnsLegacyParsers.parsePferd(sb.toString())
|
||||
assertNotNull(result)
|
||||
assertEquals("A123", result.kopfnummer)
|
||||
assertEquals("0000000001", result.satznummer)
|
||||
assertEquals("Black Beauty", result.pferdeName)
|
||||
assertEquals("123456789", result.lebensnummer)
|
||||
assertEquals(PferdeGeschlechtE.WALLACH, result.geschlecht)
|
||||
assertEquals(2010, result.geburtsdatum?.year)
|
||||
assertEquals(2010, result.geburtsjahr)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `parseRichter should extract RICHT01 correctly`() {
|
||||
val line =
|
||||
"X123456Richter, Peter GA "
|
||||
val result = ZnsLegacyParsers.parseRichter(line)
|
||||
fun `parseFunktionaer should extract RICHT01 correctly for Richter`() {
|
||||
// Real example from RICHT01.dat
|
||||
val line = "X010128Zitterbart Rainer PI-A"
|
||||
val result = ZnsLegacyParsers.parseFunktionaer(line)
|
||||
|
||||
assertNotNull(result)
|
||||
assertEquals("123456", result.richterNummer)
|
||||
assertEquals("Richter", result.nachname)
|
||||
assertEquals("Peter", result.vorname)
|
||||
assertEquals("X", result.satzID)
|
||||
assertEquals(10128, result.satzNummer)
|
||||
assertEquals("Zitterbart Rainer", result.name)
|
||||
assertEquals(listOf("PI-A"), result.qualifikationen)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `parseFunktionaer should extract RICHT01 correctly with more examples`() {
|
||||
// X139552Mc Mullen Elizabeth DIOR
|
||||
val line1 = "X139552Mc Mullen Elizabeth DIOR"
|
||||
val result1 = ZnsLegacyParsers.parseFunktionaer(line1)
|
||||
assertNotNull(result1)
|
||||
assertEquals("X", result1.satzID)
|
||||
assertEquals(139552, result1.satzNummer)
|
||||
assertEquals("Mc Mullen Elizabeth", result1.name)
|
||||
assertEquals(listOf("DIOR"), result1.qualifikationen)
|
||||
|
||||
// X014346Schubert Renate DM,DPF,GAR-SP,SPF,SS*
|
||||
val line2 = "X014346Schubert Renate DM,DPF,GAR-SP,SPF,SS*"
|
||||
val result2 = ZnsLegacyParsers.parseFunktionaer(line2)
|
||||
assertNotNull(result2)
|
||||
assertEquals(14346, result2.satzNummer)
|
||||
assertEquals("Schubert Renate", result2.name)
|
||||
assertEquals(listOf("DM", "DPF", "GAR-SP", "SPF", "SS*"), result2.qualifikationen)
|
||||
|
||||
// Y002211Salusek Andreas Christian P3,PL2
|
||||
val line3 = "Y002211Salusek Andreas Christian P3,PL2"
|
||||
val result3 = ZnsLegacyParsers.parseFunktionaer(line3)
|
||||
assertNotNull(result3)
|
||||
assertEquals("Y", result3.satzID)
|
||||
assertEquals(2211, result3.satzNummer)
|
||||
assertEquals("Salusek Andreas Christian", result3.name)
|
||||
assertEquals(listOf("P3", "PL2"), result3.qualifikationen)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `parseFunktionaer should return null for invalid lines`() {
|
||||
assertEquals(null, ZnsLegacyParsers.parseFunktionaer(""))
|
||||
assertEquals(null, ZnsLegacyParsers.parseFunktionaer("Z123456Test"))
|
||||
assertEquals(null, ZnsLegacyParsers.parseFunktionaer("XABCDEFTest"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `parsePferd should extract real PFERDE01 correctly`() {
|
||||
// Real example from PFERDE01.dat (line length approx 211 characters)
|
||||
val line = "9D56Viola B 000000017S2005Brauner Tschech. WB 10952024Tanja Kuntner 535 Latinus 5637401268"
|
||||
val result = ZnsLegacyParsers.parsePferd(line)
|
||||
|
||||
assertNotNull(result)
|
||||
assertEquals("9D56", result.kopfnummer)
|
||||
assertEquals("Viola B", result.pferdeName)
|
||||
assertEquals("000000017", result.lebensnummer)
|
||||
assertEquals(PferdeGeschlechtE.STUTE, result.geschlecht)
|
||||
assertEquals(2005, result.geburtsjahr)
|
||||
assertEquals("Brauner", result.farbe)
|
||||
assertEquals("Tschech. WB", result.abstammung)
|
||||
assertEquals(1095, result.vereinNummer)
|
||||
assertEquals(2024, result.lastPayYear)
|
||||
assertEquals("Tanja Kuntner", result.verantwortlichePersonId)
|
||||
assertEquals("535 Latinus", result.vater)
|
||||
assertEquals("5637401268", result.satznummer)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `parseLizenz should extract real LIZENZ01 correctly for Ebner Sarah`() {
|
||||
// Real example from user:
|
||||
// "100365Ebner Sarah 09Hubertus Voltigier Reit- und Fahrverein AUTR2S3 903801690699 18109450 2025W1990100310137032 R2S3 "
|
||||
val line = "100365Ebner Sarah 09Hubertus Voltigier Reit- und Fahrverein AUTR2S3 903801690699 18109450 2025W1990100310137032 R2S3 "
|
||||
|
||||
val result = ZnsLegacyParsers.parseLizenz(line)
|
||||
|
||||
assertNotNull(result)
|
||||
assertEquals("100365", result.satznummer)
|
||||
assertEquals("Ebner", result.nachname)
|
||||
assertEquals("Sarah", result.vorname)
|
||||
assertEquals(9, result.bundeslandNummer)
|
||||
assertEquals("Hubertus Voltigier Reit- und Fahrverein", result.vereinsName)
|
||||
assertEquals("AUT", result.nation)
|
||||
assertEquals("R2S3", result.reiterLizenz)
|
||||
assertEquals(90380169, result.mitgliedsNummer)
|
||||
assertEquals("0699 18109450", result.telefonNummer)
|
||||
assertEquals(2025, result.lastPayYear)
|
||||
assertEquals("W", result.geschlecht)
|
||||
assertEquals("1990-10-03", result.geburtsdatum.toString())
|
||||
assertEquals("10137032", result.feiId)
|
||||
assertEquals("R2S3", result.lizenzInfo)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `parseLizenz should extract real LIZENZ01 correctly`() {
|
||||
// Real example from LIZENZ01.dat (second line of file)
|
||||
val sb = StringBuilder()
|
||||
sb.append("000010") // 1-6
|
||||
sb.append("Aichinger ") // 7-56
|
||||
sb.append("Ewald ") // 57-81
|
||||
sb.append("02") // 82-83
|
||||
sb.append("Reitverein Geiger-Amstetten ") // 84-133
|
||||
sb.append("AUT") // 134-136
|
||||
sb.append("R2 ") // 137-140
|
||||
sb.append(" ") // 141-146 (leer)
|
||||
sb.append("20660700") // 147-154 (mitgliedsNummer)
|
||||
sb.append("0676 4825910 ") // 155-176 (telefon)
|
||||
sb.append("2023") // 177-180 (lastPayYear)
|
||||
sb.append("M") // 181 (geschlecht)
|
||||
sb.append("19571010") // 182-189 (geburtsdatum)
|
||||
sb.append(" ") // 190-199 (feiId length 10)
|
||||
sb.append(" ") // 200 (sperrliste)
|
||||
sb.append(" ") // 201-210 (lizenzinfo)
|
||||
|
||||
val result = ZnsLegacyParsers.parseLizenz(sb.toString())
|
||||
|
||||
assertNotNull(result)
|
||||
assertEquals("000010", result.satznummer)
|
||||
assertEquals("Aichinger", result.nachname)
|
||||
assertEquals("Ewald", result.vorname)
|
||||
assertEquals(2, result.bundeslandNummer)
|
||||
assertEquals("Reitverein Geiger-Amstetten", result.vereinsName)
|
||||
assertEquals("AUT", result.nation)
|
||||
assertEquals("R2", result.reiterLizenz)
|
||||
assertEquals(20660700, result.mitgliedsNummer)
|
||||
assertEquals("0676 4825910", result.telefonNummer)
|
||||
assertEquals(2023, result.lastPayYear)
|
||||
assertEquals("M", result.geschlecht)
|
||||
assertEquals("1957-10-10", result.geburtsdatum.toString())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user