feat(billing): introduce billing domain and service infrastructure

- **Billing Domain:**
  - Added Kotlin Multiplatform project with domain models (`TeilnehmerKonto`, `Buchung`, `BuchungsTyp`) to represent billing entities.
  - Defined serialization strategies using `InstantSerializer`.

- **Service Implementation:**
  - Introduced `BillingServiceApplication` as the main entry point for the billing service.
  - Developed `TeilnehmerKontoService` for account management and transactions.

- **Persistence Layer:**
  - Implemented Exposed repositories (`ExposedTeilnehmerKontoRepository`, `ExposedBillingRepositories`) for database interaction.
  - Added table definitions (`TeilnehmerKontoTable`, `BuchungTable`) with indexes for efficient querying.

- **Build Configuration:**
  - Setup Gradle build files for billing domain and service modules with dependencies for Kotlin, Serialization, Spring Boot, and Exposed.

- **Test Additions:**
  - Extended ZNS importer tests with new scenarios for qualification parsing
This commit is contained in:
2026-04-10 12:18:00 +02:00
parent bab95d14f4
commit 21f3a57e6e
12 changed files with 1237 additions and 5 deletions
@@ -306,4 +306,52 @@ class ZnsImportServiceTest {
assertThat(result.gesamtAktualisiert).isEqualTo(0)
assertThat(result.fehler).isEmpty()
}
@Test
fun `importiereZip - Funktionaer mit mehrfachen Qualifikationen`() = runTest {
// Zeile mit vielen Qualifikationen (Satznummer X014346)
val qualifikationen = "DM,DPF,GAR-SP,SPF,SS*,RD,RS"
val zeile = "X014346Schubert Renate $qualifikationen"
val zip = buildZip("RICHT01.DAT" to zeile)
coEvery { funktionaerRepository.findBySatz("X", 14346) } returns null
coEvery { funktionaerRepository.save(any()) } answers { firstArg<Funktionaer>() }
coEvery { reiterRepository.findByName(any(), any()) } returns emptyList()
val result = service.importiereZip(zip)
assertThat(result.richterImportiert).isEqualTo(1)
coVerify {
funktionaerRepository.save(match { f ->
f.qualifikationen.size == 7 &&
f.qualifikationen.containsAll(listOf("DM", "DPF", "GAR-SP", "SPF", "SS*", "RD", "RS"))
})
}
}
@Test
fun `importiereZip - Funktionaer Update Strategie (Delete+Insert)`() = runTest {
val zeile = funktionaerZeile(typ = "X", satznummer = "123456", name = "Geaendert Name")
val zip = buildZip("RICHT01.DAT" to zeile)
val existing = Funktionaer(
funktionaerId = kotlin.uuid.Uuid.random(),
satzId = "X",
satzNummer = 123456,
name = "Alt Name"
)
coEvery { funktionaerRepository.findBySatz("X", 123456) } returns existing
coEvery { funktionaerRepository.save(any()) } answers { firstArg<Funktionaer>() }
coEvery { reiterRepository.findByName(any(), any()) } returns emptyList()
val result = service.importiereZip(zip)
assertThat(result.richterAktualisiert).isEqualTo(1)
coVerify {
funktionaerRepository.save(match { f ->
f.funktionaerId == existing.funktionaerId && f.name == "Geaendert Name"
})
}
}
}