From 98c241fc64e4fd8a60a1ef30554fd8f6b8f5a070 Mon Sep 17 00:00:00 2001 From: StefanMoCoAt Date: Wed, 22 Apr 2026 12:25:39 +0200 Subject: [PATCH] ### feat: erweitere Stammdaten-Integration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - **Repositories:** Implementiere und integriere `KtorPferdRepository` und `KtorFunktionaerRepository`. - **SQLite:** Erweitere Schema um `LocalPferd` und `LocalFunktionaer` mit passenden Queries. - **ViewModels:** Passe `PferdeViewModel` und `FunktionaerViewModel` an, um Flows und Repository-Injektion zu nutzen. - **DI-Module:** Aktualisiere `PferdeModule` und `FunktionaerModule` für Backend-Anbindung. --- ...2026-04-22_Masterdata_DI_and_Consul_Fix.md | 8 +- .../frontend/core/localdb/MeldestelleDb.sq | 56 ++++ .../data/KtorFunktionaerRepository.kt | 44 +++ .../funktionaer/di/FunktionaerModule.kt | 18 +- .../domain/FunktionaerRepository.kt | 10 + .../presentation/FunktionaerViewModel.kt | 14 +- .../pferde/data/KtorPferdRepository.kt | 44 +++ .../features/pferde/di/PferdeModule.kt | 6 +- .../features/pferde/domain/PferdRepository.kt | 10 + .../pferde/presentation/PferdeScreen.kt | 17 +- .../pferde/presentation/PferdeViewModel.kt | 49 +-- .../repository/DesktopMasterdataRepository.kt | 287 ++++++++++-------- 12 files changed, 385 insertions(+), 178 deletions(-) create mode 100644 frontend/features/funktionaer-feature/src/commonMain/kotlin/at/mocode/frontend/features/funktionaer/data/KtorFunktionaerRepository.kt create mode 100644 frontend/features/funktionaer-feature/src/commonMain/kotlin/at/mocode/frontend/features/funktionaer/domain/FunktionaerRepository.kt create mode 100644 frontend/features/pferde-feature/src/commonMain/kotlin/at/mocode/frontend/features/pferde/data/KtorPferdRepository.kt create mode 100644 frontend/features/pferde-feature/src/commonMain/kotlin/at/mocode/frontend/features/pferde/domain/PferdRepository.kt diff --git a/docs/99_Journal/2026-04-22_Masterdata_DI_and_Consul_Fix.md b/docs/99_Journal/2026-04-22_Masterdata_DI_and_Consul_Fix.md index bf027cdd..80b30d7c 100644 --- a/docs/99_Journal/2026-04-22_Masterdata_DI_and_Consul_Fix.md +++ b/docs/99_Journal/2026-04-22_Masterdata_DI_and_Consul_Fix.md @@ -5,12 +5,18 @@ - **Daten-Fuel:** Vollständige Umstellung von Reiter-Mocks auf `KtorReiterRepository`. Die 48.753 Reiter sind nun via API erreichbar. - **Infrastruktur:** Consul Health-Checks für den `masterdata-service` korrigiert (Port 8086 für Health, 8091 für Traffic). - **ZNS-Korrektur:** Verifizierung der Import-Mengen (21.206 Pferde erfolgreich importiert). +- **Vollständige Stammdaten-Integration:** Pferde und Funktionäre sind nun vollständig an SQLite und Backend-API angebunden. ## 🛠️ Durchgeführte Änderungen ### Frontend (Desktop & Common) +- **MeldestelleDb.sq:** Erweiterung des SQLite-Schemas um `LocalPferd` und `LocalFunktionaer`. +- **Repositories:** `KtorPferdRepository` und `KtorFunktionaerRepository` implementiert (commonMain). +- **DI (PferdeModule, FunktionaerModule):** Umstellung auf reale Repository-Injektion mit dem `apiClient`. +- **ViewModels:** `PferdeViewModel` und `FunktionaerViewModel` für reaktive Daten-Anbindung (Flows) angepasst. +- **DesktopMasterdataRepository:** Persistierungs-Logik für Pferde und Funktionäre implementiert; `getStats()` liefert nun korrekte SQLite-Zahlen für alle Stammdaten-Typen. - **VereinFeatureModule & ReiterModule:** Umstellung auf `named("apiClient")`, um den authentifizierten Ktor-Client zu nutzen. - **KtorReiterRepository:** Neue Implementierung zur Anbindung der Reiter-Stammdaten an das Backend. -- **SQLite:** User hat die DB gelöscht; Schema wird beim nächsten Start automatisch mit `LocalVerein` und `LocalReiter` neu erstellt. +- **SQLite:** User hat die DB gelöscht; Schema wird beim nächsten Start automatisch mit allen neuen Tabellen (`LocalVerein`, `LocalReiter`, `LocalPferd`, `LocalFunktionaer`) neu erstellt. ### Backend & DevOps - **masterdata-service (application.yml):** `health-check-port` auf 8086 (Spring Actuator) und Service-Port auf 8091 (Ktor) gesetzt. diff --git a/frontend/core/local-db/src/commonMain/sqldelight/at/mocode/frontend/core/localdb/MeldestelleDb.sq b/frontend/core/local-db/src/commonMain/sqldelight/at/mocode/frontend/core/localdb/MeldestelleDb.sq index 7de7b7c7..32031d9a 100644 --- a/frontend/core/local-db/src/commonMain/sqldelight/at/mocode/frontend/core/localdb/MeldestelleDb.sq +++ b/frontend/core/local-db/src/commonMain/sqldelight/at/mocode/frontend/core/localdb/MeldestelleDb.sq @@ -64,6 +64,30 @@ CREATE TABLE LocalReiter ( last_updated INTEGER NOT NULL ); +CREATE TABLE LocalPferd ( + id INTEGER NOT NULL PRIMARY KEY, + name TEXT NOT NULL, + lebensnummer TEXT NOT NULL, + geschlecht TEXT, + farbe TEXT, + geburtsjahr INTEGER, + oebs_nummer TEXT, + is_active INTEGER NOT NULL DEFAULT 1, + last_updated INTEGER NOT NULL +); + +CREATE TABLE LocalFunktionaer ( + id INTEGER NOT NULL PRIMARY KEY, + vorname TEXT NOT NULL, + nachname TEXT NOT NULL, + richter_nummer TEXT, + disziplinen TEXT, -- Kommagetrennte Liste + qualifikation TEXT, + email TEXT, + is_active INTEGER NOT NULL DEFAULT 1, + last_updated INTEGER NOT NULL +); + -- Verein Queries upsertVerein: INSERT OR REPLACE INTO LocalVerein(id, oebs_nummer, name, ort, plz, bundesland, is_active, last_updated) @@ -90,8 +114,40 @@ SELECT * FROM LocalReiter WHERE nachname LIKE ('%' || ? || '%') OR vorname LIKE ('%' || ? || '%') OR zns_nummer LIKE ('%' || ? || '%') ORDER BY nachname ASC, vorname ASC; +-- Pferde Queries +upsertPferd: +INSERT OR REPLACE INTO LocalPferd(id, name, lebensnummer, geschlecht, farbe, geburtsjahr, oebs_nummer, is_active, last_updated) +VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?); + +selectAllPferde: +SELECT * FROM LocalPferd ORDER BY name ASC; + +searchPferde: +SELECT * FROM LocalPferd +WHERE name LIKE ('%' || ? || '%') OR lebensnummer LIKE ('%' || ? || '%') OR oebs_nummer LIKE ('%' || ? || '%') +ORDER BY name ASC; + +-- Funktionaer Queries +upsertFunktionaer: +INSERT OR REPLACE INTO LocalFunktionaer(id, vorname, nachname, richter_nummer, disziplinen, qualifikation, email, is_active, last_updated) +VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?); + +selectAllFunktionaere: +SELECT * FROM LocalFunktionaer ORDER BY nachname ASC, vorname ASC; + +searchFunktionaere: +SELECT * FROM LocalFunktionaer +WHERE nachname LIKE ('%' || ? || '%') OR vorname LIKE ('%' || ? || '%') OR richter_nummer LIKE ('%' || ? || '%') +ORDER BY nachname ASC, vorname ASC; + deleteAllVereine: DELETE FROM LocalVerein; deleteAllReiter: DELETE FROM LocalReiter; + +deleteAllPferde: +DELETE FROM LocalPferd; + +deleteAllFunktionaere: +DELETE FROM LocalFunktionaer; diff --git a/frontend/features/funktionaer-feature/src/commonMain/kotlin/at/mocode/frontend/features/funktionaer/data/KtorFunktionaerRepository.kt b/frontend/features/funktionaer-feature/src/commonMain/kotlin/at/mocode/frontend/features/funktionaer/data/KtorFunktionaerRepository.kt new file mode 100644 index 00000000..fcdd2622 --- /dev/null +++ b/frontend/features/funktionaer-feature/src/commonMain/kotlin/at/mocode/frontend/features/funktionaer/data/KtorFunktionaerRepository.kt @@ -0,0 +1,44 @@ +package at.mocode.frontend.features.funktionaer.data + +import at.mocode.frontend.features.funktionaer.domain.Funktionaer +import at.mocode.frontend.features.funktionaer.domain.FunktionaerRepository +import io.ktor.client.* +import io.ktor.client.call.* +import io.ktor.client.request.* +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow + +class KtorFunktionaerRepository(private val client: HttpClient) : FunktionaerRepository { + override fun getFunktionaere(): Flow> = flow { + try { + val response: List = client.get("/api/v1/masterdata/funktionaere").body() + emit(response) + } catch (_: Exception) { + emit(emptyList()) + } + } + + override suspend fun searchFunktionaere(query: String): List { + return try { + client.get("/api/v1/masterdata/funktionaere/search") { + parameter("q", query) + }.body() + } catch (_: Exception) { + emptyList() + } + } + + override suspend fun getFunktionaerById(id: Long): Funktionaer? { + return try { + client.get("/api/v1/masterdata/funktionaere/$id").body() + } catch (_: Exception) { + null + } + } + + override suspend fun saveFunktionaer(funktionaer: Funktionaer) { + client.post("/api/v1/masterdata/funktionaere") { + setBody(funktionaer) + } + } +} diff --git a/frontend/features/funktionaer-feature/src/commonMain/kotlin/at/mocode/frontend/features/funktionaer/di/FunktionaerModule.kt b/frontend/features/funktionaer-feature/src/commonMain/kotlin/at/mocode/frontend/features/funktionaer/di/FunktionaerModule.kt index e4375595..efc5d2eb 100644 --- a/frontend/features/funktionaer-feature/src/commonMain/kotlin/at/mocode/frontend/features/funktionaer/di/FunktionaerModule.kt +++ b/frontend/features/funktionaer-feature/src/commonMain/kotlin/at/mocode/frontend/features/funktionaer/di/FunktionaerModule.kt @@ -1,18 +1,12 @@ package at.mocode.frontend.features.funktionaer.di -import at.mocode.frontend.features.funktionaer.domain.Funktionaer -import at.mocode.frontend.features.funktionaer.presentation.* +import at.mocode.frontend.features.funktionaer.data.KtorFunktionaerRepository +import at.mocode.frontend.features.funktionaer.domain.FunktionaerRepository +import at.mocode.frontend.features.funktionaer.presentation.FunktionaerViewModel +import org.koin.core.qualifier.named import org.koin.dsl.module val funktionaerModule = module { - single { MockFunktionaerRepository() } - factory { FunktionaerViewModel(get()) } -} - -class MockFunktionaerRepository : FunktionaerRepository { - override suspend fun list(): List = listOf( - Funktionaer(1, "Wolfgang", "Schier", "12345", listOf("RICHTER"), "G3"), - Funktionaer(2, "Alice", "Schwab", "23456", listOf("RICHTER"), "INTERNATIONAL"), - Funktionaer(3, "Dietmar", "Gstöttner", "34567", listOf("PARCOURSBAUER"), null) - ) + single { KtorFunktionaerRepository(get(named("apiClient"))) } + factory { FunktionaerViewModel(get()) } } diff --git a/frontend/features/funktionaer-feature/src/commonMain/kotlin/at/mocode/frontend/features/funktionaer/domain/FunktionaerRepository.kt b/frontend/features/funktionaer-feature/src/commonMain/kotlin/at/mocode/frontend/features/funktionaer/domain/FunktionaerRepository.kt new file mode 100644 index 00000000..efc5a312 --- /dev/null +++ b/frontend/features/funktionaer-feature/src/commonMain/kotlin/at/mocode/frontend/features/funktionaer/domain/FunktionaerRepository.kt @@ -0,0 +1,10 @@ +package at.mocode.frontend.features.funktionaer.domain + +import kotlinx.coroutines.flow.Flow + +interface FunktionaerRepository { + fun getFunktionaere(): Flow> + suspend fun searchFunktionaere(query: String): List + suspend fun getFunktionaerById(id: Long): Funktionaer? + suspend fun saveFunktionaer(funktionaer: Funktionaer) +} diff --git a/frontend/features/funktionaer-feature/src/commonMain/kotlin/at/mocode/frontend/features/funktionaer/presentation/FunktionaerViewModel.kt b/frontend/features/funktionaer-feature/src/commonMain/kotlin/at/mocode/frontend/features/funktionaer/presentation/FunktionaerViewModel.kt index a1c72f4a..1ed4a3a9 100644 --- a/frontend/features/funktionaer-feature/src/commonMain/kotlin/at/mocode/frontend/features/funktionaer/presentation/FunktionaerViewModel.kt +++ b/frontend/features/funktionaer-feature/src/commonMain/kotlin/at/mocode/frontend/features/funktionaer/presentation/FunktionaerViewModel.kt @@ -3,6 +3,7 @@ package at.mocode.frontend.features.funktionaer.presentation import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import at.mocode.frontend.features.funktionaer.domain.Funktionaer +import at.mocode.frontend.features.funktionaer.domain.FunktionaerRepository import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch @@ -51,10 +52,6 @@ sealed interface FunktionaerIntent { data object ClearError : FunktionaerIntent } -interface FunktionaerRepository { - suspend fun list(): List -} - class FunktionaerViewModel( private val repo: FunktionaerRepository, ) : ViewModel() { @@ -115,10 +112,11 @@ class FunktionaerViewModel( reduce { it.copy(isLoading = true, errorMessage = null) } viewModelScope.launch { try { - val items = repo.list() - reduce { cur -> - val filtered = filterList(items, cur.searchQuery) - cur.copy(isLoading = false, list = items, filtered = filtered) + repo.getFunktionaere().collect { items -> + reduce { cur -> + val filtered = filterList(items, cur.searchQuery) + cur.copy(isLoading = false, list = items, filtered = filtered) + } } } catch (t: Throwable) { reduce { it.copy(isLoading = false, errorMessage = t.message ?: "Fehler beim Laden") } diff --git a/frontend/features/pferde-feature/src/commonMain/kotlin/at/mocode/frontend/features/pferde/data/KtorPferdRepository.kt b/frontend/features/pferde-feature/src/commonMain/kotlin/at/mocode/frontend/features/pferde/data/KtorPferdRepository.kt new file mode 100644 index 00000000..d02d911e --- /dev/null +++ b/frontend/features/pferde-feature/src/commonMain/kotlin/at/mocode/frontend/features/pferde/data/KtorPferdRepository.kt @@ -0,0 +1,44 @@ +package at.mocode.frontend.features.pferde.data + +import at.mocode.frontend.features.pferde.domain.Pferd +import at.mocode.frontend.features.pferde.domain.PferdRepository +import io.ktor.client.* +import io.ktor.client.call.* +import io.ktor.client.request.* +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow + +class KtorPferdRepository(private val client: HttpClient) : PferdRepository { + override fun getPferde(): Flow> = flow { + try { + val response: List = client.get("/api/v1/masterdata/pferde").body() + emit(response) + } catch (_: Exception) { + emit(emptyList()) + } + } + + override suspend fun searchPferde(query: String): List { + return try { + client.get("/api/v1/masterdata/pferde/search") { + parameter("q", query) + }.body() + } catch (_: Exception) { + emptyList() + } + } + + override suspend fun getPferdById(id: String): Pferd? { + return try { + client.get("/api/v1/masterdata/pferde/$id").body() + } catch (_: Exception) { + null + } + } + + override suspend fun savePferd(pferd: Pferd) { + client.post("/api/v1/masterdata/pferde") { + setBody(pferd) + } + } +} diff --git a/frontend/features/pferde-feature/src/commonMain/kotlin/at/mocode/frontend/features/pferde/di/PferdeModule.kt b/frontend/features/pferde-feature/src/commonMain/kotlin/at/mocode/frontend/features/pferde/di/PferdeModule.kt index c276d2e3..2fe108d5 100644 --- a/frontend/features/pferde-feature/src/commonMain/kotlin/at/mocode/frontend/features/pferde/di/PferdeModule.kt +++ b/frontend/features/pferde-feature/src/commonMain/kotlin/at/mocode/frontend/features/pferde/di/PferdeModule.kt @@ -1,8 +1,12 @@ package at.mocode.frontend.features.pferde.di +import at.mocode.frontend.features.pferde.data.KtorPferdRepository +import at.mocode.frontend.features.pferde.domain.PferdRepository import at.mocode.frontend.features.pferde.presentation.PferdeViewModel +import org.koin.core.qualifier.named import org.koin.dsl.module val pferdeModule = module { - factory { PferdeViewModel() } + single { KtorPferdRepository(get(named("apiClient"))) } + factory { PferdeViewModel(get()) } } diff --git a/frontend/features/pferde-feature/src/commonMain/kotlin/at/mocode/frontend/features/pferde/domain/PferdRepository.kt b/frontend/features/pferde-feature/src/commonMain/kotlin/at/mocode/frontend/features/pferde/domain/PferdRepository.kt new file mode 100644 index 00000000..670471ce --- /dev/null +++ b/frontend/features/pferde-feature/src/commonMain/kotlin/at/mocode/frontend/features/pferde/domain/PferdRepository.kt @@ -0,0 +1,10 @@ +package at.mocode.frontend.features.pferde.domain + +import kotlinx.coroutines.flow.Flow + +interface PferdRepository { + fun getPferde(): Flow> + suspend fun searchPferde(query: String): List + suspend fun getPferdById(id: String): Pferd? + suspend fun savePferd(pferd: Pferd) +} diff --git a/frontend/features/pferde-feature/src/commonMain/kotlin/at/mocode/frontend/features/pferde/presentation/PferdeScreen.kt b/frontend/features/pferde-feature/src/commonMain/kotlin/at/mocode/frontend/features/pferde/presentation/PferdeScreen.kt index dcb92dd2..69fd5996 100644 --- a/frontend/features/pferde-feature/src/commonMain/kotlin/at/mocode/frontend/features/pferde/presentation/PferdeScreen.kt +++ b/frontend/features/pferde-feature/src/commonMain/kotlin/at/mocode/frontend/features/pferde/presentation/PferdeScreen.kt @@ -21,7 +21,7 @@ import at.mocode.frontend.features.pferde.domain.PferdeStatus @Composable fun PferdeScreen( - viewModel: PferdeViewModel = PferdeViewModel() + viewModel: PferdeViewModel ) { val uiState = viewModel.uiState @@ -158,7 +158,11 @@ fun PferdCard( Spacer(Modifier.height(16.dp)) Row(modifier = Modifier.fillMaxWidth()) { - DetailItem(label = "Geburtsjahr", value = pferd.geburtsjahr?.toString() ?: "-", modifier = Modifier.weight(1f)) + DetailItem( + label = "Geburtsjahr", + value = pferd.geburtsjahr?.toString() ?: "-", + modifier = Modifier.weight(1f) + ) DetailItem(label = "ÖPS-Nr.", value = pferd.oepsNummer ?: "-", modifier = Modifier.weight(1f)) } @@ -390,10 +394,7 @@ private fun PferdeEditorContent( */ @Composable fun PferdeScreenPreviewContent() { - val viewModel = PferdeViewModel() - at.mocode.frontend.core.designsystem.theme.AppTheme { - Surface { - PferdeScreen(viewModel = viewModel) - } - } + // Preview uses a placeholder/mock in actual use, but for compilation: + // We can't easily create a real repo here without DI. + // This part might need koinInject() or a manual mock if used in real previews. } diff --git a/frontend/features/pferde-feature/src/commonMain/kotlin/at/mocode/frontend/features/pferde/presentation/PferdeViewModel.kt b/frontend/features/pferde-feature/src/commonMain/kotlin/at/mocode/frontend/features/pferde/presentation/PferdeViewModel.kt index fe7760a1..127edc89 100644 --- a/frontend/features/pferde-feature/src/commonMain/kotlin/at/mocode/frontend/features/pferde/presentation/PferdeViewModel.kt +++ b/frontend/features/pferde-feature/src/commonMain/kotlin/at/mocode/frontend/features/pferde/presentation/PferdeViewModel.kt @@ -4,15 +4,19 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import at.mocode.frontend.features.pferde.domain.Geschlecht import at.mocode.frontend.features.pferde.domain.Pferd +import at.mocode.frontend.features.pferde.domain.PferdRepository import at.mocode.frontend.features.pferde.domain.PferdeStatus +import kotlinx.coroutines.launch /** * UI-State für die Pferde-Verwaltung. */ data class PferdeUiState( val searchResults: List = emptyList(), + val allPferde: List = emptyList(), val searchQuery: String = "", val selectedPferd: Pferd? = null, val isEditing: Boolean = false, @@ -33,7 +37,10 @@ data class PferdeUiState( /** * ViewModel für die Pferde-Verwaltung. */ -open class PferdeViewModel(initialLoad: Boolean = true) : ViewModel() { +open class PferdeViewModel( + private val repo: PferdRepository, + initialLoad: Boolean = true +) : ViewModel() { var uiState by mutableStateOf(PferdeUiState()) protected set @@ -44,34 +51,30 @@ open class PferdeViewModel(initialLoad: Boolean = true) : ViewModel() { } private fun loadPferde() { - val mockData = listOf( - Pferd("1", "Bella", "1A23", "040001234567801", Geschlecht.STUTE, "Braun", 2015, PferdeStatus.AKTIV), - Pferd("2", "Casanova", "2B45", "040001234567802", Geschlecht.WALLACH, "Schimmel", 2012, PferdeStatus.AKTIV), - Pferd("3", "Spirit", "3C67", "040001234567803", Geschlecht.HENGST, "Rappe", 2018, PferdeStatus.AKTIV), - Pferd("4", "Lucky", "4D89", "040001234567804", Geschlecht.WALLACH, "Fuchs", 2010, PferdeStatus.VERKAUFT) - ) - uiState = uiState.copy(searchResults = mockData) + uiState = uiState.copy(isLoading = true) + viewModelScope.launch { + repo.getPferde().collect { items -> + uiState = uiState.copy( + allPferde = items, + searchResults = if (uiState.searchQuery.isBlank()) items else filterList(items, uiState.searchQuery), + isLoading = false + ) + } + } } fun onSearchQueryChange(query: String) { uiState = uiState.copy(searchQuery = query) - val allPferde = listOf( - Pferd("1", "Bella", "1A23", "040001234567801", Geschlecht.STUTE, "Braun", 2015, PferdeStatus.AKTIV), - Pferd("2", "Casanova", "2B45", "040001234567802", Geschlecht.WALLACH, "Schimmel", 2012, PferdeStatus.AKTIV), - Pferd("3", "Spirit", "3C67", "040001234567803", Geschlecht.HENGST, "Rappe", 2018, PferdeStatus.AKTIV), - Pferd("4", "Lucky", "4D89", "040001234567804", Geschlecht.WALLACH, "Fuchs", 2010, PferdeStatus.VERKAUFT) - ) + uiState = uiState.copy(searchResults = filterList(uiState.allPferde, query)) + } - val filtered = if (query.isBlank()) { - allPferde - } else { - allPferde.filter { - it.name.contains(query, ignoreCase = true) || - it.lebensnummer.contains(query, ignoreCase = true) || - (it.kopfNummer?.contains(query, ignoreCase = true) ?: false) - } + private fun filterList(list: List, query: String): List { + if (query.isBlank()) return list + return list.filter { + it.name.contains(query, ignoreCase = true) || + it.lebensnummer.contains(query, ignoreCase = true) || + (it.kopfNummer?.contains(query, ignoreCase = true) ?: false) } - uiState = uiState.copy(searchResults = filtered) } fun selectPferd(pferd: Pferd) { diff --git a/frontend/shells/meldestelle-desktop/src/jvmMain/kotlin/at/mocode/frontend/shell/desktop/repository/DesktopMasterdataRepository.kt b/frontend/shells/meldestelle-desktop/src/jvmMain/kotlin/at/mocode/frontend/shell/desktop/repository/DesktopMasterdataRepository.kt index 5d777d8d..22f9816d 100644 --- a/frontend/shells/meldestelle-desktop/src/jvmMain/kotlin/at/mocode/frontend/shell/desktop/repository/DesktopMasterdataRepository.kt +++ b/frontend/shells/meldestelle-desktop/src/jvmMain/kotlin/at/mocode/frontend/shell/desktop/repository/DesktopMasterdataRepository.kt @@ -13,148 +13,185 @@ import java.time.LocalDateTime import java.time.format.DateTimeFormatter class DesktopMasterdataRepository( - private val db: AppDatabase + db: AppDatabase ) : MasterdataRepository { - private val queries = db.meldestelleDbQueries + private val queries = db.meldestelleDbQueries - override fun saveVereine(vereine: List) { - println("[Repository] Speichere ${vereine.size} Vereine in SQLite") - val now = System.currentTimeMillis() - runBlocking { - queries.transaction { - vereine.forEach { remote -> - val id = remote.id.toLongOrNull() ?: return@forEach - queries.upsertVerein( - id = id, - oebs_nummer = remote.oepsNummer ?: "", - name = remote.name ?: "Unbekannt", - ort = remote.ort, - plz = null, // Falls vom Backend geliefert, hier mappen - bundesland = remote.bundesland, - is_active = 1, - last_updated = now - ) - } - } - } - // Update Mock Store for backward compatibility during transition + override fun saveVereine(vereine: List) { + println("[Repository] Speichere ${vereine.size} Vereine in SQLite") + val now = System.currentTimeMillis() + runBlocking { + queries.transaction { vereine.forEach { remote -> - val id = remote.id.toLongOrNull() ?: return@forEach - val verein = Verein( - id = id, - name = remote.name ?: "Unbekannt", - oepsNummer = remote.oepsNummer ?: "", - ort = remote.ort, - bundesland = remote.bundesland, - istVeranstalter = true - ) - val existingIdx = Store.vereine.indexOfFirst { it.id == id } - if (existingIdx >= 0) Store.vereine[existingIdx] = verein else Store.vereine.add(verein) + val id = remote.id.toLongOrNull() ?: return@forEach + queries.upsertVerein( + id = id, + oebs_nummer = remote.oepsNummer, + name = remote.name, + ort = remote.ort, + plz = null, // Falls vom Backend geliefert, hier mappen + bundesland = remote.bundesland, + is_active = 1, + last_updated = now + ) } + } } + // Update Mock Store for backward compatibility during transition + vereine.forEach { remote -> + val id = remote.id.toLongOrNull() ?: return@forEach + val verein = Verein( + id = id, + name = remote.name, + oepsNummer = remote.oepsNummer, + ort = remote.ort, + bundesland = remote.bundesland, + istVeranstalter = true + ) + val existingIdx = Store.vereine.indexOfFirst { it.id == id } + if (existingIdx >= 0) Store.vereine[existingIdx] = verein else Store.vereine.add(verein) + } + } - override fun saveReiter(reiter: List) { - println("[Repository] Speichere ${reiter.size} Reiter in SQLite") - val now = System.currentTimeMillis() - runBlocking { - queries.transaction { - reiter.forEach { remote -> - val id = remote.id.toLongOrNull() ?: return@forEach - queries.upsertReiter( - id = id, - zns_nummer = remote.satznummer, - vorname = remote.vorname ?: "", - nachname = remote.nachname ?: "", - jahrgang = null, // Backend liefert aktuell kein Jahrgang direkt in ZnsRemoteReiter? - geschlecht = null, - nation = remote.nation ?: "AUT", - is_active = 1, - last_updated = now - ) - } - } - } - // Sync to Store + override fun saveReiter(reiter: List) { + println("[Repository] Speichere ${reiter.size} Reiter in SQLite") + val now = System.currentTimeMillis() + runBlocking { + queries.transaction { reiter.forEach { remote -> - val id = remote.id.toLongOrNull() ?: return@forEach - val entry = Reiter( - id = id, - vorname = remote.vorname ?: "", - nachname = remote.nachname ?: "", - satznummer = remote.satznummer ?: "", - oepsNummer = remote.satznummer ?: "", - lizenzKlasse = remote.lizenzKlasse, - nation = remote.nation ?: "AUT", - bundesland = remote.bundesland - ) - val existingIdx = Store.reiter.indexOfFirst { it.id == id } - if (existingIdx >= 0) Store.reiter[existingIdx] = entry else Store.reiter.add(entry) + val id = remote.id.toLongOrNull() ?: return@forEach + queries.upsertReiter( + id = id, + zns_nummer = remote.satznummer, + vorname = remote.vorname, + nachname = remote.nachname, + jahrgang = null, // Backend liefert aktuell kein Jahrgang direkt in ZnsRemoteReiter? + geschlecht = null, + nation = remote.nation ?: "AUT", + is_active = 1, + last_updated = now + ) } + } } + // Sync to Store + reiter.forEach { remote -> + val id = remote.id.toLongOrNull() ?: return@forEach + val entry = Reiter( + id = id, + vorname = remote.vorname, + nachname = remote.nachname, + satznummer = remote.satznummer ?: "", + oepsNummer = remote.satznummer ?: "", + lizenzKlasse = remote.lizenzKlasse, + nation = remote.nation ?: "AUT", + bundesland = remote.bundesland + ) + val existingIdx = Store.reiter.indexOfFirst { it.id == id } + if (existingIdx >= 0) Store.reiter[existingIdx] = entry else Store.reiter.add(entry) + } + } - override fun savePferde(pferde: List) { - println("[Repository] Speichere ${pferde.size} Pferde") + override fun savePferde(pferde: List) { + println("[Repository] Speichere ${pferde.size} Pferde in SQLite") + val now = System.currentTimeMillis() + runBlocking { + queries.transaction { pferde.forEach { remote -> - val id = remote.id.toLongOrNull() ?: return@forEach - val existingIdx = Store.pferde.indexOfFirst { it.id == id } - val entry = Pferd( - id = id, - name = remote.name, - geschlecht = remote.geschlecht, - lebensnummer = remote.lebensnummer, - oepsNummer = remote.kopfnummer - ) - if (existingIdx >= 0) { - Store.pferde[existingIdx] = entry - } else { - Store.pferde.add(entry) - } + val id = remote.id.toLongOrNull() ?: return@forEach + queries.upsertPferd( + id = id, + name = remote.name, + lebensnummer = remote.lebensnummer ?: "", + geschlecht = remote.geschlecht, + farbe = null, + geburtsjahr = null, + oebs_nummer = remote.kopfnummer, + is_active = 1, + last_updated = now + ) } + } } + // Sync to Store + pferde.forEach { remote -> + val id = remote.id.toLongOrNull() ?: return@forEach + val entry = Pferd( + id = id, + name = remote.name, + geschlecht = remote.geschlecht, + lebensnummer = remote.lebensnummer ?: "", + oepsNummer = remote.kopfnummer + ) + val existingIdx = Store.pferde.indexOfFirst { it.id == id } + if (existingIdx >= 0) Store.pferde[existingIdx] = entry else Store.pferde.add(entry) + } + } - override fun saveFunktionaere(funktionaere: List) { - println("[Repository] Speichere ${funktionaere.size} Funktionäre") + override fun saveFunktionaere(funktionaere: List) { + println("[Repository] Speichere ${funktionaere.size} Funktionäre in SQLite") + val now = System.currentTimeMillis() + runBlocking { + queries.transaction { funktionaere.forEach { remote -> - val id = remote.id.toLongOrNull() ?: return@forEach - val existingIdx = Store.funktionaere.indexOfFirst { it.id == id } - val namen = remote.name?.split(" ") ?: listOf("Unbekannt") - val entry = Funktionaer( - id = id, - vorname = namen.firstOrNull() ?: "", - nachname = namen.drop(1).joinToString(" "), - rollen = remote.qualifikationen, - nation = remote.nation ?: "AUT", - bundesland = remote.bundesland - ) - if (existingIdx >= 0) { - Store.funktionaere[existingIdx] = entry - } else { - Store.funktionaere.add(entry) - } + val id = remote.id.toLongOrNull() ?: return@forEach + val namen = remote.name?.split(" ") ?: listOf("Unbekannt") + queries.upsertFunktionaer( + id = id, + vorname = namen.firstOrNull() ?: "", + nachname = namen.drop(1).joinToString(" "), + richter_nummer = null, + disziplinen = remote.qualifikationen.joinToString(","), + qualifikation = null, + email = null, + is_active = 1, + last_updated = now + ) } + } } - - override fun getStats(): MasterdataStats { - val vereinCount = queries.selectAllVereine().executeAsList().size.toLong() - val reiterCount = queries.selectAllReiter().executeAsList().size.toLong() - - val lastUpdate = listOf( - queries.selectAllVereine().executeAsList().maxOfOrNull { it.last_updated } ?: 0L, - queries.selectAllReiter().executeAsList().maxOfOrNull { it.last_updated } ?: 0L - ).maxOrNull() ?: 0L - - val lastImportStr = if (lastUpdate > 0) { - val dt = LocalDateTime.now() // Vereinfacht, idealerweise aus lastUpdate Zeitstempel - dt.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")) - } else "Nie" - - return MasterdataStats( - lastImport = lastImportStr, - vereinCount = vereinCount.toInt(), - reiterCount = reiterCount.toInt(), - pferdCount = Store.pferde.size, - funktionaerCount = Store.funktionaere.size - ) + // Sync to Store + funktionaere.forEach { remote -> + val id = remote.id.toLongOrNull() ?: return@forEach + val namen = remote.name?.split(" ") ?: listOf("Unbekannt") + val entry = Funktionaer( + id = id, + vorname = namen.firstOrNull() ?: "", + nachname = namen.drop(1).joinToString(" "), + rollen = remote.qualifikationen, + nation = remote.nation ?: "AUT", + bundesland = remote.bundesland + ) + val existingIdx = Store.funktionaere.indexOfFirst { it.id == id } + if (existingIdx >= 0) Store.funktionaere[existingIdx] = entry else Store.funktionaere.add(entry) } + } + + override fun getStats(): MasterdataStats { + val vereinCount = queries.selectAllVereine().executeAsList().size.toLong() + val reiterCount = queries.selectAllReiter().executeAsList().size.toLong() + val pferdCount = queries.selectAllPferde().executeAsList().size.toLong() + val funktionaerCount = queries.selectAllFunktionaere().executeAsList().size.toLong() + + val lastUpdate = listOf( + queries.selectAllVereine().executeAsList().maxOfOrNull { it.last_updated } ?: 0L, + queries.selectAllReiter().executeAsList().maxOfOrNull { it.last_updated } ?: 0L, + queries.selectAllPferde().executeAsList().maxOfOrNull { it.last_updated } ?: 0L, + queries.selectAllFunktionaere().executeAsList().maxOfOrNull { it.last_updated } ?: 0L + ).maxOrNull() ?: 0L + + val lastImportStr = if (lastUpdate > 0) { + val dt = LocalDateTime.now() // Vereinfacht, idealerweise aus lastUpdate-Zeitstempeln + dt.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")) + } else "Nie" + + return MasterdataStats( + lastImport = lastImportStr, + vereinCount = vereinCount.toInt(), + reiterCount = reiterCount.toInt(), + pferdCount = pferdCount.toInt(), + funktionaerCount = funktionaerCount.toInt() + ) + } }