Document and implement CSN-C-NEU mandatory division rules: Add TURNIER_KLASSEN.md detailing forced divisions by license categories; establish uniform labels and keys (e.g., LZF_ONLY and R1_PLUS); draft optional youth division rules. Extend validation in Validierungsregeln.md. Implement FEI-ID resolver and mapping endpoint in Masterdata service.

This commit is contained in:
2026-04-02 18:36:59 +02:00
parent bbe5b1a357
commit 4ae701d969
8 changed files with 540 additions and 25 deletions
@@ -0,0 +1,29 @@
package at.mocode.masterdata.domain.service
/**
* Auflösung von FEI-IDs: akzeptiert numerische IDs (PassThrough) und LegacyReferenzcodes
* und liefert wenn bekannt die zugehörige numerische FEIID zurück.
*/
interface FeiIdResolver {
/**
* Löst eine eingegebene FEIKennung auf.
*
* @param input Benutzer-/Importeingabe (numerisch 78 Stellen oder LegacyCode 3Z+2B+2Z)
* @return [FeiIdResolution] mit normalisierter numerischer ID, oder null wenn unbekannt/ungültig
*/
fun resolve(input: String): FeiIdResolution?
}
/**
* Ergebnis der FEIID Auflösung.
*
* @property normalizedNumericId Numerische FEIID (78 Ziffern) als String
* @property sourceFormat "NUMERIC" | "LEGACY_CODE"
* @property wasMapped true, wenn aus Legacy nach numerisch gemappt wurde
*/
data class FeiIdResolution(
val normalizedNumericId: String,
val sourceFormat: String,
val wasMapped: Boolean
)
@@ -31,6 +31,7 @@ dependencies {
implementation(libs.spring.boot.starter.web)
implementation(libs.spring.boot.starter.validation)
implementation(libs.spring.boot.starter.actuator)
implementation(libs.jackson.module.kotlin)
//implementation(libs.springdoc.openapi.starter.webmvc.ui)
// Ktor Server (für SCS: eigener kleiner HTTP-Server pro Kontext)
@@ -0,0 +1,50 @@
package at.mocode.masterdata.service.fei
import at.mocode.masterdata.domain.service.FeiIdResolution
import at.mocode.masterdata.domain.service.FeiIdResolver
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
@RestController
@RequestMapping("/api/fei")
class FeiIdController(
private val resolver: FeiIdResolver
) {
data class ResolveResponse(
val input: String,
val normalizedNumericId: String?,
val sourceFormat: String?,
val wasMapped: Boolean,
val found: Boolean
)
@GetMapping("/resolve/{id}")
fun resolve(@PathVariable("id") id: String): ResponseEntity<ResolveResponse> {
val res: FeiIdResolution? = resolver.resolve(id)
return if (res != null) {
ResponseEntity.ok(
ResolveResponse(
input = id,
normalizedNumericId = res.normalizedNumericId,
sourceFormat = res.sourceFormat,
wasMapped = res.wasMapped,
found = true
)
)
} else {
ResponseEntity.status(404).body(
ResolveResponse(
input = id,
normalizedNumericId = null,
sourceFormat = null,
wasMapped = false,
found = false
)
)
}
}
}
@@ -0,0 +1,43 @@
package at.mocode.masterdata.service.fei
import at.mocode.masterdata.domain.service.FeiIdResolution
import at.mocode.masterdata.domain.service.FeiIdResolver
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
import org.springframework.core.io.ClassPathResource
import org.springframework.stereotype.Component
/**
* Einfache InMemory Implementierung des [FeiIdResolver],
* lädt ein JSONMapping aus Ressourcen: `data/fei-id-mapping.json`.
*/
@Component
class FeiIdResolverImpl : FeiIdResolver {
private val legacyToNumeric: Map<String, String> by lazy {
val res = ClassPathResource("data/fei-id-mapping.json")
if (!res.exists()) return@lazy emptyMap<String, String>()
val mapper = jacksonObjectMapper()
res.inputStream.use { input ->
mapper.readValue<Map<String, String>>(input)
.mapKeys { it.key.trim().uppercase() }
}
}
private val numericRegex = Regex("^[0-9]{7,8}$")
private val legacyRegex = Regex("^[0-9]{3}[A-Z]{2}[0-9]{2}$")
override fun resolve(input: String): FeiIdResolution? {
val s = input.trim().uppercase()
// Numerisch: PassThrough
if (numericRegex.matches(s)) {
return FeiIdResolution(normalizedNumericId = s, sourceFormat = "NUMERIC", wasMapped = false)
}
// Legacy: lookup
if (legacyRegex.matches(s)) {
val mapped = legacyToNumeric[s] ?: return null
return FeiIdResolution(normalizedNumericId = mapped, sourceFormat = "LEGACY_CODE", wasMapped = true)
}
return null
}
}
@@ -0,0 +1,6 @@
{
"104FE22": "10011469",
"103RW04": "10019075",
"102UB51": "10028445",
"104UD89": "10011111"
}