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:
+6
@@ -41,4 +41,10 @@ object ApiRoutes {
|
||||
const val ROOT = "/api/v1/series"
|
||||
fun stand(serieId: String) = "$ROOT/$serieId/stand"
|
||||
}
|
||||
|
||||
object Billing {
|
||||
const val ROOT = "/api/v1/billing"
|
||||
const val KONTEN = "$ROOT/konten"
|
||||
fun buchungen(kontoId: String) = "$KONTEN/$kontoId/buchungen"
|
||||
}
|
||||
}
|
||||
|
||||
+39
@@ -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()
|
||||
}
|
||||
}
|
||||
+4
-1
@@ -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()) }
|
||||
}
|
||||
|
||||
+29
@@ -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>
|
||||
}
|
||||
+37
-57
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user