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
|
### 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:** 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.
|
- **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.
|
- **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.core.utils.database.DatabaseFactory
|
||||||
import at.mocode.masterdata.domain.model.Funktionaer
|
import at.mocode.masterdata.domain.model.Funktionaer
|
||||||
import at.mocode.masterdata.domain.repository.FunktionaerRepository
|
import at.mocode.masterdata.domain.repository.FunktionaerRepository
|
||||||
import org.jetbrains.exposed.v1.core.ResultRow
|
import at.mocode.masterdata.infrastructure.persistence.funktionaer.*
|
||||||
import org.jetbrains.exposed.v1.core.and
|
import org.jetbrains.exposed.v1.core.*
|
||||||
import org.jetbrains.exposed.v1.core.eq
|
import org.jetbrains.exposed.v1.jdbc.*
|
||||||
import org.jetbrains.exposed.v1.core.inList
|
import org.slf4j.LoggerFactory
|
||||||
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 kotlin.uuid.Uuid
|
import kotlin.uuid.Uuid
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Exposed-basierte Implementierung des Funktionaer-Repositorys.
|
* Exposed-basierte Implementierung des Funktionaer-Repositorys.
|
||||||
*/
|
*/
|
||||||
class FunktionaerExposedRepository : FunktionaerRepository {
|
class FunktionaerExposedRepository : FunktionaerRepository {
|
||||||
|
private val log = LoggerFactory.getLogger(FunktionaerExposedRepository::class.java)
|
||||||
|
|
||||||
private fun rowToDomFunktionaer(row: ResultRow, qualifikationen: List<String> = emptyList()): Funktionaer {
|
private fun rowToDomFunktionaer(row: ResultRow, qualifikationen: List<String> = emptyList()): Funktionaer {
|
||||||
return Funktionaer(
|
return Funktionaer(
|
||||||
|
|
@ -37,9 +34,9 @@ class FunktionaerExposedRepository : FunktionaerRepository {
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun findById(id: Uuid): Funktionaer? = DatabaseFactory.dbQuery {
|
override suspend fun findById(id: Uuid): Funktionaer? = DatabaseFactory.dbQuery {
|
||||||
val qualifikationen = FunktionaerQualifikationTable
|
val qualifikationen = (FunktionaerQualifikationTable innerJoin QualifikationMasterTable)
|
||||||
.selectAll().where { FunktionaerQualifikationTable.funktionaerId eq id }
|
.selectAll().where { FunktionaerQualifikationTable.funktionaerId eq id }
|
||||||
.map { it[FunktionaerQualifikationTable.qualifikation] }
|
.map { it[QualifikationMasterTable.code] }
|
||||||
|
|
||||||
FunktionaerTable.selectAll().where { FunktionaerTable.id eq id }
|
FunktionaerTable.selectAll().where { FunktionaerTable.id eq id }
|
||||||
.map { rowToDomFunktionaer(it, qualifikationen) }
|
.map { rowToDomFunktionaer(it, qualifikationen) }
|
||||||
|
|
@ -51,9 +48,9 @@ class FunktionaerExposedRepository : FunktionaerRepository {
|
||||||
.where { (FunktionaerTable.satzId eq satzID) and (FunktionaerTable.satzNummer eq satzNummer) }
|
.where { (FunktionaerTable.satzId eq satzID) and (FunktionaerTable.satzNummer eq satzNummer) }
|
||||||
.singleOrNull() ?: return@dbQuery null
|
.singleOrNull() ?: return@dbQuery null
|
||||||
|
|
||||||
val qualifikationen = FunktionaerQualifikationTable
|
val qualifikationen = (FunktionaerQualifikationTable innerJoin QualifikationMasterTable)
|
||||||
.selectAll().where { FunktionaerQualifikationTable.funktionaerId eq row[FunktionaerTable.id] }
|
.selectAll().where { FunktionaerQualifikationTable.funktionaerId eq row[FunktionaerTable.id] }
|
||||||
.map { it[FunktionaerQualifikationTable.qualifikation] }
|
.map { it[QualifikationMasterTable.code] }
|
||||||
|
|
||||||
rowToDomFunktionaer(row, qualifikationen)
|
rowToDomFunktionaer(row, qualifikationen)
|
||||||
}
|
}
|
||||||
|
|
@ -64,9 +61,9 @@ class FunktionaerExposedRepository : FunktionaerRepository {
|
||||||
.toList()
|
.toList()
|
||||||
|
|
||||||
val ids = funktionaere.map { it[FunktionaerTable.id] }
|
val ids = funktionaere.map { it[FunktionaerTable.id] }
|
||||||
val qualisMap = FunktionaerQualifikationTable
|
val qualisMap = (FunktionaerQualifikationTable innerJoin QualifikationMasterTable)
|
||||||
.selectAll().where { FunktionaerQualifikationTable.funktionaerId inList ids }
|
.selectAll().where { FunktionaerQualifikationTable.funktionaerId inList ids }
|
||||||
.groupBy({ it[FunktionaerQualifikationTable.funktionaerId] }) { it[FunktionaerQualifikationTable.qualifikation] }
|
.groupBy({ it[FunktionaerQualifikationTable.funktionaerId] }) { it[QualifikationMasterTable.code] }
|
||||||
|
|
||||||
funktionaere.map { row ->
|
funktionaere.map { row ->
|
||||||
rowToDomFunktionaer(row, qualisMap[row[FunktionaerTable.id]] ?: emptyList())
|
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 }
|
FunktionaerQualifikationTable.deleteWhere { funktionaerId eq funktionaer.funktionaerId }
|
||||||
funktionaer.qualifikationen.forEach { 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 {
|
FunktionaerQualifikationTable.insert {
|
||||||
it[funktionaerId] = funktionaer.funktionaerId
|
it[funktionaerId] = funktionaer.funktionaerId
|
||||||
it[qualifikation] = quali
|
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") {
|
object FunktionaerQualifikationTable : Table("funktionaer_qualifikation") {
|
||||||
val funktionaerId = uuid("funktionaer_id").references(FunktionaerTable.id)
|
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.*
|
||||||
|
import at.mocode.masterdata.infrastructure.persistence.funktionaer.FunktionaerQualifikationTable
|
||||||
import at.mocode.masterdata.infrastructure.persistence.funktionaer.FunktionaerTable
|
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.pferd.HorseTable
|
||||||
import at.mocode.masterdata.infrastructure.persistence.reiter.ReiterTable
|
import at.mocode.masterdata.infrastructure.persistence.reiter.ReiterTable
|
||||||
import at.mocode.masterdata.infrastructure.persistence.verein.VereinTable
|
import at.mocode.masterdata.infrastructure.persistence.verein.VereinTable
|
||||||
|
|
@ -49,6 +51,8 @@ class MasterdataDatabaseConfiguration(
|
||||||
HorseTable,
|
HorseTable,
|
||||||
VereinTable,
|
VereinTable,
|
||||||
FunktionaerTable,
|
FunktionaerTable,
|
||||||
|
QualifikationMasterTable,
|
||||||
|
FunktionaerQualifikationTable,
|
||||||
TurnierklasseTable,
|
TurnierklasseTable,
|
||||||
LicenseTable,
|
LicenseTable,
|
||||||
RichtverfahrenTable,
|
RichtverfahrenTable,
|
||||||
|
|
@ -95,6 +99,8 @@ class MasterdataTestDatabaseConfiguration {
|
||||||
HorseTable,
|
HorseTable,
|
||||||
VereinTable,
|
VereinTable,
|
||||||
FunktionaerTable,
|
FunktionaerTable,
|
||||||
|
QualifikationMasterTable,
|
||||||
|
FunktionaerQualifikationTable,
|
||||||
TurnierklasseTable,
|
TurnierklasseTable,
|
||||||
LicenseTable,
|
LicenseTable,
|
||||||
RichtverfahrenTable,
|
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
|
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.FunktionaerTable
|
||||||
|
import at.mocode.masterdata.infrastructure.persistence.funktionaer.QualifikationMasterTable
|
||||||
import at.mocode.masterdata.infrastructure.persistence.pferd.HorseTable
|
import at.mocode.masterdata.infrastructure.persistence.pferd.HorseTable
|
||||||
import at.mocode.masterdata.infrastructure.persistence.reiter.ReiterTable
|
import at.mocode.masterdata.infrastructure.persistence.reiter.ReiterTable
|
||||||
import at.mocode.masterdata.infrastructure.persistence.verein.VereinTable
|
import at.mocode.masterdata.infrastructure.persistence.verein.VereinTable
|
||||||
|
|
@ -28,7 +30,12 @@ class ZnsImportDatabaseConfiguration(
|
||||||
Database.connect(jdbcUrl, user = username, password = password)
|
Database.connect(jdbcUrl, user = username, password = password)
|
||||||
transaction {
|
transaction {
|
||||||
val statements = MigrationUtils.statementsRequiredForDatabaseMigration(
|
val statements = MigrationUtils.statementsRequiredForDatabaseMigration(
|
||||||
VereinTable, ReiterTable, HorseTable, FunktionaerTable
|
VereinTable,
|
||||||
|
ReiterTable,
|
||||||
|
HorseTable,
|
||||||
|
FunktionaerTable,
|
||||||
|
QualifikationMasterTable,
|
||||||
|
FunktionaerQualifikationTable
|
||||||
)
|
)
|
||||||
statements.forEach { exec(it) }
|
statements.forEach { exec(it) }
|
||||||
log.info("Datenbank-Schema erfolgreich initialisiert ({} Statements)", statements.size)
|
log.info("Datenbank-Schema erfolgreich initialisiert ({} Statements)", statements.size)
|
||||||
|
|
|
||||||
|
|
@ -123,6 +123,9 @@ und über definierte Schnittstellen kommunizieren.
|
||||||
|
|
||||||
#### 👷 Agent: Backend Developer
|
#### 👷 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] **`actor-context`:** Domain-Modelle für `Pferd`, `Funktionaer`, `Verein` implementiert.
|
||||||
* [x] **`registration-context`:** `DomBewerb`, `DomAbteilung`, `DomStartliste` implementiert.
|
* [x] **`registration-context`:** `DomBewerb`, `DomAbteilung`, `DomStartliste` implementiert.
|
||||||
* [x] **`event-management-context`:** `DomVeranstaltung`, `DomTurnier`, `DomAusschreibung` implementiert.
|
* [x] **`event-management-context`:** `DomVeranstaltung`, `DomTurnier`, `DomAusschreibung` implementiert.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user