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:
@@ -16,6 +16,10 @@ Versionierung folgt [Semantic Versioning](https://semver.org/lang/de/).
|
|||||||
### [Unreleased]
|
### [Unreleased]
|
||||||
|
|
||||||
### Hinzugefügt
|
### Hinzugefügt
|
||||||
|
- **Phase 12 (Abrechnung & Infrastruktur) - 12.04.2026:**
|
||||||
|
- **Infrastruktur:** Docker-Integration für `billing-service` (Port 8087) und API-Gateway Routing vervollständigt.
|
||||||
|
- **Service Discovery:** Alle relevanten Microservices (`masterdata`, `events`, `results`, `series`, `billing`) sind nun bei Consul registriert.
|
||||||
|
- **Frontend Billing:** `BillingRepository` und `BillingViewModel` auf reale API-Anbindung (Ktor) umgestellt; `BillingScreen` funktionalisiert.
|
||||||
- **Backend Fixes - 12.04.2026:**
|
- **Backend Fixes - 12.04.2026:**
|
||||||
- **Infrastruktur:** Behebung von Startfehlern im `events-service` (DataSource) und `masterdata-service` (Consul).
|
- **Infrastruktur:** Behebung von Startfehlern im `events-service` (DataSource) und `masterdata-service` (Consul).
|
||||||
- **Build:** Integration von `results-service` und `series-service` in `settings.gradle.kts`.
|
- **Build:** Integration von `results-service` und `series-service` in `settings.gradle.kts`.
|
||||||
|
|||||||
+6
-1
@@ -15,7 +15,8 @@ class GatewayConfig(
|
|||||||
@Value("\${events.service.url:http://localhost:8085}") private val eventsServiceUrl: String,
|
@Value("\${events.service.url:http://localhost:8085}") private val eventsServiceUrl: String,
|
||||||
@Value("\${zns.import.service.url:http://localhost:8095}") private val znsImportServiceUrl: String,
|
@Value("\${zns.import.service.url:http://localhost:8095}") private val znsImportServiceUrl: String,
|
||||||
@Value("\${results.service.url:http://localhost:8088}") private val resultsServiceUrl: String,
|
@Value("\${results.service.url:http://localhost:8088}") private val resultsServiceUrl: String,
|
||||||
@Value("\${series.service.url:http://localhost:8089}") private val seriesServiceUrl: String
|
@Value("\${series.service.url:http://localhost:8089}") private val seriesServiceUrl: String,
|
||||||
|
@Value("\${billing.service.url:http://localhost:8087}") private val billingServiceUrl: String
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@@ -52,6 +53,10 @@ class GatewayConfig(
|
|||||||
path("/api/v1/series/**")
|
path("/api/v1/series/**")
|
||||||
uri(seriesServiceUrl)
|
uri(seriesServiceUrl)
|
||||||
}
|
}
|
||||||
|
route(id = "billing-service") {
|
||||||
|
path("/api/v1/billing/**")
|
||||||
|
uri(billingServiceUrl)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,141 @@
|
|||||||
|
# syntax=docker/dockerfile:1.7
|
||||||
|
|
||||||
|
# ===================================================================
|
||||||
|
# Dockerfile for Billing Service
|
||||||
|
# Based on Spring Boot Service Template with Billing-specific configuration
|
||||||
|
# ===================================================================
|
||||||
|
|
||||||
|
# === CENTRALIZED BUILD ARGUMENTS ===
|
||||||
|
ARG GRADLE_VERSION
|
||||||
|
ARG JAVA_VERSION
|
||||||
|
ARG BUILD_DATE
|
||||||
|
ARG VERSION
|
||||||
|
|
||||||
|
# Service-specific arguments
|
||||||
|
ARG SERVICE_PATH=billing/billing-service
|
||||||
|
ARG SERVICE_NAME=billing-service
|
||||||
|
|
||||||
|
# ===================================================================
|
||||||
|
# Build Stage
|
||||||
|
# ===================================================================
|
||||||
|
FROM gradle:${GRADLE_VERSION}-jdk${JAVA_VERSION}-alpine AS builder
|
||||||
|
|
||||||
|
# Re-declare build arguments for this stage
|
||||||
|
ARG SERVICE_PATH=billing/billing-service
|
||||||
|
ARG SERVICE_NAME=billing-service
|
||||||
|
|
||||||
|
LABEL stage=builder
|
||||||
|
LABEL maintainer="Meldestelle Development Team"
|
||||||
|
|
||||||
|
WORKDIR /workspace
|
||||||
|
|
||||||
|
# Gradle optimizations
|
||||||
|
ENV GRADLE_OPTS="-Dorg.gradle.caching=true \
|
||||||
|
-Dorg.gradle.daemon=false \
|
||||||
|
-Dorg.gradle.parallel=true \
|
||||||
|
-Dorg.gradle.configureondemand=true \
|
||||||
|
-Xmx2g"
|
||||||
|
|
||||||
|
# Copy build files in optimal order for caching
|
||||||
|
COPY gradlew gradlew.bat gradle.properties settings.gradle.kts ./
|
||||||
|
COPY gradle/ gradle/
|
||||||
|
|
||||||
|
# Make gradlew executable
|
||||||
|
RUN chmod +x gradlew
|
||||||
|
|
||||||
|
COPY platform/ platform/
|
||||||
|
COPY core/ core/
|
||||||
|
COPY build.gradle.kts ./
|
||||||
|
|
||||||
|
# Copy billing service modules
|
||||||
|
COPY backend/services/billing/billing-domain/ backend/services/billing/billing-domain/
|
||||||
|
COPY backend/services/billing/billing-service/ backend/services/billing/billing-service/
|
||||||
|
|
||||||
|
# Build billing service
|
||||||
|
RUN echo "Building Billing Service..." && \
|
||||||
|
./gradlew :backend:services:billing:billing-service:dependencies --no-daemon --info && \
|
||||||
|
./gradlew :backend:services:billing:billing-service:bootJar --no-daemon --info
|
||||||
|
|
||||||
|
# Extract JAR layers
|
||||||
|
WORKDIR /builder
|
||||||
|
RUN cp /workspace/backend/services/billing/billing-service/build/libs/*.jar app.jar && \
|
||||||
|
java -Djarmode=layertools -jar app.jar extract
|
||||||
|
|
||||||
|
# ===================================================================
|
||||||
|
# Runtime Stage
|
||||||
|
# ===================================================================
|
||||||
|
FROM eclipse-temurin:${JAVA_VERSION}-jre-alpine AS runtime
|
||||||
|
|
||||||
|
# Metadata
|
||||||
|
LABEL service="billing-service" \
|
||||||
|
version="1.0.0" \
|
||||||
|
description="Billing and Financial Service for Austrian Equestrian Federation" \
|
||||||
|
maintainer="Meldestelle Development Team" \
|
||||||
|
java.version="${JAVA_VERSION}"
|
||||||
|
|
||||||
|
# Build arguments
|
||||||
|
ARG APP_USER=billinguser
|
||||||
|
ARG APP_GROUP=billinggroup
|
||||||
|
ARG APP_UID=1008
|
||||||
|
ARG APP_GID=1008
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# System setup
|
||||||
|
RUN apk update && \
|
||||||
|
apk upgrade && \
|
||||||
|
apk add --no-cache curl jq tzdata && \
|
||||||
|
rm -rf /var/cache/apk/*
|
||||||
|
|
||||||
|
# Non-root user creation
|
||||||
|
RUN addgroup -g ${APP_GID} -S ${APP_GROUP} && \
|
||||||
|
adduser -u ${APP_UID} -S ${APP_USER} -G ${APP_GROUP} -h /app -s /bin/sh
|
||||||
|
|
||||||
|
# Directory setup
|
||||||
|
RUN mkdir -p /app/logs /app/tmp && \
|
||||||
|
chown -R ${APP_USER}:${APP_GROUP} /app
|
||||||
|
|
||||||
|
# Re-declare build arguments for runtime stage
|
||||||
|
ARG SERVICE_PATH=billing/billing-service
|
||||||
|
ARG SERVICE_NAME=billing-service
|
||||||
|
|
||||||
|
# Copy Spring Boot layers
|
||||||
|
COPY --from=builder --chown=${APP_USER}:${APP_GROUP} /builder/dependencies/ ./
|
||||||
|
COPY --from=builder --chown=${APP_USER}:${APP_GROUP} /builder/spring-boot-loader/ ./
|
||||||
|
COPY --from=builder --chown=${APP_USER}:${APP_GROUP} /builder/snapshot-dependencies/ ./
|
||||||
|
COPY --from=builder --chown=${APP_USER}:${APP_GROUP} /builder/application/ ./
|
||||||
|
|
||||||
|
USER ${APP_USER}
|
||||||
|
|
||||||
|
# Expose application port and debug port
|
||||||
|
EXPOSE 8087 5012
|
||||||
|
|
||||||
|
# Health check
|
||||||
|
HEALTHCHECK --interval=15s --timeout=3s --start-period=40s --retries=3 \
|
||||||
|
CMD curl -fsS --max-time 2 http://localhost:8087/actuator/health/readiness || exit 1
|
||||||
|
|
||||||
|
# JVM configuration
|
||||||
|
ENV JAVA_OPTS="-XX:MaxRAMPercentage=80.0 \
|
||||||
|
-XX:+UseG1GC \
|
||||||
|
-XX:+UseStringDeduplication \
|
||||||
|
-XX:+UseContainerSupport \
|
||||||
|
-Djava.security.egd=file:/dev/./urandom \
|
||||||
|
-Djava.awt.headless=true \
|
||||||
|
-Dfile.encoding=UTF-8 \
|
||||||
|
-Duser.timezone=Europe/Vienna"
|
||||||
|
|
||||||
|
# Spring Boot configuration
|
||||||
|
ENV SPRING_OUTPUT_ANSI_ENABLED=ALWAYS \
|
||||||
|
SERVER_PORT=8087 \
|
||||||
|
LOGGING_LEVEL_ROOT=INFO \
|
||||||
|
LOGGING_LEVEL_AT_MOCODE_BILLING=DEBUG
|
||||||
|
|
||||||
|
# Startup command
|
||||||
|
ENTRYPOINT ["sh", "-c", "\
|
||||||
|
echo 'Starting Billing Service on port 8087...'; \
|
||||||
|
if [ \"${DEBUG:-false}\" = \"true\" ]; then \
|
||||||
|
echo 'Debug mode enabled on port 5012'; \
|
||||||
|
exec java $JAVA_OPTS -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5012 org.springframework.boot.loader.launch.JarLauncher; \
|
||||||
|
else \
|
||||||
|
exec java $JAVA_OPTS org.springframework.boot.loader.launch.JarLauncher; \
|
||||||
|
fi"]
|
||||||
@@ -21,6 +21,10 @@ dependencies {
|
|||||||
implementation(libs.spring.boot.starter.validation)
|
implementation(libs.spring.boot.starter.validation)
|
||||||
implementation(libs.spring.boot.starter.actuator)
|
implementation(libs.spring.boot.starter.actuator)
|
||||||
implementation(libs.jackson.module.kotlin)
|
implementation(libs.jackson.module.kotlin)
|
||||||
|
implementation(libs.spring.cloud.starter.consul.discovery)
|
||||||
|
implementation(libs.micrometer.tracing.bridge.brave)
|
||||||
|
implementation(libs.zipkin.reporter.brave)
|
||||||
|
implementation(libs.zipkin.sender.okhttp3)
|
||||||
|
|
||||||
// Datenbank-Abhängigkeiten
|
// Datenbank-Abhängigkeiten
|
||||||
implementation(libs.exposed.core)
|
implementation(libs.exposed.core)
|
||||||
|
|||||||
+2
@@ -4,8 +4,10 @@ package at.mocode.billing.service
|
|||||||
|
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication
|
import org.springframework.boot.autoconfigure.SpringBootApplication
|
||||||
import org.springframework.boot.runApplication
|
import org.springframework.boot.runApplication
|
||||||
|
import org.springframework.cloud.client.discovery.EnableDiscoveryClient
|
||||||
import kotlin.uuid.ExperimentalUuidApi
|
import kotlin.uuid.ExperimentalUuidApi
|
||||||
|
|
||||||
|
@EnableDiscoveryClient
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
class BillingServiceApplication
|
class BillingServiceApplication
|
||||||
|
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ services:
|
|||||||
EVENTS_SERVICE_URL: "http://events-service:8085"
|
EVENTS_SERVICE_URL: "http://events-service:8085"
|
||||||
ZNS_IMPORT_SERVICE_URL: "http://zns-import-service:8095"
|
ZNS_IMPORT_SERVICE_URL: "http://zns-import-service:8095"
|
||||||
RESULTS_SERVICE_URL: "http://results-service:8088"
|
RESULTS_SERVICE_URL: "http://results-service:8088"
|
||||||
|
BILLING_SERVICE_URL: "http://billing-service:8087"
|
||||||
|
|
||||||
depends_on:
|
depends_on:
|
||||||
postgres:
|
postgres:
|
||||||
@@ -466,6 +467,78 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- ./config/app/base-application.yaml:/workspace/config/application.yml:Z
|
- ./config/app/base-application.yaml:/workspace/config/application.yml:Z
|
||||||
|
|
||||||
|
# --- MICROSERVICE: Billing Service ---
|
||||||
|
billing-service:
|
||||||
|
image: "${DOCKER_REGISTRY:-git.mo-code.at/mo-code}/billing-service:${DOCKER_TAG:-latest}"
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: backend/services/billing/billing-service/Dockerfile
|
||||||
|
args:
|
||||||
|
GRADLE_VERSION: "${DOCKER_GRADLE_VERSION:-9.3.1}"
|
||||||
|
JAVA_VERSION: "${DOCKER_JAVA_VERSION:-25}"
|
||||||
|
VERSION: "${DOCKER_VERSION:-1.0.0-SNAPSHOT}"
|
||||||
|
BUILD_DATE: "${DOCKER_BUILD_DATE}"
|
||||||
|
labels:
|
||||||
|
- "org.opencontainers.image.created=${DOCKER_BUILD_DATE}"
|
||||||
|
container_name: "${PROJECT_NAME:-meldestelle}-billing-service"
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "${BILLING_PORT:-8087:8087}"
|
||||||
|
- "${BILLING_DEBUG_PORT:-5012:5012}"
|
||||||
|
environment:
|
||||||
|
SPRING_PROFILES_ACTIVE: "${BILLING_SPRING_PROFILES_ACTIVE:-docker}"
|
||||||
|
DEBUG: "${BILLING_DEBUG:-true}"
|
||||||
|
SERVER_PORT: "${BILLING_SERVER_PORT:-8087}"
|
||||||
|
|
||||||
|
# --- KEYCLOAK ---
|
||||||
|
SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI: "${KC_ISSUER_URI:-http://localhost:8180/realms/meldestelle}"
|
||||||
|
SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWK_SET_URI: "${KC_JWK_SET_URI:-http://keycloak:8080/realms/meldestelle/protocol/openid-connect/certs}"
|
||||||
|
|
||||||
|
# --- CONSUL ---
|
||||||
|
SPRING_CLOUD_CONSUL_HOST: "${CONSUL_HOST:-consul}"
|
||||||
|
SPRING_CLOUD_CONSUL_PORT: "${CONSUL_HTTP_PORT:-8500}"
|
||||||
|
SPRING_CLOUD_CONSUL_DISCOVERY_SERVICE_NAME: "${BILLING_SERVICE_NAME:-billing-service}"
|
||||||
|
SPRING_CLOUD_CONSUL_DISCOVERY_PREFER_IP_ADDRESS: "${BILLING_CONSUL_PREFER_IP:-true}"
|
||||||
|
|
||||||
|
# - DATENBANK VERBINDUNG -
|
||||||
|
SPRING_DATASOURCE_URL: "${POSTGRES_DB_URL:-jdbc:postgresql://postgres:5432/pg-meldestelle-db}"
|
||||||
|
SPRING_DATASOURCE_USERNAME: "${POSTGRES_USER:-pg-user}"
|
||||||
|
SPRING_DATASOURCE_PASSWORD: "${POSTGRES_PASSWORD:-pg-password}"
|
||||||
|
|
||||||
|
# --- VALKEY ---
|
||||||
|
SPRING_DATA_VALKEY_HOST: "${VALKEY_SERVER_HOSTNAME:-valkey}"
|
||||||
|
SPRING_DATA_VALKEY_PORT: "${VALKEY_SERVER_PORT:-6379}"
|
||||||
|
|
||||||
|
# --- ZIPKIN ---
|
||||||
|
MANAGEMENT_ZIPKIN_TRACING_ENDPOINT: "${ZIPKIN_ENDPOINT:-http://zipkin:9411/api/v2/spans}"
|
||||||
|
|
||||||
|
depends_on:
|
||||||
|
postgres:
|
||||||
|
condition: "service_healthy"
|
||||||
|
keycloak:
|
||||||
|
condition: "service_healthy"
|
||||||
|
consul:
|
||||||
|
condition: "service_healthy"
|
||||||
|
valkey:
|
||||||
|
condition: "service_healthy"
|
||||||
|
zipkin:
|
||||||
|
condition: "service_started"
|
||||||
|
|
||||||
|
healthcheck:
|
||||||
|
test: [ "CMD", "wget", "--spider", "-q", "http://localhost:8087/actuator/health/readiness" ]
|
||||||
|
interval: 15s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
start_period: 40s
|
||||||
|
|
||||||
|
networks:
|
||||||
|
meldestelle-network:
|
||||||
|
aliases:
|
||||||
|
- "billing-service"
|
||||||
|
profiles: [ "backend", "all" ]
|
||||||
|
volumes:
|
||||||
|
- ./config/app/base-application.yaml:/workspace/config/application.yml:Z
|
||||||
|
|
||||||
# --- MICROSERVICE: Series Service ---
|
# --- MICROSERVICE: Series Service ---
|
||||||
series-service:
|
series-service:
|
||||||
image: "${DOCKER_REGISTRY:-git.mo-code.at/mo-code}/series-service:${DOCKER_TAG:-latest}"
|
image: "${DOCKER_REGISTRY:-git.mo-code.at/mo-code}/series-service:${DOCKER_TAG:-latest}"
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
# Curator Log: Phase 12 - Abrechnung (Billing) & Infrastruktur-Fixes
|
||||||
|
**Datum:** 2026-04-12
|
||||||
|
**Status:** In Arbeit / Integration abgeschlossen
|
||||||
|
|
||||||
|
## 🏗️ Infrastruktur-Updates
|
||||||
|
- **Billing Service:**
|
||||||
|
- Dockerfile für `billing-service` erstellt (Multi-Stage Build mit JRE 25).
|
||||||
|
- Service in `dc-backend.yaml` integriert (Port 8087, Debug 5012).
|
||||||
|
- Gateway-Routing in `GatewayConfig.kt` für `/api/v1/billing/**` konfiguriert.
|
||||||
|
- Spring Cloud Consul Discovery im `billing-service` aktiviert und Abhängigkeiten in `build.gradle.kts` ergänzt.
|
||||||
|
|
||||||
|
## 🎨 Frontend-Integration (Billing Context)
|
||||||
|
- **Domain & Data:**
|
||||||
|
- `BillingRepository` Interface definiert für Kontenverwaltung und Buchungshistorie.
|
||||||
|
- `DefaultBillingRepository` implementiert mit Ktor-Client.
|
||||||
|
- `ApiRoutes` um Billing-Konstanten erweitert.
|
||||||
|
- **UI & State:**
|
||||||
|
- `BillingViewModel` auf das reale Repository umgestellt (Mocks entfernt).
|
||||||
|
- `BillingModule` (Koin) um Repository-Injektion erweitert.
|
||||||
|
- `TurnierAbrechnungTab` im Turnier-Feature nutzt nun den funktionalen `BillingScreen`.
|
||||||
|
|
||||||
|
## 🧹 Fixes & Aufräumarbeiten
|
||||||
|
- Behebung von `Unresolved reference` Fehlern in der DI-Konfiguration des `billing-service`.
|
||||||
|
- Konsolidierung der Koin-Module im `billing-feature`.
|
||||||
|
|
||||||
|
## 🛤️ Roadmap-Status
|
||||||
|
- Phase 12 (Billing) von "Geplant" auf "In Arbeit" gesetzt.
|
||||||
|
- Backend-Kommunikation für Konten und Buchungen ist verifiziert.
|
||||||
|
|
||||||
|
---
|
||||||
|
*Dokumentiert durch den Curator am 12.04.2026*
|
||||||
+6
@@ -41,4 +41,10 @@ object ApiRoutes {
|
|||||||
const val ROOT = "/api/v1/series"
|
const val ROOT = "/api/v1/series"
|
||||||
fun stand(serieId: String) = "$ROOT/$serieId/stand"
|
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
|
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.BillingCalculator
|
||||||
|
import at.mocode.frontend.features.billing.domain.BillingRepository
|
||||||
import at.mocode.frontend.features.billing.presentation.BillingViewModel
|
import at.mocode.frontend.features.billing.presentation.BillingViewModel
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
|
||||||
val billingModule = module {
|
val billingModule = module {
|
||||||
single { BillingCalculator() }
|
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)
|
@OptIn(ExperimentalUuidApi::class)
|
||||||
class BillingViewModel : ViewModel() {
|
class BillingViewModel(
|
||||||
|
private val repository: BillingRepository
|
||||||
|
) : ViewModel() {
|
||||||
private val _uiState = MutableStateFlow(BillingUiState())
|
private val _uiState = MutableStateFlow(BillingUiState())
|
||||||
val uiState = _uiState.asStateFlow()
|
val uiState = _uiState.asStateFlow()
|
||||||
|
|
||||||
fun loadKonten(veranstaltungId: Long) {
|
fun loadKonto(veranstaltungId: String, personId: String, personName: String) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
_uiState.value = _uiState.value.copy(isLoading = true)
|
_uiState.value = _uiState.value.copy(isLoading = true)
|
||||||
// TODO: Echter API-Call zum backend:billing-service
|
repository.getOrCreateKonto(veranstaltungId, personId, personName)
|
||||||
// Simuliere Daten für MVP (Mock)
|
.onSuccess { konto ->
|
||||||
val mockKonten = listOf(
|
_uiState.value = _uiState.value.copy(selectedKonto = konto, error = null)
|
||||||
TeilnehmerKontoDto(
|
loadBuchungen(konto.id)
|
||||||
id = Uuid.random().toString(),
|
}
|
||||||
veranstaltungId = veranstaltungId.toString(),
|
.onFailure {
|
||||||
personId = Uuid.random().toString(),
|
_uiState.value = _uiState.value.copy(isLoading = false, error = it.message)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun selectKonto(konto: TeilnehmerKontoDto) {
|
private fun loadBuchungen(kontoId: String) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
_uiState.value = _uiState.value.copy(selectedKonto = konto, isLoading = true)
|
_uiState.value = _uiState.value.copy(isLoading = true)
|
||||||
// TODO: API-Call für Buchungen
|
repository.getBuchungen(kontoId)
|
||||||
val mockBuchungen = listOf(
|
.onSuccess { buchungen ->
|
||||||
BuchungDto(
|
_uiState.value = _uiState.value.copy(buchungen = buchungen, isLoading = false, error = null)
|
||||||
id = Uuid.random().toString(),
|
}
|
||||||
kontoId = konto.id,
|
.onFailure {
|
||||||
betragCent = -4000L,
|
_uiState.value = _uiState.value.copy(isLoading = false, error = it.message)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun buche(betragCent: Long, zweck: String) {
|
fun buche(betragCent: Long, zweck: String) {
|
||||||
val konto = _uiState.value.selectedKonto ?: return
|
val konto = _uiState.value.selectedKonto ?: return
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
// TODO: API-Call POST /billing/konten/{id}/buchungen
|
_uiState.value = _uiState.value.copy(isLoading = true)
|
||||||
val neueBuchung = BuchungDto(
|
val request = BuchungRequest(betragCent = betragCent, verwendungszweck = zweck)
|
||||||
id = Uuid.random().toString(),
|
repository.addBuchung(konto.id, request)
|
||||||
kontoId = konto.id,
|
.onSuccess { aktualisiertesKonto ->
|
||||||
betragCent = betragCent,
|
_uiState.value = _uiState.value.copy(selectedKonto = aktualisiertesKonto)
|
||||||
typ = "MANUELL",
|
loadBuchungen(konto.id)
|
||||||
verwendungszweck = zweck,
|
}
|
||||||
gebuchtAm = "2026-04-10T13:00:00Z"
|
.onFailure {
|
||||||
)
|
_uiState.value = _uiState.value.copy(isLoading = false, error = it.message)
|
||||||
_uiState.value = _uiState.value.copy(
|
}
|
||||||
buchungen = listOf(neueBuchung) + _uiState.value.buchungen,
|
|
||||||
selectedKonto = konto.copy(saldoCent = konto.saldoCent + betragCent)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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