Integrate qualification master data system (QualifikationMasterTable) for functionaries, refactor mapping logic in repositories, enhance database initialization for ZNS and Masterdata services, and add a seeder for ÖTO/FEI qualification data. Fix PSQLException during ZNS imports.
This commit is contained in:
parent
933ef9cd6c
commit
c35869f8ee
|
|
@ -36,6 +36,8 @@ Versionierung folgt [Semantic Versioning](https://semver.org/lang/de/).
|
|||
|
||||
### Behoben
|
||||
|
||||
- **Masterdata:** Qualifikations-Management für Funktionäre (Richter/Parcoursbauer) professionalisiert: Umstellung von unstrukturiertem Text auf offizielle ÖTO/FEI Master-Daten Referenzen (`QualifikationMasterTable`).
|
||||
- **Masterdata:** Fehlende Tabelle `funktionaer_qualifikation` in der Initialisierung beider Services (`masterdata` und `zns-import`) ergänzt, um `PSQLException` während des ZNS-Imports zu beheben.
|
||||
- **Infrastructure:** Start-Probleme des `masterdata-service` endgültig behoben: Port-Konflikt zwischen Spring Boot (Management/Actuator) und dem Gateway (8081) durch Umzug auf Port 8086 (gemäß Infrastruktur-Vorgaben) gelöst.
|
||||
- **Infrastructure:** Port-Konflikt im `masterdata-service` durch Trennung der Bind-Adressen (Spring: 127.0.0.1, Ktor: 0.0.0.0) und Bereinigung verwaister Prozesse stabilisiert.
|
||||
- **Core:** Veraltete `ZnsLegacyParsersTest.kt` entfernt; alle Tests sind nun in `ZnsParserTest.kt` konsolidiert.
|
||||
|
|
|
|||
|
|
@ -5,20 +5,17 @@ import at.mocode.core.domain.model.DatenQuelleE
|
|||
import at.mocode.core.utils.database.DatabaseFactory
|
||||
import at.mocode.masterdata.domain.model.Funktionaer
|
||||
import at.mocode.masterdata.domain.repository.FunktionaerRepository
|
||||
import org.jetbrains.exposed.v1.core.ResultRow
|
||||
import org.jetbrains.exposed.v1.core.and
|
||||
import org.jetbrains.exposed.v1.core.eq
|
||||
import org.jetbrains.exposed.v1.core.inList
|
||||
import org.jetbrains.exposed.v1.jdbc.deleteWhere
|
||||
import org.jetbrains.exposed.v1.jdbc.insert
|
||||
import org.jetbrains.exposed.v1.jdbc.selectAll
|
||||
import org.jetbrains.exposed.v1.jdbc.update
|
||||
import at.mocode.masterdata.infrastructure.persistence.funktionaer.*
|
||||
import org.jetbrains.exposed.v1.core.*
|
||||
import org.jetbrains.exposed.v1.jdbc.*
|
||||
import org.slf4j.LoggerFactory
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
/**
|
||||
* Exposed-basierte Implementierung des Funktionaer-Repositorys.
|
||||
*/
|
||||
class FunktionaerExposedRepository : FunktionaerRepository {
|
||||
private val log = LoggerFactory.getLogger(FunktionaerExposedRepository::class.java)
|
||||
|
||||
private fun rowToDomFunktionaer(row: ResultRow, qualifikationen: List<String> = emptyList()): Funktionaer {
|
||||
return Funktionaer(
|
||||
|
|
@ -37,9 +34,9 @@ class FunktionaerExposedRepository : FunktionaerRepository {
|
|||
}
|
||||
|
||||
override suspend fun findById(id: Uuid): Funktionaer? = DatabaseFactory.dbQuery {
|
||||
val qualifikationen = FunktionaerQualifikationTable
|
||||
val qualifikationen = (FunktionaerQualifikationTable innerJoin QualifikationMasterTable)
|
||||
.selectAll().where { FunktionaerQualifikationTable.funktionaerId eq id }
|
||||
.map { it[FunktionaerQualifikationTable.qualifikation] }
|
||||
.map { it[QualifikationMasterTable.code] }
|
||||
|
||||
FunktionaerTable.selectAll().where { FunktionaerTable.id eq id }
|
||||
.map { rowToDomFunktionaer(it, qualifikationen) }
|
||||
|
|
@ -51,9 +48,9 @@ class FunktionaerExposedRepository : FunktionaerRepository {
|
|||
.where { (FunktionaerTable.satzId eq satzID) and (FunktionaerTable.satzNummer eq satzNummer) }
|
||||
.singleOrNull() ?: return@dbQuery null
|
||||
|
||||
val qualifikationen = FunktionaerQualifikationTable
|
||||
val qualifikationen = (FunktionaerQualifikationTable innerJoin QualifikationMasterTable)
|
||||
.selectAll().where { FunktionaerQualifikationTable.funktionaerId eq row[FunktionaerTable.id] }
|
||||
.map { it[FunktionaerQualifikationTable.qualifikation] }
|
||||
.map { it[QualifikationMasterTable.code] }
|
||||
|
||||
rowToDomFunktionaer(row, qualifikationen)
|
||||
}
|
||||
|
|
@ -64,9 +61,9 @@ class FunktionaerExposedRepository : FunktionaerRepository {
|
|||
.toList()
|
||||
|
||||
val ids = funktionaere.map { it[FunktionaerTable.id] }
|
||||
val qualisMap = FunktionaerQualifikationTable
|
||||
val qualisMap = (FunktionaerQualifikationTable innerJoin QualifikationMasterTable)
|
||||
.selectAll().where { FunktionaerQualifikationTable.funktionaerId inList ids }
|
||||
.groupBy({ it[FunktionaerQualifikationTable.funktionaerId] }) { it[FunktionaerQualifikationTable.qualifikation] }
|
||||
.groupBy({ it[FunktionaerQualifikationTable.funktionaerId] }) { it[QualifikationMasterTable.code] }
|
||||
|
||||
funktionaere.map { row ->
|
||||
rowToDomFunktionaer(row, qualisMap[row[FunktionaerTable.id]] ?: emptyList())
|
||||
|
|
@ -101,12 +98,25 @@ class FunktionaerExposedRepository : FunktionaerRepository {
|
|||
}
|
||||
}
|
||||
|
||||
// Qualifikationen synchronisieren
|
||||
// Qualifikationen synchronisieren (über Master-Daten Auflösung)
|
||||
FunktionaerQualifikationTable.deleteWhere { funktionaerId eq funktionaer.funktionaerId }
|
||||
funktionaer.qualifikationen.forEach { quali ->
|
||||
FunktionaerQualifikationTable.insert {
|
||||
it[funktionaerId] = funktionaer.funktionaerId
|
||||
it[qualifikation] = quali
|
||||
|
||||
val typ = if (funktionaer.istRichter()) "RICHTER" else "PARCOURSBAUER"
|
||||
|
||||
funktionaer.qualifikationen.forEach { code ->
|
||||
val masterId = QualifikationMasterTable
|
||||
.selectAll().where { (QualifikationMasterTable.code eq code) and (QualifikationMasterTable.typ eq typ) }
|
||||
.map { it[QualifikationMasterTable.id] }
|
||||
.singleOrNull()
|
||||
|
||||
if (masterId != null) {
|
||||
FunktionaerQualifikationTable.insert {
|
||||
it[funktionaerId] = funktionaer.funktionaerId
|
||||
it[qualifikationId] = masterId
|
||||
}
|
||||
} else {
|
||||
log.warn("Qualifikation '{}' für Typ '{}' nicht in Master-Daten gefunden. Überspringe Zuordnung für Funktionär {}.",
|
||||
code, typ, funktionaer.name)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -53,11 +53,27 @@ object FunktionaerTable : Table("funktionaer") {
|
|||
}
|
||||
|
||||
/**
|
||||
* Exposed-Tabellendefinition für die Qualifikationen eines Funktionärs.
|
||||
* Exposed-Tabellendefinition für die Qualifikation-Master-Daten.
|
||||
*/
|
||||
object QualifikationMasterTable : Table("qualifikation_master") {
|
||||
val id = uuid("qualifikation_id")
|
||||
val code = varchar("code", 10) // z.B. "D", "S", "SPF", "P1"
|
||||
val bezeichnung = varchar("bezeichnung", 100) // z.B. "Dressur", "Springpferde"
|
||||
val typ = varchar("typ", 20) // "RICHTER" oder "PARCOURSBAUER"
|
||||
|
||||
override val primaryKey = PrimaryKey(id)
|
||||
|
||||
init {
|
||||
index("idx_qualifikation_code_typ", isUnique = true, code, typ)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exposed-Tabellendefinition für die Zuordnung von Qualifikationen zu Funktionären (Join-Tabelle).
|
||||
*/
|
||||
object FunktionaerQualifikationTable : Table("funktionaer_qualifikation") {
|
||||
val funktionaerId = uuid("funktionaer_id").references(FunktionaerTable.id)
|
||||
val qualifikation = varchar("qualifikation", 20)
|
||||
val qualifikationId = uuid("qualifikation_id").references(QualifikationMasterTable.id)
|
||||
|
||||
override val primaryKey = PrimaryKey(funktionaerId, qualifikation)
|
||||
override val primaryKey = PrimaryKey(funktionaerId, qualifikationId)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@ package at.mocode.masterdata.service.config
|
|||
|
||||
|
||||
import at.mocode.masterdata.infrastructure.persistence.*
|
||||
import at.mocode.masterdata.infrastructure.persistence.funktionaer.FunktionaerQualifikationTable
|
||||
import at.mocode.masterdata.infrastructure.persistence.funktionaer.FunktionaerTable
|
||||
import at.mocode.masterdata.infrastructure.persistence.funktionaer.QualifikationMasterTable
|
||||
import at.mocode.masterdata.infrastructure.persistence.pferd.HorseTable
|
||||
import at.mocode.masterdata.infrastructure.persistence.reiter.ReiterTable
|
||||
import at.mocode.masterdata.infrastructure.persistence.verein.VereinTable
|
||||
|
|
@ -49,6 +51,8 @@ class MasterdataDatabaseConfiguration(
|
|||
HorseTable,
|
||||
VereinTable,
|
||||
FunktionaerTable,
|
||||
QualifikationMasterTable,
|
||||
FunktionaerQualifikationTable,
|
||||
TurnierklasseTable,
|
||||
LicenseTable,
|
||||
RichtverfahrenTable,
|
||||
|
|
@ -95,6 +99,8 @@ class MasterdataTestDatabaseConfiguration {
|
|||
HorseTable,
|
||||
VereinTable,
|
||||
FunktionaerTable,
|
||||
QualifikationMasterTable,
|
||||
FunktionaerQualifikationTable,
|
||||
TurnierklasseTable,
|
||||
LicenseTable,
|
||||
RichtverfahrenTable,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,86 @@
|
|||
@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class)
|
||||
package at.mocode.masterdata.service.config
|
||||
|
||||
import at.mocode.masterdata.infrastructure.persistence.funktionaer.QualifikationMasterTable
|
||||
import jakarta.annotation.PostConstruct
|
||||
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
|
||||
import org.jetbrains.exposed.v1.core.*
|
||||
import org.jetbrains.exposed.v1.jdbc.*
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.context.annotation.DependsOn
|
||||
import org.springframework.context.annotation.Profile
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
/**
|
||||
* Seeder für die offiziellen ÖTO/FEI Qualifikations-Kürzel.
|
||||
* Befüllt die QualifikationMasterTable mit Standard-Werten.
|
||||
*/
|
||||
@Configuration
|
||||
@Profile("!test")
|
||||
@DependsOn("masterdataDatabaseConfiguration")
|
||||
class QualifikationMasterSeeder {
|
||||
private val log = LoggerFactory.getLogger(QualifikationMasterSeeder::class.java)
|
||||
|
||||
@PostConstruct
|
||||
fun seed() {
|
||||
log.info("Starte Seeding der Qualifikations-Master-Daten (ÖTO/FEI)...")
|
||||
transaction {
|
||||
seedRichter()
|
||||
seedParcoursbauer()
|
||||
}
|
||||
log.info("Seeding der Qualifikations-Master-Daten abgeschlossen.")
|
||||
}
|
||||
|
||||
private fun seedRichter() {
|
||||
val richterQualis = listOf(
|
||||
"D" to "Dressur",
|
||||
"S" to "Springen",
|
||||
"DPF" to "Dressurpferde",
|
||||
"SPF" to "Springpferde",
|
||||
"G" to "Gelände",
|
||||
"STW" to "Steward",
|
||||
"DM" to "Dressur Master",
|
||||
"SM" to "Springen Master",
|
||||
"GA" to "Grundausbildung",
|
||||
"G3" to "Gruppe 3",
|
||||
"G2" to "Gruppe 2",
|
||||
"G1" to "Gruppe 1"
|
||||
)
|
||||
|
||||
richterQualis.forEach { (code, bezeichnung) ->
|
||||
upsertQuali(code, bezeichnung, "RICHTER")
|
||||
}
|
||||
}
|
||||
|
||||
private fun seedParcoursbauer() {
|
||||
val pbQualis = listOf(
|
||||
"P1" to "Einsteiger",
|
||||
"P2" to "Fortgeschritten",
|
||||
"P3" to "National",
|
||||
"P4" to "Grand Prix",
|
||||
"SP" to "Springen",
|
||||
"VS" to "Vielseitigkeit"
|
||||
)
|
||||
|
||||
pbQualis.forEach { (code, bezeichnung) ->
|
||||
upsertQuali(code, bezeichnung, "PARCOURSBAUER")
|
||||
}
|
||||
}
|
||||
|
||||
private fun upsertQuali(code: String, bezeichnung: String, typ: String) {
|
||||
val exists = QualifikationMasterTable.selectAll()
|
||||
.where { (QualifikationMasterTable.code eq code) and (QualifikationMasterTable.typ eq typ) }
|
||||
.any()
|
||||
|
||||
if (!exists) {
|
||||
QualifikationMasterTable.insert {
|
||||
it[id] = Uuid.random()
|
||||
it[QualifikationMasterTable.code] = code
|
||||
it[QualifikationMasterTable.bezeichnung] = bezeichnung
|
||||
it[QualifikationMasterTable.typ] = typ
|
||||
}
|
||||
log.debug("QualifikationMaster '{}' ({}) angelegt.", code, typ)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
package at.mocode.zns.import.service.config
|
||||
|
||||
import at.mocode.masterdata.infrastructure.persistence.funktionaer.FunktionaerQualifikationTable
|
||||
import at.mocode.masterdata.infrastructure.persistence.funktionaer.FunktionaerTable
|
||||
import at.mocode.masterdata.infrastructure.persistence.funktionaer.QualifikationMasterTable
|
||||
import at.mocode.masterdata.infrastructure.persistence.pferd.HorseTable
|
||||
import at.mocode.masterdata.infrastructure.persistence.reiter.ReiterTable
|
||||
import at.mocode.masterdata.infrastructure.persistence.verein.VereinTable
|
||||
|
|
@ -28,7 +30,12 @@ class ZnsImportDatabaseConfiguration(
|
|||
Database.connect(jdbcUrl, user = username, password = password)
|
||||
transaction {
|
||||
val statements = MigrationUtils.statementsRequiredForDatabaseMigration(
|
||||
VereinTable, ReiterTable, HorseTable, FunktionaerTable
|
||||
VereinTable,
|
||||
ReiterTable,
|
||||
HorseTable,
|
||||
FunktionaerTable,
|
||||
QualifikationMasterTable,
|
||||
FunktionaerQualifikationTable
|
||||
)
|
||||
statements.forEach { exec(it) }
|
||||
log.info("Datenbank-Schema erfolgreich initialisiert ({} Statements)", statements.size)
|
||||
|
|
|
|||
|
|
@ -123,6 +123,9 @@ und über definierte Schnittstellen kommunizieren.
|
|||
|
||||
#### 👷 Agent: Backend Developer
|
||||
|
||||
* [x] **ZNS-Importer:** Support für Richter-Import (RICHT01.DAT) vervollständigt.
|
||||
* [x] **Masterdata:** Qualifikations-System auf professionelle Master-Daten-Referenzierung (`QualifikationMasterTable`) umgestellt.
|
||||
* [x] **Database:** Initialisierung der Funktionärs-Tabellen stabilisiert (PSQLException Fix).
|
||||
* [x] **`actor-context`:** Domain-Modelle für `Pferd`, `Funktionaer`, `Verein` implementiert.
|
||||
* [x] **`registration-context`:** `DomBewerb`, `DomAbteilung`, `DomStartliste` implementiert.
|
||||
* [x] **`event-management-context`:** `DomVeranstaltung`, `DomTurnier`, `DomAusschreibung` implementiert.
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user