Refactor BundeslandRepository, implement V012 migration for Reiter master data changes, harmonize domain models, resolve repository interface inconsistencies, and enhance ZNS import with relational mapping improvements.

This commit is contained in:
Stefan Mogeritsch 2026-04-06 16:51:14 +02:00
parent 0ae9a1f1b8
commit 1b6f8e7c59
8 changed files with 75 additions and 28 deletions

View File

@ -57,6 +57,20 @@ Versionierung folgt [Semantic Versioning](https://semver.org/lang/de/).
--- ---
## [1.0.3-SNAPSHOT] — 2026-04-06
### Hinzugefügt
- **Masterdata:** Refactoring der Reiter-Stammdaten (LIZENZ01.DAT). Bundesland, Verein und Nation werden nun über Master-Tabellen referenziert.
- **Domain:** Validierungslogik für die 8-stellige OEPS-Mitgliedsnummer im `Reiter`-Modell implementiert.
- **Infrastructure:** Neue Tabellen `reiter_lizenz` (1:n Beziehung) und Migration `V012` zur Schemaanpassung und Datenbereinigung eingeführt.
- **ZNS-Import:** Automatisches Auflösen von Relationen (Verein nach Name, Bundesland nach Nummer, Nation nach ISO-Code) während des Reiter-Imports.
### Behoben
- **Build:** Kompilierfehler in `BundeslandExposedRepository.kt` behoben (inkonsistente Rückgabetypen im `BundeslandRepository`-Interface).
- **Infrastruktur:** Fehlendes Autowiring im `zns-import-service` durch explizite Bean-Definitionen für alle Repositories in `ZnsImportServiceApplication.kt` behoben.
- **Domain:** Kompilierfehler in `Bundesland.kt` behoben (uninitialisierte Eigenschaft `bundeslandId` entfernt).
- **Migration:** SQL-Syntaxfehler in `V012` behoben (korrekter Fremdschlüssel-Constraint für `reiter_lizenz` und Wiederherstellung des `DO $$`-Blocks).
## [1.0.2-SNAPSHOT] — 2026-04-06 ## [1.0.2-SNAPSHOT] — 2026-04-06
### Geändert ### Geändert

View File

@ -1,6 +1,7 @@
@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class) @file:OptIn(kotlin.uuid.ExperimentalUuidApi::class)
package at.mocode.masterdata.domain.repository package at.mocode.masterdata.domain.repository
import at.mocode.masterdata.domain.model.Bundesland
import at.mocode.masterdata.domain.model.BundeslandDefinition import at.mocode.masterdata.domain.model.BundeslandDefinition
import kotlin.uuid.Uuid import kotlin.uuid.Uuid
@ -12,7 +13,7 @@ interface BundeslandRepository {
/** /**
* ZNS-Spezifisch: Sucht ein Bundesland anhand seiner Nummer (01-09). * ZNS-Spezifisch: Sucht ein Bundesland anhand seiner Nummer (01-09).
*/ */
suspend fun findByNr(nr: Int): at.mocode.masterdata.domain.model.BundeslandDefinition? suspend fun findByNr(nr: Int): BundeslandDefinition?
/** /**
* Finds a federal state by its unique ID. * Finds a federal state by its unique ID.

View File

@ -181,7 +181,7 @@ class BundeslandRepositoryImpl : BundeslandRepository {
it[createdAt] = bundesland.createdAt it[createdAt] = bundesland.createdAt
it[updatedAt] = bundesland.updatedAt it[updatedAt] = bundesland.updatedAt
} }
} catch (e: Exception) { } catch (_: Exception) {
// Race-Condition → erneut Update // Race-Condition → erneut Update
if (bundesland.kuerzel != null) { if (bundesland.kuerzel != null) {
BundeslandTable.update({ (BundeslandTable.landId eq bundesland.landId) and (BundeslandTable.kuerzel eq bundesland.kuerzel) }) { BundeslandTable.update({ (BundeslandTable.landId eq bundesland.landId) and (BundeslandTable.kuerzel eq bundesland.kuerzel) }) {

View File

@ -2,10 +2,11 @@
package at.mocode.masterdata.infrastructure.persistence.reiter package at.mocode.masterdata.infrastructure.persistence.reiter
import at.mocode.masterdata.domain.model.Bundesland
import at.mocode.masterdata.domain.model.BundeslandDefinition import at.mocode.masterdata.domain.model.BundeslandDefinition
import at.mocode.masterdata.domain.repository.BundeslandRepository import at.mocode.masterdata.domain.repository.BundeslandRepository
import at.mocode.core.utils.database.DatabaseFactory import at.mocode.core.utils.database.DatabaseFactory
import at.mocode.masterdata.infrastructure.persistence.BundeslandTable
import kotlinx.datetime.Clock
import org.jetbrains.exposed.v1.core.ResultRow import org.jetbrains.exposed.v1.core.ResultRow
import org.jetbrains.exposed.v1.core.eq import org.jetbrains.exposed.v1.core.eq
import org.jetbrains.exposed.v1.jdbc.* import org.jetbrains.exposed.v1.jdbc.*
@ -16,14 +17,22 @@ import kotlin.uuid.Uuid
* Hinweis: Implementiert nur die für den ZNS-Import notwendigen Methoden. * Hinweis: Implementiert nur die für den ZNS-Import notwendigen Methoden.
*/ */
class BundeslandExposedRepository : BundeslandRepository { class BundeslandExposedRepository : BundeslandRepository {
private fun rowToDom(row: ResultRow) = Bundesland( private fun rowToDom(row: ResultRow) = BundeslandDefinition(
id = row[BundeslandTable.id], bundeslandId = row[BundeslandTable.id],
landId = row[BundeslandTable.landId],
bundeslandNr = row[BundeslandTable.bundeslandNr], bundeslandNr = row[BundeslandTable.bundeslandNr],
bezeichnung = row[BundeslandTable.bezeichnung], oepsCode = row[BundeslandTable.oepsCode],
wappenUrl = row[BundeslandTable.wappenUrl] iso3166_2_Code = row[BundeslandTable.iso3166_2_Code],
name = row[BundeslandTable.name],
kuerzel = row[BundeslandTable.kuerzel],
wappenUrl = row[BundeslandTable.wappenUrl],
istAktiv = row[BundeslandTable.istAktiv],
sortierReihenfolge = row[BundeslandTable.sortierReihenfolge],
createdAt = row[BundeslandTable.createdAt],
updatedAt = row[BundeslandTable.updatedAt]
) )
override suspend fun findByNr(nr: Int): Bundesland? = DatabaseFactory.dbQuery { override suspend fun findByNr(nr: Int): BundeslandDefinition? = DatabaseFactory.dbQuery {
BundeslandTable.selectAll().where { BundeslandTable.bundeslandNr eq nr } BundeslandTable.selectAll().where { BundeslandTable.bundeslandNr eq nr }
.map(::rowToDom) .map(::rowToDom)
.singleOrNull() .singleOrNull()

View File

@ -9,7 +9,6 @@ import at.mocode.masterdata.domain.repository.VereinRepository
import org.jetbrains.exposed.v1.core.ResultRow import org.jetbrains.exposed.v1.core.ResultRow
import org.jetbrains.exposed.v1.core.eq import org.jetbrains.exposed.v1.core.eq
import org.jetbrains.exposed.v1.core.like import org.jetbrains.exposed.v1.core.like
import org.jetbrains.exposed.v1.core.or
import org.jetbrains.exposed.v1.jdbc.* import org.jetbrains.exposed.v1.jdbc.*
import kotlin.uuid.ExperimentalUuidApi import kotlin.uuid.ExperimentalUuidApi
import kotlin.uuid.Uuid import kotlin.uuid.Uuid

View File

@ -36,16 +36,15 @@ ALTER TABLE reiter ADD COLUMN IF NOT EXISTS nation_id UUID;
-- Fremdschlüssel-Constraints -- Fremdschlüssel-Constraints
ALTER TABLE reiter ADD CONSTRAINT fk_reiter_verein FOREIGN KEY (verein_id) REFERENCES verein(verein_id); ALTER TABLE reiter ADD CONSTRAINT fk_reiter_verein FOREIGN KEY (verein_id) REFERENCES verein(verein_id);
ALTER TABLE reiter ADD CONSTRAINT fk_reiter_bundesland FOREIGN KEY (bundesland_id) REFERENCES bundesland(id); ALTER TABLE reiter ADD CONSTRAINT fk_reiter_bundesland FOREIGN KEY (bundesland_id) REFERENCES bundesland(id);
ALTER TABLE reiter ADD CONSTRAINT fk_reiter_nation FOREIGN KEY (nation_id) REFERENCES land(id); ALTER TABLE reiter ADD CONSTRAINT fk_reiter_nation FOREIGN KEY (nation_id) REFERENCES land(land_id);
ALTER TABLE reiter ADD CONSTRAINT fk_reiter_lizenz_reiter FOREIGN KEY (reiter_id) REFERENCES reiter(reiter_id) ON DELETE CASCADE; ALTER TABLE reiter_lizenz ADD CONSTRAINT fk_reiter_lizenz_reiter FOREIGN KEY (reiter_id) REFERENCES reiter(reiter_id) ON DELETE CASCADE;
-- 3. Daten gemäß OEPS-Spezifikation korrigieren (für Österreich) -- 3. Daten gemäß OEPS-Spezifikation korrigieren (für Österreich)
DO $$ DO $$
DECLARE DECLARE
austria_id UUID; austria_id UUID;
BEGIN BEGIN
SELECT id INTO austria_id FROM land WHERE iso_alpha2_code = 'AT'; SELECT land_id INTO austria_id FROM land WHERE iso_alpha2_code = 'AT';
IF austria_id IS NOT NULL THEN IF austria_id IS NOT NULL THEN
-- Bestehende Einträge löschen oder aktualisieren -- Bestehende Einträge löschen oder aktualisieren

View File

@ -1,32 +1,57 @@
package at.mocode.zns.import.service package at.mocode.zns.import.service
import at.mocode.masterdata.domain.repository.FunktionaerRepository import at.mocode.masterdata.domain.repository.*
import at.mocode.masterdata.domain.repository.HorseRepository import at.mocode.masterdata.infrastructure.persistence.*
import at.mocode.masterdata.domain.repository.ReiterRepository import at.mocode.masterdata.infrastructure.persistence.funktionaer.FunktionaerExposedRepository
import at.mocode.masterdata.domain.repository.VereinRepository import at.mocode.masterdata.infrastructure.persistence.pferd.HorseExposedRepository
import at.mocode.masterdata.infrastructure.persistence.reiter.ReiterExposedRepository
import at.mocode.masterdata.infrastructure.persistence.verein.VereinExposedRepository
import at.mocode.zns.importer.ZnsImportService import at.mocode.zns.importer.ZnsImportService
import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication import org.springframework.boot.runApplication
import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.ComponentScan
@SpringBootApplication @SpringBootApplication
@ComponentScan(
basePackages = [
"at.mocode.zns.import.service",
"at.mocode.masterdata.infrastructure"
]
)
class ZnsImportServiceApplication { class ZnsImportServiceApplication {
// Manuelle Bean-Definitionen für die Repositories, da diese in der Infrastruktur
// keine @Repository-Annotationen haben und wir MasterdataConfiguration nicht importieren wollen.
@Bean
fun landRepository(): LandRepository = LandRepositoryImpl()
@Bean
fun bundeslandRepository(): BundeslandRepository = BundeslandRepositoryImpl()
@Bean
fun vereinRepository(): VereinRepository = VereinExposedRepository()
@Bean
fun reiterRepository(): ReiterRepository = ReiterExposedRepository()
@Bean
fun horseRepository(): HorseRepository = HorseExposedRepository()
@Bean
fun funktionaerRepository(): FunktionaerRepository = FunktionaerExposedRepository()
@Bean @Bean
fun znsImportService( fun znsImportService(
vereinRepository: VereinRepository, vereinRepository: VereinRepository,
reiterRepository: ReiterRepository, reiterRepository: ReiterRepository,
horseRepository: HorseRepository, horseRepository: HorseRepository,
funktionaerRepository: FunktionaerRepository funktionaerRepository: FunktionaerRepository,
landRepository: LandRepository,
bundeslandRepository: BundeslandRepository
): ZnsImportService { ): ZnsImportService {
return ZnsImportService(vereinRepository, reiterRepository, horseRepository, funktionaerRepository) return ZnsImportService(
vereinRepository,
reiterRepository,
horseRepository,
funktionaerRepository,
landRepository,
bundeslandRepository
)
} }
} }

View File

@ -125,8 +125,8 @@ und über definierte Schnittstellen kommunizieren.
#### 👷 Agent: Backend Developer #### 👷 Agent: Backend Developer
* [x] **ZNS-Importer:** Support für Richter-Import (RICHT01.DAT) vervollständigt. * [x] **ZNS-Importer:** Support für Richter-Import (RICHT01.DAT) und Reiter-Refactoring (LIZENZ01.DAT) vervollständigt.
* [x] **Masterdata:** Qualifikations-System auf professionelle Master-Daten-Referenzierung (`QualifikationMasterTable`) umgestellt. * [x] **Masterdata:** Qualifikations-System und Personen-Referenzen (Vereine, Bundesländer, Nationen) auf professionelle Master-Daten umgestellt.
* [x] **Database:** Initialisierung der Funktionärs-Tabellen stabilisiert (PSQLException Fix). * [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.