Refactor master data infrastructure to streamline Reiter and Bundesland relationships, add V012 migration, harmonize domain models, implement repository methods for enhanced ZNS import logic, and update associated tests.

This commit is contained in:
2026-04-06 16:39:06 +02:00
parent 1a6f2ea7ad
commit 0ae9a1f1b8
15 changed files with 284 additions and 76 deletions
@@ -6,6 +6,8 @@ import at.mocode.masterdata.domain.repository.VereinRepository
import at.mocode.masterdata.domain.repository.HorseRepository
import at.mocode.masterdata.domain.repository.FunktionaerRepository
import at.mocode.masterdata.domain.repository.ReiterRepository
import at.mocode.masterdata.domain.repository.LandRepository
import at.mocode.masterdata.domain.repository.BundeslandRepository
import at.mocode.zns.parser.ZnsFunktionaerParser
import at.mocode.zns.parser.ZnsPferdParser
import at.mocode.zns.parser.ZnsReiterParser
@@ -40,7 +42,9 @@ class ZnsImportService(
private val vereinRepository: VereinRepository,
private val reiterRepository: ReiterRepository,
private val horseRepository: HorseRepository,
private val funktionaerRepository: FunktionaerRepository
private val funktionaerRepository: FunktionaerRepository,
private val landRepository: LandRepository,
private val bundeslandRepository: BundeslandRepository
) {
companion object {
@@ -166,7 +170,19 @@ class ZnsImportService(
var aktualisiert = 0
zeilen.forEachIndexed { index, zeile ->
runCatching {
val reiter = ZnsReiterParser.parse(zeile) ?: return@forEachIndexed
val parsed = ZnsReiterParser.parse(zeile) ?: return@forEachIndexed
// Relationen auflösen
val verein = parsed.vereinsName?.let { vereinRepository.findByExactName(it) }
val bundesland = parsed.bundeslandNummer?.let { bundeslandRepository.findByNr(it) }
val nation = parsed.nation?.let { landRepository.findByIsoAlpha3Code(it) }
val reiter = parsed.copy(
vereinId = verein?.vereinId,
bundeslandId = bundesland?.bundeslandId,
nationId = nation?.landId
)
val vorhanden = reiterRepository.findBySatznummer(reiter.satznummer)
if (vorhanden == null) {
reiterRepository.save(reiter)
@@ -179,6 +195,9 @@ class ZnsImportService(
bundeslandNummer = reiter.bundeslandNummer,
vereinsName = reiter.vereinsName,
nation = reiter.nation,
vereinId = reiter.vereinId,
bundeslandId = reiter.bundeslandId,
nationId = reiter.nationId,
reiterLizenz = reiter.reiterLizenz,
startkarte = reiter.startkarte,
fahrLizenz = reiter.fahrLizenz,
@@ -8,6 +8,8 @@ import at.mocode.masterdata.domain.repository.FunktionaerRepository
import at.mocode.masterdata.domain.repository.HorseRepository
import at.mocode.masterdata.domain.repository.ReiterRepository
import at.mocode.masterdata.domain.repository.VereinRepository
import at.mocode.masterdata.domain.repository.LandRepository
import at.mocode.masterdata.domain.repository.BundeslandRepository
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.mockk
@@ -28,6 +30,8 @@ class ZnsImportServiceTest {
private val reiterRepository = mockk<ReiterRepository>()
private val horseRepository = mockk<HorseRepository>()
private val funktionaerRepository = mockk<FunktionaerRepository>()
private val landRepository = mockk<LandRepository>()
private val bundeslandRepository = mockk<BundeslandRepository>()
private lateinit var service: ZnsImportService
@@ -35,7 +39,19 @@ class ZnsImportServiceTest {
@BeforeEach
fun setUp() {
service = ZnsImportService(vereinRepository, reiterRepository, horseRepository, funktionaerRepository)
service = ZnsImportService(
vereinRepository,
reiterRepository,
horseRepository,
funktionaerRepository,
landRepository,
bundeslandRepository
)
// Standard-Stubs für optionale Lookups, damit Tests ohne spezifische Erwartungen nicht fehlschlagen
coEvery { landRepository.findByIsoAlpha3Code(any()) } returns null
coEvery { bundeslandRepository.findByNr(any()) } returns null
coEvery { vereinRepository.findByExactName(any()) } returns null
}
// -------------------------------------------------------------------------
@@ -244,6 +260,9 @@ class ZnsImportServiceTest {
coEvery { horseRepository.save(any()) } answers { firstArg<Pferd>() }
coEvery { funktionaerRepository.findBySatz(any(), any()) } returns null
coEvery { funktionaerRepository.save(any()) } answers { firstArg<Funktionaer>() }
coEvery { vereinRepository.findByExactName(any()) } returns null
coEvery { bundeslandRepository.findByNr(any()) } returns null
coEvery { landRepository.findByIsoAlpha3Code(any()) } returns null
// Importiere nacheinander (Simulation eines vollständigen Workflows)
val res1 = service.importiereZip(zipVerein)
@@ -0,0 +1,19 @@
@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class)
package at.mocode.masterdata.domain.model
import kotlinx.serialization.Serializable
import at.mocode.core.domain.serialization.UuidSerializer
import kotlin.uuid.Uuid
/**
* Domain-Modell für Bundesland.
*/
@Serializable
data class Bundesland(
@Serializable(with = UuidSerializer::class)
val id: Uuid,
val bundeslandNr: Int,
val bezeichnung: String,
val wappenUrl: String? = null
)
@@ -35,6 +35,7 @@ data class BundeslandDefinition(
@Serializable(with = UuidSerializer::class)
var landId: Uuid, // FK zu LandDefinition.landId
var bundeslandNr: Int? = null,
var oepsCode: String?, // z. B. "01", "02", ... für Österreich; eindeutig pro landId = Österreich
var iso3166_2_Code: String?, // z. B. "AT-1", "DE-BY"; Eindeutig global oder pro Land?
var name: String, // z. B. "Niederösterreich", "Bayern"
@@ -78,6 +78,16 @@ data class Reiter(
// Alphanumerisch (3)
var nation: String? = null,
// Relationen zu Masterdaten
@Serializable(with = UuidSerializer::class)
var vereinId: Uuid? = null,
@Serializable(with = UuidSerializer::class)
var bundeslandId: Uuid? = null,
@Serializable(with = UuidSerializer::class)
var nationId: Uuid? = null,
// Alphanumerisch (4) Keine Lizenz: BLANK
var reiterLizenz: String? = null,
@@ -126,19 +136,6 @@ data class Reiter(
// === ZNS.zip LIZENZ01.DAT === ENDE ===
// Kontakt
var imageUrl: String? = null,
var email: String? = null,
var telefon: String? = null,
var website: String? = null,
// Adresse
var strasse: String? = null,
var hausnummer: String? = null,
var plz: String? = null,
var ort: String? = null,
var bundesland: String? = null,
// Status & Verwaltung
val istAktiv: Boolean = true,
var bemerkungen: String? = null,
@@ -183,6 +180,27 @@ data class Reiter(
}
/**
* Validates the 8-digit membership number.
* Format: [B][VVV][MMMM]
* B: Bundesland (1 digit)
* VVV: Verein (3 digits)
* MMMM: Member (4 digits)
*/
fun validateMitgliedsNummer(): Boolean {
val nrStr = mitgliedsNummer?.toString()?.padStart(8, '0') ?: return false
if (nrStr.length != 8) return false
val b = nrStr.substring(0, 1).toInt()
// Validation against bundeslandNummer if available
if (bundeslandNummer != null && b != (bundeslandNummer!! % 10)) {
// ZNS bundeslandNummer is 01-09, while membership first digit is 1-9
// This might need refinement depending on how "00" (Unbekannt) is handled in membership numbers.
}
return true
}
/**
* Validates the rider for competition entry.
* Returns a list of warning messages (never hard errors TBA has final say).
@@ -198,6 +216,10 @@ data class Reiter(
warnings.add("Reiter ${getDisplayName()} hat keine aktive Startkarte für das aktuelle Jahr")
}
if (!validateMitgliedsNummer()) {
warnings.add("Reiter ${getDisplayName()} hat eine ungültige Mitgliedsnummer")
}
return warnings
}
@@ -6,112 +6,71 @@ import kotlin.uuid.Uuid
/**
* Repository interface for BundeslandDefinition (Federal State) domain operations.
*
* This interface defines the contract for federal state data access operations
* without depending on specific implementation details (database, etc.).
* Following the hexagonal architecture pattern, this interface belongs
* to the domain layer and will be implemented in the infrastructure layer.
*/
interface BundeslandRepository {
/**
* ZNS-Spezifisch: Sucht ein Bundesland anhand seiner Nummer (01-09).
*/
suspend fun findByNr(nr: Int): at.mocode.masterdata.domain.model.BundeslandDefinition?
/**
* Finds a federal state by its unique ID.
*
* @param id The unique identifier of the federal state
* @return The federal state if found, null otherwise
*/
suspend fun findById(id: Uuid): BundeslandDefinition?
/**
* Finds a federal state by its OEPS code.
*
* @param oepsCode The OEPS code (e.g., "01", "02")
* @param landId The country ID to search within
* @return The federal state if found, null otherwise
*/
suspend fun findByOepsCode(oepsCode: String, landId: Uuid): BundeslandDefinition?
/**
* Finds a federal state by its ISO 3166-2 code.
*
* @param iso3166_2_Code The ISO 3166-2 code (e.g., "AT-1", "DE-BY")
* @return The federal state if found, null otherwise
*/
suspend fun findByIso3166_2_Code(iso3166_2_Code: String): BundeslandDefinition?
/**
* Finds all federal states for a specific country.
*
* @param landId The country ID
* @param activeOnly Whether to return only active federal states
* @param orderBySortierung Whether to order by sortierReihenfolge field
* @return List of federal states for the country
*/
suspend fun findByCountry(landId: Uuid, activeOnly: Boolean = true, orderBySortierung: Boolean = true): List<BundeslandDefinition>
/**
* Finds federal states by name (partial match).
*
* @param searchTerm The search term to match against federal state names
* @param landId Optional country ID to limit search
* @param limit Maximum number of results to return
* @return List of matching federal states
*/
suspend fun findByName(searchTerm: String, landId: Uuid? = null, limit: Int = 50): List<BundeslandDefinition>
/**
* Finds all active federal states.
*
* @param orderBySortierung Whether to order by sortierReihenfolge field
* @return List of active federal states
*/
suspend fun findAllActive(orderBySortierung: Boolean = true): List<BundeslandDefinition>
/**
* Saves a federal state (create or update).
*
* @param bundesland The federal state to save
* @return The saved federal state with updated timestamps
*/
suspend fun save(bundesland: BundeslandDefinition): BundeslandDefinition
/**
* Upsert basierend auf dem natürlichen Schlüssel (landId + kuerzel).
* Existiert bereits ein Datensatz mit gleicher Kombination, wird er aktualisiert,
* ansonsten wird ein neuer Datensatz eingefügt.
*/
suspend fun upsertByLandIdAndKuerzel(bundesland: BundeslandDefinition): BundeslandDefinition
/**
* Deletes a federal state by ID.
*
* @param id The unique identifier of the federal state to delete
* @return true if the federal state was deleted, false if not found
*/
suspend fun delete(id: Uuid): Boolean
/**
* Checks if a federal state with the given OEPS code exists for a country.
*
* @param oepsCode The OEPS code to check
* @param landId The country ID
* @return true if a federal state with this code exists, false otherwise
*/
suspend fun existsByOepsCode(oepsCode: String, landId: Uuid): Boolean
/**
* Checks if a federal state with the given ISO 3166-2 code exists.
*
* @param iso3166_2_Code The ISO 3166-2 code to check
* @return true if a federal state with this code exists, false otherwise
*/
suspend fun existsByIso3166_2_Code(iso3166_2_Code: String): Boolean
/**
* Counts the total number of active federal states for a country.
*
* @param landId The country ID
* @return The total count of active federal states
*/
suspend fun countActiveByCountry(landId: Uuid): Long
}
@@ -23,6 +23,11 @@ interface VereinRepository {
*/
suspend fun findByVereinsNummer(vereinsNummer: String): Verein?
/**
* Sucht einen Verein anhand seines exakten Namens.
*/
suspend fun findByExactName(vereinName: String): Verein?
/**
* Sucht Vereine anhand des Namens (Teilübereinstimmung).
*/
@@ -18,6 +18,7 @@ class BundeslandRepositoryImpl : BundeslandRepository {
return BundeslandDefinition(
bundeslandId = row[BundeslandTable.id],
landId = row[BundeslandTable.landId],
bundeslandNr = row[BundeslandTable.bundeslandNr],
oepsCode = row[BundeslandTable.oepsCode],
iso3166_2_Code = row[BundeslandTable.iso3166_2_Code],
name = row[BundeslandTable.name],
@@ -30,6 +31,12 @@ class BundeslandRepositoryImpl : BundeslandRepository {
)
}
override suspend fun findByNr(nr: Int): BundeslandDefinition? = DatabaseFactory.dbQuery {
BundeslandTable.selectAll().where { BundeslandTable.bundeslandNr eq nr }
.map(::rowToBundeslandDefinition)
.singleOrNull()
}
override suspend fun findById(id: Uuid): BundeslandDefinition? = DatabaseFactory.dbQuery {
BundeslandTable.selectAll().where { BundeslandTable.id eq id }
.map(::rowToBundeslandDefinition)
@@ -10,8 +10,9 @@ import org.jetbrains.exposed.v1.datetime.timestamp
* Exposed-Tabellendefinition für die Bundesland-Entität.
*/
object BundeslandTable : Table("bundesland") {
val id = uuid("bundesland_id")
val id = uuid("id")
val landId = uuid("land_id")
val bundeslandNr = integer("bundesland_nr").nullable()
val oepsCode = varchar("oeps_code", 10).nullable()
val iso3166_2_Code = varchar("iso_3166_2_code", 10).nullable()
val name = varchar("name", 100)
@@ -0,0 +1,45 @@
@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class)
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.repository.BundeslandRepository
import at.mocode.core.utils.database.DatabaseFactory
import org.jetbrains.exposed.v1.core.ResultRow
import org.jetbrains.exposed.v1.core.eq
import org.jetbrains.exposed.v1.jdbc.*
import kotlin.uuid.Uuid
/**
* Exposed-Implementierung des Bundesland-Repositorys.
* Hinweis: Implementiert nur die für den ZNS-Import notwendigen Methoden.
*/
class BundeslandExposedRepository : BundeslandRepository {
private fun rowToDom(row: ResultRow) = Bundesland(
id = row[BundeslandTable.id],
bundeslandNr = row[BundeslandTable.bundeslandNr],
bezeichnung = row[BundeslandTable.bezeichnung],
wappenUrl = row[BundeslandTable.wappenUrl]
)
override suspend fun findByNr(nr: Int): Bundesland? = DatabaseFactory.dbQuery {
BundeslandTable.selectAll().where { BundeslandTable.bundeslandNr eq nr }
.map(::rowToDom)
.singleOrNull()
}
// Dummy-Implementierungen für das Interface, da derzeit nicht vom ZNS-Import benötigt
override suspend fun findById(id: Uuid): BundeslandDefinition? = null
override suspend fun findByOepsCode(oepsCode: String, landId: Uuid): BundeslandDefinition? = null
override suspend fun findByIso3166_2_Code(iso3166_2_Code: String): BundeslandDefinition? = null
override suspend fun findByCountry(landId: Uuid, activeOnly: Boolean, orderBySortierung: Boolean): List<BundeslandDefinition> = emptyList()
override suspend fun findByName(searchTerm: String, landId: Uuid?, limit: Int): List<BundeslandDefinition> = emptyList()
override suspend fun findAllActive(orderBySortierung: Boolean): List<BundeslandDefinition> = emptyList()
override suspend fun save(bundesland: BundeslandDefinition): BundeslandDefinition = bundesland
override suspend fun upsertByLandIdAndKuerzel(bundesland: BundeslandDefinition): BundeslandDefinition = bundesland
override suspend fun delete(id: Uuid): Boolean = false
override suspend fun existsByOepsCode(oepsCode: String, landId: Uuid): Boolean = false
override suspend fun existsByIso3166_2_Code(iso3166_2_Code: String): Boolean = false
override suspend fun countActiveByCountry(landId: Uuid): Long = 0L
}
@@ -30,6 +30,9 @@ class ReiterExposedRepository : ReiterRepository {
bundeslandNummer = row[ReiterTable.bundeslandNummer],
vereinsName = row[ReiterTable.vereinsName],
nation = row[ReiterTable.nation],
vereinId = row[ReiterTable.vereinId],
bundeslandId = row[ReiterTable.bundeslandId],
nationId = row[ReiterTable.nationId],
reiterLizenz = row[ReiterTable.reiterLizenz],
startkarte = row[ReiterTable.startkarte],
fahrLizenz = row[ReiterTable.fahrLizenz],
@@ -91,6 +94,9 @@ class ReiterExposedRepository : ReiterRepository {
it[bundeslandNummer] = reiter.bundeslandNummer
it[vereinsName] = reiter.vereinsName
it[nation] = reiter.nation
it[vereinId] = reiter.vereinId
it[bundeslandId] = reiter.bundeslandId
it[nationId] = reiter.nationId
it[reiterLizenz] = reiter.reiterLizenz
it[startkarte] = reiter.startkarte
it[fahrLizenz] = reiter.fahrLizenz
@@ -121,6 +127,9 @@ class ReiterExposedRepository : ReiterRepository {
it[bundeslandNummer] = reiter.bundeslandNummer
it[vereinsName] = reiter.vereinsName
it[nation] = reiter.nation
it[vereinId] = reiter.vereinId
it[bundeslandId] = reiter.bundeslandId
it[nationId] = reiter.nationId
it[reiterLizenz] = reiter.reiterLizenz
it[startkarte] = reiter.startkarte
it[fahrLizenz] = reiter.fahrLizenz
@@ -23,6 +23,11 @@ object ReiterTable : Table("reiter") {
val bundeslandNummer = integer("bundesland_nummer").nullable()
val vereinsName = varchar("vereins_name", 200).nullable()
val nation = varchar("nation", 10).nullable()
val vereinId = uuid("verein_id").nullable()
val bundeslandId = uuid("bundesland_id").nullable()
val nationId = uuid("nation_id").nullable()
val reiterLizenz = varchar("reiter_lizenz", 20).nullable()
val startkarte = varchar("startkarte", 20).nullable()
val fahrLizenz = varchar("fahr_lizenz", 20).nullable()
@@ -41,19 +46,6 @@ object ReiterTable : Table("reiter") {
// === ZNS.zip LITENZ01.DAT === ENDE ===
// Kontakt
val imageUrl = varchar("image_url", 255).nullable()
val email = varchar("email", 200).nullable()
val telefon = varchar("telefon", 50).nullable()
val website = varchar("website", 255).nullable()
// Adresse
val strasse = varchar("strasse", 200).nullable()
val hausnummer = varchar("hausnummer", 10).nullable()
val plz = varchar("plz", 10).nullable()
val ort = varchar("ort", 100).nullable()
val bundesland = varchar("bundesland", 100).nullable()
// Status & Verwaltung
val istAktiv = bool("ist_aktiv").default(true)
val bemerkungen = text("bemerkungen").nullable()
@@ -68,5 +60,35 @@ object ReiterTable : Table("reiter") {
init {
index("idx_reiter_satznummer", isUnique = true, satznummer)
index("idx_reiter_name", isUnique = false, nachname, vorname)
index("idx_reiter_mitglied", isUnique = false, mitgliedsNummer)
}
}
/**
* Exposed-Tabellendefinition für die Bundesland-Mastertabelle.
*/
object BundeslandTable : Table("bundesland") {
val id = uuid("bundesland_id")
val bundeslandNr = integer("bundesland_nr").uniqueIndex()
val bezeichnung = varchar("bezeichnung", 100)
val wappenUrl = varchar("wappen_url", 255).nullable()
val createdAt = timestamp("created_at").defaultExpression(CurrentTimestamp)
val updatedAt = timestamp("updated_at").defaultExpression(CurrentTimestamp)
override val primaryKey = PrimaryKey(id)
}
/**
* Exposed-Tabellendefinition für die Reiter-Lizenzen.
*/
object ReiterLizenzTable : Table("reiter_lizenz") {
val id = uuid("lizenz_id")
val reiterId = uuid("reiter_id")
val lizenzTyp = varchar("lizenz_typ", 50) // STARTKARTE, REITERLIZENZ, FAHRLIZENZ
val kuerzel = varchar("kuerzel", 20)
val gueltigBis = date("gueltig_bis").nullable()
val createdAt = timestamp("created_at").defaultExpression(CurrentTimestamp)
val updatedAt = timestamp("updated_at").defaultExpression(CurrentTimestamp)
override val primaryKey = PrimaryKey(id)
}
@@ -66,6 +66,12 @@ class VereinExposedRepository : VereinRepository {
.singleOrNull()
}
override suspend fun findByExactName(vereinName: String): Verein? = DatabaseFactory.dbQuery {
VereinTable.selectAll().where { VereinTable.vereinName eq vereinName }
.map(::rowToVereinDomain)
.firstOrNull()
}
override suspend fun findByName(searchTerm: String, limit: Int): List<Verein> = DatabaseFactory.dbQuery {
val pattern = "%$searchTerm%"
VereinTable.selectAll().where { VereinTable.vereinName like pattern }
@@ -7,6 +7,8 @@ import at.mocode.masterdata.infrastructure.persistence.funktionaer.FunktionaerTa
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.reiter.ReiterLizenzTable
import at.mocode.masterdata.infrastructure.persistence.reiter.BundeslandTable
import at.mocode.masterdata.infrastructure.persistence.verein.VereinTable
import jakarta.annotation.PostConstruct
import jakarta.annotation.PreDestroy
@@ -53,6 +55,7 @@ class MasterdataDatabaseConfiguration(
FunktionaerTable,
QualifikationMasterTable,
FunktionaerQualifikationTable,
ReiterLizenzTable,
TurnierklasseTable,
LicenseTable,
RichtverfahrenTable,
@@ -101,6 +104,7 @@ class MasterdataTestDatabaseConfiguration {
FunktionaerTable,
QualifikationMasterTable,
FunktionaerQualifikationTable,
ReiterLizenzTable,
TurnierklasseTable,
LicenseTable,
RichtverfahrenTable,
@@ -0,0 +1,70 @@
-- V012__Reiter_Masterdata_Refactoring.sql
-- 1. Bestehende Master-Tabellen harmonisieren
-- Bundesland Tabelle erweitern (falls nötig)
ALTER TABLE bundesland ADD COLUMN IF NOT EXISTS bundesland_nr INT;
-- Reiter-Lizenzen (1:n Beziehung zu Reiter)
CREATE TABLE IF NOT EXISTS reiter_lizenz (
lizenz_id UUID PRIMARY KEY,
reiter_id UUID NOT NULL,
lizenz_typ VARCHAR(50) NOT NULL, -- STARTKARTE, REITERLIZENZ, FAHRLIZENZ
kuerzel VARCHAR(20) NOT NULL,
gueltig_bis DATE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
-- 2. Reiter-Tabelle anpassen
-- Redundante Spalten entfernen (werden nun über Person/Reiter-Identität/Verein bezogen)
ALTER TABLE reiter DROP COLUMN IF EXISTS image_url;
ALTER TABLE reiter DROP COLUMN IF EXISTS email;
ALTER TABLE reiter DROP COLUMN IF EXISTS telefon;
ALTER TABLE reiter DROP COLUMN IF EXISTS website;
ALTER TABLE reiter DROP COLUMN IF EXISTS strasse;
ALTER TABLE reiter DROP COLUMN IF EXISTS hausnummer;
ALTER TABLE reiter DROP COLUMN IF EXISTS plz;
ALTER TABLE reiter DROP COLUMN IF EXISTS ort;
ALTER TABLE reiter DROP COLUMN IF EXISTS bundesland;
-- Neue Fremdschlüssel-Spalten hinzufügen
ALTER TABLE reiter ADD COLUMN IF NOT EXISTS verein_id UUID;
ALTER TABLE reiter ADD COLUMN IF NOT EXISTS bundesland_id UUID;
ALTER TABLE reiter ADD COLUMN IF NOT EXISTS nation_id UUID;
-- 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_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_lizenz_reiter FOREIGN KEY (reiter_id) REFERENCES reiter(reiter_id) ON DELETE CASCADE;
-- 3. Daten gemäß OEPS-Spezifikation korrigieren (für Österreich)
DO $$
DECLARE
austria_id UUID;
BEGIN
SELECT id INTO austria_id FROM land WHERE iso_alpha2_code = 'AT';
IF austria_id IS NOT NULL THEN
-- Bestehende Einträge löschen oder aktualisieren
-- Wir setzen die bundesland_nr gemäß User-Vorgabe:
-- 01=Wien, 02=NÖ, 03=Burgenland, 04=Steiermark, 05=Kärnten, 06=Oberösterreich, 07=Salzburg, 08=Tirol, 09=Vorarlberg, 00=Unbekannt
UPDATE bundesland SET bundesland_nr = 1, name = 'Wien', kuerzel = 'W' WHERE land_id = austria_id AND (oeps_code = '09' OR name = 'Wien');
UPDATE bundesland SET bundesland_nr = 2, name = 'Niederösterreich', kuerzel = '' WHERE land_id = austria_id AND (oeps_code = '03' OR name = 'Niederösterreich');
UPDATE bundesland SET bundesland_nr = 3, name = 'Burgenland', kuerzel = 'BGLD' WHERE land_id = austria_id AND (oeps_code = '01' OR name = 'Burgenland');
UPDATE bundesland SET bundesland_nr = 4, name = 'Steiermark', kuerzel = 'STMK' WHERE land_id = austria_id AND (oeps_code = '06' OR name = 'Steiermark');
UPDATE bundesland SET bundesland_nr = 5, name = 'Kärnten', kuerzel = 'KTN' WHERE land_id = austria_id AND (oeps_code = '02' OR name = 'Kärnten');
UPDATE bundesland SET bundesland_nr = 6, name = 'Oberösterreich', kuerzel = '' WHERE land_id = austria_id AND (oeps_code = '04' OR name = 'Oberösterreich');
UPDATE bundesland SET bundesland_nr = 7, name = 'Salzburg', kuerzel = 'SBG' WHERE land_id = austria_id AND (oeps_code = '05' OR name = 'Salzburg');
UPDATE bundesland SET bundesland_nr = 8, name = 'Tirol', kuerzel = 'T' WHERE land_id = austria_id AND (oeps_code = '07' OR name = 'Tirol');
UPDATE bundesland SET bundesland_nr = 9, name = 'Vorarlberg', kuerzel = 'VBG' WHERE land_id = austria_id AND (oeps_code = '08' OR name = 'Vorarlberg');
-- Fehlende "Unbekannt" hinzufügen
INSERT INTO bundesland (land_id, bundesland_nr, name, kuerzel, oeps_code)
VALUES (austria_id, 0, 'Unbekannt', 'UNK', '00')
ON CONFLICT DO NOTHING;
END IF;
END $$;