feat(zns-import): DAT-Dateisupport hinzugefügt, Fehlerbehebung und UI-Anpassungen
Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
This commit is contained in:
@@ -62,3 +62,12 @@ Parameter sind hiermit fixiert und umgesetzt.
|
|||||||
`management.endpoint.health.probes.enabled: true`).
|
`management.endpoint.health.probes.enabled: true`).
|
||||||
* **Optimierung**: Healthcheck in `dc-backend.yaml` auf `wget --no-verbose` umgestellt für bessere
|
* **Optimierung**: Healthcheck in `dc-backend.yaml` auf `wget --no-verbose` umgestellt für bessere
|
||||||
Diagnosemöglichkeiten.
|
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.
|
||||||
|
|||||||
+1
-1
@@ -35,7 +35,7 @@ class ConnectivityTracker : KoinComponent {
|
|||||||
|
|
||||||
private suspend fun checkConnection(): Boolean {
|
private suspend fun checkConnection(): Boolean {
|
||||||
return try {
|
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
|
response.status.value in 200..299
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
false
|
false
|
||||||
|
|||||||
+11
-5
@@ -24,7 +24,7 @@ import kotlin.time.Duration.Companion.milliseconds
|
|||||||
|
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
internal data class JobIdResponse(val jobId: String)
|
data class ImportStartResponse(val jobId: String)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
internal data class JobStatusResponse(
|
internal data class JobStatusResponse(
|
||||||
@@ -57,8 +57,12 @@ class ZnsImportViewModel(
|
|||||||
override fun startImport(mode: String) {
|
override fun startImport(mode: String) {
|
||||||
val filePath = state.selectedFilePath ?: return
|
val filePath = state.selectedFilePath ?: return
|
||||||
val file = File(filePath)
|
val file = File(filePath)
|
||||||
if (!file.exists() || !file.name.endsWith(".zip", ignoreCase = true)) {
|
if (!file.exists() || !(file.name.endsWith(".zip", ignoreCase = true) || file.name.endsWith(
|
||||||
state = state.copy(errorMessage = "Bitte eine gültige .zip-Datei auswählen.")
|
".dat",
|
||||||
|
ignoreCase = true
|
||||||
|
))
|
||||||
|
) {
|
||||||
|
state = state.copy(errorMessage = "Bitte eine gültige .zip oder .dat-Datei auswählen.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,15 +76,17 @@ class ZnsImportViewModel(
|
|||||||
val response: HttpResponse = httpClient.post("${NetworkConfig.baseUrl}/api/v1/import/zns") {
|
val response: HttpResponse = httpClient.post("${NetworkConfig.baseUrl}/api/v1/import/zns") {
|
||||||
parameter("mode", mode)
|
parameter("mode", mode)
|
||||||
if (token != null) header(HttpHeaders.Authorization, "Bearer $token")
|
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 {
|
setBody(MultiPartFormDataContent(formData {
|
||||||
append("file", file.readBytes(), Headers.build {
|
append("file", file.readBytes(), Headers.build {
|
||||||
append(HttpHeaders.ContentDisposition, "filename=\"${file.name}\"")
|
append(HttpHeaders.ContentDisposition, "filename=\"${file.name}\"")
|
||||||
append(HttpHeaders.ContentType, "application/zip")
|
append(HttpHeaders.ContentType, contentType)
|
||||||
})
|
})
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
if (response.status == HttpStatusCode.Accepted) {
|
if (response.status == HttpStatusCode.Accepted) {
|
||||||
val body = json.decodeFromString<JobIdResponse>(response.bodyAsText())
|
val body = json.decodeFromString<ImportStartResponse>(response.bodyAsText())
|
||||||
state = state.copy(isUploading = false, jobId = body.jobId, jobStatus = "AUSSTEHEND")
|
state = state.copy(isUploading = false, jobId = body.jobId, jobStatus = "AUSSTEHEND")
|
||||||
startPolling(body.jobId)
|
startPolling(body.jobId)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+10
-6
@@ -1587,7 +1587,11 @@ fun ZnsImportWizardSection(
|
|||||||
Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
Icon(Icons.Default.CloudUpload, contentDescription = null, tint = MaterialTheme.colorScheme.primary)
|
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))
|
Spacer(Modifier.weight(1f))
|
||||||
if (state.isFinished || state.errorMessage != null) {
|
if (state.isFinished || state.errorMessage != null) {
|
||||||
TextButton(onClick = onReset) {
|
TextButton(onClick = onReset) {
|
||||||
@@ -1609,14 +1613,14 @@ fun ZnsImportWizardSection(
|
|||||||
value = state.selectedFilePath ?: "",
|
value = state.selectedFilePath ?: "",
|
||||||
onValueChange = {},
|
onValueChange = {},
|
||||||
readOnly = true,
|
readOnly = true,
|
||||||
placeholder = { Text("ZNS.zip auswählen...") },
|
placeholder = { Text("ZNS-Datei auswählen (.zip, .dat)...") },
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
textStyle = MaterialTheme.typography.bodySmall
|
textStyle = MaterialTheme.typography.bodySmall
|
||||||
)
|
)
|
||||||
Button(
|
Button(
|
||||||
onClick = {
|
onClick = {
|
||||||
val path = pickZipFile()
|
val path = pickZnsFile()
|
||||||
if (path != null) onFileSelect(path)
|
if (path != null) onFileSelect(path)
|
||||||
},
|
},
|
||||||
enabled = !state.isUploading
|
enabled = !state.isUploading
|
||||||
@@ -1714,10 +1718,10 @@ fun ZnsImportWizardSection(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun pickZipFile(): String? {
|
private fun pickZnsFile(): String? {
|
||||||
val chooser = JFileChooser()
|
val chooser = JFileChooser()
|
||||||
chooser.dialogTitle = "ZNS.zip auswählen"
|
chooser.dialogTitle = "ZNS-Datei auswählen"
|
||||||
chooser.fileFilter = FileNameExtensionFilter("ZIP-Archiv (*.zip)", "zip")
|
chooser.fileFilter = FileNameExtensionFilter("ZNS Dateien (*.zip, *.dat)", "zip", "dat")
|
||||||
chooser.isAcceptAllFileFilterUsed = false
|
chooser.isAcceptAllFileFilterUsed = false
|
||||||
val result = chooser.showOpenDialog(null)
|
val result = chooser.showOpenDialog(null)
|
||||||
return if (result == JFileChooser.APPROVE_OPTION) chooser.selectedFile.absolutePath else null
|
return if (result == JFileChooser.APPROVE_OPTION) chooser.selectedFile.absolutePath else null
|
||||||
|
|||||||
Reference in New Issue
Block a user