### feat: erweitere Stammdaten-Integration
- **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.
This commit is contained in:
parent
d4cc0eb77d
commit
98c241fc64
|
|
@ -5,12 +5,18 @@
|
||||||
- **Daten-Fuel:** Vollständige Umstellung von Reiter-Mocks auf `KtorReiterRepository`. Die 48.753 Reiter sind nun via API erreichbar.
|
- **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).
|
- **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).
|
- **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
|
## 🛠️ Durchgeführte Änderungen
|
||||||
### Frontend (Desktop & Common)
|
### 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.
|
- **VereinFeatureModule & ReiterModule:** Umstellung auf `named("apiClient")`, um den authentifizierten Ktor-Client zu nutzen.
|
||||||
- **KtorReiterRepository:** Neue Implementierung zur Anbindung der Reiter-Stammdaten an das Backend.
|
- **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
|
### Backend & DevOps
|
||||||
- **masterdata-service (application.yml):** `health-check-port` auf 8086 (Spring Actuator) und Service-Port auf 8091 (Ktor) gesetzt.
|
- **masterdata-service (application.yml):** `health-check-port` auf 8086 (Spring Actuator) und Service-Port auf 8091 (Ktor) gesetzt.
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,30 @@ CREATE TABLE LocalReiter (
|
||||||
last_updated INTEGER NOT NULL
|
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
|
-- Verein Queries
|
||||||
upsertVerein:
|
upsertVerein:
|
||||||
INSERT OR REPLACE INTO LocalVerein(id, oebs_nummer, name, ort, plz, bundesland, is_active, last_updated)
|
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 ('%' || ? || '%')
|
WHERE nachname LIKE ('%' || ? || '%') OR vorname LIKE ('%' || ? || '%') OR zns_nummer LIKE ('%' || ? || '%')
|
||||||
ORDER BY nachname ASC, vorname ASC;
|
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:
|
deleteAllVereine:
|
||||||
DELETE FROM LocalVerein;
|
DELETE FROM LocalVerein;
|
||||||
|
|
||||||
deleteAllReiter:
|
deleteAllReiter:
|
||||||
DELETE FROM LocalReiter;
|
DELETE FROM LocalReiter;
|
||||||
|
|
||||||
|
deleteAllPferde:
|
||||||
|
DELETE FROM LocalPferd;
|
||||||
|
|
||||||
|
deleteAllFunktionaere:
|
||||||
|
DELETE FROM LocalFunktionaer;
|
||||||
|
|
|
||||||
|
|
@ -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<List<Funktionaer>> = flow {
|
||||||
|
try {
|
||||||
|
val response: List<Funktionaer> = client.get("/api/v1/masterdata/funktionaere").body()
|
||||||
|
emit(response)
|
||||||
|
} catch (_: Exception) {
|
||||||
|
emit(emptyList())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun searchFunktionaere(query: String): List<Funktionaer> {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,18 +1,12 @@
|
||||||
package at.mocode.frontend.features.funktionaer.di
|
package at.mocode.frontend.features.funktionaer.di
|
||||||
|
|
||||||
import at.mocode.frontend.features.funktionaer.domain.Funktionaer
|
import at.mocode.frontend.features.funktionaer.data.KtorFunktionaerRepository
|
||||||
import at.mocode.frontend.features.funktionaer.presentation.*
|
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
|
import org.koin.dsl.module
|
||||||
|
|
||||||
val funktionaerModule = module {
|
val funktionaerModule = module {
|
||||||
single<FunktionaerRepository> { MockFunktionaerRepository() }
|
single<FunktionaerRepository> { KtorFunktionaerRepository(get(named("apiClient"))) }
|
||||||
factory { FunktionaerViewModel(get()) }
|
factory { FunktionaerViewModel(get()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
class MockFunktionaerRepository : FunktionaerRepository {
|
|
||||||
override suspend fun list(): List<Funktionaer> = 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)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
package at.mocode.frontend.features.funktionaer.domain
|
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
interface FunktionaerRepository {
|
||||||
|
fun getFunktionaere(): Flow<List<Funktionaer>>
|
||||||
|
suspend fun searchFunktionaere(query: String): List<Funktionaer>
|
||||||
|
suspend fun getFunktionaerById(id: Long): Funktionaer?
|
||||||
|
suspend fun saveFunktionaer(funktionaer: Funktionaer)
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,7 @@ package at.mocode.frontend.features.funktionaer.presentation
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import at.mocode.frontend.features.funktionaer.domain.Funktionaer
|
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.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
@ -51,10 +52,6 @@ sealed interface FunktionaerIntent {
|
||||||
data object ClearError : FunktionaerIntent
|
data object ClearError : FunktionaerIntent
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FunktionaerRepository {
|
|
||||||
suspend fun list(): List<Funktionaer>
|
|
||||||
}
|
|
||||||
|
|
||||||
class FunktionaerViewModel(
|
class FunktionaerViewModel(
|
||||||
private val repo: FunktionaerRepository,
|
private val repo: FunktionaerRepository,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
@ -115,11 +112,12 @@ class FunktionaerViewModel(
|
||||||
reduce { it.copy(isLoading = true, errorMessage = null) }
|
reduce { it.copy(isLoading = true, errorMessage = null) }
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
try {
|
try {
|
||||||
val items = repo.list()
|
repo.getFunktionaere().collect { items ->
|
||||||
reduce { cur ->
|
reduce { cur ->
|
||||||
val filtered = filterList(items, cur.searchQuery)
|
val filtered = filterList(items, cur.searchQuery)
|
||||||
cur.copy(isLoading = false, list = items, filtered = filtered)
|
cur.copy(isLoading = false, list = items, filtered = filtered)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} catch (t: Throwable) {
|
} catch (t: Throwable) {
|
||||||
reduce { it.copy(isLoading = false, errorMessage = t.message ?: "Fehler beim Laden") }
|
reduce { it.copy(isLoading = false, errorMessage = t.message ?: "Fehler beim Laden") }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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<List<Pferd>> = flow {
|
||||||
|
try {
|
||||||
|
val response: List<Pferd> = client.get("/api/v1/masterdata/pferde").body()
|
||||||
|
emit(response)
|
||||||
|
} catch (_: Exception) {
|
||||||
|
emit(emptyList())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun searchPferde(query: String): List<Pferd> {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,12 @@
|
||||||
package at.mocode.frontend.features.pferde.di
|
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 at.mocode.frontend.features.pferde.presentation.PferdeViewModel
|
||||||
|
import org.koin.core.qualifier.named
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
|
||||||
val pferdeModule = module {
|
val pferdeModule = module {
|
||||||
factory { PferdeViewModel() }
|
single<PferdRepository> { KtorPferdRepository(get(named("apiClient"))) }
|
||||||
|
factory { PferdeViewModel(get()) }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
package at.mocode.frontend.features.pferde.domain
|
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
interface PferdRepository {
|
||||||
|
fun getPferde(): Flow<List<Pferd>>
|
||||||
|
suspend fun searchPferde(query: String): List<Pferd>
|
||||||
|
suspend fun getPferdById(id: String): Pferd?
|
||||||
|
suspend fun savePferd(pferd: Pferd)
|
||||||
|
}
|
||||||
|
|
@ -21,7 +21,7 @@ import at.mocode.frontend.features.pferde.domain.PferdeStatus
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun PferdeScreen(
|
fun PferdeScreen(
|
||||||
viewModel: PferdeViewModel = PferdeViewModel()
|
viewModel: PferdeViewModel
|
||||||
) {
|
) {
|
||||||
val uiState = viewModel.uiState
|
val uiState = viewModel.uiState
|
||||||
|
|
||||||
|
|
@ -158,7 +158,11 @@ fun PferdCard(
|
||||||
Spacer(Modifier.height(16.dp))
|
Spacer(Modifier.height(16.dp))
|
||||||
|
|
||||||
Row(modifier = Modifier.fillMaxWidth()) {
|
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))
|
DetailItem(label = "ÖPS-Nr.", value = pferd.oepsNummer ?: "-", modifier = Modifier.weight(1f))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -390,10 +394,7 @@ private fun PferdeEditorContent(
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun PferdeScreenPreviewContent() {
|
fun PferdeScreenPreviewContent() {
|
||||||
val viewModel = PferdeViewModel()
|
// Preview uses a placeholder/mock in actual use, but for compilation:
|
||||||
at.mocode.frontend.core.designsystem.theme.AppTheme {
|
// We can't easily create a real repo here without DI.
|
||||||
Surface {
|
// This part might need koinInject() or a manual mock if used in real previews.
|
||||||
PferdeScreen(viewModel = viewModel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,15 +4,19 @@ import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
import at.mocode.frontend.features.pferde.domain.Geschlecht
|
import at.mocode.frontend.features.pferde.domain.Geschlecht
|
||||||
import at.mocode.frontend.features.pferde.domain.Pferd
|
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 at.mocode.frontend.features.pferde.domain.PferdeStatus
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* UI-State für die Pferde-Verwaltung.
|
* UI-State für die Pferde-Verwaltung.
|
||||||
*/
|
*/
|
||||||
data class PferdeUiState(
|
data class PferdeUiState(
|
||||||
val searchResults: List<Pferd> = emptyList(),
|
val searchResults: List<Pferd> = emptyList(),
|
||||||
|
val allPferde: List<Pferd> = emptyList(),
|
||||||
val searchQuery: String = "",
|
val searchQuery: String = "",
|
||||||
val selectedPferd: Pferd? = null,
|
val selectedPferd: Pferd? = null,
|
||||||
val isEditing: Boolean = false,
|
val isEditing: Boolean = false,
|
||||||
|
|
@ -33,7 +37,10 @@ data class PferdeUiState(
|
||||||
/**
|
/**
|
||||||
* ViewModel für die Pferde-Verwaltung.
|
* 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())
|
var uiState by mutableStateOf(PferdeUiState())
|
||||||
protected set
|
protected set
|
||||||
|
|
||||||
|
|
@ -44,35 +51,31 @@ open class PferdeViewModel(initialLoad: Boolean = true) : ViewModel() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadPferde() {
|
private fun loadPferde() {
|
||||||
val mockData = listOf(
|
uiState = uiState.copy(isLoading = true)
|
||||||
Pferd("1", "Bella", "1A23", "040001234567801", Geschlecht.STUTE, "Braun", 2015, PferdeStatus.AKTIV),
|
viewModelScope.launch {
|
||||||
Pferd("2", "Casanova", "2B45", "040001234567802", Geschlecht.WALLACH, "Schimmel", 2012, PferdeStatus.AKTIV),
|
repo.getPferde().collect { items ->
|
||||||
Pferd("3", "Spirit", "3C67", "040001234567803", Geschlecht.HENGST, "Rappe", 2018, PferdeStatus.AKTIV),
|
uiState = uiState.copy(
|
||||||
Pferd("4", "Lucky", "4D89", "040001234567804", Geschlecht.WALLACH, "Fuchs", 2010, PferdeStatus.VERKAUFT)
|
allPferde = items,
|
||||||
|
searchResults = if (uiState.searchQuery.isBlank()) items else filterList(items, uiState.searchQuery),
|
||||||
|
isLoading = false
|
||||||
)
|
)
|
||||||
uiState = uiState.copy(searchResults = mockData)
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onSearchQueryChange(query: String) {
|
fun onSearchQueryChange(query: String) {
|
||||||
uiState = uiState.copy(searchQuery = query)
|
uiState = uiState.copy(searchQuery = query)
|
||||||
val allPferde = listOf(
|
uiState = uiState.copy(searchResults = filterList(uiState.allPferde, query))
|
||||||
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)
|
|
||||||
)
|
|
||||||
|
|
||||||
val filtered = if (query.isBlank()) {
|
private fun filterList(list: List<Pferd>, query: String): List<Pferd> {
|
||||||
allPferde
|
if (query.isBlank()) return list
|
||||||
} else {
|
return list.filter {
|
||||||
allPferde.filter {
|
|
||||||
it.name.contains(query, ignoreCase = true) ||
|
it.name.contains(query, ignoreCase = true) ||
|
||||||
it.lebensnummer.contains(query, ignoreCase = true) ||
|
it.lebensnummer.contains(query, ignoreCase = true) ||
|
||||||
(it.kopfNummer?.contains(query, ignoreCase = true) ?: false)
|
(it.kopfNummer?.contains(query, ignoreCase = true) ?: false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
uiState = uiState.copy(searchResults = filtered)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun selectPferd(pferd: Pferd) {
|
fun selectPferd(pferd: Pferd) {
|
||||||
uiState = uiState.copy(
|
uiState = uiState.copy(
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ import java.time.LocalDateTime
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
|
|
||||||
class DesktopMasterdataRepository(
|
class DesktopMasterdataRepository(
|
||||||
private val db: AppDatabase
|
db: AppDatabase
|
||||||
) : MasterdataRepository {
|
) : MasterdataRepository {
|
||||||
|
|
||||||
private val queries = db.meldestelleDbQueries
|
private val queries = db.meldestelleDbQueries
|
||||||
|
|
@ -27,8 +27,8 @@ class DesktopMasterdataRepository(
|
||||||
val id = remote.id.toLongOrNull() ?: return@forEach
|
val id = remote.id.toLongOrNull() ?: return@forEach
|
||||||
queries.upsertVerein(
|
queries.upsertVerein(
|
||||||
id = id,
|
id = id,
|
||||||
oebs_nummer = remote.oepsNummer ?: "",
|
oebs_nummer = remote.oepsNummer,
|
||||||
name = remote.name ?: "Unbekannt",
|
name = remote.name,
|
||||||
ort = remote.ort,
|
ort = remote.ort,
|
||||||
plz = null, // Falls vom Backend geliefert, hier mappen
|
plz = null, // Falls vom Backend geliefert, hier mappen
|
||||||
bundesland = remote.bundesland,
|
bundesland = remote.bundesland,
|
||||||
|
|
@ -43,8 +43,8 @@ class DesktopMasterdataRepository(
|
||||||
val id = remote.id.toLongOrNull() ?: return@forEach
|
val id = remote.id.toLongOrNull() ?: return@forEach
|
||||||
val verein = Verein(
|
val verein = Verein(
|
||||||
id = id,
|
id = id,
|
||||||
name = remote.name ?: "Unbekannt",
|
name = remote.name,
|
||||||
oepsNummer = remote.oepsNummer ?: "",
|
oepsNummer = remote.oepsNummer,
|
||||||
ort = remote.ort,
|
ort = remote.ort,
|
||||||
bundesland = remote.bundesland,
|
bundesland = remote.bundesland,
|
||||||
istVeranstalter = true
|
istVeranstalter = true
|
||||||
|
|
@ -64,8 +64,8 @@ class DesktopMasterdataRepository(
|
||||||
queries.upsertReiter(
|
queries.upsertReiter(
|
||||||
id = id,
|
id = id,
|
||||||
zns_nummer = remote.satznummer,
|
zns_nummer = remote.satznummer,
|
||||||
vorname = remote.vorname ?: "",
|
vorname = remote.vorname,
|
||||||
nachname = remote.nachname ?: "",
|
nachname = remote.nachname,
|
||||||
jahrgang = null, // Backend liefert aktuell kein Jahrgang direkt in ZnsRemoteReiter?
|
jahrgang = null, // Backend liefert aktuell kein Jahrgang direkt in ZnsRemoteReiter?
|
||||||
geschlecht = null,
|
geschlecht = null,
|
||||||
nation = remote.nation ?: "AUT",
|
nation = remote.nation ?: "AUT",
|
||||||
|
|
@ -80,8 +80,8 @@ class DesktopMasterdataRepository(
|
||||||
val id = remote.id.toLongOrNull() ?: return@forEach
|
val id = remote.id.toLongOrNull() ?: return@forEach
|
||||||
val entry = Reiter(
|
val entry = Reiter(
|
||||||
id = id,
|
id = id,
|
||||||
vorname = remote.vorname ?: "",
|
vorname = remote.vorname,
|
||||||
nachname = remote.nachname ?: "",
|
nachname = remote.nachname,
|
||||||
satznummer = remote.satznummer ?: "",
|
satznummer = remote.satznummer ?: "",
|
||||||
oepsNummer = remote.satznummer ?: "",
|
oepsNummer = remote.satznummer ?: "",
|
||||||
lizenzKlasse = remote.lizenzKlasse,
|
lizenzKlasse = remote.lizenzKlasse,
|
||||||
|
|
@ -94,30 +94,66 @@ class DesktopMasterdataRepository(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun savePferde(pferde: List<ZnsRemotePferd>) {
|
override fun savePferde(pferde: List<ZnsRemotePferd>) {
|
||||||
println("[Repository] Speichere ${pferde.size} Pferde")
|
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
|
||||||
|
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 ->
|
pferde.forEach { remote ->
|
||||||
val id = remote.id.toLongOrNull() ?: return@forEach
|
val id = remote.id.toLongOrNull() ?: return@forEach
|
||||||
val existingIdx = Store.pferde.indexOfFirst { it.id == id }
|
|
||||||
val entry = Pferd(
|
val entry = Pferd(
|
||||||
id = id,
|
id = id,
|
||||||
name = remote.name,
|
name = remote.name,
|
||||||
geschlecht = remote.geschlecht,
|
geschlecht = remote.geschlecht,
|
||||||
lebensnummer = remote.lebensnummer,
|
lebensnummer = remote.lebensnummer ?: "",
|
||||||
oepsNummer = remote.kopfnummer
|
oepsNummer = remote.kopfnummer
|
||||||
)
|
)
|
||||||
if (existingIdx >= 0) {
|
val existingIdx = Store.pferde.indexOfFirst { it.id == id }
|
||||||
Store.pferde[existingIdx] = entry
|
if (existingIdx >= 0) Store.pferde[existingIdx] = entry else Store.pferde.add(entry)
|
||||||
} else {
|
|
||||||
Store.pferde.add(entry)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun saveFunktionaere(funktionaere: List<ZnsRemoteFunktionaer>) {
|
override fun saveFunktionaere(funktionaere: List<ZnsRemoteFunktionaer>) {
|
||||||
println("[Repository] Speichere ${funktionaere.size} Funktionäre")
|
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 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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Sync to Store
|
||||||
funktionaere.forEach { remote ->
|
funktionaere.forEach { remote ->
|
||||||
val id = remote.id.toLongOrNull() ?: return@forEach
|
val id = remote.id.toLongOrNull() ?: return@forEach
|
||||||
val existingIdx = Store.funktionaere.indexOfFirst { it.id == id }
|
|
||||||
val namen = remote.name?.split(" ") ?: listOf("Unbekannt")
|
val namen = remote.name?.split(" ") ?: listOf("Unbekannt")
|
||||||
val entry = Funktionaer(
|
val entry = Funktionaer(
|
||||||
id = id,
|
id = id,
|
||||||
|
|
@ -127,25 +163,26 @@ class DesktopMasterdataRepository(
|
||||||
nation = remote.nation ?: "AUT",
|
nation = remote.nation ?: "AUT",
|
||||||
bundesland = remote.bundesland
|
bundesland = remote.bundesland
|
||||||
)
|
)
|
||||||
if (existingIdx >= 0) {
|
val existingIdx = Store.funktionaere.indexOfFirst { it.id == id }
|
||||||
Store.funktionaere[existingIdx] = entry
|
if (existingIdx >= 0) Store.funktionaere[existingIdx] = entry else Store.funktionaere.add(entry)
|
||||||
} else {
|
|
||||||
Store.funktionaere.add(entry)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getStats(): MasterdataStats {
|
override fun getStats(): MasterdataStats {
|
||||||
val vereinCount = queries.selectAllVereine().executeAsList().size.toLong()
|
val vereinCount = queries.selectAllVereine().executeAsList().size.toLong()
|
||||||
val reiterCount = queries.selectAllReiter().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(
|
val lastUpdate = listOf(
|
||||||
queries.selectAllVereine().executeAsList().maxOfOrNull { it.last_updated } ?: 0L,
|
queries.selectAllVereine().executeAsList().maxOfOrNull { it.last_updated } ?: 0L,
|
||||||
queries.selectAllReiter().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
|
).maxOrNull() ?: 0L
|
||||||
|
|
||||||
val lastImportStr = if (lastUpdate > 0) {
|
val lastImportStr = if (lastUpdate > 0) {
|
||||||
val dt = LocalDateTime.now() // Vereinfacht, idealerweise aus lastUpdate Zeitstempel
|
val dt = LocalDateTime.now() // Vereinfacht, idealerweise aus lastUpdate-Zeitstempeln
|
||||||
dt.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"))
|
dt.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"))
|
||||||
} else "Nie"
|
} else "Nie"
|
||||||
|
|
||||||
|
|
@ -153,8 +190,8 @@ class DesktopMasterdataRepository(
|
||||||
lastImport = lastImportStr,
|
lastImport = lastImportStr,
|
||||||
vereinCount = vereinCount.toInt(),
|
vereinCount = vereinCount.toInt(),
|
||||||
reiterCount = reiterCount.toInt(),
|
reiterCount = reiterCount.toInt(),
|
||||||
pferdCount = Store.pferde.size,
|
pferdCount = pferdCount.toInt(),
|
||||||
funktionaerCount = Store.funktionaere.size
|
funktionaerCount = funktionaerCount.toInt()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user