Refactor license matrix and tokenizer logic: rename LicenseTable to LizenzTable, replace LicenseMatrixService with LizenzMatrixService, enhance tokenizer with normalized and fallback token handling, improve ZNS import for license extraction, and update related documentation.
Desktop CI — Headless Tests & Build / Compose Desktop — Tests (headless) & Build (push) Waiting to run
Build and Publish Docker Images / build-and-push (., backend/infrastructure/gateway/Dockerfile, api-gateway, api-gateway) (push) Waiting to run
Build and Publish Docker Images / build-and-push (., backend/services/ping/Dockerfile, ping-service, ping-service) (push) Waiting to run
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Waiting to run
Build and Publish Docker Images / build-and-push (., config/docker/keycloak/Dockerfile, keycloak, keycloak) (push) Waiting to run
Desktop CI — Headless Tests & Build / Compose Desktop — Tests (headless) & Build (push) Waiting to run
Build and Publish Docker Images / build-and-push (., backend/infrastructure/gateway/Dockerfile, api-gateway, api-gateway) (push) Waiting to run
Build and Publish Docker Images / build-and-push (., backend/services/ping/Dockerfile, ping-service, ping-service) (push) Waiting to run
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Waiting to run
Build and Publish Docker Images / build-and-push (., config/docker/keycloak/Dockerfile, keycloak, keycloak) (push) Waiting to run
This commit is contained in:
@@ -4,6 +4,7 @@ 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 at.mocode.masterdata.domain.model.ReiterLizenz
|
||||
import kotlin.uuid.ExperimentalUuidApi
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
@@ -50,17 +51,33 @@ object ZnsReiterParser {
|
||||
val feiId = reader.getString(190, 8)
|
||||
val sperrListe = reader.getString(198, 1)
|
||||
val lizenzInfo = reader.getString(201, 10)
|
||||
val lizenzKlasse = mapLizenz(reiterLizenzCode)
|
||||
|
||||
val lizenzen = mutableListOf<at.mocode.masterdata.domain.model.ReiterLizenz>()
|
||||
// Lizenz-Token aus gesamter Zeile robust extrahieren und normalisieren
|
||||
val parsedToken = ZnsReiterlicenseTokenizer.parseFromLine(line)
|
||||
|
||||
val lizenzen = mutableListOf<ReiterLizenz>()
|
||||
// Aus festem Feld
|
||||
if (reiterLizenzCode.isNotBlank()) {
|
||||
lizenzen.add(at.mocode.masterdata.domain.model.ReiterLizenz(lizenzTyp = "REITERLIZENZ", kuerzel = reiterLizenzCode))
|
||||
lizenzen.add(ReiterLizenz(lizenzTyp = "REITERLIZENZ", kuerzel = reiterLizenzCode))
|
||||
}
|
||||
// Aus erkanntem kombinierten Token weitere Einträge ergänzen
|
||||
parsedToken?.normalizedKuerzel?.forEach { code ->
|
||||
if (code.isNotBlank() && lizenzen.none { it.kuerzel.equals(code, ignoreCase = true) }) {
|
||||
lizenzen.add(ReiterLizenz(lizenzTyp = "REITERLIZENZ", kuerzel = code))
|
||||
}
|
||||
}
|
||||
if (startkarteCode.isNotBlank()) {
|
||||
lizenzen.add(at.mocode.masterdata.domain.model.ReiterLizenz(lizenzTyp = "STARTKARTE", kuerzel = startkarteCode))
|
||||
lizenzen.add(ReiterLizenz(lizenzTyp = "STARTKARTE", kuerzel = startkarteCode))
|
||||
}
|
||||
if (fahrLizenzCode.isNotBlank()) {
|
||||
lizenzen.add(at.mocode.masterdata.domain.model.ReiterLizenz(lizenzTyp = "FAHRLIZENZ", kuerzel = fahrLizenzCode))
|
||||
lizenzen.add(ReiterLizenz(lizenzTyp = "FAHRLIZENZ", kuerzel = fahrLizenzCode))
|
||||
}
|
||||
|
||||
// lizenzKlasse befüllen: bevorzugt aus festem Feld, sonst aus Token ableiten (präferiere Dressur, sonst Springen)
|
||||
val lizenzKlasse = when {
|
||||
reiterLizenzCode.isNotBlank() -> mapLizenz(reiterLizenzCode)
|
||||
parsedToken != null -> parsedToken.primaryKlasse
|
||||
else -> ReiterLizenzKlasseE.LIZENZFREI
|
||||
}
|
||||
|
||||
return Reiter(
|
||||
@@ -71,7 +88,8 @@ object ZnsReiterParser {
|
||||
bundeslandNummer = bundeslandNummer,
|
||||
vereinsName = vereinsName.ifBlank { null },
|
||||
nation = nation.ifBlank { null },
|
||||
reiterLizenz = reiterLizenzCode.ifBlank { null },
|
||||
reiterLizenz = (reiterLizenzCode.ifBlank { null }
|
||||
?: parsedToken?.primaryKuerzel),
|
||||
startkarte = startkarteCode.ifBlank { null },
|
||||
fahrLizenz = fahrLizenzCode.ifBlank { null },
|
||||
altersklasseJgJrU25 = altersklasseEnum,
|
||||
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
package at.mocode.zns.parser
|
||||
|
||||
import at.mocode.core.domain.model.ReiterLizenzKlasseE
|
||||
|
||||
/**
|
||||
* Erkennung und Normalisierung von Lizenz-Token in einer LIZENZ01.DAT-Zeile.
|
||||
* Unterstützt Einzel- und Kombinationsformen (R{n}, RD{m}, S{n}, D{m}, R{n}S{k}, R{n}D{m}, RDS4).
|
||||
*/
|
||||
object ZnsReiterlicenseTokenizer {
|
||||
private val tokenRegex = Regex(
|
||||
pattern = "(RDS4|R[1-4]D[2-4]|R[1-4]S[2-4]|RD[1-4]|R[1-4]|S[1-4]|D[2-4])",
|
||||
options = setOf(RegexOption.IGNORE_CASE)
|
||||
)
|
||||
|
||||
data class Parsed(
|
||||
val rawToken: String,
|
||||
/** Normalisierte Kürzel-Liste, z.B. ["R3"], ["R2","RD3"] */
|
||||
val normalizedKuerzel: List<String>,
|
||||
/** Primäre Lizenzklasse für das vorhandene Domainfeld (Fallback): bevorzugt RD*, sonst R* */
|
||||
val primaryKlasse: ReiterLizenzKlasseE,
|
||||
/** Primäres Kürzel passend zur primaryKlasse */
|
||||
val primaryKuerzel: String?
|
||||
)
|
||||
|
||||
fun parseFromLine(line: String): Parsed? {
|
||||
val matches = tokenRegex.findAll(line)
|
||||
val last = matches.lastOrNull() ?: return null
|
||||
val token = last.value.uppercase()
|
||||
return normalize(token)
|
||||
}
|
||||
|
||||
private fun normalize(token: String): Parsed? {
|
||||
val upper = token.uppercase()
|
||||
return when {
|
||||
upper == "RDS4" -> Parsed(
|
||||
rawToken = token,
|
||||
normalizedKuerzel = listOf("R4", "RD3"),
|
||||
primaryKlasse = ReiterLizenzKlasseE.RD3,
|
||||
primaryKuerzel = "RD3"
|
||||
)
|
||||
upper.matches(Regex("R[1-4]D[2-4]")) -> {
|
||||
val r = upper.substring(0, 2) // Rn
|
||||
// upper is RnDm -> build RDm and ggf. kappen
|
||||
val d = capRd4ToRd3("RD" + upper.substring(3, 4))
|
||||
Parsed(token, listOf(r, d), mapPrimary(d, r), pickPrimaryKuerzel(d, r))
|
||||
}
|
||||
upper.matches(Regex("R[1-4]S[2-4]")) -> {
|
||||
val r1 = upper.substring(0, 2)
|
||||
val r2 = "R" + upper.substring(3, 4) // S{k} -> R{k}
|
||||
val best = maxR(r1, r2)
|
||||
Parsed(token, listOf(best), ReiterLizenzKlasseE.valueOf(best), best)
|
||||
}
|
||||
upper.matches(Regex("RD[1-4]")) -> {
|
||||
val d = capRd4ToRd3(upper)
|
||||
Parsed(token, listOf(d), mapPrimary(d, null), d)
|
||||
}
|
||||
upper.matches(Regex("R[1-4]")) -> {
|
||||
Parsed(token, listOf(upper), ReiterLizenzKlasseE.valueOf(upper), upper)
|
||||
}
|
||||
upper.matches(Regex("S[1-4]")) -> {
|
||||
val r = "R" + upper.substring(1, 2)
|
||||
Parsed(token, listOf(r), ReiterLizenzKlasseE.valueOf(r), r)
|
||||
}
|
||||
upper.matches(Regex("D[2-4]")) -> {
|
||||
val d = capRd4ToRd3("RD" + upper.substring(1, 2))
|
||||
Parsed(token, listOf(d), mapPrimary(d, null), d)
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun capRd4ToRd3(code: String): String = if (code.equals("RD4", true)) "RD3" else code
|
||||
|
||||
private fun maxR(r1: String, r2: String): String {
|
||||
val n1 = r1.removePrefix("R").toIntOrNull() ?: 0
|
||||
val n2 = r2.removePrefix("R").toIntOrNull() ?: 0
|
||||
val n = maxOf(n1, n2).coerceIn(1, 4)
|
||||
return "R$n"
|
||||
}
|
||||
|
||||
private fun mapPrimary(d: String?, r: String?): ReiterLizenzKlasseE = when {
|
||||
d != null -> ReiterLizenzKlasseE.valueOf(d)
|
||||
r != null -> ReiterLizenzKlasseE.valueOf(r)
|
||||
else -> ReiterLizenzKlasseE.LIZENZFREI
|
||||
}
|
||||
|
||||
private fun pickPrimaryKuerzel(d: String?, r: String?): String? = d ?: r
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package at.mocode.zns.parser
|
||||
|
||||
import at.mocode.core.domain.model.ReiterLizenzKlasseE
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotNull
|
||||
import kotlin.test.assertNull
|
||||
|
||||
class LicenseTokenizerTest {
|
||||
|
||||
@Test
|
||||
fun `detects R2S3 and normalizes to R3`() {
|
||||
val line = "... AUTR2S3 903801690699 18109450 2025W1990100310137032 R2S3 "
|
||||
val parsed = ZnsReiterlicenseTokenizer.parseFromLine(line)
|
||||
assertNotNull(parsed)
|
||||
assertEquals(listOf("R3"), parsed.normalizedKuerzel)
|
||||
assertEquals(ReiterLizenzKlasseE.R3, parsed.primaryKlasse)
|
||||
assertEquals("R3", parsed.primaryKuerzel)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `detects R2D4 and caps to RD3`() {
|
||||
val line = "... AUTR2D4 105500130676 6820868 2025W1987090510112093 R2D4 "
|
||||
val parsed = ZnsReiterlicenseTokenizer.parseFromLine(line)
|
||||
assertNotNull(parsed)
|
||||
assertEquals(listOf("R2", "RD3"), parsed.normalizedKuerzel)
|
||||
assertEquals(ReiterLizenzKlasseE.RD3, parsed.primaryKlasse)
|
||||
assertEquals("RD3", parsed.primaryKuerzel)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `detects RD2`() {
|
||||
val line = "... AUTRD2 900308190664 3462613 2021W19650928 "
|
||||
val parsed = ZnsReiterlicenseTokenizer.parseFromLine(line)
|
||||
assertNotNull(parsed)
|
||||
assertEquals(listOf("RD2"), parsed.normalizedKuerzel)
|
||||
assertEquals(ReiterLizenzKlasseE.RD2, parsed.primaryKlasse)
|
||||
assertEquals("RD2", parsed.primaryKuerzel)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns null when no token present`() {
|
||||
val line = "some random line without token"
|
||||
val parsed = ZnsReiterlicenseTokenizer.parseFromLine(line)
|
||||
assertNull(parsed)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user