### feat: implementiere SQLite-Integration und Repository-Refactoring
Desktop CI — Headless Tests & Build / Compose Desktop — Tests (headless) & Build (push) Failing after 58s
Build and Publish Docker Images / build-and-push (., backend/infrastructure/gateway/Dockerfile, api-gateway, api-gateway) (push) Successful in 6m0s
Build and Publish Docker Images / build-and-push (., backend/services/ping/Dockerfile, ping-service, ping-service) (push) Successful in 6m10s
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Failing after 2m0s
Build and Publish Docker Images / build-and-push (., config/docker/keycloak/Dockerfile, keycloak, keycloak) (push) Successful in 1m55s

- Erstelle Persistenz-Layer mit SQLite-Tabellen für `Verein` und `Reiter` inkl. Queries.
- Entferne Mock-Daten in `ReiterViewModel` und nutze Repository-Injektion.
- Integriere neue Tabellen und Queries im `DesktopMasterdataRepository`.
- Erweitere `VeranstalterWizardViewModel` um lokale Suche mit SQLite-Queries.
- Harmonisiere Feldnamen (`remoteReiterResults`) über alle Module hinweg.
- Aktualisiere DI-Module (`VeranstalterModule`, `ReiterModule`, `DesktopModule`) mit SQLite-Injektionen.
- Refaktor UI-Komponenten und Screens (`ReiterScreen`, `StammdatenImportScreen`) mit neuer Logik.
This commit is contained in:
2026-04-22 02:20:51 +02:00
parent f18b002f4e
commit e0b1ce8836
22 changed files with 301 additions and 73 deletions
@@ -1,13 +1,13 @@
package at.mocode.frontend.shell.desktop.di
import at.mocode.frontend.shell.desktop.navigation.DesktopNavigationPort
import at.mocode.frontend.shell.desktop.repository.DesktopMasterdataRepository
import at.mocode.frontend.core.auth.data.local.AuthTokenManager
import at.mocode.frontend.core.domain.models.User
import at.mocode.frontend.core.domain.repository.MasterdataRepository
import at.mocode.frontend.core.navigation.CurrentUserProvider
import at.mocode.frontend.core.navigation.DeepLinkHandler
import at.mocode.frontend.core.navigation.NavigationPort
import at.mocode.frontend.shell.desktop.navigation.DesktopNavigationPort
import at.mocode.frontend.shell.desktop.repository.DesktopMasterdataRepository
import org.koin.dsl.module
/**
@@ -34,5 +34,5 @@ val desktopModule = module {
single<NavigationPort> { get<DesktopNavigationPort>() }
single<CurrentUserProvider> { DesktopCurrentUserProvider(get()) }
single { DeepLinkHandler(get(), get()) }
single<MasterdataRepository> { DesktopMasterdataRepository() }
single<MasterdataRepository> { DesktopMasterdataRepository(get()) }
}
@@ -6,51 +6,90 @@ import at.mocode.frontend.core.domain.zns.ZnsRemoteFunktionaer
import at.mocode.frontend.core.domain.zns.ZnsRemotePferd
import at.mocode.frontend.core.domain.zns.ZnsRemoteReiter
import at.mocode.frontend.core.domain.zns.ZnsRemoteVerein
import at.mocode.frontend.core.localdb.AppDatabase
import at.mocode.frontend.shell.desktop.data.*
import kotlinx.coroutines.runBlocking
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
class DesktopMasterdataRepository : MasterdataRepository {
class DesktopMasterdataRepository(
private val db: AppDatabase
) : MasterdataRepository {
private val queries = db.meldestelleDbQueries
override fun saveVereine(vereine: List<ZnsRemoteVerein>) {
println("[Repository] Speichere ${vereine.size} Vereine")
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
vereine.forEach { remote ->
val id = remote.id.toLongOrNull() ?: return@forEach
val existingIdx = Store.vereine.indexOfFirst { it.id == id }
val verein = Verein(
id = id,
name = remote.name,
oepsNummer = remote.oepsNummer,
name = remote.name ?: "Unbekannt",
oepsNummer = remote.oepsNummer ?: "",
ort = remote.ort,
bundesland = remote.bundesland,
istVeranstalter = true // In der Meldestelle sind importierte ZNS-Vereine meist potenzielle Veranstalter
istVeranstalter = true
)
if (existingIdx >= 0) {
Store.vereine[existingIdx] = verein
} else {
Store.vereine.add(verein)
}
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<ZnsRemoteReiter>) {
println("[Repository] Speichere ${reiter.size} Reiter")
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
reiter.forEach { remote ->
val id = remote.id.toLongOrNull() ?: return@forEach
val existingIdx = Store.reiter.indexOfFirst { it.id == id }
val entry = Reiter(
id = id,
vorname = remote.vorname,
nachname = remote.nachname,
satznummer = remote.satznummer,
oepsNummer = remote.satznummer, // Oft identisch oder Mapping nötig
vorname = remote.vorname ?: "",
nachname = remote.nachname ?: "",
satznummer = remote.satznummer ?: "",
oepsNummer = remote.satznummer ?: "",
lizenzKlasse = remote.lizenzKlasse,
nation = remote.nation ?: "AUT",
bundesland = remote.bundesland
)
if (existingIdx >= 0) {
Store.reiter[existingIdx] = entry
} else {
Store.reiter.add(entry)
}
val existingIdx = Store.reiter.indexOfFirst { it.id == id }
if (existingIdx >= 0) Store.reiter[existingIdx] = entry else Store.reiter.add(entry)
}
}
@@ -97,10 +136,23 @@ class DesktopMasterdataRepository : MasterdataRepository {
}
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 = "2026-04-20 18:45", // Mock-Wert könnte aus Settings kommen
vereinCount = Store.vereine.size,
reiterCount = Store.reiter.size,
lastImport = lastImportStr,
vereinCount = vereinCount.toInt(),
reiterCount = reiterCount.toInt(),
pferdCount = Store.pferde.size,
funktionaerCount = Store.funktionaere.size
)