diff --git a/docs/99_Journal/2026-04-16_ZNS-First-Wizard-Strategy.md b/docs/99_Journal/2026-04-16_ZNS-First-Wizard-Strategy.md index 5ad683fe..46999eea 100644 --- a/docs/99_Journal/2026-04-16_ZNS-First-Wizard-Strategy.md +++ b/docs/99_Journal/2026-04-16_ZNS-First-Wizard-Strategy.md @@ -62,3 +62,12 @@ Parameter sind hiermit fixiert und umgesetzt. `management.endpoint.health.probes.enabled: true`). * **Optimierung**: Healthcheck in `dc-backend.yaml` auf `wget --no-verbose` umgestellt für bessere Diagnosemöglichkeiten. + +### Nachtrag: 2026-04-16 15:30 - Connectivity & Serialization Fixes + +* **Connectivity**: `ConnectivityTracker` wurde auf den korrekten Health-Pfad `/api/ping/health` umgestellt. Der Footer + zeigt nun korrekt den Online-Status ("Cloud synchronisiert") an. +* **Serialization**: Behebung des Fehlers `Serializer for class 'JobIdResponse' is not found`. Die DTO-Klasse wurde im + Frontend in `ImportStartResponse` umbenannt (passend zum Backend) und die Sichtbarkeit auf `public` erhöht. +* **Flexibilität**: Der ZNS-Importer unterstützt nun sowohl `.zip` als auch `.dat` Dateien (z.B. direkte + `VEREIN01.dat`). Die UI (`pickZnsFile`) und das ViewModel wurden entsprechend erweitert. diff --git a/frontend/core/network/src/commonMain/kotlin/at/mocode/frontend/core/network/ConnectivityTracker.kt b/frontend/core/network/src/commonMain/kotlin/at/mocode/frontend/core/network/ConnectivityTracker.kt index c4870400..e34db40c 100644 --- a/frontend/core/network/src/commonMain/kotlin/at/mocode/frontend/core/network/ConnectivityTracker.kt +++ b/frontend/core/network/src/commonMain/kotlin/at/mocode/frontend/core/network/ConnectivityTracker.kt @@ -35,7 +35,7 @@ class ConnectivityTracker : KoinComponent { private suspend fun checkConnection(): Boolean { return try { - val response = client.get(NetworkConfig.baseUrl.trimEnd('/') + "/ping") + val response = client.get(NetworkConfig.baseUrl.trimEnd('/') + "/api/ping/health") response.status.value in 200..299 } catch (e: Exception) { false diff --git a/frontend/features/zns-import-feature/src/jvmMain/kotlin/at/mocode/zns/feature/ZnsImportViewModel.kt b/frontend/features/zns-import-feature/src/jvmMain/kotlin/at/mocode/zns/feature/ZnsImportViewModel.kt index 65683ef5..25af497b 100644 --- a/frontend/features/zns-import-feature/src/jvmMain/kotlin/at/mocode/zns/feature/ZnsImportViewModel.kt +++ b/frontend/features/zns-import-feature/src/jvmMain/kotlin/at/mocode/zns/feature/ZnsImportViewModel.kt @@ -24,7 +24,7 @@ import kotlin.time.Duration.Companion.milliseconds @Serializable -internal data class JobIdResponse(val jobId: String) +data class ImportStartResponse(val jobId: String) @Serializable internal data class JobStatusResponse( @@ -57,8 +57,12 @@ class ZnsImportViewModel( override fun startImport(mode: String) { val filePath = state.selectedFilePath ?: return val file = File(filePath) - if (!file.exists() || !file.name.endsWith(".zip", ignoreCase = true)) { - state = state.copy(errorMessage = "Bitte eine gültige .zip-Datei auswählen.") + if (!file.exists() || !(file.name.endsWith(".zip", ignoreCase = true) || file.name.endsWith( + ".dat", + ignoreCase = true + )) + ) { + state = state.copy(errorMessage = "Bitte eine gültige .zip oder .dat-Datei auswählen.") return } @@ -72,15 +76,17 @@ class ZnsImportViewModel( val response: HttpResponse = httpClient.post("${NetworkConfig.baseUrl}/api/v1/import/zns") { parameter("mode", mode) if (token != null) header(HttpHeaders.Authorization, "Bearer $token") + val contentType = + if (file.name.endsWith(".zip", ignoreCase = true)) "application/zip" else "application/octet-stream" setBody(MultiPartFormDataContent(formData { append("file", file.readBytes(), Headers.build { append(HttpHeaders.ContentDisposition, "filename=\"${file.name}\"") - append(HttpHeaders.ContentType, "application/zip") + append(HttpHeaders.ContentType, contentType) }) })) } if (response.status == HttpStatusCode.Accepted) { - val body = json.decodeFromString(response.bodyAsText()) + val body = json.decodeFromString(response.bodyAsText()) state = state.copy(isUploading = false, jobId = body.jobId, jobStatus = "AUSSTEHEND") startPolling(body.jobId) } else { diff --git a/frontend/shells/meldestelle-desktop/src/jvmMain/kotlin/at/mocode/desktop/v2/VeranstaltungScreens.kt b/frontend/shells/meldestelle-desktop/src/jvmMain/kotlin/at/mocode/desktop/v2/VeranstaltungScreens.kt index 10958d2e..349a0a2c 100644 --- a/frontend/shells/meldestelle-desktop/src/jvmMain/kotlin/at/mocode/desktop/v2/VeranstaltungScreens.kt +++ b/frontend/shells/meldestelle-desktop/src/jvmMain/kotlin/at/mocode/desktop/v2/VeranstaltungScreens.kt @@ -1587,7 +1587,11 @@ fun ZnsImportWizardSection( Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) { Icon(Icons.Default.CloudUpload, contentDescription = null, tint = MaterialTheme.colorScheme.primary) - Text("ZNS-Stammdaten Import (ZIP)", style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold) + Text( + "ZNS-Stammdaten Import (ZIP/DAT)", + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold + ) Spacer(Modifier.weight(1f)) if (state.isFinished || state.errorMessage != null) { TextButton(onClick = onReset) { @@ -1609,14 +1613,14 @@ fun ZnsImportWizardSection( value = state.selectedFilePath ?: "", onValueChange = {}, readOnly = true, - placeholder = { Text("ZNS.zip auswählen...") }, + placeholder = { Text("ZNS-Datei auswählen (.zip, .dat)...") }, modifier = Modifier.weight(1f), singleLine = true, textStyle = MaterialTheme.typography.bodySmall ) Button( onClick = { - val path = pickZipFile() + val path = pickZnsFile() if (path != null) onFileSelect(path) }, enabled = !state.isUploading @@ -1714,10 +1718,10 @@ fun ZnsImportWizardSection( } } -private fun pickZipFile(): String? { +private fun pickZnsFile(): String? { val chooser = JFileChooser() - chooser.dialogTitle = "ZNS.zip auswählen" - chooser.fileFilter = FileNameExtensionFilter("ZIP-Archiv (*.zip)", "zip") + chooser.dialogTitle = "ZNS-Datei auswählen" + chooser.fileFilter = FileNameExtensionFilter("ZNS Dateien (*.zip, *.dat)", "zip", "dat") chooser.isAcceptAllFileFilterUsed = false val result = chooser.showOpenDialog(null) return if (result == JFileChooser.APPROVE_OPTION) chooser.selectedFile.absolutePath else null