Integrate billing-service microservice: add API gateway routing, service discovery with Consul, Docker support, and Spring configuration. Update frontend with API integration, BillingRepository, and BillingViewModel.

This commit is contained in:
2026-04-12 18:00:38 +02:00
parent 11abbf0179
commit 0f2060fc14
12 changed files with 376 additions and 59 deletions
@@ -0,0 +1,39 @@
package at.mocode.frontend.features.billing.data
import at.mocode.frontend.core.network.ApiRoutes
import at.mocode.frontend.features.billing.domain.*
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.request.*
import io.ktor.http.*
class DefaultBillingRepository(
private val client: HttpClient
) : BillingRepository {
override suspend fun getOrCreateKonto(
veranstaltungId: String,
personId: String,
personName: String
): Result<TeilnehmerKontoDto> = runCatching {
client.get(ApiRoutes.Billing.KONTEN) {
parameter("veranstaltungId", veranstaltungId)
parameter("personId", personId)
parameter("personName", personName)
}.body()
}
override suspend fun getBuchungen(kontoId: String): Result<List<BuchungDto>> = runCatching {
client.get(ApiRoutes.Billing.buchungen(kontoId)).body()
}
override suspend fun addBuchung(
kontoId: String,
request: BuchungRequest
): Result<TeilnehmerKontoDto> = runCatching {
client.post(ApiRoutes.Billing.buchungen(kontoId)) {
contentType(ContentType.Application.Json)
setBody(request)
}.body()
}
}
@@ -1,10 +1,13 @@
package at.mocode.frontend.features.billing.di
import at.mocode.frontend.features.billing.data.DefaultBillingRepository
import at.mocode.frontend.features.billing.domain.BillingCalculator
import at.mocode.frontend.features.billing.domain.BillingRepository
import at.mocode.frontend.features.billing.presentation.BillingViewModel
import org.koin.dsl.module
val billingModule = module {
single { BillingCalculator() }
factory { BillingViewModel() }
single<BillingRepository> { DefaultBillingRepository(get()) }
factory { BillingViewModel(get()) }
}
@@ -0,0 +1,29 @@
package at.mocode.frontend.features.billing.domain
interface BillingRepository {
/**
* Holt das Konto eines Teilnehmers für eine Veranstaltung.
* Erstellt das Konto automatisch im Backend, falls es noch nicht existiert.
*/
suspend fun getOrCreateKonto(
veranstaltungId: String,
personId: String,
personName: String = "Unbekannt"
): Result<TeilnehmerKontoDto>
/**
* Holt die Buchungshistorie für ein bestimmtes Konto.
*/
suspend fun getBuchungen(
kontoId: String
): Result<List<BuchungDto>>
/**
* Fügt eine neue Buchung zu einem Konto hinzu.
*/
suspend fun addBuchung(
kontoId: String,
request: BuchungRequest
): Result<TeilnehmerKontoDto>
}
@@ -18,78 +18,58 @@ data class BillingUiState(
)
@OptIn(ExperimentalUuidApi::class)
class BillingViewModel : ViewModel() {
class BillingViewModel(
private val repository: BillingRepository
) : ViewModel() {
private val _uiState = MutableStateFlow(BillingUiState())
val uiState = _uiState.asStateFlow()
fun loadKonten(veranstaltungId: Long) {
fun loadKonto(veranstaltungId: String, personId: String, personName: String) {
viewModelScope.launch {
_uiState.value = _uiState.value.copy(isLoading = true)
// TODO: Echter API-Call zum backend:billing-service
// Simuliere Daten für MVP (Mock)
val mockKonten = listOf(
TeilnehmerKontoDto(
id = Uuid.random().toString(),
veranstaltungId = veranstaltungId.toString(),
personId = Uuid.random().toString(),
personName = "Max Mustermann",
saldoCent = -4500L,
bemerkungen = "Stallbox reserviert"
),
TeilnehmerKontoDto(
id = Uuid.random().toString(),
veranstaltungId = veranstaltungId.toString(),
personId = Uuid.random().toString(),
personName = "Erika Musterreiterin",
saldoCent = 1250L
)
)
_uiState.value = _uiState.value.copy(konten = mockKonten, isLoading = false)
repository.getOrCreateKonto(veranstaltungId, personId, personName)
.onSuccess { konto ->
_uiState.value = _uiState.value.copy(selectedKonto = konto, error = null)
loadBuchungen(konto.id)
}
.onFailure {
_uiState.value = _uiState.value.copy(isLoading = false, error = it.message)
}
}
}
fun selectKonto(konto: TeilnehmerKontoDto) {
private fun loadBuchungen(kontoId: String) {
viewModelScope.launch {
_uiState.value = _uiState.value.copy(selectedKonto = konto, isLoading = true)
// TODO: API-Call für Buchungen
val mockBuchungen = listOf(
BuchungDto(
id = Uuid.random().toString(),
kontoId = konto.id,
betragCent = -4000L,
typ = "NENNUNG",
verwendungszweck = "Nenngeld Bewerb 1",
gebuchtAm = "2026-04-10T10:00:00Z"
),
BuchungDto(
id = Uuid.random().toString(),
kontoId = konto.id,
betragCent = -500L,
typ = "GEBUEHR",
verwendungszweck = "Systemgebühr",
gebuchtAm = "2026-04-10T10:05:00Z"
)
)
_uiState.value = _uiState.value.copy(buchungen = mockBuchungen, isLoading = false)
_uiState.value = _uiState.value.copy(isLoading = true)
repository.getBuchungen(kontoId)
.onSuccess { buchungen ->
_uiState.value = _uiState.value.copy(buchungen = buchungen, isLoading = false, error = null)
}
.onFailure {
_uiState.value = _uiState.value.copy(isLoading = false, error = it.message)
}
}
}
fun buche(betragCent: Long, zweck: String) {
val konto = _uiState.value.selectedKonto ?: return
viewModelScope.launch {
// TODO: API-Call POST /billing/konten/{id}/buchungen
val neueBuchung = BuchungDto(
id = Uuid.random().toString(),
kontoId = konto.id,
betragCent = betragCent,
typ = "MANUELL",
verwendungszweck = zweck,
gebuchtAm = "2026-04-10T13:00:00Z"
)
_uiState.value = _uiState.value.copy(
buchungen = listOf(neueBuchung) + _uiState.value.buchungen,
selectedKonto = konto.copy(saldoCent = konto.saldoCent + betragCent)
)
_uiState.value = _uiState.value.copy(isLoading = true)
val request = BuchungRequest(betragCent = betragCent, verwendungszweck = zweck)
repository.addBuchung(konto.id, request)
.onSuccess { aktualisiertesKonto ->
_uiState.value = _uiState.value.copy(selectedKonto = aktualisiertesKonto)
loadBuchungen(konto.id)
}
.onFailure {
_uiState.value = _uiState.value.copy(isLoading = false, error = it.message)
}
}
}
// Für Abwärtskompatibilität oder Listenansicht (optional)
fun selectKonto(konto: TeilnehmerKontoDto) {
_uiState.value = _uiState.value.copy(selectedKonto = konto)
loadBuchungen(konto.id)
}
}