Enhance billing logic: add REST support for manual and automated transactions, refine billing routes, adapt frontend API integration, and implement transaction type validation.

This commit is contained in:
2026-04-12 18:35:49 +02:00
parent 03950f8b0c
commit 9754f3e36b
10 changed files with 162 additions and 27 deletions
@@ -13,6 +13,7 @@ import kotlin.uuid.Uuid
interface TeilnehmerKontoRepository {
fun findByVeranstaltungAndPerson(veranstaltungId: Uuid, personId: Uuid): TeilnehmerKonto?
fun findById(kontoId: Uuid): TeilnehmerKonto?
fun findByVeranstaltung(veranstaltungId: Uuid): List<TeilnehmerKonto>
fun save(konto: TeilnehmerKonto): TeilnehmerKonto
fun updateSaldo(kontoId: Uuid, saldoCent: Long): Long
}
@@ -53,18 +53,39 @@ class TeilnehmerKontoService(
return transaction {
val konto = kontoRepository.findById(kontoId) ?: throw IllegalArgumentException("Konto nicht gefunden: $kontoId")
// Validierung: Bestimmte Typen sind immer "Soll" (negativ), andere "Haben" (positiv/Zahlung)
val validierterBetrag = when (typ) {
BuchungsTyp.NENNGELD,
BuchungsTyp.NENNGEBUEHR,
BuchungsTyp.NACHNENNGEBUEHR,
BuchungsTyp.STARTGEBUEHR,
BuchungsTyp.BOXENGEBUEHR -> if (betragCent > 0) -betragCent else betragCent
BuchungsTyp.ZAHLUNG_BAR,
BuchungsTyp.ZAHLUNG_KARTE,
BuchungsTyp.GUTSCHRIFT -> if (betragCent < 0) -betragCent else betragCent
BuchungsTyp.STORNIERUNG -> betragCent // Storno kann beides sein (Gegenbuchung)
}
val buchung = Buchung(
kontoId = kontoId,
betragCent = betragCent,
betragCent = validierterBetrag,
typ = typ,
verwendungszweck = zweck
)
buchungRepository.save(buchung)
val neuerSaldo = konto.saldoCent + betragCent
val neuerSaldo = konto.saldoCent + validierterBetrag
kontoRepository.updateSaldo(kontoId, neuerSaldo)
kontoRepository.findById(kontoId)!!
}
}
fun getKontenFuerVeranstaltung(veranstaltungId: Uuid): List<TeilnehmerKonto> {
return transaction {
kontoRepository.findByVeranstaltung(veranstaltungId)
}
}
}
@@ -0,0 +1,65 @@
@file:OptIn(ExperimentalUuidApi::class)
package at.mocode.billing.service.api
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 org.springframework.web.bind.annotation.*
import kotlin.uuid.ExperimentalUuidApi
import kotlin.uuid.Uuid
@RestController
@RequestMapping("/api/v1/billing")
class BillingController(
private val kontoService: TeilnehmerKontoService
) {
@GetMapping("/konten/{kontoId}")
fun getKonto(@PathVariable kontoId: String): TeilnehmerKonto? {
return kontoService.getKontoById(Uuid.parse(kontoId))
}
@GetMapping("/veranstaltungen/{veranstaltungId}/personen/{personId}")
fun getOrCreateKonto(
@PathVariable veranstaltungId: String,
@PathVariable personId: String,
@RequestParam(required = false) personName: String?
): TeilnehmerKonto {
return kontoService.getOrCreateKonto(
Uuid.parse(veranstaltungId),
Uuid.parse(personId),
personName ?: "Unbekannter Teilnehmer"
)
}
@GetMapping("/konten/{kontoId}/historie")
fun getHistorie(@PathVariable kontoId: String): List<Buchung> {
return kontoService.getBuchungsHistorie(Uuid.parse(kontoId))
}
@GetMapping("/veranstaltungen/{veranstaltungId}/konten")
fun getKonten(@PathVariable veranstaltungId: String): List<TeilnehmerKonto> {
return kontoService.getKontenFuerVeranstaltung(Uuid.parse(veranstaltungId))
}
@PostMapping("/konten/{kontoId}/buche")
fun buche(
@PathVariable kontoId: String,
@RequestBody request: BuchungRequest
): TeilnehmerKonto {
return kontoService.buche(
Uuid.parse(kontoId),
request.betragCent,
request.typ,
request.verwendungszweck
)
}
}
data class BuchungRequest(
val betragCent: Long,
val typ: BuchungsTyp,
val verwendungszweck: String
)
@@ -37,6 +37,13 @@ class ExposedTeilnehmerKontoRepository : TeilnehmerKontoRepository {
?.toModel()
}
override fun findByVeranstaltung(veranstaltungId: Uuid): List<TeilnehmerKonto> {
return TeilnehmerKontoTable
.selectAll()
.where { TeilnehmerKontoTable.veranstaltungId eq veranstaltungId }
.map { it.toModel() }
}
override fun save(konto: TeilnehmerKonto): TeilnehmerKonto {
val existing = findById(konto.kontoId)
if (existing == null) {