Remove deprecated ZnsLegacyParsersTest.kt, synchronize database schema with Exposed domain models (migration V010), add license-related fields to Reiter, integrate updated LicenseMatrixService fallback logic, improve ZnsImportService with file archiving, and add ZNS testing runbook.
This commit is contained in:
parent
e94dc5a803
commit
aa9e2da3a3
|
|
@ -18,7 +18,11 @@ Versionierung folgt [Semantic Versioning](https://semver.org/lang/de/).
|
||||||
### Hinzugefügt
|
### Hinzugefügt
|
||||||
|
|
||||||
- **Core:** Modularisierte ZNS-Parser eingeführt (`ZnsVereinParser`, `ZnsReiterParser`, `ZnsPferdParser`, `ZnsFunktionaerParser`) zur Verbesserung der Wartbarkeit und Unterstützung von Einzelimporten.
|
- **Core:** Modularisierte ZNS-Parser eingeführt (`ZnsVereinParser`, `ZnsReiterParser`, `ZnsPferdParser`, `ZnsFunktionaerParser`) zur Verbesserung der Wartbarkeit und Unterstützung von Einzelimporten.
|
||||||
|
- **Infrastructure:** Datenbank-Migration `V010` hinzugefügt, um das Schema final mit den `Exposed`-Modellen zu synchronisieren.
|
||||||
|
- **Infrastructure:** Datei-Archivierung für hochgeladene ZNS-ZIP-Dateien im `ZnsImportOrchestrator` implementiert.
|
||||||
|
- **Infrastructure:** `ZnsImportService` vollständig auf die neuen spezialisierten Parser umgestellt und als Spring-Bean im Backend registriert.
|
||||||
- **QA:** Umfassende Test-Suite `ZnsParserTest.kt` mit realen ZNS-Daten (Hämmerle, Neuwirth, etc.) erstellt; Korrektur der Extraktions-Logik für Mitgliedsnummern (Position 147) und Funktionär-Daten (RICHT01).
|
- **QA:** Umfassende Test-Suite `ZnsParserTest.kt` mit realen ZNS-Daten (Hämmerle, Neuwirth, etc.) erstellt; Korrektur der Extraktions-Logik für Mitgliedsnummern (Position 147) und Funktionär-Daten (RICHT01).
|
||||||
|
- **QA:** Neue Betriebsanleitung für ZNS-Importer Tests erstellt: `docs/07_Infrastructure/runbooks/ZNS_Importer_Test_Manual.md`.
|
||||||
- **Domain:** Legacy-Spezifikationen für ZNS-Schnittstellen (Import/Export) formalisiert:
|
- **Domain:** Legacy-Spezifikationen für ZNS-Schnittstellen (Import/Export) formalisiert:
|
||||||
- `docs/03_Domain/02_Reference/Legacy_Specs/OETO-2026_Meldestelle_Pflichtenheft_V2.4.md` (Basis-Satzarten A-N)
|
- `docs/03_Domain/02_Reference/Legacy_Specs/OETO-2026_Meldestelle_Pflichtenheft_V2.4.md` (Basis-Satzarten A-N)
|
||||||
- `docs/03_Domain/02_Reference/Legacy_Specs/OETO-2026_Meldestelle_Erweiterung-Schnittstelle_2014.md` (XML-Erweiterung, LinkID-Logik)
|
- `docs/03_Domain/02_Reference/Legacy_Specs/OETO-2026_Meldestelle_Erweiterung-Schnittstelle_2014.md` (XML-Erweiterung, LinkID-Logik)
|
||||||
|
|
@ -30,6 +34,10 @@ Versionierung folgt [Semantic Versioning](https://semver.org/lang/de/).
|
||||||
|
|
||||||
### Behoben
|
### Behoben
|
||||||
|
|
||||||
|
- **Core:** Veraltete `ZnsLegacyParsersTest.kt` entfernt; alle Tests sind nun in `ZnsParserTest.kt` konsolidiert.
|
||||||
|
- **Domain:** Fehlschlagenden `LicenseMatrixServiceTest` behoben; fehlende `reiterLizenz`-Daten in Test-Reitern ergänzt und Fallback-Logik in `LicenseMatrixServiceImpl` für spartenübergreifende Lizenzen (z.B. Springlizenz für Dressur-Basis) stabilisiert.
|
||||||
|
- **Infrastructure:** Fehlschlagenden `RegulationSeedVerificationTest` behoben; Testdaten an das neue Modell (`reiterLizenz` Feld) angepasst.
|
||||||
|
- **Infrastructure:** Kompilierfehler 'Unresolved reference lizenzKlasse' in `ReiterExposedRepository` behoben; fehlendes Feld `lizenzKlasse` zu `ReiterTable` und Datenbank-Migration `V010` hinzugefügt.
|
||||||
- **Onboarding:** `remember` → `rememberSaveable` für `geraetName`, `sharedKey`, `znsStatus` in `OnboardingScreen.kt` (
|
- **Onboarding:** `remember` → `rememberSaveable` für `geraetName`, `sharedKey`, `znsStatus` in `OnboardingScreen.kt` (
|
||||||
Felder gingen bei Zurück-Navigation verloren)
|
Felder gingen bei Zurück-Navigation verloren)
|
||||||
- **AbteilungsRegelService:** CSN-C-NEU Pflicht-Teilungslogik implementiert (≤95 cm: ohne/mit Lizenz; ≥100 cm: R1/R2+);
|
- **AbteilungsRegelService:** CSN-C-NEU Pflicht-Teilungslogik implementiert (≤95 cm: ohne/mit Lizenz; ≥100 cm: R1/R2+);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package at.mocode.masterdata.domain.service
|
package at.mocode.masterdata.domain.service
|
||||||
|
|
||||||
|
import at.mocode.core.domain.model.ReiterLizenzKlasseE
|
||||||
import at.mocode.core.domain.model.SparteE
|
import at.mocode.core.domain.model.SparteE
|
||||||
import at.mocode.masterdata.domain.model.Reiter
|
import at.mocode.masterdata.domain.model.Reiter
|
||||||
import at.mocode.masterdata.domain.model.LicenseMatrixEntry
|
import at.mocode.masterdata.domain.model.LicenseMatrixEntry
|
||||||
|
|
@ -42,6 +43,14 @@ class LicenseMatrixServiceImpl : LicenseMatrixService {
|
||||||
// Suche passenden Eintrag in der Matrix für (Sparte, Lizenzklasse)
|
// Suche passenden Eintrag in der Matrix für (Sparte, Lizenzklasse)
|
||||||
val entry = matrix.find { it.sparte == sparte && it.lizenzKlasse == reiter.lizenzKlasse }
|
val entry = matrix.find { it.sparte == sparte && it.lizenzKlasse == reiter.lizenzKlasse }
|
||||||
?: matrix.find { it.sparte == SparteE.DRESSUR && sparte == SparteE.DRESSUR && it.lizenzKlasse == reiter.lizenzKlasse } // Fallback/Spezial
|
?: matrix.find { it.sparte == SparteE.DRESSUR && sparte == SparteE.DRESSUR && it.lizenzKlasse == reiter.lizenzKlasse } // Fallback/Spezial
|
||||||
|
?: if (reiter.lizenzKlasse == ReiterLizenzKlasseE.R1 ||
|
||||||
|
reiter.lizenzKlasse == ReiterLizenzKlasseE.R2 ||
|
||||||
|
reiter.lizenzKlasse == ReiterLizenzKlasseE.R3 ||
|
||||||
|
reiter.lizenzKlasse == ReiterLizenzKlasseE.R4) {
|
||||||
|
// Fallback für Dressur, wenn man eine Springlizenz hat (R1 gilt oft auch als RD1 etc. in manchen Kontexten,
|
||||||
|
// aber hier schauen wir primär ob die Matrix einen generischen Eintrag hat)
|
||||||
|
matrix.find { it.sparte == sparte && it.lizenzKlasse == ReiterLizenzKlasseE.LIZENZFREI }
|
||||||
|
} else null
|
||||||
|
|
||||||
return entry?.maxTurnierklasseCode
|
return entry?.maxTurnierklasseCode
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -95,6 +95,7 @@ class LicenseMatrixServiceTest {
|
||||||
satznummer = "1",
|
satznummer = "1",
|
||||||
nachname = "R1",
|
nachname = "R1",
|
||||||
vorname = "Reiter",
|
vorname = "Reiter",
|
||||||
|
reiterLizenz = "R1",
|
||||||
lizenzKlasse = ReiterLizenzKlasseE.R1
|
lizenzKlasse = ReiterLizenzKlasseE.R1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -114,6 +115,7 @@ class LicenseMatrixServiceTest {
|
||||||
satznummer = "2",
|
satznummer = "2",
|
||||||
nachname = "RD1",
|
nachname = "RD1",
|
||||||
vorname = "Reiter",
|
vorname = "Reiter",
|
||||||
|
reiterLizenz = "RD1",
|
||||||
lizenzKlasse = ReiterLizenzKlasseE.RD1
|
lizenzKlasse = ReiterLizenzKlasseE.RD1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ import at.mocode.core.domain.model.ReiterLizenzKlasseE
|
||||||
import at.mocode.core.utils.database.DatabaseFactory
|
import at.mocode.core.utils.database.DatabaseFactory
|
||||||
import at.mocode.masterdata.domain.model.Reiter
|
import at.mocode.masterdata.domain.model.Reiter
|
||||||
import at.mocode.masterdata.domain.repository.ReiterRepository
|
import at.mocode.masterdata.domain.repository.ReiterRepository
|
||||||
import at.mocode.masterdata.infrastructure.persistence.LicenseTable.lizenzKlasse
|
|
||||||
import org.jetbrains.exposed.v1.core.ResultRow
|
import org.jetbrains.exposed.v1.core.ResultRow
|
||||||
import org.jetbrains.exposed.v1.core.eq
|
import org.jetbrains.exposed.v1.core.eq
|
||||||
import org.jetbrains.exposed.v1.jdbc.*
|
import org.jetbrains.exposed.v1.jdbc.*
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ object ReiterTable : Table("reiter") {
|
||||||
val feiId = varchar("fei_id", 20).nullable()
|
val feiId = varchar("fei_id", 20).nullable()
|
||||||
val sperrListe = varchar("sperr_liste", 50).nullable()
|
val sperrListe = varchar("sperr_liste", 50).nullable()
|
||||||
val lizenzInfo = varchar("lizenz_info", 100).nullable()
|
val lizenzInfo = varchar("lizenz_info", 100).nullable()
|
||||||
|
val lizenzKlasse = varchar("lizenz_klasse", 20).default("LIZENZFREI")
|
||||||
|
|
||||||
// === ZNS.zip LITENZ01.DAT === ENDE ===
|
// === ZNS.zip LITENZ01.DAT === ENDE ===
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -98,6 +98,7 @@ class RegulationSeedVerificationTest {
|
||||||
satznummer = "123456",
|
satznummer = "123456",
|
||||||
nachname = "Müller",
|
nachname = "Müller",
|
||||||
vorname = "Hans",
|
vorname = "Hans",
|
||||||
|
reiterLizenz = "R1",
|
||||||
lizenzKlasse = ReiterLizenzKlasseE.R1
|
lizenzKlasse = ReiterLizenzKlasseE.R1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,87 @@
|
||||||
|
-- V010: Synchronize Database Schema with Exposed Domain Models
|
||||||
|
-- Harmonisiert die Spaltennamen und Typen mit den aktuellen Kotlin-Definitionen.
|
||||||
|
|
||||||
|
-- 1. Tabelle VEREIN anpassen
|
||||||
|
ALTER TABLE verein RENAME COLUMN name TO verein_name;
|
||||||
|
ALTER TABLE verein DROP COLUMN IF EXISTS kurzname;
|
||||||
|
ALTER TABLE verein DROP COLUMN IF EXISTS oeps_region_nummer;
|
||||||
|
ALTER TABLE verein ADD COLUMN IF NOT EXISTS person_id UUID;
|
||||||
|
ALTER TABLE verein ADD COLUMN IF NOT EXISTS image_url VARCHAR(255);
|
||||||
|
ALTER TABLE verein ADD COLUMN IF NOT EXISTS hausnummer VARCHAR(10);
|
||||||
|
|
||||||
|
-- 2. Tabelle REITER anpassen
|
||||||
|
ALTER TABLE reiter ADD COLUMN IF NOT EXISTS bundesland_nummer INTEGER;
|
||||||
|
ALTER TABLE reiter ADD COLUMN IF NOT EXISTS reiter_lizenz VARCHAR(20);
|
||||||
|
ALTER TABLE reiter ADD COLUMN IF NOT EXISTS startkarte VARCHAR(20);
|
||||||
|
ALTER TABLE reiter ADD COLUMN IF NOT EXISTS fahr_lizenz VARCHAR(20);
|
||||||
|
ALTER TABLE reiter ADD COLUMN IF NOT EXISTS altersklasse_jg_jr_u25 VARCHAR(10);
|
||||||
|
ALTER TABLE reiter ADD COLUMN IF NOT EXISTS altersklasse_y VARCHAR(10);
|
||||||
|
ALTER TABLE reiter ADD COLUMN IF NOT EXISTS mitglieds_nummer INTEGER;
|
||||||
|
ALTER TABLE reiter ADD COLUMN IF NOT EXISTS telefon_nummer VARCHAR(50);
|
||||||
|
ALTER TABLE reiter ADD COLUMN IF NOT EXISTS kader VARCHAR(50);
|
||||||
|
ALTER TABLE reiter ADD COLUMN IF NOT EXISTS last_pay_year INTEGER;
|
||||||
|
ALTER TABLE reiter ADD COLUMN IF NOT EXISTS geschlecht VARCHAR(10);
|
||||||
|
ALTER TABLE reiter ADD COLUMN IF NOT EXISTS sperr_liste VARCHAR(50);
|
||||||
|
ALTER TABLE reiter ADD COLUMN IF NOT EXISTS lizenz_info VARCHAR(100);
|
||||||
|
ALTER TABLE reiter ADD COLUMN IF NOT EXISTS lizenz_klasse VARCHAR(20) DEFAULT 'LIZENZFREI';
|
||||||
|
ALTER TABLE reiter ADD COLUMN IF NOT EXISTS image_url VARCHAR(255);
|
||||||
|
ALTER TABLE reiter ADD COLUMN IF NOT EXISTS website VARCHAR(255);
|
||||||
|
ALTER TABLE reiter ADD COLUMN IF NOT EXISTS strasse VARCHAR(200);
|
||||||
|
ALTER TABLE reiter ADD COLUMN IF NOT EXISTS hausnummer VARCHAR(10);
|
||||||
|
ALTER TABLE reiter ADD COLUMN IF NOT EXISTS plz VARCHAR(10);
|
||||||
|
ALTER TABLE reiter ADD COLUMN IF NOT EXISTS ort VARCHAR(100);
|
||||||
|
ALTER TABLE reiter ADD COLUMN IF NOT EXISTS bundesland VARCHAR(100);
|
||||||
|
|
||||||
|
-- 3. Tabelle HORSE anpassen (in Exposed "HorseTable" aber in SQL "horse")
|
||||||
|
ALTER TABLE horse ADD COLUMN IF NOT EXISTS kopfnummer VARCHAR(4);
|
||||||
|
ALTER TABLE horse ADD COLUMN IF NOT EXISTS geburtsjahr INTEGER;
|
||||||
|
ALTER TABLE horse ADD COLUMN IF NOT EXISTS abstammung VARCHAR(100);
|
||||||
|
ALTER TABLE horse ADD COLUMN IF NOT EXISTS verein_nummer INTEGER;
|
||||||
|
ALTER TABLE horse ADD COLUMN IF NOT EXISTS last_pay_year INTEGER;
|
||||||
|
ALTER TABLE horse ADD COLUMN IF NOT EXISTS vater VARCHAR(200);
|
||||||
|
ALTER TABLE horse ADD COLUMN IF NOT EXISTS fei_pass VARCHAR(50);
|
||||||
|
ALTER TABLE horse ADD COLUMN IF NOT EXISTS satznummer VARCHAR(10);
|
||||||
|
-- Aufräumen von Feldern die nicht im Exposed Model sind (aus V006)
|
||||||
|
ALTER TABLE horse DROP COLUMN IF EXISTS geburtsdatum;
|
||||||
|
ALTER TABLE horse DROP COLUMN IF EXISTS rasse;
|
||||||
|
ALTER TABLE horse DROP COLUMN IF EXISTS besitzer_id;
|
||||||
|
ALTER TABLE horse DROP COLUMN IF EXISTS zuechter_name;
|
||||||
|
ALTER TABLE horse DROP COLUMN IF EXISTS zuchtbuch_nummer;
|
||||||
|
ALTER TABLE horse DROP COLUMN IF EXISTS chip_nummer;
|
||||||
|
ALTER TABLE horse DROP COLUMN IF EXISTS pass_nummer;
|
||||||
|
ALTER TABLE horse DROP COLUMN IF EXISTS oeps_nummer;
|
||||||
|
ALTER TABLE horse DROP COLUMN IF EXISTS fei_nummer;
|
||||||
|
ALTER TABLE horse DROP COLUMN IF EXISTS vater_name;
|
||||||
|
ALTER TABLE horse DROP COLUMN IF EXISTS mutter_name;
|
||||||
|
ALTER TABLE horse DROP COLUMN IF EXISTS mutter_vater_name;
|
||||||
|
ALTER TABLE horse DROP COLUMN IF EXISTS stockmass;
|
||||||
|
|
||||||
|
-- 4. Tabelle FUNKTIONAER anpassen
|
||||||
|
ALTER TABLE funktionaer DROP COLUMN IF EXISTS richter_nummer;
|
||||||
|
ALTER TABLE funktionaer DROP COLUMN IF EXISTS vorname;
|
||||||
|
ALTER TABLE funktionaer DROP COLUMN IF EXISTS nachname;
|
||||||
|
ALTER TABLE funktionaer DROP COLUMN IF EXISTS geburtsdatum;
|
||||||
|
ALTER TABLE funktionaer DROP COLUMN IF EXISTS vereins_nummer;
|
||||||
|
ALTER TABLE funktionaer ADD COLUMN IF NOT EXISTS person_id UUID;
|
||||||
|
ALTER TABLE funktionaer ADD COLUMN IF NOT EXISTS satz_id VARCHAR(1);
|
||||||
|
ALTER TABLE funktionaer ADD COLUMN IF NOT EXISTS satz_nummer INTEGER;
|
||||||
|
ALTER TABLE funktionaer ADD COLUMN IF NOT EXISTS name VARCHAR(200);
|
||||||
|
ALTER TABLE funktionaer ADD COLUMN IF NOT EXISTS image_url VARCHAR(255);
|
||||||
|
ALTER TABLE funktionaer ADD COLUMN IF NOT EXISTS website VARCHAR(255);
|
||||||
|
ALTER TABLE funktionaer ADD COLUMN IF NOT EXISTS strasse VARCHAR(200);
|
||||||
|
ALTER TABLE funktionaer ADD COLUMN IF NOT EXISTS hausnummer VARCHAR(10);
|
||||||
|
ALTER TABLE funktionaer ADD COLUMN IF NOT EXISTS plz VARCHAR(10);
|
||||||
|
ALTER TABLE funktionaer ADD COLUMN IF NOT EXISTS ort VARCHAR(100);
|
||||||
|
ALTER TABLE funktionaer ADD COLUMN IF NOT EXISTS bundesland VARCHAR(100);
|
||||||
|
|
||||||
|
-- 5. Qualifikations-Tabelle für Funktionäre
|
||||||
|
CREATE TABLE IF NOT EXISTS funktionaer_qualifikation (
|
||||||
|
funktionaer_id UUID NOT NULL REFERENCES funktionaer(funktionaer_id),
|
||||||
|
qualifikation VARCHAR(20) NOT NULL,
|
||||||
|
PRIMARY KEY (funktionaer_id, qualifikation)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Indizes (Exposed-Style)
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_horse_satznummer ON horse (satznummer);
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_reiter_satznummer ON reiter (satznummer);
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_funktionaer_satz ON funktionaer (satz_id, satz_nummer);
|
||||||
|
|
@ -1,7 +1,13 @@
|
||||||
package at.mocode.zns.import.service
|
package at.mocode.zns.import.service
|
||||||
|
|
||||||
|
import at.mocode.masterdata.domain.repository.FunktionaerRepository
|
||||||
|
import at.mocode.masterdata.domain.repository.HorseRepository
|
||||||
|
import at.mocode.masterdata.domain.repository.ReiterRepository
|
||||||
|
import at.mocode.masterdata.domain.repository.VereinRepository
|
||||||
|
import at.mocode.zns.importer.ZnsImportService
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication
|
import org.springframework.boot.autoconfigure.SpringBootApplication
|
||||||
import org.springframework.boot.runApplication
|
import org.springframework.boot.runApplication
|
||||||
|
import org.springframework.context.annotation.Bean
|
||||||
import org.springframework.context.annotation.ComponentScan
|
import org.springframework.context.annotation.ComponentScan
|
||||||
|
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
|
|
@ -11,7 +17,18 @@ import org.springframework.context.annotation.ComponentScan
|
||||||
"at.mocode.masterdata.infrastructure"
|
"at.mocode.masterdata.infrastructure"
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
class ZnsImportServiceApplication
|
class ZnsImportServiceApplication {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
fun znsImportService(
|
||||||
|
vereinRepository: VereinRepository,
|
||||||
|
reiterRepository: ReiterRepository,
|
||||||
|
horseRepository: HorseRepository,
|
||||||
|
funktionaerRepository: FunktionaerRepository
|
||||||
|
): ZnsImportService {
|
||||||
|
return ZnsImportService(vereinRepository, reiterRepository, horseRepository, funktionaerRepository)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun main(args: Array<String>) {
|
fun main(args: Array<String>) {
|
||||||
runApplication<ZnsImportServiceApplication>(*args)
|
runApplication<ZnsImportServiceApplication>(*args)
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import java.util.concurrent.ConcurrentHashMap
|
||||||
import kotlin.uuid.ExperimentalUuidApi
|
import kotlin.uuid.ExperimentalUuidApi
|
||||||
import kotlin.uuid.Uuid
|
import kotlin.uuid.Uuid
|
||||||
|
|
||||||
enum class ImportJobStatus { AUSSTEHEND, ENTPACKEN, LADE_VEREINE, LADE_REITER, LADE_PFERDE, LADE_RICHTER, ABGESCHLOSSEN, FEHLER }
|
enum class ImportJobStatus { AUSSTEHEND, ENTPACKEN, VERARBEITUNG, ABGESCHLOSSEN, FEHLER }
|
||||||
|
|
||||||
data class ImportJob(
|
data class ImportJob(
|
||||||
val jobId: String,
|
val jobId: String,
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,21 @@
|
||||||
package at.mocode.zns.import.service.job
|
package at.mocode.zns.import.service.job
|
||||||
|
|
||||||
import at.mocode.masterdata.domain.repository.VereinRepository
|
|
||||||
import at.mocode.masterdata.domain.repository.HorseRepository
|
|
||||||
import at.mocode.masterdata.domain.repository.FunktionaerRepository
|
|
||||||
import at.mocode.masterdata.domain.repository.ReiterRepository
|
|
||||||
import at.mocode.zns.importer.ZnsImportService
|
|
||||||
import at.mocode.zns.importer.ZnsImportResult
|
import at.mocode.zns.importer.ZnsImportResult
|
||||||
|
import at.mocode.zns.importer.ZnsImportService
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import org.springframework.beans.factory.annotation.Value
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
|
import java.io.File
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
import java.time.format.DateTimeFormatter
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
class ZnsImportOrchestrator(
|
class ZnsImportOrchestrator(
|
||||||
private val vereinRepository: VereinRepository,
|
private val service: ZnsImportService,
|
||||||
private val reiterRepository: ReiterRepository,
|
private val jobRegistry: ImportJobRegistry,
|
||||||
private val horseRepository: HorseRepository,
|
@Value("\${app.zns.archive-path}") private val archivePath: String
|
||||||
private val funktionaerRepository: FunktionaerRepository,
|
|
||||||
private val jobRegistry: ImportJobRegistry
|
|
||||||
) {
|
) {
|
||||||
private val scope = CoroutineScope(Dispatchers.IO)
|
private val scope = CoroutineScope(Dispatchers.IO)
|
||||||
|
|
||||||
|
|
@ -26,38 +24,15 @@ class ZnsImportOrchestrator(
|
||||||
runCatching {
|
runCatching {
|
||||||
jobRegistry.aktualisiereStatus(jobId, ImportJobStatus.ENTPACKEN, "Entpacke ZIP-Datei...", 5)
|
jobRegistry.aktualisiereStatus(jobId, ImportJobStatus.ENTPACKEN, "Entpacke ZIP-Datei...", 5)
|
||||||
|
|
||||||
val service = ZnsImportService(vereinRepository, reiterRepository, horseRepository, funktionaerRepository)
|
// Archivierung
|
||||||
|
archiviereZip(zipBytes)
|
||||||
|
|
||||||
val dateien = service.extrahiereDateien(zipBytes.inputStream())
|
jobRegistry.aktualisiereStatus(jobId, ImportJobStatus.VERARBEITUNG, "Verarbeite ZNS-Daten...", 20)
|
||||||
|
val result = service.importiereZip(zipBytes.inputStream())
|
||||||
jobRegistry.aktualisiereStatus(jobId, ImportJobStatus.LADE_VEREINE, "Lade Vereine...", 20)
|
|
||||||
val vereineResult = service.importiereVereine(dateien["VEREIN01.DAT"] ?: emptyList(), mutableListOf())
|
|
||||||
|
|
||||||
jobRegistry.aktualisiereStatus(jobId, ImportJobStatus.LADE_REITER, "Lade Reiter...", 40)
|
|
||||||
val reiterResult = service.importiereReiter(dateien["LIZENZ01.DAT"] ?: emptyList(), mutableListOf(), mutableListOf())
|
|
||||||
|
|
||||||
jobRegistry.aktualisiereStatus(jobId, ImportJobStatus.LADE_PFERDE, "Lade Pferde...", 60)
|
|
||||||
val pferdeResult = service.importierePferde(dateien["PFERDE01.DAT"] ?: emptyList(), mutableListOf())
|
|
||||||
|
|
||||||
jobRegistry.aktualisiereStatus(jobId, ImportJobStatus.LADE_RICHTER, "Lade Funktionäre...", 80)
|
|
||||||
val richterResult = service.importiereFunktionaere(dateien["RICHT01.DAT"] ?: emptyList(), mutableListOf(), mutableListOf())
|
|
||||||
|
|
||||||
val result = ZnsImportResult(
|
|
||||||
vereineImportiert = vereineResult.first,
|
|
||||||
vereineAktualisiert = vereineResult.second,
|
|
||||||
reiterImportiert = reiterResult.first,
|
|
||||||
reiterAktualisiert = reiterResult.second,
|
|
||||||
pferdeImportiert = pferdeResult.first,
|
|
||||||
pferdeAktualisiert = pferdeResult.second,
|
|
||||||
richterImportiert = richterResult.first,
|
|
||||||
richterAktualisiert = richterResult.second
|
|
||||||
)
|
|
||||||
|
|
||||||
jobRegistry.aktualisiereStatus(
|
jobRegistry.aktualisiereStatus(
|
||||||
jobId, ImportJobStatus.ABGESCHLOSSEN,
|
jobId, ImportJobStatus.ABGESCHLOSSEN,
|
||||||
"Import abgeschlossen: ${result.vereineImportiert} Vereine, " +
|
result.zusammenfassung(), 100
|
||||||
"${result.reiterImportiert} Reiter, ${result.pferdeImportiert} Pferde, " +
|
|
||||||
"${result.richterImportiert} Richter importiert.", 100
|
|
||||||
)
|
)
|
||||||
|
|
||||||
jobRegistry.findeJob(jobId)?.let { job ->
|
jobRegistry.findeJob(jobId)?.let { job ->
|
||||||
|
|
@ -70,4 +45,18 @@ class ZnsImportOrchestrator(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun archiviereZip(bytes: ByteArray) {
|
||||||
|
try {
|
||||||
|
val dir = File(archivePath)
|
||||||
|
if (!dir.exists()) dir.mkdirs()
|
||||||
|
|
||||||
|
val timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"))
|
||||||
|
val archiveFile = File(dir, "zns_import_$timestamp.zip")
|
||||||
|
archiveFile.writeBytes(bytes)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// Archivierung schlägt fehl -> Loggen aber Import nicht abbrechen
|
||||||
|
println("[WARN] Archivierung der ZNS-Datei fehlgeschlagen: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,3 +42,5 @@ management:
|
||||||
|
|
||||||
app:
|
app:
|
||||||
service-name: ${spring.application.name}
|
service-name: ${spring.application.name}
|
||||||
|
zns:
|
||||||
|
archive-path: ${ZNS_ARCHIVE_PATH:/data/zns/archive}
|
||||||
|
|
|
||||||
|
|
@ -1,242 +0,0 @@
|
||||||
package at.mocode.zns.parser
|
|
||||||
|
|
||||||
import at.mocode.core.domain.model.PferdeGeschlechtE
|
|
||||||
import kotlin.test.Test
|
|
||||||
import kotlin.test.assertEquals
|
|
||||||
import kotlin.test.assertNotNull
|
|
||||||
|
|
||||||
class ZnsLegacyParsersTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `parseVerein should extract VEREIN01 correctly`() {
|
|
||||||
val line = "1234Reitverein Test "
|
|
||||||
val result = ZnsLegacyParsers.parseVerein(line)
|
|
||||||
|
|
||||||
assertNotNull(result)
|
|
||||||
assertEquals("1234", result.vereinsNummer)
|
|
||||||
assertEquals("Reitverein Test", result.vereinName)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `parseLizenz should extract LIZENZ01 correctly`() {
|
|
||||||
val sb = StringBuilder()
|
|
||||||
sb.append("123456") // 1-6
|
|
||||||
sb.append("Mustermann ") // 7-56
|
|
||||||
sb.append("Max ") // 57-81
|
|
||||||
sb.append("01") // 82-83
|
|
||||||
sb.append("Reitverein Wien ") // 84-133
|
|
||||||
sb.append("AUT") // 134-136
|
|
||||||
sb.append("R1 ") // 137-140
|
|
||||||
sb.append(" ") // 141-146 (leer)
|
|
||||||
sb.append("00000001") // 147-154 (mitgliedsNummer)
|
|
||||||
sb.append("0676 12345678 ") // 155-176 (telefonNummer length 22)
|
|
||||||
sb.append("2026") // 177-180 (lastPayYear)
|
|
||||||
sb.append("M") // 181 (geschlecht)
|
|
||||||
sb.append("19800101") // 182-189 (geburtsdatum)
|
|
||||||
sb.append("1000000001") // 190-199 (feiId length 10)
|
|
||||||
sb.append("S") // 200 (sperrListe)
|
|
||||||
sb.append("INFO1 ") // 201-210 (lizenzInfo)
|
|
||||||
|
|
||||||
val result = ZnsLegacyParsers.parseLizenz(sb.toString())
|
|
||||||
assertNotNull(result)
|
|
||||||
assertEquals("123456", result.satznummer)
|
|
||||||
assertEquals("Mustermann", result.nachname)
|
|
||||||
assertEquals("Max", result.vorname)
|
|
||||||
assertEquals(1, result.bundeslandNummer)
|
|
||||||
assertEquals("Reitverein Wien", result.vereinsName)
|
|
||||||
assertEquals("AUT", result.nation)
|
|
||||||
assertEquals("R1", result.reiterLizenz)
|
|
||||||
assertEquals(2026, result.lastPayYear)
|
|
||||||
assertEquals("M", result.geschlecht)
|
|
||||||
assertEquals("1980-01-01", result.geburtsdatum.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `parsePferd should extract PFERDE01 correctly`() {
|
|
||||||
val sb = StringBuilder()
|
|
||||||
sb.append("A123")
|
|
||||||
sb.append("Black Beauty ")
|
|
||||||
sb.append("123456789")
|
|
||||||
sb.append("W")
|
|
||||||
sb.append("2010")
|
|
||||||
|
|
||||||
while (sb.length < 201) {
|
|
||||||
sb.append(" ")
|
|
||||||
}
|
|
||||||
sb.append("0000000001")
|
|
||||||
|
|
||||||
val result = ZnsLegacyParsers.parsePferd(sb.toString())
|
|
||||||
assertNotNull(result)
|
|
||||||
assertEquals("A123", result.kopfnummer)
|
|
||||||
assertEquals("0000000001", result.satznummer)
|
|
||||||
assertEquals("Black Beauty", result.pferdeName)
|
|
||||||
assertEquals("123456789", result.lebensnummer)
|
|
||||||
assertEquals(PferdeGeschlechtE.WALLACH, result.geschlecht)
|
|
||||||
assertEquals(2010, result.geburtsjahr)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `parseFunktionaer should extract RICHT01 correctly for Richter`() {
|
|
||||||
// Real example from RICHT01.dat
|
|
||||||
val line = "X010128Zitterbart Rainer PI-A"
|
|
||||||
val result = ZnsLegacyParsers.parseFunktionaer(line)
|
|
||||||
|
|
||||||
assertNotNull(result)
|
|
||||||
assertEquals("X", result.satzId)
|
|
||||||
assertEquals(10128, result.satzNummer)
|
|
||||||
assertEquals("Zitterbart Rainer", result.name)
|
|
||||||
assertEquals(listOf("PI-A"), result.qualifikationen)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `parseFunktionaer should extract RICHT01 correctly with more examples`() {
|
|
||||||
// X139552Mc Mullen Elizabeth DIOR
|
|
||||||
val line1 = "X139552Mc Mullen Elizabeth DIOR"
|
|
||||||
val result1 = ZnsLegacyParsers.parseFunktionaer(line1)
|
|
||||||
assertNotNull(result1)
|
|
||||||
assertEquals("X", result1.satzId)
|
|
||||||
assertEquals(139552, result1.satzNummer)
|
|
||||||
assertEquals("Mc Mullen Elizabeth", result1.name)
|
|
||||||
assertEquals(listOf("DIOR"), result1.qualifikationen)
|
|
||||||
|
|
||||||
// X014346Schubert Renate DM,DPF,GAR-SP,SPF,SS*
|
|
||||||
val line2 = "X014346Schubert Renate DM,DPF,GAR-SP,SPF,SS*"
|
|
||||||
val result2 = ZnsLegacyParsers.parseFunktionaer(line2)
|
|
||||||
assertNotNull(result2)
|
|
||||||
assertEquals(14346, result2.satzNummer)
|
|
||||||
assertEquals("Schubert Renate", result2.name)
|
|
||||||
assertEquals(listOf("DM", "DPF", "GAR-SP", "SPF", "SS*"), result2.qualifikationen)
|
|
||||||
|
|
||||||
// Y002211Salusek Andreas Christian P3,PL2
|
|
||||||
val line3 = "Y002211Salusek Andreas Christian P3,PL2"
|
|
||||||
val result3 = ZnsLegacyParsers.parseFunktionaer(line3)
|
|
||||||
assertNotNull(result3)
|
|
||||||
assertEquals("Y", result3.satzId)
|
|
||||||
assertEquals(2211, result3.satzNummer)
|
|
||||||
assertEquals("Salusek Andreas Christian", result3.name)
|
|
||||||
assertEquals(listOf("P3", "PL2"), result3.qualifikationen)
|
|
||||||
|
|
||||||
// X001061Kager Franz DPF,DSGP,GAR-SP,GAR-VS,SPF
|
|
||||||
val line4 = "X001061Kager Franz DPF,DSGP,GAR-SP,GAR-VS,SPF"
|
|
||||||
val result4 = ZnsLegacyParsers.parseFunktionaer(line4)
|
|
||||||
assertNotNull(result4)
|
|
||||||
assertEquals("X", result4.satzId)
|
|
||||||
assertEquals(1061, result4.satzNummer)
|
|
||||||
assertEquals("Kager Franz", result4.name)
|
|
||||||
assertEquals(listOf("DPF", "DSGP", "GAR-SP", "GAR-VS", "SPF"), result4.qualifikationen)
|
|
||||||
|
|
||||||
// X001112Keiblinger Brigitta DPF,DSGP,SPF,SS,VS,VSILEV1"
|
|
||||||
val line5 = "X001112Keiblinger Brigitta DPF,DSGP,SPF,SS,VS,VSILEV1"
|
|
||||||
val result5 = ZnsLegacyParsers.parseFunktionaer(line5)
|
|
||||||
assertNotNull(result5)
|
|
||||||
assertEquals("X", result5.satzId)
|
|
||||||
assertEquals(1112, result5.satzNummer)
|
|
||||||
assertEquals("Keiblinger Brigitta", result5.name)
|
|
||||||
assertEquals(listOf("DPF", "DSGP", "SPF", "SS", "VS", "VSILEV1"), result5.qualifikationen)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `parseFunktionaer should return null for invalid lines`() {
|
|
||||||
assertEquals(null, ZnsLegacyParsers.parseFunktionaer(""))
|
|
||||||
assertEquals(null, ZnsLegacyParsers.parseFunktionaer("Z123456Test"))
|
|
||||||
assertEquals(null, ZnsLegacyParsers.parseFunktionaer("XABCDEFTest"))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `parsePferd should extract real PFERDE01 correctly`() {
|
|
||||||
// Real example from PFERDE01.dat (line length approx 211 characters)
|
|
||||||
val line = "9D56Viola B 000000017S2005Brauner Tschech. WB 10952024Tanja Kuntner 535 Latinus 5637401268"
|
|
||||||
val result = ZnsLegacyParsers.parsePferd(line)
|
|
||||||
|
|
||||||
assertNotNull(result)
|
|
||||||
assertEquals("9D56", result.kopfnummer)
|
|
||||||
assertEquals("Viola B", result.pferdeName)
|
|
||||||
assertEquals("000000017", result.lebensnummer)
|
|
||||||
assertEquals(PferdeGeschlechtE.STUTE, result.geschlecht)
|
|
||||||
assertEquals(2005, result.geburtsjahr)
|
|
||||||
assertEquals("Brauner", result.farbe)
|
|
||||||
assertEquals("Tschech. WB", result.abstammung)
|
|
||||||
assertEquals(1095, result.vereinNummer)
|
|
||||||
assertEquals(2024, result.lastPayYear)
|
|
||||||
assertEquals("Tanja Kuntner", result.verantwortlichePersonId)
|
|
||||||
assertEquals("535 Latinus", result.vater)
|
|
||||||
assertEquals("5637401268", result.satznummer)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `parsePferd should extract shortened PFERDE01 correctly`() {
|
|
||||||
// A line that ends after the name
|
|
||||||
val line = "1234Fuchur"
|
|
||||||
val result = ZnsLegacyParsers.parsePferd(line)
|
|
||||||
|
|
||||||
assertNotNull(result)
|
|
||||||
assertEquals("1234", result.kopfnummer)
|
|
||||||
assertEquals("Fuchur", result.pferdeName)
|
|
||||||
assertEquals(null, result.satznummer)
|
|
||||||
assertEquals(null, result.lebensnummer)
|
|
||||||
assertEquals(PferdeGeschlechtE.UNBEKANNT, result.geschlecht)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `parseLizenz should extract real LIZENZ01 correctly for Ebner Sarah`() {
|
|
||||||
// Real example from user:
|
|
||||||
// "100365Ebner Sarah 09Hubertus Voltigier Reit- und Fahrverein AUTR2S3 903801690699 18109450 2025W1990100310137032 R2S3 "
|
|
||||||
val line = "100365Ebner Sarah 09Hubertus Voltigier Reit- und Fahrverein AUTR2S3 903801690699 18109450 2025W1990100310137032 R2S3 "
|
|
||||||
|
|
||||||
val result = ZnsLegacyParsers.parseLizenz(line)
|
|
||||||
|
|
||||||
assertNotNull(result)
|
|
||||||
assertEquals("100365", result.satznummer)
|
|
||||||
assertEquals("Ebner", result.nachname)
|
|
||||||
assertEquals("Sarah", result.vorname)
|
|
||||||
assertEquals(9, result.bundeslandNummer)
|
|
||||||
assertEquals("Hubertus Voltigier Reit- und Fahrverein", result.vereinsName)
|
|
||||||
assertEquals("AUT", result.nation)
|
|
||||||
assertEquals("R2S3", result.reiterLizenz)
|
|
||||||
assertEquals(90380169, result.mitgliedsNummer)
|
|
||||||
assertEquals("0699 18109450", result.telefonNummer)
|
|
||||||
assertEquals(2025, result.lastPayYear)
|
|
||||||
assertEquals("W", result.geschlecht)
|
|
||||||
assertEquals("1990-10-03", result.geburtsdatum.toString())
|
|
||||||
assertEquals("10137032", result.feiId)
|
|
||||||
assertEquals("R2S3", result.lizenzInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `parseLizenz should extract real LIZENZ01 correctly`() {
|
|
||||||
// Real example from LIZENZ01.dat (second line of file)
|
|
||||||
val sb = StringBuilder()
|
|
||||||
sb.append("000010") // 1-6
|
|
||||||
sb.append("Aichinger ") // 7-56
|
|
||||||
sb.append("Ewald ") // 57-81
|
|
||||||
sb.append("02") // 82-83
|
|
||||||
sb.append("Reitverein Geiger-Amstetten ") // 84-133
|
|
||||||
sb.append("AUT") // 134-136
|
|
||||||
sb.append("R2 ") // 137-140
|
|
||||||
sb.append(" ") // 141-146 (leer)
|
|
||||||
sb.append("20660700") // 147-154 (mitgliedsNummer)
|
|
||||||
sb.append("0676 4825910 ") // 155-176 (telefon)
|
|
||||||
sb.append("2023") // 177-180 (lastPayYear)
|
|
||||||
sb.append("M") // 181 (geschlecht)
|
|
||||||
sb.append("19571010") // 182-189 (geburtsdatum)
|
|
||||||
sb.append(" ") // 190-199 (feiId length 10)
|
|
||||||
sb.append(" ") // 200 (sperrliste)
|
|
||||||
sb.append(" ") // 201-210 (lizenzinfo)
|
|
||||||
|
|
||||||
val result = ZnsLegacyParsers.parseLizenz(sb.toString())
|
|
||||||
|
|
||||||
assertNotNull(result)
|
|
||||||
assertEquals("000010", result.satznummer)
|
|
||||||
assertEquals("Aichinger", result.nachname)
|
|
||||||
assertEquals("Ewald", result.vorname)
|
|
||||||
assertEquals(2, result.bundeslandNummer)
|
|
||||||
assertEquals("Reitverein Geiger-Amstetten", result.vereinsName)
|
|
||||||
assertEquals("AUT", result.nation)
|
|
||||||
assertEquals("R2", result.reiterLizenz)
|
|
||||||
assertEquals(20660700, result.mitgliedsNummer)
|
|
||||||
assertEquals("0676 4825910", result.telefonNummer)
|
|
||||||
assertEquals(2023, result.lastPayYear)
|
|
||||||
assertEquals("M", result.geschlecht)
|
|
||||||
assertEquals("1957-10-10", result.geburtsdatum.toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
104
docs/07_Infrastructure/runbooks/ZNS_Importer_Test_Manual.md
Normal file
104
docs/07_Infrastructure/runbooks/ZNS_Importer_Test_Manual.md
Normal file
|
|
@ -0,0 +1,104 @@
|
||||||
|
# 🐎 ZNS-Importer Test-Anleitung (Runbook)
|
||||||
|
|
||||||
|
Diese Anleitung beschreibt den Prozess, um den ZNS-Importer in einer lokalen Entwicklungsumgebung zu starten und mit Postman vollständig zu testen.
|
||||||
|
|
||||||
|
## 1. Infrastruktur starten (Docker)
|
||||||
|
|
||||||
|
Bevor der Service gestartet werden kann, müssen die Basis-Dienste (Datenbank, Discovery, Auth) laufen.
|
||||||
|
|
||||||
|
### 1.1 Docker Container starten
|
||||||
|
Öffne ein Terminal im Projekt-Root (`/mocode/Meldestelle`) und führe folgenden Befehl aus:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose up -d postgres consul keycloak valkey zipkin
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.2 Status prüfen
|
||||||
|
Stelle sicher, dass alle Container "healthy" sind:
|
||||||
|
* **PostgreSQL:** `localhost:5432`
|
||||||
|
* **Consul UI:** [http://localhost:8500](http://localhost:8500) (Hier muss der Status aller Dienste grün sein)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Backend Services starten
|
||||||
|
|
||||||
|
Der ZNS-Importer benötigt den `masterdata-service` (für die Datenbank-Tabellen) und den `zns-import-service`.
|
||||||
|
|
||||||
|
### 2.1 Masterdata Service (DB-Migrationen)
|
||||||
|
Starte den Masterdata-Service, damit die Tabellen (Verein, Reiter, Pferd, Funktionär) angelegt werden:
|
||||||
|
```bash
|
||||||
|
./gradlew :backend:services:masterdata:masterdata-service:bootRun
|
||||||
|
```
|
||||||
|
*Warte bis im Log erscheint: `Started MasterdataServiceApplication`*
|
||||||
|
|
||||||
|
### 2.2 ZNS-Import Service
|
||||||
|
Starte den Import-Service in einem neuen Terminal:
|
||||||
|
```bash
|
||||||
|
./gradlew :backend:services:zns-import:zns-import-service:bootRun
|
||||||
|
```
|
||||||
|
*Warte bis im Log erscheint: `Started ZnsImportServiceApplication`*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Postman Test-Ablauf
|
||||||
|
|
||||||
|
### 3.1 Health-Check (Optionaler Smoke-Test)
|
||||||
|
Prüfe ob der Service erreichbar ist:
|
||||||
|
* **Methode:** `GET`
|
||||||
|
* **URL:** `http://localhost:8095/actuator/health`
|
||||||
|
* **Erwartetes Ergebnis:** `{"status":"UP"}`
|
||||||
|
|
||||||
|
### 3.2 ZNS-ZIP Upload (Import starten)
|
||||||
|
Dieser Schritt lädt die ZNS-Daten (ZIP-Datei mit .DAT Files) hoch und startet den asynchronen Prozess.
|
||||||
|
|
||||||
|
* **Methode:** `POST`
|
||||||
|
* **URL:** `http://localhost:8095/api/v1/import/zns`
|
||||||
|
* **Body:** `form-data`
|
||||||
|
* Key: `file`
|
||||||
|
* Type: `File`
|
||||||
|
* Value: Wähle deine `ZNS_EXPORT.zip` aus.
|
||||||
|
* **Erwartete Antwort (202 Accepted):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"jobId": "7d3a...-..."
|
||||||
|
}
|
||||||
|
```
|
||||||
|
*(Kopiere die `jobId` für den nächsten Schritt!)*
|
||||||
|
|
||||||
|
### 3.3 Status Polling
|
||||||
|
Da der Import im Hintergrund läuft, musst du den Status abfragen:
|
||||||
|
|
||||||
|
* **Methode:** `GET`
|
||||||
|
* **URL:** `http://localhost:8095/api/v1/import/zns/{jobId}/status`
|
||||||
|
* **Erwartete Antwort (währenddessen):** `status: "VERARBEITUNG"`
|
||||||
|
* **Erwartete Antwort (Erfolg):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"jobId": "...",
|
||||||
|
"status": "ABGESCHLOSSEN",
|
||||||
|
"fortschritt": 100,
|
||||||
|
"meldungen": ["Import erfolgreich: 4 Dateien verarbeitet (150 Reiter, 200 Pferde, ...)"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Erfolgskontrolle (Nach dem Import)
|
||||||
|
|
||||||
|
### 4.1 Datenbank prüfen (pgAdmin / SQL)
|
||||||
|
Prüfe in der Tabelle `reiter` oder `horse`, ob Daten vorhanden sind:
|
||||||
|
```sql
|
||||||
|
SELECT count(*) FROM reiter;
|
||||||
|
SELECT * FROM horse LIMIT 10;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 Archiv-Ordner
|
||||||
|
Prüfe ob die Datei erfolgreich archiviert wurde:
|
||||||
|
* Pfad (laut `application.yaml`): `/data/zns/archive` (oder der konfigurierte Pfad)
|
||||||
|
* Die Datei sollte `zns_import_YYYYMMDD_HHMMSS.zip` heißen.
|
||||||
|
|
||||||
|
## 5. Troubleshooting
|
||||||
|
|
||||||
|
* **404 Not Found:** Prüfe ob der Service auf Port 8095 läuft.
|
||||||
|
* **500 Internal Server Error:** Prüfe die Konsolenausgabe des `zns-import-service` auf Stacktraces.
|
||||||
|
* **Import bleibt bei 0% hängen:** Prüfe ob die ZIP-Datei die richtigen Dateinamen enthält (z.B. `VEREIN01.DAT`, `LIZENZ01.DAT`).
|
||||||
Loading…
Reference in New Issue
Block a user