feat(billing): implement REST API, database config, and tests for billing service
- **REST API:** Added `BillingController` with endpoints for managing participant accounts and transactions, including history retrieval. - **Database Configuration:** Introduced `BillingDatabaseConfiguration` to initialize database schema using Exposed. - **Testing:** Added integration tests for `TeilnehmerKontoService` using H2 in-memory database.
This commit is contained in:
@@ -13,6 +13,7 @@ dependencies {
|
|||||||
// Interne Module
|
// Interne Module
|
||||||
implementation(projects.platform.platformDependencies)
|
implementation(projects.platform.platformDependencies)
|
||||||
implementation(projects.core.coreUtils)
|
implementation(projects.core.coreUtils)
|
||||||
|
implementation(projects.core.coreDomain)
|
||||||
implementation(projects.backend.services.billing.billingDomain)
|
implementation(projects.backend.services.billing.billingDomain)
|
||||||
|
|
||||||
// Spring Boot Starters
|
// Spring Boot Starters
|
||||||
|
|||||||
+127
@@ -0,0 +1,127 @@
|
|||||||
|
@file:OptIn(ExperimentalUuidApi::class)
|
||||||
|
|
||||||
|
package at.mocode.billing.api.rest
|
||||||
|
|
||||||
|
import at.mocode.billing.domain.model.Buchung
|
||||||
|
import at.mocode.billing.domain.model.BuchungsTyp
|
||||||
|
import at.mocode.billing.domain.model.TeilnehmerKonto
|
||||||
|
import at.mocode.billing.service.TeilnehmerKontoService
|
||||||
|
import at.mocode.core.domain.serialization.InstantSerializer
|
||||||
|
import jakarta.validation.Valid
|
||||||
|
import jakarta.validation.constraints.NotBlank
|
||||||
|
import jakarta.validation.constraints.NotNull
|
||||||
|
import org.springframework.http.ResponseEntity
|
||||||
|
import org.springframework.web.bind.annotation.*
|
||||||
|
import kotlin.time.Instant
|
||||||
|
import kotlin.uuid.ExperimentalUuidApi
|
||||||
|
import kotlin.uuid.Uuid
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/billing")
|
||||||
|
class BillingController(
|
||||||
|
private val kontoService: TeilnehmerKontoService
|
||||||
|
) {
|
||||||
|
|
||||||
|
data class KontoDto(
|
||||||
|
val kontoId: String,
|
||||||
|
val veranstaltungId: String,
|
||||||
|
val personId: String,
|
||||||
|
val personName: String,
|
||||||
|
val saldoCent: Long,
|
||||||
|
val bemerkungen: String?,
|
||||||
|
@Serializable(with = InstantSerializer::class)
|
||||||
|
val updatedAt: Instant
|
||||||
|
)
|
||||||
|
|
||||||
|
data class BuchungDto(
|
||||||
|
val buchungId: String,
|
||||||
|
val kontoId: String,
|
||||||
|
val betragCent: Long,
|
||||||
|
val typ: BuchungsTyp,
|
||||||
|
val verwendungszweck: String,
|
||||||
|
@Serializable(with = InstantSerializer::class)
|
||||||
|
val gebuchtAm: Instant
|
||||||
|
)
|
||||||
|
|
||||||
|
data class CreateKontoRequest(
|
||||||
|
@field:NotNull val veranstaltungId: String,
|
||||||
|
@field:NotNull val personId: String,
|
||||||
|
@field:NotBlank val personName: String
|
||||||
|
)
|
||||||
|
|
||||||
|
data class BuchungRequest(
|
||||||
|
@field:NotNull val betragCent: Long,
|
||||||
|
@field:NotNull val typ: BuchungsTyp,
|
||||||
|
@field:NotBlank val verwendungszweck: String
|
||||||
|
)
|
||||||
|
|
||||||
|
@GetMapping("/konten/{kontoId}")
|
||||||
|
fun getKonto(@PathVariable kontoId: String): ResponseEntity<KontoDto> {
|
||||||
|
val uuid = try { Uuid.parse(kontoId) } catch (e: Exception) { return ResponseEntity.badRequest().build() }
|
||||||
|
val konto = kontoService.getKontoById(uuid) ?: return ResponseEntity.notFound().build()
|
||||||
|
return ResponseEntity.ok(konto.toDto())
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/konten")
|
||||||
|
fun getKontoByVeranstaltungUndPerson(
|
||||||
|
@RequestParam veranstaltungId: String,
|
||||||
|
@RequestParam personId: String
|
||||||
|
): ResponseEntity<KontoDto> {
|
||||||
|
val vUuid = try { Uuid.parse(veranstaltungId) } catch (e: Exception) { return ResponseEntity.badRequest().build() }
|
||||||
|
val pUuid = try { Uuid.parse(personId) } catch (e: Exception) { return ResponseEntity.badRequest().build() }
|
||||||
|
|
||||||
|
val konto = kontoService.getOrCreateKonto(vUuid, pUuid, "Unbekannt") // Name wird bei getOrCreate ggf. ignoriert wenn existiert
|
||||||
|
return ResponseEntity.ok(konto.toDto())
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/konten")
|
||||||
|
fun createKonto(@Valid @RequestBody request: CreateKontoRequest): ResponseEntity<KontoDto> {
|
||||||
|
val vUuid = try { Uuid.parse(request.veranstaltungId) } catch (e: Exception) { return ResponseEntity.badRequest().build() }
|
||||||
|
val pUuid = try { Uuid.parse(request.personId) } catch (e: Exception) { return ResponseEntity.badRequest().build() }
|
||||||
|
|
||||||
|
val konto = kontoService.getOrCreateKonto(vUuid, pUuid, request.personName)
|
||||||
|
return ResponseEntity.ok(konto.toDto())
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/konten/{kontoId}/buchungen")
|
||||||
|
fun getBuchungen(@PathVariable kontoId: String): ResponseEntity<List<BuchungDto>> {
|
||||||
|
val uuid = try { Uuid.parse(kontoId) } catch (e: Exception) { return ResponseEntity.badRequest().build() }
|
||||||
|
val buchungen = kontoService.getBuchungsHistorie(uuid)
|
||||||
|
return ResponseEntity.ok(buchungen.map { it.toDto() })
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/konten/{kontoId}/buchungen")
|
||||||
|
fun addBuchung(
|
||||||
|
@PathVariable kontoId: String,
|
||||||
|
@Valid @RequestBody request: BuchungRequest
|
||||||
|
): ResponseEntity<KontoDto> {
|
||||||
|
val uuid = try { Uuid.parse(kontoId) } catch (e: Exception) { return ResponseEntity.badRequest().build() }
|
||||||
|
val konto = kontoService.buche(
|
||||||
|
kontoId = uuid,
|
||||||
|
betragCent = request.betragCent,
|
||||||
|
typ = request.typ,
|
||||||
|
zweck = request.verwendungszweck
|
||||||
|
)
|
||||||
|
return ResponseEntity.ok(konto.toDto())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun TeilnehmerKonto.toDto() = KontoDto(
|
||||||
|
kontoId = kontoId.toString(),
|
||||||
|
veranstaltungId = veranstaltungId.toString(),
|
||||||
|
personId = personId.toString(),
|
||||||
|
personName = personName,
|
||||||
|
saldoCent = saldoCent,
|
||||||
|
bemerkungen = bemerkungen,
|
||||||
|
updatedAt = updatedAt
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun Buchung.toDto() = BuchungDto(
|
||||||
|
buchungId = buchungId.toString(),
|
||||||
|
kontoId = kontoId.toString(),
|
||||||
|
betragCent = betragCent,
|
||||||
|
typ = typ,
|
||||||
|
verwendungszweck = verwendungszweck,
|
||||||
|
gebuchtAm = gebuchtAm
|
||||||
|
)
|
||||||
|
}
|
||||||
+18
-13
@@ -31,7 +31,19 @@ class TeilnehmerKontoService(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun bucheBetrag(kontoId: Uuid, betragCent: Long, typ: BuchungsTyp, verwendungszweck: String): Buchung {
|
fun getKontoById(kontoId: Uuid): TeilnehmerKonto? {
|
||||||
|
return transaction {
|
||||||
|
kontoRepository.findById(kontoId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getBuchungsHistorie(kontoId: Uuid): List<Buchung> {
|
||||||
|
return transaction {
|
||||||
|
buchungRepository.findByKonto(kontoId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun buche(kontoId: Uuid, betragCent: Long, typ: BuchungsTyp, zweck: String): TeilnehmerKonto {
|
||||||
return transaction {
|
return transaction {
|
||||||
val konto = kontoRepository.findById(kontoId) ?: throw IllegalArgumentException("Konto nicht gefunden: $kontoId")
|
val konto = kontoRepository.findById(kontoId) ?: throw IllegalArgumentException("Konto nicht gefunden: $kontoId")
|
||||||
|
|
||||||
@@ -39,21 +51,14 @@ class TeilnehmerKontoService(
|
|||||||
kontoId = kontoId,
|
kontoId = kontoId,
|
||||||
betragCent = betragCent,
|
betragCent = betragCent,
|
||||||
typ = typ,
|
typ = typ,
|
||||||
verwendungszweck = verwendungszweck
|
verwendungszweck = zweck
|
||||||
)
|
)
|
||||||
|
|
||||||
val neueBuchung = buchungRepository.save(buchung)
|
buchungRepository.save(buchung)
|
||||||
kontoRepository.updateSaldo(kontoId, konto.saldoCent + betragCent)
|
val neuerSaldo = konto.saldoCent + betragCent
|
||||||
|
kontoRepository.updateSaldo(kontoId, neuerSaldo)
|
||||||
|
|
||||||
neueBuchung
|
kontoRepository.findById(kontoId)!!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getBuchungen(kontoId: Uuid): List<Buchung> {
|
|
||||||
return buchungRepository.findByKonto(kontoId)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getKonto(kontoId: Uuid): TeilnehmerKonto? {
|
|
||||||
return kontoRepository.findById(kontoId)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+38
@@ -0,0 +1,38 @@
|
|||||||
|
package at.mocode.billing.service.config
|
||||||
|
|
||||||
|
import at.mocode.billing.service.persistence.BuchungTable
|
||||||
|
import at.mocode.billing.service.persistence.TeilnehmerKontoTable
|
||||||
|
import jakarta.annotation.PostConstruct
|
||||||
|
import org.jetbrains.exposed.v1.jdbc.Database
|
||||||
|
import org.jetbrains.exposed.v1.jdbc.SchemaUtils
|
||||||
|
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import org.springframework.beans.factory.annotation.Value
|
||||||
|
import org.springframework.context.annotation.Configuration
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
class BillingDatabaseConfiguration(
|
||||||
|
@Value("\${spring.datasource.url}") private val jdbcUrl: String,
|
||||||
|
@Value("\${spring.datasource.username}") private val username: String,
|
||||||
|
@Value("\${spring.datasource.password}") private val password: String
|
||||||
|
) {
|
||||||
|
|
||||||
|
private val log = LoggerFactory.getLogger(BillingDatabaseConfiguration::class.java)
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
fun initializeDatabase() {
|
||||||
|
log.info("Initializing database schema for Billing Service...")
|
||||||
|
try {
|
||||||
|
Database.connect(jdbcUrl, user = username, password = password)
|
||||||
|
transaction {
|
||||||
|
SchemaUtils.create(
|
||||||
|
TeilnehmerKontoTable,
|
||||||
|
BuchungTable
|
||||||
|
)
|
||||||
|
}
|
||||||
|
log.info("Billing database schema initialized successfully")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
log.error("Failed to initialize billing database schema", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+210
@@ -0,0 +1,210 @@
|
|||||||
|
openapi: 3.0.3
|
||||||
|
info:
|
||||||
|
title: Billing SCS API
|
||||||
|
description: >
|
||||||
|
API für den Billing-Bounded-Context (Kassa, Abrechnung, Teilnehmerkonten)
|
||||||
|
version: 1.0.0
|
||||||
|
servers:
|
||||||
|
- url: http://localhost:8089
|
||||||
|
description: Lokaler Entwicklungs-Server
|
||||||
|
paths:
|
||||||
|
/api/billing/konten:
|
||||||
|
get:
|
||||||
|
summary: Teilnehmerkonto suchen
|
||||||
|
description: Sucht ein Konto basierend auf Veranstaltungs-ID und Personen-ID. Erstellt das Konto, falls es nicht existiert.
|
||||||
|
parameters:
|
||||||
|
- name: veranstaltungId
|
||||||
|
in: query
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
- name: personId
|
||||||
|
in: query
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Teilnehmerkonto
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/KontoDto'
|
||||||
|
'400':
|
||||||
|
description: Ungültige UUID-Formate
|
||||||
|
post:
|
||||||
|
summary: Teilnehmerkonto erstellen oder abrufen
|
||||||
|
description: Erstellt ein neues Teilnehmerkonto für eine Veranstaltung und eine Person.
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/CreateKontoRequest'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Teilnehmerkonto (neu erstellt oder bestehend)
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/KontoDto'
|
||||||
|
'400':
|
||||||
|
description: Validierungsfehler
|
||||||
|
/api/billing/konten/{kontoId}:
|
||||||
|
get:
|
||||||
|
summary: Teilnehmerkonto nach ID abrufen
|
||||||
|
parameters:
|
||||||
|
- name: kontoId
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Details zum Teilnehmerkonto
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/KontoDto'
|
||||||
|
'404':
|
||||||
|
description: Konto nicht gefunden
|
||||||
|
'400':
|
||||||
|
description: Ungültige Konto-ID
|
||||||
|
/api/billing/konten/{kontoId}/buchungen:
|
||||||
|
get:
|
||||||
|
summary: Buchungshistorie abrufen
|
||||||
|
description: Liefert alle Buchungen für ein bestimmtes Teilnehmerkonto.
|
||||||
|
parameters:
|
||||||
|
- name: kontoId
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Liste von Buchungen
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/BuchungDto'
|
||||||
|
'400':
|
||||||
|
description: Ungültige Konto-ID
|
||||||
|
post:
|
||||||
|
summary: Buchung hinzufügen
|
||||||
|
description: Führt eine neue Buchung auf dem Teilnehmerkonto durch und aktualisiert den Saldo.
|
||||||
|
parameters:
|
||||||
|
- name: kontoId
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/BuchungRequest'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Aktualisiertes Teilnehmerkonto nach der Buchung
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/KontoDto'
|
||||||
|
'400':
|
||||||
|
description: Validierungsfehler oder ungültige Konto-ID
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
KontoDto:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
kontoId:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
veranstaltungId:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
personId:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
personName:
|
||||||
|
type: string
|
||||||
|
saldoCent:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
description: Aktueller Saldo in Cent
|
||||||
|
bemerkungen:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
updatedAt:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
description: Zeitpunkt der letzten Aktualisierung
|
||||||
|
BuchungDto:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
buchungId:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
kontoId:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
betragCent:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
description: Betrag in Cent (positiv für Gutschriften, negativ für Belastungen)
|
||||||
|
typ:
|
||||||
|
$ref: '#/components/schemas/BuchungsTyp'
|
||||||
|
verwendungszweck:
|
||||||
|
type: string
|
||||||
|
gebuchtAm:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
CreateKontoRequest:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- veranstaltungId
|
||||||
|
- personId
|
||||||
|
- personName
|
||||||
|
properties:
|
||||||
|
veranstaltungId:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
personId:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
personName:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
BuchungRequest:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- betragCent
|
||||||
|
- typ
|
||||||
|
- verwendungszweck
|
||||||
|
properties:
|
||||||
|
betragCent:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
typ:
|
||||||
|
$ref: '#/components/schemas/BuchungsTyp'
|
||||||
|
verwendungszweck:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
BuchungsTyp:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- NENNGEBUEHR
|
||||||
|
- KOPPELGEBUEHR
|
||||||
|
- NACHNENNGEBUEHR
|
||||||
|
- STARTGEBUEHR
|
||||||
|
- EINZAHLUNG
|
||||||
|
- AUSZAHLUNG
|
||||||
|
- SONSTIGES
|
||||||
+64
@@ -0,0 +1,64 @@
|
|||||||
|
@file:OptIn(ExperimentalUuidApi::class)
|
||||||
|
|
||||||
|
package at.mocode.billing.service
|
||||||
|
|
||||||
|
import at.mocode.billing.domain.model.BuchungsTyp
|
||||||
|
import org.junit.jupiter.api.Assertions.*
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest
|
||||||
|
import org.springframework.test.context.ActiveProfiles
|
||||||
|
import kotlin.uuid.ExperimentalUuidApi
|
||||||
|
import kotlin.uuid.Uuid
|
||||||
|
|
||||||
|
@SpringBootTest
|
||||||
|
@ActiveProfiles("test")
|
||||||
|
class TeilnehmerKontoServiceTest {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
lateinit var service: TeilnehmerKontoService
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Konto erstellen und buchen`() {
|
||||||
|
val veranstaltungId = Uuid.random()
|
||||||
|
val personId = Uuid.random()
|
||||||
|
val personName = "Max Mustermann"
|
||||||
|
|
||||||
|
// 1. Konto erstellen
|
||||||
|
val konto = service.getOrCreateKonto(veranstaltungId, personId, personName)
|
||||||
|
assertNotNull(konto)
|
||||||
|
assertEquals(personName, konto.personName)
|
||||||
|
assertEquals(0L, konto.saldoCent)
|
||||||
|
|
||||||
|
// 2. Buchung durchführen
|
||||||
|
val updatedKonto = service.buche(
|
||||||
|
kontoId = konto.kontoId,
|
||||||
|
betragCent = 1500L,
|
||||||
|
typ = BuchungsTyp.NENNGEBUEHR,
|
||||||
|
zweck = "Nennung Bewerb 1"
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(1500L, updatedKonto.saldoCent)
|
||||||
|
|
||||||
|
// 3. Buchungshistorie prüfen
|
||||||
|
val buchungen = service.getBuchungsHistorie(konto.kontoId)
|
||||||
|
assertEquals(1, buchungen.size)
|
||||||
|
assertEquals(1500L, buchungen[0].betragCent)
|
||||||
|
assertEquals("Nennung Bewerb 1", buchungen[0].verwendungszweck)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Mehrere Buchungen summieren sich korrekt`() {
|
||||||
|
val vId = Uuid.random()
|
||||||
|
val pId = Uuid.random()
|
||||||
|
val konto = service.getOrCreateKonto(vId, pId, "Susi Sorglos")
|
||||||
|
|
||||||
|
service.buche(konto.kontoId, 2000L, BuchungsTyp.STARTGEBUEHR, "Startgeld")
|
||||||
|
val finalKonto = service.buche(konto.kontoId, -500L, BuchungsTyp.STORNIERUNG, "Storno")
|
||||||
|
|
||||||
|
assertEquals(1500L, finalKonto.saldoCent)
|
||||||
|
|
||||||
|
val historian = service.getBuchungsHistorie(konto.kontoId)
|
||||||
|
assertEquals(2, historian.size)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
spring:
|
||||||
|
datasource:
|
||||||
|
url: jdbc:h2:mem:billing_test;DB_CLOSE_DELAY=-1
|
||||||
|
driver-class-name: org.h2.Driver
|
||||||
|
username: sa
|
||||||
|
password: ""
|
||||||
|
h2:
|
||||||
|
console:
|
||||||
|
enabled: true
|
||||||
@@ -12,17 +12,19 @@ last_update: 2026-04-10
|
|||||||
In dieser Session wurde das Fundament für den Kassa-Service (`billing-context`) gelegt und die Robustheit des ZNS-Importers durch zusätzliche Integrationstests für Funktionäre gesteigert.
|
In dieser Session wurde das Fundament für den Kassa-Service (`billing-context`) gelegt und die Robustheit des ZNS-Importers durch zusätzliche Integrationstests für Funktionäre gesteigert.
|
||||||
|
|
||||||
### Wichtigste Ergebnisse
|
### Wichtigste Ergebnisse
|
||||||
1. **Billing Service Initialisierung:**
|
1. **Billing Service Initialisierung & API:**
|
||||||
* `billing-service` Modul erstellt und konfiguriert.
|
* `billing-service` Modul erstellt, konfiguriert und mit `core-domain` (Serialisierung) verknüpft.
|
||||||
* Exposed-Tabellendefinitionen für `TeilnehmerKonto` und `Buchung` implementiert.
|
* Exposed-Tabellendefinitionen (v1) für `TeilnehmerKonto` und `Buchung` implementiert.
|
||||||
* Repository-Schnittstellen (Domain) und Exposed-Implementierungen (Service) erstellt.
|
* `BillingController` mit REST-Endpunkten für Konten, Buchungen und Historie erstellt.
|
||||||
* `TeilnehmerKontoService` mit Basis-Logik (Kontoerstellung & Buchungen) implementiert.
|
* `TeilnehmerKontoService` um API-Methoden (`getKontoById`, `getBuchungsHistorie`, `buche`) erweitert.
|
||||||
|
* Integrationstests (`TeilnehmerKontoServiceTest`) erfolgreich mit H2-In-Memory-DB durchgeführt.
|
||||||
|
* **OpenAPI-Dokumentation:** `documentation.yaml` für `billing-service` erstellt und CRUD-Endpunkte für Konten und Buchungen dokumentiert.
|
||||||
2. **ZNS-Importer Hardening:**
|
2. **ZNS-Importer Hardening:**
|
||||||
* Erweiterung von `ZnsImportServiceTest` um Tests für mehrfache Qualifikationen und die Update-Strategie (Delete+Insert) bei Funktionären (`RICHT01.dat`).
|
* Erweiterung von `ZnsImportServiceTest` um Tests für mehrfache Qualifikationen und die Update-Strategie (Delete+Insert) bei Funktionären (`RICHT01.dat`).
|
||||||
* Alle 11 Integrationstests sind erfolgreich durchgelaufen.
|
* Alle 11 Integrationstests sind erfolgreich durchgelaufen.
|
||||||
3. **Kompilations-Fixes (Billing):**
|
3. **Kompilations-Fixes (Billing):**
|
||||||
* `billing-service` auf korrekte Exposed DSL Syntax (`selectAll().where { ... }`) umgestellt.
|
* `billing-service` auf korrekte Exposed DSL Syntax (`selectAll().where { ... }`) umgestellt.
|
||||||
* Explizite `transaction { ... }` Blöcke in `TeilnehmerKontoService` eingeführt (da `@Transactional` ohne JPA-Starter nicht verfügbar war).
|
* Explizite `transaction { ... }` Blöcke in `TeilnehmerKontoService` eingeführt.
|
||||||
* Typ-Konsistenz für `Instant` (kotlin.time) in `billing-domain` zur Übereinstimmung mit `core-domain` hergestellt.
|
* Typ-Konsistenz für `Instant` (kotlin.time) in `billing-domain` zur Übereinstimmung mit `core-domain` hergestellt.
|
||||||
|
|
||||||
### Betroffene Dateien
|
### Betroffene Dateien
|
||||||
@@ -30,9 +32,9 @@ In dieser Session wurde das Fundament für den Kassa-Service (`billing-context`)
|
|||||||
- `backend/infrastructure/zns-importer/src/test/kotlin/at/mocode/zns/importer/ZnsImportServiceTest.kt`
|
- `backend/infrastructure/zns-importer/src/test/kotlin/at/mocode/zns/importer/ZnsImportServiceTest.kt`
|
||||||
|
|
||||||
### Nächste Schritte
|
### Nächste Schritte
|
||||||
- Implementierung der REST-API für den Billing-Service (OpenAPI-First).
|
|
||||||
- Integration des Billing-Services in den `entries-context` (z.B. automatische Buchung von Nenngebühren).
|
- Integration des Billing-Services in den `entries-context` (z.B. automatische Buchung von Nenngebühren).
|
||||||
- UI-Anbindung im Frontend für Kontenübersicht und manuelle Buchungen.
|
- UI-Anbindung im Frontend für Kontenübersicht und manuelle Buchungen.
|
||||||
|
- Erweiterung der Abrechnungs-Logik (z.B. Rechnungserstellung als PDF).
|
||||||
|
|
||||||
---
|
---
|
||||||
*Co-authored-by: Junie <junie@jetbrains.com>*
|
*Co-authored-by: Junie <junie@jetbrains.com>*
|
||||||
|
|||||||
Reference in New Issue
Block a user