refactor: Migrate from monolithic to modular architecture
1. **Docker-Compose für Entwicklung optimieren** 2. **Umgebungsvariablen für lokale Entwicklung** 3. **Service-Abhängigkeiten** 4. **Docker-Compose für Produktion** 5. **Dokumentation**
This commit is contained in:
+34
-22
@@ -1,5 +1,5 @@
|
|||||||
# ----------- Stage 1: Build Stage -----------
|
# ----------- Stage 1: Build Stage -----------
|
||||||
FROM gradle:8.14-jdk21 AS build
|
FROM gradle:8.14.3-jdk21 AS build
|
||||||
WORKDIR /home/gradle/src
|
WORKDIR /home/gradle/src
|
||||||
|
|
||||||
# Copy only the files needed for dependency resolution first
|
# Copy only the files needed for dependency resolution first
|
||||||
@@ -7,28 +7,30 @@ WORKDIR /home/gradle/src
|
|||||||
COPY build.gradle.kts settings.gradle.kts gradle.properties ./
|
COPY build.gradle.kts settings.gradle.kts gradle.properties ./
|
||||||
COPY gradle ./gradle
|
COPY gradle ./gradle
|
||||||
|
|
||||||
# Download dependencies and cache them
|
# Download dependencies and cache them in separate layer
|
||||||
RUN gradle dependencies --no-daemon
|
RUN gradle dependencies --no-daemon --quiet
|
||||||
|
|
||||||
# Copy source code
|
# Copy source code in order of change frequency (least to most likely to change)
|
||||||
COPY shared-kernel ./shared-kernel
|
COPY core ./core
|
||||||
COPY api-gateway ./api-gateway
|
COPY platform ./platform
|
||||||
COPY master-data ./master-data
|
COPY infrastructure ./infrastructure
|
||||||
COPY member-management ./member-management
|
COPY masterdata ./masterdata
|
||||||
COPY horse-registry ./horse-registry
|
COPY members ./members
|
||||||
COPY event-management ./event-management
|
COPY horses ./horses
|
||||||
COPY composeApp ./composeApp
|
COPY events ./events
|
||||||
COPY server ./server
|
|
||||||
|
|
||||||
# Build with optimized settings
|
# Build with optimized settings
|
||||||
RUN gradle :api-gateway:shadowJar --no-daemon --parallel --build-cache
|
RUN gradle :infrastructure:gateway:shadowJar --no-daemon --parallel --build-cache --quiet
|
||||||
|
|
||||||
# ----------- Stage 2: Runtime Stage -----------
|
# ----------- Stage 2: Runtime Stage -----------
|
||||||
FROM openjdk:21-slim-bookworm AS runtime
|
FROM eclipse-temurin:21-jre-alpine AS runtime
|
||||||
|
|
||||||
|
# Install curl for health checks and ca-certificates for SSL
|
||||||
|
RUN apk add --no-cache curl ca-certificates tzdata
|
||||||
|
|
||||||
# Add non-root user for security
|
# Add non-root user for security
|
||||||
RUN addgroup --system --gid 1001 appuser && \
|
RUN addgroup -g 1001 -S appuser && \
|
||||||
adduser --system --uid 1001 --gid 1001 appuser
|
adduser -u 1001 -S appuser -G appuser
|
||||||
|
|
||||||
# Set timezone
|
# Set timezone
|
||||||
ENV TZ=Europe/Vienna
|
ENV TZ=Europe/Vienna
|
||||||
@@ -37,7 +39,7 @@ RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
|
|||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Copy the jar file from the build stage
|
# Copy the jar file from the build stage
|
||||||
COPY --from=build /home/gradle/src/api-gateway/build/libs/*.jar ./app.jar
|
COPY --from=build /home/gradle/src/infrastructure/gateway/build/libs/*.jar ./app.jar
|
||||||
|
|
||||||
# Set ownership to non-root user
|
# Set ownership to non-root user
|
||||||
RUN chown -R appuser:appuser /app
|
RUN chown -R appuser:appuser /app
|
||||||
@@ -45,26 +47,36 @@ RUN chown -R appuser:appuser /app
|
|||||||
# Switch to non-root user
|
# Switch to non-root user
|
||||||
USER appuser
|
USER appuser
|
||||||
|
|
||||||
# Add metadata labels
|
# Add metadata labels (OCI Image Format Specification)
|
||||||
LABEL org.opencontainers.image.title="Meldestelle API Gateway"
|
LABEL org.opencontainers.image.title="Meldestelle API Gateway"
|
||||||
LABEL org.opencontainers.image.description="API Gateway for Meldestelle application"
|
LABEL org.opencontainers.image.description="API Gateway for Meldestelle horse sport registration system"
|
||||||
LABEL org.opencontainers.image.vendor="MoCode"
|
LABEL org.opencontainers.image.vendor="MoCode"
|
||||||
LABEL org.opencontainers.image.version="1.0.0"
|
LABEL org.opencontainers.image.version="1.0.0"
|
||||||
LABEL org.opencontainers.image.created="2025-07-21"
|
LABEL org.opencontainers.image.created="2025-07-24"
|
||||||
|
LABEL org.opencontainers.image.source="https://github.com/mocode/meldestelle"
|
||||||
|
LABEL org.opencontainers.image.documentation="https://github.com/mocode/meldestelle/blob/main/README.md"
|
||||||
|
LABEL org.opencontainers.image.licenses="MIT"
|
||||||
|
|
||||||
# Expose the application port
|
# Expose the application port
|
||||||
EXPOSE 8081
|
EXPOSE 8081
|
||||||
|
|
||||||
# Define health check
|
# Define health check
|
||||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
|
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
|
||||||
CMD curl -f http://localhost:8081/health || exit 1
|
CMD curl -f http://localhost:8081/health || exit 1
|
||||||
|
|
||||||
# Run the application with optimized JVM settings
|
# Run the application with optimized JVM settings for containerized environment
|
||||||
ENTRYPOINT ["java", \
|
ENTRYPOINT ["java", \
|
||||||
|
"-XX:+UseContainerSupport", \
|
||||||
|
"-XX:MaxRAMPercentage=75.0", \
|
||||||
"-XX:+UseG1GC", \
|
"-XX:+UseG1GC", \
|
||||||
"-XX:MaxGCPauseMillis=100", \
|
"-XX:MaxGCPauseMillis=100", \
|
||||||
"-XX:+ParallelRefProcEnabled", \
|
"-XX:+ParallelRefProcEnabled", \
|
||||||
"-XX:+HeapDumpOnOutOfMemoryError", \
|
"-XX:+HeapDumpOnOutOfMemoryError", \
|
||||||
"-XX:HeapDumpPath=/tmp/heapdump.hprof", \
|
"-XX:HeapDumpPath=/tmp/heapdump.hprof", \
|
||||||
|
"-XX:+ExitOnOutOfMemoryError", \
|
||||||
|
"-XX:+UnlockExperimentalVMOptions", \
|
||||||
|
"-XX:+UseCGroupMemoryLimitForHeap", \
|
||||||
"-Djava.security.egd=file:/dev/./urandom", \
|
"-Djava.security.egd=file:/dev/./urandom", \
|
||||||
|
"-Dfile.encoding=UTF-8", \
|
||||||
|
"-Duser.timezone=Europe/Vienna", \
|
||||||
"-jar", "/app/app.jar"]
|
"-jar", "/app/app.jar"]
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ Das Projekt wurde kürzlich auf eine modulare Architektur migriert, um die Wartb
|
|||||||
## Systemanforderungen
|
## Systemanforderungen
|
||||||
|
|
||||||
- Java 21
|
- Java 21
|
||||||
- Kotlin 2.1.20
|
- Kotlin 2.1.21
|
||||||
- Gradle 8.14
|
- Gradle 8.14
|
||||||
- Docker und Docker Compose
|
- Docker und Docker Compose
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -64,8 +64,9 @@ subprojects {
|
|||||||
// Include all tests that have "Integration" in their name
|
// Include all tests that have "Integration" in their name
|
||||||
include("**/*Integration*Test.kt")
|
include("**/*Integration*Test.kt")
|
||||||
|
|
||||||
// Exclude tests that are not integration tests
|
// Exclude unit tests (but keep integration tests)
|
||||||
exclude("**/*Test.kt")
|
exclude("**/*Test.kt")
|
||||||
|
include("**/*IntegrationTest.kt")
|
||||||
|
|
||||||
// Set system properties for integration tests
|
// Set system properties for integration tests
|
||||||
systemProperty("spring.profiles.active", "integration-test")
|
systemProperty("spring.profiles.active", "integration-test")
|
||||||
|
|||||||
-44
@@ -1,44 +0,0 @@
|
|||||||
package at.mocode.client.common.components.events
|
|
||||||
|
|
||||||
import at.mocode.events.domain.model.Veranstaltung
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Simple JS-specific utility functions for event management UI
|
|
||||||
*/
|
|
||||||
object EventUIUtils {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Formats an event for display in the browser
|
|
||||||
*/
|
|
||||||
fun formatEventForDisplay(event: Veranstaltung): String {
|
|
||||||
return buildString {
|
|
||||||
append("Event: ${event.name}")
|
|
||||||
append(" | Location: ${event.ort}")
|
|
||||||
append(" | From: ${event.startDatum} to: ${event.endDatum}")
|
|
||||||
if (event.beschreibung != null) {
|
|
||||||
append(" | Description: ${event.beschreibung}")
|
|
||||||
}
|
|
||||||
if (event.sparten.isNotEmpty()) {
|
|
||||||
append(" | Sports: ${event.sparten.joinToString(", ") { it.name }}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a simple HTML representation of an event
|
|
||||||
*/
|
|
||||||
fun createEventHtml(event: Veranstaltung): String {
|
|
||||||
return """
|
|
||||||
<div class="event-card">
|
|
||||||
<h3>${event.name}</h3>
|
|
||||||
<p><strong>Location:</strong> ${event.ort}</p>
|
|
||||||
<p><strong>Date:</strong> ${event.startDatum} - ${event.endDatum}</p>
|
|
||||||
${if (event.beschreibung != null) "<p><strong>Description:</strong> ${event.beschreibung}</p>" else ""}
|
|
||||||
${if (event.sparten.isNotEmpty())
|
|
||||||
"<p><strong>Sports:</strong> ${event.sparten.joinToString(", ") { it.name }}</p>"
|
|
||||||
else ""}
|
|
||||||
</div>
|
|
||||||
""".trimIndent()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
-176
@@ -1,176 +0,0 @@
|
|||||||
package at.mocode.client.common.components.events
|
|
||||||
|
|
||||||
import at.mocode.events.domain.model.Veranstaltung
|
|
||||||
import io.ktor.client.*
|
|
||||||
import io.ktor.client.call.*
|
|
||||||
import io.ktor.client.plugins.contentnegotiation.*
|
|
||||||
import io.ktor.client.request.*
|
|
||||||
import io.ktor.serialization.kotlinx.json.*
|
|
||||||
import kotlinx.coroutines.MainScope
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import react.*
|
|
||||||
import react.dom.html.ReactHTML.div
|
|
||||||
import react.dom.html.ReactHTML.h1
|
|
||||||
import react.dom.html.ReactHTML.h3
|
|
||||||
import react.dom.html.ReactHTML.p
|
|
||||||
import react.dom.html.ReactHTML.span
|
|
||||||
import emotion.react.css
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Props for the VeranstaltungsListe component
|
|
||||||
*/
|
|
||||||
external interface VeranstaltungsListeProps : Props
|
|
||||||
|
|
||||||
// Create Ktor client for API calls
|
|
||||||
private val apiClient = HttpClient {
|
|
||||||
install(ContentNegotiation) {
|
|
||||||
json(Json {
|
|
||||||
ignoreUnknownKeys = true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* React component that displays a list of events (Veranstaltungen).
|
|
||||||
*
|
|
||||||
* This component loads event data from the API and renders it as HTML.
|
|
||||||
* Uses useState for state management and useEffectOnce for data loading.
|
|
||||||
*/
|
|
||||||
val VeranstaltungsListe = FC<VeranstaltungsListeProps> { _ ->
|
|
||||||
// State management with useState
|
|
||||||
var events by useState<List<Veranstaltung>>(emptyList())
|
|
||||||
var loading by useState(true)
|
|
||||||
var error by useState<String?>(null)
|
|
||||||
|
|
||||||
// Data loading with useEffectOnce hook
|
|
||||||
useEffectOnce {
|
|
||||||
val scope = MainScope()
|
|
||||||
scope.launch {
|
|
||||||
try {
|
|
||||||
loading = true
|
|
||||||
error = null
|
|
||||||
// Load data with Ktor client
|
|
||||||
val response = apiClient.get("http://localhost:8080/api/events")
|
|
||||||
val loadedEvents: List<Veranstaltung> = response.body()
|
|
||||||
events = loadedEvents
|
|
||||||
} catch (e: Exception) {
|
|
||||||
error = "Fehler beim Laden der Veranstaltungen: ${e.message}"
|
|
||||||
console.error("Error loading events:", e)
|
|
||||||
} finally {
|
|
||||||
loading = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render HTML with React DOM elements
|
|
||||||
div {
|
|
||||||
css {
|
|
||||||
// Basic styling for the main container
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
+"Veranstaltungen"
|
|
||||||
}
|
|
||||||
|
|
||||||
when {
|
|
||||||
loading -> {
|
|
||||||
div {
|
|
||||||
+"Lade Veranstaltungen..."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
error != null -> {
|
|
||||||
div {
|
|
||||||
+error!!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
events.isEmpty() -> {
|
|
||||||
div {
|
|
||||||
+"Keine Veranstaltungen verfügbar"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
div {
|
|
||||||
events.forEach { event ->
|
|
||||||
div {
|
|
||||||
h3 {
|
|
||||||
+event.name
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
span {
|
|
||||||
+"📍"
|
|
||||||
}
|
|
||||||
+" ${event.ort}"
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
span {
|
|
||||||
+"📅"
|
|
||||||
}
|
|
||||||
if (event.isMultiDay()) {
|
|
||||||
+" ${event.startDatum} - ${event.endDatum} (${event.getDurationInDays()} Tage)"
|
|
||||||
} else {
|
|
||||||
+" ${event.startDatum} (Eintägige Veranstaltung)"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Status indicators
|
|
||||||
val statusList = mutableListOf<String>()
|
|
||||||
if (event.istAktiv) statusList.add("Aktiv")
|
|
||||||
if (event.istOeffentlich) statusList.add("Öffentlich")
|
|
||||||
if (event.isRegistrationOpen()) statusList.add("Anmeldung offen")
|
|
||||||
if (statusList.isNotEmpty()) {
|
|
||||||
p {
|
|
||||||
span {
|
|
||||||
+"ℹ️"
|
|
||||||
}
|
|
||||||
+" Status: ${statusList.joinToString(", ")}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Description
|
|
||||||
if (!event.beschreibung.isNullOrBlank()) {
|
|
||||||
p {
|
|
||||||
span {
|
|
||||||
+"📝"
|
|
||||||
}
|
|
||||||
+" ${event.beschreibung}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sports/Sparten
|
|
||||||
if (event.sparten.isNotEmpty()) {
|
|
||||||
p {
|
|
||||||
span {
|
|
||||||
+"🏆"
|
|
||||||
}
|
|
||||||
+" Sparten: ${event.sparten.joinToString(", ") { it.name }}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Additional info
|
|
||||||
event.maxTeilnehmer?.let { max ->
|
|
||||||
p {
|
|
||||||
span {
|
|
||||||
+"👥"
|
|
||||||
}
|
|
||||||
+" Max. Teilnehmer: $max"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
event.anmeldeschluss?.let { deadline ->
|
|
||||||
p {
|
|
||||||
span {
|
|
||||||
+"⏰"
|
|
||||||
}
|
|
||||||
+" Anmeldeschluss: $deadline"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-308
@@ -1,308 +0,0 @@
|
|||||||
package at.mocode.client.common.components.horses
|
|
||||||
|
|
||||||
import at.mocode.horses.domain.model.DomPferd
|
|
||||||
import io.ktor.client.*
|
|
||||||
import io.ktor.client.call.*
|
|
||||||
import io.ktor.client.plugins.contentnegotiation.*
|
|
||||||
import io.ktor.client.request.*
|
|
||||||
import io.ktor.serialization.kotlinx.json.*
|
|
||||||
import kotlinx.coroutines.MainScope
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import react.*
|
|
||||||
import react.dom.html.ReactHTML.div
|
|
||||||
import react.dom.html.ReactHTML.h1
|
|
||||||
import react.dom.html.ReactHTML.h3
|
|
||||||
import react.dom.html.ReactHTML.p
|
|
||||||
import react.dom.html.ReactHTML.span
|
|
||||||
import emotion.react.css
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Props for the PferdeListe component
|
|
||||||
*/
|
|
||||||
external interface PferdeListeProps : Props
|
|
||||||
|
|
||||||
// Create Ktor client for API calls
|
|
||||||
private val apiClient = HttpClient {
|
|
||||||
install(ContentNegotiation) {
|
|
||||||
json(Json {
|
|
||||||
ignoreUnknownKeys = true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* React component that displays a list of horses (Pferde).
|
|
||||||
*
|
|
||||||
* This component loads horse data from the API and renders it as HTML.
|
|
||||||
* Uses useState for state management and useEffectOnce for data loading.
|
|
||||||
*/
|
|
||||||
val PferdeListe = FC<PferdeListeProps> { _ ->
|
|
||||||
// State management with useState
|
|
||||||
var horses by useState<List<DomPferd>>(emptyList())
|
|
||||||
var loading by useState(true)
|
|
||||||
var error by useState<String?>(null)
|
|
||||||
|
|
||||||
// Data loading with useEffectOnce hook
|
|
||||||
useEffectOnce {
|
|
||||||
val scope = MainScope()
|
|
||||||
scope.launch {
|
|
||||||
try {
|
|
||||||
loading = true
|
|
||||||
error = null
|
|
||||||
// Load data with Ktor client
|
|
||||||
val response = apiClient.get("http://localhost:8080/api/horses")
|
|
||||||
val loadedHorses: List<DomPferd> = response.body()
|
|
||||||
horses = loadedHorses
|
|
||||||
} catch (e: Exception) {
|
|
||||||
error = "Fehler beim Laden der Pferde: ${e.message}"
|
|
||||||
console.error("Error loading horses:", e)
|
|
||||||
} finally {
|
|
||||||
loading = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render HTML with React DOM elements
|
|
||||||
div {
|
|
||||||
css {
|
|
||||||
// Basic styling for the main container
|
|
||||||
"padding" to "20px"
|
|
||||||
"fontFamily" to "Arial, sans-serif"
|
|
||||||
"maxWidth" to "1200px"
|
|
||||||
"margin" to "0 auto"
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
css {
|
|
||||||
"color" to "#2c3e50"
|
|
||||||
"borderBottom" to "2px solid #3498db"
|
|
||||||
"paddingBottom" to "10px"
|
|
||||||
"marginBottom" to "20px"
|
|
||||||
}
|
|
||||||
+"Pferde-Register"
|
|
||||||
}
|
|
||||||
|
|
||||||
when {
|
|
||||||
loading -> {
|
|
||||||
div {
|
|
||||||
css {
|
|
||||||
"padding" to "20px"
|
|
||||||
"textAlign" to "center"
|
|
||||||
"color" to "#666"
|
|
||||||
"fontSize" to "18px"
|
|
||||||
}
|
|
||||||
+"Lade Pferde..."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
error != null -> {
|
|
||||||
div {
|
|
||||||
css {
|
|
||||||
"padding" to "20px"
|
|
||||||
"textAlign" to "center"
|
|
||||||
"color" to "#e74c3c"
|
|
||||||
"backgroundColor" to "#fdeaea"
|
|
||||||
"border" to "1px solid #e74c3c"
|
|
||||||
"borderRadius" to "8px"
|
|
||||||
"margin" to "20px 0"
|
|
||||||
}
|
|
||||||
+error!!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
horses.isEmpty() -> {
|
|
||||||
div {
|
|
||||||
css {
|
|
||||||
"padding" to "20px"
|
|
||||||
"textAlign" to "center"
|
|
||||||
"color" to "#666"
|
|
||||||
"backgroundColor" to "#f8f9fa"
|
|
||||||
"border" to "1px solid #e0e0e0"
|
|
||||||
"borderRadius" to "8px"
|
|
||||||
"margin" to "20px 0"
|
|
||||||
}
|
|
||||||
+"Keine Pferde verfügbar"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
div {
|
|
||||||
css {
|
|
||||||
"display" to "grid"
|
|
||||||
"gridTemplateColumns" to "repeat(auto-fill, minmax(300px, 1fr))"
|
|
||||||
"gap" to "20px"
|
|
||||||
}
|
|
||||||
horses.forEach { horse ->
|
|
||||||
div {
|
|
||||||
css {
|
|
||||||
"border" to "1px solid #e0e0e0"
|
|
||||||
"borderRadius" to "8px"
|
|
||||||
"padding" to "15px"
|
|
||||||
"backgroundColor" to "#f9f9f9"
|
|
||||||
"boxShadow" to "0 2px 4px rgba(0,0,0,0.1)"
|
|
||||||
"transition" to "transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out"
|
|
||||||
"hover" to {
|
|
||||||
"transform" to "translateY(-5px)"
|
|
||||||
"boxShadow" to "0 5px 15px rgba(0,0,0,0.1)"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
h3 {
|
|
||||||
css {
|
|
||||||
"color" to "#3498db"
|
|
||||||
"marginTop" to "0"
|
|
||||||
"marginBottom" to "10px"
|
|
||||||
"borderBottom" to "1px solid #e0e0e0"
|
|
||||||
"paddingBottom" to "5px"
|
|
||||||
}
|
|
||||||
+horse.getDisplayName()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Basic information
|
|
||||||
p {
|
|
||||||
span {
|
|
||||||
+"🐎"
|
|
||||||
}
|
|
||||||
+" Geschlecht: ${horse.geschlecht.name}"
|
|
||||||
}
|
|
||||||
|
|
||||||
horse.geburtsdatum?.let { birthDate ->
|
|
||||||
p {
|
|
||||||
span {
|
|
||||||
+"📅"
|
|
||||||
}
|
|
||||||
+" Geburtsdatum: $birthDate"
|
|
||||||
horse.getAge()?.let { age ->
|
|
||||||
+" (${age} Jahre alt)"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
horse.rasse?.let { breed ->
|
|
||||||
p {
|
|
||||||
span {
|
|
||||||
+"🏇"
|
|
||||||
}
|
|
||||||
+" Rasse: $breed"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
horse.farbe?.let { color ->
|
|
||||||
p {
|
|
||||||
span {
|
|
||||||
+"🎨"
|
|
||||||
}
|
|
||||||
+" Farbe: $color"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
horse.stockmass?.let { height ->
|
|
||||||
p {
|
|
||||||
span {
|
|
||||||
+"📏"
|
|
||||||
}
|
|
||||||
+" Stockmaß: ${height} cm"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Identification numbers
|
|
||||||
val identificationNumbers = mutableListOf<String>()
|
|
||||||
horse.lebensnummer?.let { identificationNumbers.add("Lebensnummer: $it") }
|
|
||||||
horse.chipNummer?.let { identificationNumbers.add("Chip: $it") }
|
|
||||||
horse.passNummer?.let { identificationNumbers.add("Pass: $it") }
|
|
||||||
horse.oepsNummer?.let { identificationNumbers.add("OEPS: $it") }
|
|
||||||
horse.feiNummer?.let { identificationNumbers.add("FEI: $it") }
|
|
||||||
|
|
||||||
if (identificationNumbers.isNotEmpty()) {
|
|
||||||
p {
|
|
||||||
span {
|
|
||||||
+"🆔"
|
|
||||||
}
|
|
||||||
+" Identifikation: ${identificationNumbers.joinToString(", ")}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pedigree information
|
|
||||||
val pedigreeInfo = mutableListOf<String>()
|
|
||||||
horse.vaterName?.let { pedigreeInfo.add("Vater: $it") }
|
|
||||||
horse.mutterName?.let { pedigreeInfo.add("Mutter: $it") }
|
|
||||||
horse.mutterVaterName?.let { pedigreeInfo.add("Muttervater: $it") }
|
|
||||||
|
|
||||||
if (pedigreeInfo.isNotEmpty()) {
|
|
||||||
p {
|
|
||||||
span {
|
|
||||||
+"🧬"
|
|
||||||
}
|
|
||||||
+" Abstammung: ${pedigreeInfo.joinToString(", ")}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Breeding information
|
|
||||||
horse.zuechterName?.let { breeder ->
|
|
||||||
p {
|
|
||||||
span {
|
|
||||||
+"👨🌾"
|
|
||||||
}
|
|
||||||
+" Züchter: $breeder"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
horse.zuchtbuchNummer?.let { studbook ->
|
|
||||||
p {
|
|
||||||
span {
|
|
||||||
+"📖"
|
|
||||||
}
|
|
||||||
+" Zuchtbuchnummer: $studbook"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Status indicators
|
|
||||||
val statusList = mutableListOf<String>()
|
|
||||||
if (horse.istAktiv) statusList.add("Aktiv") else statusList.add("Inaktiv")
|
|
||||||
if (horse.hasCompleteIdentification()) statusList.add("Vollständig identifiziert")
|
|
||||||
if (horse.isOepsRegistered()) statusList.add("OEPS registriert")
|
|
||||||
if (horse.isFeiRegistered()) statusList.add("FEI registriert")
|
|
||||||
|
|
||||||
p {
|
|
||||||
span {
|
|
||||||
+"ℹ️"
|
|
||||||
}
|
|
||||||
+" Status: ${statusList.joinToString(", ")}"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Data source
|
|
||||||
p {
|
|
||||||
span {
|
|
||||||
+"📊"
|
|
||||||
}
|
|
||||||
+" Datenquelle: ${horse.datenQuelle.name}"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Notes
|
|
||||||
horse.bemerkungen?.let { notes ->
|
|
||||||
p {
|
|
||||||
span {
|
|
||||||
+"📝"
|
|
||||||
}
|
|
||||||
+" Bemerkungen: $notes"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creation and update dates
|
|
||||||
p {
|
|
||||||
span {
|
|
||||||
+"📅"
|
|
||||||
}
|
|
||||||
+" Erstellt am: ${horse.createdAt}"
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
span {
|
|
||||||
+"🔄"
|
|
||||||
}
|
|
||||||
+" Zuletzt geändert: ${horse.updatedAt}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-257
@@ -1,257 +0,0 @@
|
|||||||
package at.mocode.client.common.components.masterdata
|
|
||||||
|
|
||||||
import at.mocode.masterdata.domain.model.LandDefinition
|
|
||||||
import io.ktor.client.*
|
|
||||||
import io.ktor.client.call.*
|
|
||||||
import io.ktor.client.plugins.contentnegotiation.*
|
|
||||||
import io.ktor.client.request.*
|
|
||||||
import io.ktor.serialization.kotlinx.json.*
|
|
||||||
import kotlinx.coroutines.MainScope
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import react.*
|
|
||||||
import react.dom.html.ReactHTML.div
|
|
||||||
import react.dom.html.ReactHTML.h1
|
|
||||||
import react.dom.html.ReactHTML.h2
|
|
||||||
import react.dom.html.ReactHTML.h3
|
|
||||||
import react.dom.html.ReactHTML.p
|
|
||||||
import react.dom.html.ReactHTML.span
|
|
||||||
import emotion.react.css
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Props for the StammdatenListe component
|
|
||||||
*/
|
|
||||||
external interface StammdatenListeProps : Props
|
|
||||||
|
|
||||||
// Create Ktor client for API calls
|
|
||||||
private val apiClient = HttpClient {
|
|
||||||
install(ContentNegotiation) {
|
|
||||||
json(Json {
|
|
||||||
ignoreUnknownKeys = true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* React component that displays master data (Stammdaten).
|
|
||||||
*
|
|
||||||
* This component loads master data from the API and renders it as HTML.
|
|
||||||
* Currently focuses on countries (LandDefinition) but can be extended for other master data types.
|
|
||||||
* Uses useState for state management and useEffectOnce for data loading.
|
|
||||||
*/
|
|
||||||
val StammdatenListe = FC<StammdatenListeProps> { _ ->
|
|
||||||
// State management with useState
|
|
||||||
var countries by useState<List<LandDefinition>>(emptyList())
|
|
||||||
var loading by useState(true)
|
|
||||||
var error by useState<String?>(null)
|
|
||||||
|
|
||||||
// Data loading with useEffectOnce hook
|
|
||||||
useEffectOnce {
|
|
||||||
val scope = MainScope()
|
|
||||||
scope.launch {
|
|
||||||
try {
|
|
||||||
loading = true
|
|
||||||
error = null
|
|
||||||
// Load data with Ktor client
|
|
||||||
val response = apiClient.get("http://localhost:8080/api/masterdata/countries")
|
|
||||||
val loadedCountries: List<LandDefinition> = response.body()
|
|
||||||
countries = loadedCountries
|
|
||||||
} catch (e: Exception) {
|
|
||||||
error = "Fehler beim Laden der Stammdaten: ${e.message}"
|
|
||||||
console.error("Error loading master data:", e)
|
|
||||||
} finally {
|
|
||||||
loading = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render HTML with React DOM elements
|
|
||||||
div {
|
|
||||||
css {
|
|
||||||
// Basic styling for the main container
|
|
||||||
"padding" to "20px"
|
|
||||||
"fontFamily" to "Arial, sans-serif"
|
|
||||||
"maxWidth" to "1200px"
|
|
||||||
"margin" to "0 auto"
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
css {
|
|
||||||
"color" to "#2c3e50"
|
|
||||||
"borderBottom" to "2px solid #3498db"
|
|
||||||
"paddingBottom" to "10px"
|
|
||||||
"marginBottom" to "20px"
|
|
||||||
}
|
|
||||||
+"Stammdaten"
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
css {
|
|
||||||
"color" to "#34495e"
|
|
||||||
"marginTop" to "20px"
|
|
||||||
"marginBottom" to "15px"
|
|
||||||
"fontSize" to "1.5em"
|
|
||||||
}
|
|
||||||
+"Länder"
|
|
||||||
}
|
|
||||||
|
|
||||||
when {
|
|
||||||
loading -> {
|
|
||||||
div {
|
|
||||||
css {
|
|
||||||
"padding" to "20px"
|
|
||||||
"textAlign" to "center"
|
|
||||||
"color" to "#666"
|
|
||||||
"fontSize" to "18px"
|
|
||||||
}
|
|
||||||
+"Lade Stammdaten..."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
error != null -> {
|
|
||||||
div {
|
|
||||||
css {
|
|
||||||
"padding" to "20px"
|
|
||||||
"textAlign" to "center"
|
|
||||||
"color" to "#e74c3c"
|
|
||||||
"backgroundColor" to "#fdeaea"
|
|
||||||
"border" to "1px solid #e74c3c"
|
|
||||||
"borderRadius" to "8px"
|
|
||||||
"margin" to "20px 0"
|
|
||||||
}
|
|
||||||
+error!!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
countries.isEmpty() -> {
|
|
||||||
div {
|
|
||||||
css {
|
|
||||||
"padding" to "20px"
|
|
||||||
"textAlign" to "center"
|
|
||||||
"color" to "#666"
|
|
||||||
"backgroundColor" to "#f8f9fa"
|
|
||||||
"border" to "1px solid #e0e0e0"
|
|
||||||
"borderRadius" to "8px"
|
|
||||||
"margin" to "20px 0"
|
|
||||||
}
|
|
||||||
+"Keine Länder verfügbar"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
div {
|
|
||||||
css {
|
|
||||||
"display" to "grid"
|
|
||||||
"gridTemplateColumns" to "repeat(auto-fill, minmax(300px, 1fr))"
|
|
||||||
"gap" to "20px"
|
|
||||||
}
|
|
||||||
countries.forEach { country ->
|
|
||||||
div {
|
|
||||||
css {
|
|
||||||
"border" to "1px solid #e0e0e0"
|
|
||||||
"borderRadius" to "8px"
|
|
||||||
"padding" to "15px"
|
|
||||||
"backgroundColor" to "#f9f9f9"
|
|
||||||
"boxShadow" to "0 2px 4px rgba(0,0,0,0.1)"
|
|
||||||
"transition" to "transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out"
|
|
||||||
"hover" to {
|
|
||||||
"transform" to "translateY(-5px)"
|
|
||||||
"boxShadow" to "0 5px 15px rgba(0,0,0,0.1)"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
h3 {
|
|
||||||
css {
|
|
||||||
"color" to "#3498db"
|
|
||||||
"marginTop" to "0"
|
|
||||||
"marginBottom" to "10px"
|
|
||||||
"borderBottom" to "1px solid #e0e0e0"
|
|
||||||
"paddingBottom" to "5px"
|
|
||||||
}
|
|
||||||
+country.nameDeutsch
|
|
||||||
}
|
|
||||||
|
|
||||||
// ISO codes
|
|
||||||
p {
|
|
||||||
span {
|
|
||||||
+"🌍"
|
|
||||||
}
|
|
||||||
+" ISO-Codes: ${country.isoAlpha2Code} / ${country.isoAlpha3Code}"
|
|
||||||
country.isoNumerischerCode?.let { numCode ->
|
|
||||||
+" / $numCode"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// English name if available
|
|
||||||
country.nameEnglisch?.let { englishName ->
|
|
||||||
p {
|
|
||||||
span {
|
|
||||||
+"🇬🇧"
|
|
||||||
}
|
|
||||||
+" Englischer Name: $englishName"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// EU/EWR membership
|
|
||||||
val membershipInfo = mutableListOf<String>()
|
|
||||||
country.istEuMitglied?.let { isEuMember ->
|
|
||||||
if (isEuMember) membershipInfo.add("EU-Mitglied")
|
|
||||||
}
|
|
||||||
country.istEwrMitglied?.let { isEwrMember ->
|
|
||||||
if (isEwrMember) membershipInfo.add("EWR-Mitglied")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (membershipInfo.isNotEmpty()) {
|
|
||||||
p {
|
|
||||||
span {
|
|
||||||
+"🇪🇺"
|
|
||||||
}
|
|
||||||
+" Mitgliedschaft: ${membershipInfo.joinToString(", ")}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Status
|
|
||||||
p {
|
|
||||||
span {
|
|
||||||
+"ℹ️"
|
|
||||||
}
|
|
||||||
+" Status: ${if (country.istAktiv) "Aktiv" else "Inaktiv"}"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort order if available
|
|
||||||
country.sortierReihenfolge?.let { sortOrder ->
|
|
||||||
p {
|
|
||||||
span {
|
|
||||||
+"🔢"
|
|
||||||
}
|
|
||||||
+" Sortierreihenfolge: $sortOrder"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Coat of arms/flag URL if available
|
|
||||||
country.wappenUrl?.let { flagUrl ->
|
|
||||||
p {
|
|
||||||
span {
|
|
||||||
+"🏴"
|
|
||||||
}
|
|
||||||
+" Wappen/Flagge: $flagUrl"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creation and update dates
|
|
||||||
p {
|
|
||||||
span {
|
|
||||||
+"📅"
|
|
||||||
}
|
|
||||||
+" Erstellt am: ${country.createdAt}"
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
span {
|
|
||||||
+"🔄"
|
|
||||||
}
|
|
||||||
+" Zuletzt geändert: ${country.updatedAt}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,148 +0,0 @@
|
|||||||
package at.mocode.client.common.di
|
|
||||||
|
|
||||||
import at.mocode.members.application.usecase.CreatePersonUseCase
|
|
||||||
import at.mocode.members.domain.repository.PersonRepository
|
|
||||||
import at.mocode.members.domain.repository.VereinRepository
|
|
||||||
import at.mocode.members.domain.service.MasterDataService
|
|
||||||
import at.mocode.client.web.viewmodel.CreatePersonViewModel
|
|
||||||
import at.mocode.client.web.viewmodel.PersonListViewModel
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Simple dependency injection container for the application.
|
|
||||||
* In a real application, you might want to use a proper DI framework like Koin.
|
|
||||||
*/
|
|
||||||
object AppDependencies {
|
|
||||||
|
|
||||||
// Mock implementations for demonstration
|
|
||||||
// In a real application, these would be proper implementations
|
|
||||||
private val mockPersonRepository = object : PersonRepository {
|
|
||||||
override suspend fun save(person: at.mocode.members.domain.model.DomPerson): at.mocode.members.domain.model.DomPerson {
|
|
||||||
// Mock implementation - just return the person with an ID
|
|
||||||
return person.copy(personId = com.benasher44.uuid.uuid4())
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun findById(id: com.benasher44.uuid.Uuid): at.mocode.members.domain.model.DomPerson? {
|
|
||||||
return null // Mock implementation
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun findByOepsSatzNr(oepsSatzNr: String): at.mocode.members.domain.model.DomPerson? {
|
|
||||||
return null // Mock implementation
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun findByStammVereinId(vereinId: com.benasher44.uuid.Uuid): List<at.mocode.members.domain.model.DomPerson> {
|
|
||||||
return emptyList() // Mock implementation
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun findByName(searchTerm: String, limit: Int): List<at.mocode.members.domain.model.DomPerson> {
|
|
||||||
return emptyList() // Mock implementation
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun findAllActive(limit: Int, offset: Int): List<at.mocode.members.domain.model.DomPerson> {
|
|
||||||
return emptyList() // Mock implementation
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun countActive(): Long {
|
|
||||||
return 0L // Mock implementation
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun existsByOepsSatzNr(oepsSatzNr: String): Boolean {
|
|
||||||
return false // Mock implementation - no duplicates for demo
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun delete(id: com.benasher44.uuid.Uuid): Boolean {
|
|
||||||
return true // Mock implementation
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val mockVereinRepository = object : VereinRepository {
|
|
||||||
override suspend fun findById(id: com.benasher44.uuid.Uuid): at.mocode.members.domain.model.DomVerein? {
|
|
||||||
return null // Mock implementation
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun findByOepsVereinsNr(oepsVereinsNr: String): at.mocode.members.domain.model.DomVerein? {
|
|
||||||
return null // Mock implementation
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun findByName(searchTerm: String, limit: Int): List<at.mocode.members.domain.model.DomVerein> {
|
|
||||||
return emptyList() // Mock implementation
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun findByBundeslandId(bundeslandId: com.benasher44.uuid.Uuid): List<at.mocode.members.domain.model.DomVerein> {
|
|
||||||
return emptyList() // Mock implementation
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun findByLandId(landId: com.benasher44.uuid.Uuid): List<at.mocode.members.domain.model.DomVerein> {
|
|
||||||
return emptyList() // Mock implementation
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun findAllActive(limit: Int, offset: Int): List<at.mocode.members.domain.model.DomVerein> {
|
|
||||||
return emptyList() // Mock implementation
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun findByLocation(searchTerm: String, limit: Int): List<at.mocode.members.domain.model.DomVerein> {
|
|
||||||
return emptyList() // Mock implementation
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun save(verein: at.mocode.members.domain.model.DomVerein): at.mocode.members.domain.model.DomVerein {
|
|
||||||
return verein.copy(vereinId = com.benasher44.uuid.uuid4()) // Mock implementation
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun delete(id: com.benasher44.uuid.Uuid): Boolean {
|
|
||||||
return true // Mock implementation
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun existsByOepsVereinsNr(oepsVereinsNr: String): Boolean {
|
|
||||||
return false // Mock implementation
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun countActive(): Long {
|
|
||||||
return 0L // Mock implementation
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun countActiveByBundeslandId(bundeslandId: com.benasher44.uuid.Uuid): Long {
|
|
||||||
return 0L // Mock implementation
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val mockMasterDataService = object : MasterDataService {
|
|
||||||
override suspend fun countryExists(countryId: com.benasher44.uuid.Uuid): Boolean {
|
|
||||||
return true // Mock implementation - assume all countries exist
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun stateExists(stateId: com.benasher44.uuid.Uuid): Boolean {
|
|
||||||
return true // Mock implementation - assume all states exist
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getCountryById(countryId: com.benasher44.uuid.Uuid): MasterDataService.CountryInfo? {
|
|
||||||
return null // Mock implementation
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getStateById(stateId: com.benasher44.uuid.Uuid): MasterDataService.StateInfo? {
|
|
||||||
return null // Mock implementation
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getAllCountries(): List<MasterDataService.CountryInfo> {
|
|
||||||
return emptyList() // Mock implementation
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getStatesByCountry(countryId: com.benasher44.uuid.Uuid): List<MasterDataService.StateInfo> {
|
|
||||||
return emptyList() // Mock implementation
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use case instances
|
|
||||||
private val createPersonUseCase = CreatePersonUseCase(
|
|
||||||
personRepository = mockPersonRepository,
|
|
||||||
vereinRepository = mockVereinRepository,
|
|
||||||
masterDataService = mockMasterDataService
|
|
||||||
)
|
|
||||||
|
|
||||||
// ViewModel factory methods
|
|
||||||
fun createPersonViewModel(): CreatePersonViewModel {
|
|
||||||
return CreatePersonViewModel(createPersonUseCase)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun personListViewModel(): PersonListViewModel {
|
|
||||||
return PersonListViewModel(mockPersonRepository)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
plugins {
|
plugins {
|
||||||
kotlin("jvm")
|
kotlin("jvm")
|
||||||
kotlin("plugin.spring")
|
kotlin("plugin.spring")
|
||||||
id("org.springframework.boot") version "3.2.3"
|
id("org.springframework.boot")
|
||||||
id("io.spring.dependency-management") version "1.1.4"
|
id("io.spring.dependency-management") version "1.1.4"
|
||||||
id("org.jetbrains.compose") version "1.7.3"
|
id("org.jetbrains.compose") version "1.7.3"
|
||||||
id("org.jetbrains.kotlin.plugin.compose") version "2.1.21"
|
id("org.jetbrains.kotlin.plugin.compose") version "2.1.21"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
plugins {
|
plugins {
|
||||||
kotlin("jvm")
|
kotlin("jvm")
|
||||||
kotlin("plugin.spring")
|
kotlin("plugin.spring")
|
||||||
id("org.springframework.boot") version "3.2.3"
|
id("org.springframework.boot")
|
||||||
id("io.spring.dependency-management") version "1.1.4"
|
id("io.spring.dependency-management") version "1.1.4"
|
||||||
id("org.jetbrains.compose") version "1.7.3"
|
id("org.jetbrains.compose") version "1.7.3"
|
||||||
id("org.jetbrains.kotlin.plugin.compose") version "2.1.21"
|
id("org.jetbrains.kotlin.plugin.compose") version "2.1.21"
|
||||||
@@ -12,38 +12,8 @@ repositories {
|
|||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure tests to exclude failing tests
|
|
||||||
tasks.withType<Test> {
|
tasks.withType<Test> {
|
||||||
useJUnitPlatform()
|
useJUnitPlatform()
|
||||||
filter {
|
|
||||||
// Exclude all tests for now
|
|
||||||
excludeTestsMatching("at.mocode.client.web.*")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Configure Kotlin source sets to exclude problematic files
|
|
||||||
kotlin {
|
|
||||||
sourceSets {
|
|
||||||
main {
|
|
||||||
kotlin {
|
|
||||||
// Exclude backup directories
|
|
||||||
exclude("at/mocode/client/web/screens/bak/**")
|
|
||||||
exclude("at/mocode/client/web/viewmodel/bak/**")
|
|
||||||
// We're now fixing these files, so don't exclude them
|
|
||||||
// exclude("at/mocode/client/web/di/AppDependencies.kt")
|
|
||||||
// exclude("**/screens/CreatePersonScreen.kt")
|
|
||||||
// exclude("**/screens/PersonListScreen.kt")
|
|
||||||
// exclude("**/viewmodel/CreatePersonViewModel.kt")
|
|
||||||
// exclude("**/viewmodel/PersonListViewModel.kt")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
test {
|
|
||||||
kotlin {
|
|
||||||
// Exclude all test files for now
|
|
||||||
exclude("**/*Test.kt")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|||||||
@@ -1,148 +0,0 @@
|
|||||||
package at.mocode.client.web.di
|
|
||||||
|
|
||||||
import at.mocode.members.application.usecase.CreatePersonUseCase
|
|
||||||
import at.mocode.members.domain.repository.PersonRepository
|
|
||||||
import at.mocode.members.domain.repository.VereinRepository
|
|
||||||
import at.mocode.members.domain.service.MasterDataService
|
|
||||||
import at.mocode.client.web.viewmodel.CreatePersonViewModel
|
|
||||||
import at.mocode.client.web.viewmodel.PersonListViewModel
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Simple dependency injection container for the application.
|
|
||||||
* In a real application, you might want to use a proper DI framework like Koin.
|
|
||||||
*/
|
|
||||||
object AppDependencies {
|
|
||||||
|
|
||||||
// Mock implementations for demonstration
|
|
||||||
// In a real application, these would be proper implementations
|
|
||||||
private val mockPersonRepository = object : PersonRepository {
|
|
||||||
override suspend fun save(person: at.mocode.members.domain.model.DomPerson): at.mocode.members.domain.model.DomPerson {
|
|
||||||
// Mock implementation - just return the person with an ID
|
|
||||||
return person.copy(personId = com.benasher44.uuid.uuid4())
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun findById(id: com.benasher44.uuid.Uuid): at.mocode.members.domain.model.DomPerson? {
|
|
||||||
return null // Mock implementation
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun findByOepsSatzNr(oepsSatzNr: String): at.mocode.members.domain.model.DomPerson? {
|
|
||||||
return null // Mock implementation
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun findByStammVereinId(vereinId: com.benasher44.uuid.Uuid): List<at.mocode.members.domain.model.DomPerson> {
|
|
||||||
return emptyList() // Mock implementation
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun findByName(searchTerm: String, limit: Int): List<at.mocode.members.domain.model.DomPerson> {
|
|
||||||
return emptyList() // Mock implementation
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun findAllActive(limit: Int, offset: Int): List<at.mocode.members.domain.model.DomPerson> {
|
|
||||||
return emptyList() // Mock implementation
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun countActive(): Long {
|
|
||||||
return 0L // Mock implementation
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun existsByOepsSatzNr(oepsSatzNr: String): Boolean {
|
|
||||||
return false // Mock implementation - no duplicates for demo
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun delete(id: com.benasher44.uuid.Uuid): Boolean {
|
|
||||||
return true // Mock implementation
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val mockVereinRepository = object : VereinRepository {
|
|
||||||
override suspend fun findById(id: com.benasher44.uuid.Uuid): at.mocode.members.domain.model.DomVerein? {
|
|
||||||
return null // Mock implementation
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun findByOepsVereinsNr(oepsVereinsNr: String): at.mocode.members.domain.model.DomVerein? {
|
|
||||||
return null // Mock implementation
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun findByName(searchTerm: String, limit: Int): List<at.mocode.members.domain.model.DomVerein> {
|
|
||||||
return emptyList() // Mock implementation
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun findByBundeslandId(bundeslandId: com.benasher44.uuid.Uuid): List<at.mocode.members.domain.model.DomVerein> {
|
|
||||||
return emptyList() // Mock implementation
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun findByLandId(landId: com.benasher44.uuid.Uuid): List<at.mocode.members.domain.model.DomVerein> {
|
|
||||||
return emptyList() // Mock implementation
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun findAllActive(limit: Int, offset: Int): List<at.mocode.members.domain.model.DomVerein> {
|
|
||||||
return emptyList() // Mock implementation
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun findByLocation(searchTerm: String, limit: Int): List<at.mocode.members.domain.model.DomVerein> {
|
|
||||||
return emptyList() // Mock implementation
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun save(verein: at.mocode.members.domain.model.DomVerein): at.mocode.members.domain.model.DomVerein {
|
|
||||||
return verein.copy(vereinId = com.benasher44.uuid.uuid4()) // Mock implementation
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun delete(id: com.benasher44.uuid.Uuid): Boolean {
|
|
||||||
return true // Mock implementation
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun existsByOepsVereinsNr(oepsVereinsNr: String): Boolean {
|
|
||||||
return false // Mock implementation
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun countActive(): Long {
|
|
||||||
return 0L // Mock implementation
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun countActiveByBundeslandId(bundeslandId: com.benasher44.uuid.Uuid): Long {
|
|
||||||
return 0L // Mock implementation
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val mockMasterDataService = object : MasterDataService {
|
|
||||||
override suspend fun countryExists(countryId: com.benasher44.uuid.Uuid): Boolean {
|
|
||||||
return true // Mock implementation - assume all countries exist
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun stateExists(stateId: com.benasher44.uuid.Uuid): Boolean {
|
|
||||||
return true // Mock implementation - assume all states exist
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getCountryById(countryId: com.benasher44.uuid.Uuid): MasterDataService.CountryInfo? {
|
|
||||||
return null // Mock implementation
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getStateById(stateId: com.benasher44.uuid.Uuid): MasterDataService.StateInfo? {
|
|
||||||
return null // Mock implementation
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getAllCountries(): List<MasterDataService.CountryInfo> {
|
|
||||||
return emptyList() // Mock implementation
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getStatesByCountry(countryId: com.benasher44.uuid.Uuid): List<MasterDataService.StateInfo> {
|
|
||||||
return emptyList() // Mock implementation
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use case instances
|
|
||||||
private val createPersonUseCase = CreatePersonUseCase(
|
|
||||||
personRepository = mockPersonRepository,
|
|
||||||
vereinRepository = mockVereinRepository,
|
|
||||||
masterDataService = mockMasterDataService
|
|
||||||
)
|
|
||||||
|
|
||||||
// ViewModel factory methods
|
|
||||||
fun createPersonViewModel(): CreatePersonViewModel {
|
|
||||||
return CreatePersonViewModel(createPersonUseCase)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun personListViewModel(): PersonListViewModel {
|
|
||||||
return PersonListViewModel(mockPersonRepository)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-319
@@ -1,319 +0,0 @@
|
|||||||
package at.mocode.client.web.screens
|
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.*
|
|
||||||
import androidx.compose.foundation.rememberScrollState
|
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
|
||||||
import androidx.compose.foundation.verticalScroll
|
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
|
||||||
import androidx.compose.material.icons.filled.ArrowBack
|
|
||||||
import androidx.compose.material3.*
|
|
||||||
import androidx.compose.runtime.*
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.text.input.KeyboardType
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import at.mocode.core.domain.model.GeschlechtE
|
|
||||||
import at.mocode.client.web.viewmodel.CreatePersonViewModel
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
|
||||||
fun CreatePersonScreen(
|
|
||||||
viewModel: CreatePersonViewModel,
|
|
||||||
onNavigateBack: () -> Unit
|
|
||||||
) {
|
|
||||||
var showGeschlechtDropdown by remember { mutableStateOf(false) }
|
|
||||||
|
|
||||||
// Handle success navigation
|
|
||||||
LaunchedEffect(viewModel.isSuccess) {
|
|
||||||
if (viewModel.isSuccess) {
|
|
||||||
onNavigateBack()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Scaffold(
|
|
||||||
topBar = {
|
|
||||||
TopAppBar(
|
|
||||||
title = { Text("Person erstellen") },
|
|
||||||
navigationIcon = {
|
|
||||||
IconButton(onClick = onNavigateBack) {
|
|
||||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Zurück")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
) { paddingValues ->
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.padding(paddingValues)
|
|
||||||
.verticalScroll(rememberScrollState())
|
|
||||||
.padding(16.dp),
|
|
||||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
|
||||||
) {
|
|
||||||
// Error message
|
|
||||||
viewModel.errorMessage?.let { error ->
|
|
||||||
Card(
|
|
||||||
colors = CardDefaults.cardColors(
|
|
||||||
containerColor = MaterialTheme.colorScheme.errorContainer
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = error,
|
|
||||||
modifier = Modifier.padding(16.dp),
|
|
||||||
color = MaterialTheme.colorScheme.onErrorContainer
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Basic Information Section
|
|
||||||
Text(
|
|
||||||
text = "Grunddaten",
|
|
||||||
style = MaterialTheme.typography.headlineSmall,
|
|
||||||
color = MaterialTheme.colorScheme.primary
|
|
||||||
)
|
|
||||||
|
|
||||||
OutlinedTextField(
|
|
||||||
value = viewModel.nachname,
|
|
||||||
onValueChange = viewModel::updateNachname,
|
|
||||||
label = { Text("Nachname *") },
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
singleLine = true
|
|
||||||
)
|
|
||||||
|
|
||||||
OutlinedTextField(
|
|
||||||
value = viewModel.vorname,
|
|
||||||
onValueChange = viewModel::updateVorname,
|
|
||||||
label = { Text("Vorname *") },
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
singleLine = true
|
|
||||||
)
|
|
||||||
|
|
||||||
OutlinedTextField(
|
|
||||||
value = viewModel.titel,
|
|
||||||
onValueChange = viewModel::updateTitel,
|
|
||||||
label = { Text("Titel") },
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
singleLine = true,
|
|
||||||
placeholder = { Text("z.B. Dr., Ing.") }
|
|
||||||
)
|
|
||||||
|
|
||||||
OutlinedTextField(
|
|
||||||
value = viewModel.oepsSatzNr,
|
|
||||||
onValueChange = viewModel::updateOepsSatzNr,
|
|
||||||
label = { Text("OEPS Satznummer") },
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
singleLine = true,
|
|
||||||
placeholder = { Text("6-stellige Nummer") },
|
|
||||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
|
|
||||||
)
|
|
||||||
|
|
||||||
OutlinedTextField(
|
|
||||||
value = viewModel.geburtsdatum,
|
|
||||||
onValueChange = viewModel::updateGeburtsdatum,
|
|
||||||
label = { Text("Geburtsdatum") },
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
singleLine = true,
|
|
||||||
placeholder = { Text("YYYY-MM-DD") }
|
|
||||||
)
|
|
||||||
|
|
||||||
// Gender Dropdown
|
|
||||||
ExposedDropdownMenuBox(
|
|
||||||
expanded = showGeschlechtDropdown,
|
|
||||||
onExpandedChange = { showGeschlechtDropdown = !showGeschlechtDropdown }
|
|
||||||
) {
|
|
||||||
OutlinedTextField(
|
|
||||||
value = viewModel.geschlecht?.let {
|
|
||||||
when(it) {
|
|
||||||
GeschlechtE.M -> "Männlich"
|
|
||||||
GeschlechtE.W -> "Weiblich"
|
|
||||||
GeschlechtE.D -> "Divers"
|
|
||||||
GeschlechtE.UNBEKANNT -> "Unbekannt"
|
|
||||||
}
|
|
||||||
} ?: "",
|
|
||||||
onValueChange = { },
|
|
||||||
readOnly = true,
|
|
||||||
label = { Text("Geschlecht") },
|
|
||||||
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = showGeschlechtDropdown) },
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
)
|
|
||||||
ExposedDropdownMenu(
|
|
||||||
expanded = showGeschlechtDropdown,
|
|
||||||
onDismissRequest = { showGeschlechtDropdown = false }
|
|
||||||
) {
|
|
||||||
GeschlechtE.entries.forEach { option ->
|
|
||||||
DropdownMenuItem(
|
|
||||||
text = {
|
|
||||||
Text(when(option) {
|
|
||||||
GeschlechtE.M -> "Männlich"
|
|
||||||
GeschlechtE.W -> "Weiblich"
|
|
||||||
GeschlechtE.D -> "Divers"
|
|
||||||
GeschlechtE.UNBEKANNT -> "Unbekannt"
|
|
||||||
})
|
|
||||||
},
|
|
||||||
onClick = {
|
|
||||||
viewModel.updateGeschlecht(option)
|
|
||||||
showGeschlechtDropdown = false
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Contact Information Section
|
|
||||||
Text(
|
|
||||||
text = "Kontaktdaten",
|
|
||||||
style = MaterialTheme.typography.headlineSmall,
|
|
||||||
color = MaterialTheme.colorScheme.primary
|
|
||||||
)
|
|
||||||
|
|
||||||
OutlinedTextField(
|
|
||||||
value = viewModel.telefon,
|
|
||||||
onValueChange = viewModel::updateTelefon,
|
|
||||||
label = { Text("Telefon") },
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
singleLine = true,
|
|
||||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Phone)
|
|
||||||
)
|
|
||||||
|
|
||||||
OutlinedTextField(
|
|
||||||
value = viewModel.email,
|
|
||||||
onValueChange = viewModel::updateEmail,
|
|
||||||
label = { Text("E-Mail") },
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
singleLine = true,
|
|
||||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Email)
|
|
||||||
)
|
|
||||||
|
|
||||||
// Address Section
|
|
||||||
Text(
|
|
||||||
text = "Adresse",
|
|
||||||
style = MaterialTheme.typography.headlineSmall,
|
|
||||||
color = MaterialTheme.colorScheme.primary
|
|
||||||
)
|
|
||||||
|
|
||||||
OutlinedTextField(
|
|
||||||
value = viewModel.strasse,
|
|
||||||
onValueChange = viewModel::updateStrasse,
|
|
||||||
label = { Text("Straße und Hausnummer") },
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
singleLine = true
|
|
||||||
)
|
|
||||||
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
|
||||||
) {
|
|
||||||
OutlinedTextField(
|
|
||||||
value = viewModel.plz,
|
|
||||||
onValueChange = viewModel::updatePlz,
|
|
||||||
label = { Text("PLZ") },
|
|
||||||
modifier = Modifier.weight(1f),
|
|
||||||
singleLine = true,
|
|
||||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
|
|
||||||
)
|
|
||||||
|
|
||||||
OutlinedTextField(
|
|
||||||
value = viewModel.ort,
|
|
||||||
onValueChange = viewModel::updateOrt,
|
|
||||||
label = { Text("Ort") },
|
|
||||||
modifier = Modifier.weight(2f),
|
|
||||||
singleLine = true
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
OutlinedTextField(
|
|
||||||
value = viewModel.adresszusatz,
|
|
||||||
onValueChange = viewModel::updateAdresszusatz,
|
|
||||||
label = { Text("Adresszusatz") },
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
singleLine = true
|
|
||||||
)
|
|
||||||
|
|
||||||
// Additional Information Section
|
|
||||||
Text(
|
|
||||||
text = "Weitere Informationen",
|
|
||||||
style = MaterialTheme.typography.headlineSmall,
|
|
||||||
color = MaterialTheme.colorScheme.primary
|
|
||||||
)
|
|
||||||
|
|
||||||
OutlinedTextField(
|
|
||||||
value = viewModel.feiId,
|
|
||||||
onValueChange = viewModel::updateFeiId,
|
|
||||||
label = { Text("FEI ID") },
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
singleLine = true
|
|
||||||
)
|
|
||||||
|
|
||||||
OutlinedTextField(
|
|
||||||
value = viewModel.mitgliedsNummer,
|
|
||||||
onValueChange = viewModel::updateMitgliedsNummer,
|
|
||||||
label = { Text("Mitgliedsnummer beim Stammverein") },
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
singleLine = true
|
|
||||||
)
|
|
||||||
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
Checkbox(
|
|
||||||
checked = viewModel.istGesperrt,
|
|
||||||
onCheckedChange = viewModel::updateIstGesperrt
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
|
||||||
Text("Person ist gesperrt")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (viewModel.istGesperrt) {
|
|
||||||
OutlinedTextField(
|
|
||||||
value = viewModel.sperrGrund,
|
|
||||||
onValueChange = viewModel::updateSperrGrund,
|
|
||||||
label = { Text("Sperrgrund") },
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
maxLines = 3
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
OutlinedTextField(
|
|
||||||
value = viewModel.notizen,
|
|
||||||
onValueChange = viewModel::updateNotizen,
|
|
||||||
label = { Text("Interne Notizen") },
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
maxLines = 4
|
|
||||||
)
|
|
||||||
|
|
||||||
// Action Buttons
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
|
||||||
) {
|
|
||||||
OutlinedButton(
|
|
||||||
onClick = onNavigateBack,
|
|
||||||
modifier = Modifier.weight(1f),
|
|
||||||
enabled = !viewModel.isLoading
|
|
||||||
) {
|
|
||||||
Text("Abbrechen")
|
|
||||||
}
|
|
||||||
|
|
||||||
Button(
|
|
||||||
onClick = {
|
|
||||||
viewModel.createPerson()
|
|
||||||
},
|
|
||||||
modifier = Modifier.weight(1f),
|
|
||||||
enabled = !viewModel.isLoading
|
|
||||||
) {
|
|
||||||
if (viewModel.isLoading) {
|
|
||||||
CircularProgressIndicator(
|
|
||||||
modifier = Modifier.size(16.dp),
|
|
||||||
strokeWidth = 2.dp
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Text("Erstellen")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-212
@@ -1,212 +0,0 @@
|
|||||||
package at.mocode.client.web.screens
|
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.*
|
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
|
||||||
import androidx.compose.foundation.lazy.items
|
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.filled.Add
|
|
||||||
import androidx.compose.material.icons.filled.Person
|
|
||||||
import androidx.compose.material3.*
|
|
||||||
import androidx.compose.runtime.*
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import at.mocode.members.domain.model.DomPerson
|
|
||||||
import at.mocode.core.domain.model.GeschlechtE
|
|
||||||
import at.mocode.core.domain.model.DatenQuelleE
|
|
||||||
import at.mocode.client.web.viewmodel.PersonListViewModel
|
|
||||||
import kotlinx.datetime.LocalDate
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
|
||||||
fun PersonListScreen(
|
|
||||||
viewModel: PersonListViewModel,
|
|
||||||
onNavigateToCreatePerson: () -> Unit
|
|
||||||
) {
|
|
||||||
|
|
||||||
Scaffold(
|
|
||||||
floatingActionButton = {
|
|
||||||
FloatingActionButton(
|
|
||||||
onClick = onNavigateToCreatePerson
|
|
||||||
) {
|
|
||||||
Icon(Icons.Default.Add, contentDescription = "Person hinzufügen")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
) { paddingValues ->
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.padding(paddingValues)
|
|
||||||
.padding(16.dp)
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = "Personen",
|
|
||||||
style = MaterialTheme.typography.headlineMedium,
|
|
||||||
fontWeight = FontWeight.Bold,
|
|
||||||
modifier = Modifier.padding(bottom = 16.dp)
|
|
||||||
)
|
|
||||||
|
|
||||||
// Error handling
|
|
||||||
viewModel.errorMessage?.let { error ->
|
|
||||||
Card(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(bottom = 16.dp),
|
|
||||||
colors = CardDefaults.cardColors(
|
|
||||||
containerColor = MaterialTheme.colorScheme.errorContainer
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.padding(16.dp),
|
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = error,
|
|
||||||
color = MaterialTheme.colorScheme.onErrorContainer,
|
|
||||||
modifier = Modifier.weight(1f)
|
|
||||||
)
|
|
||||||
TextButton(
|
|
||||||
onClick = { viewModel.clearError() }
|
|
||||||
) {
|
|
||||||
Text("OK")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Loading indicator
|
|
||||||
if (viewModel.isLoading) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
|
||||||
CircularProgressIndicator()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!viewModel.isLoading && viewModel.persons.isEmpty()) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier.fillMaxSize(),
|
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
Icons.Default.Person,
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.size(64.dp),
|
|
||||||
tint = MaterialTheme.colorScheme.onSurfaceVariant
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
|
||||||
Text(
|
|
||||||
text = "Keine Personen vorhanden",
|
|
||||||
style = MaterialTheme.typography.bodyLarge,
|
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
LazyColumn(
|
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
|
||||||
) {
|
|
||||||
items(viewModel.persons) { person ->
|
|
||||||
PersonCard(person = person)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
|
||||||
private fun PersonCard(person: DomPerson) {
|
|
||||||
Card(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier.padding(16.dp)
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
|
||||||
verticalAlignment = Alignment.Top
|
|
||||||
) {
|
|
||||||
Column(modifier = Modifier.weight(1f)) {
|
|
||||||
Text(
|
|
||||||
text = "${person.titel?.let { "$it " } ?: ""}${person.vorname} ${person.nachname}",
|
|
||||||
style = MaterialTheme.typography.titleMedium,
|
|
||||||
fontWeight = FontWeight.Bold
|
|
||||||
)
|
|
||||||
|
|
||||||
person.oepsSatzNr?.let { oepsNr ->
|
|
||||||
Text(
|
|
||||||
text = "OEPS: $oepsNr",
|
|
||||||
style = MaterialTheme.typography.bodySmall,
|
|
||||||
color = MaterialTheme.colorScheme.primary
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
person.geburtsdatum?.let { birthDate ->
|
|
||||||
Text(
|
|
||||||
text = "Geboren: $birthDate",
|
|
||||||
style = MaterialTheme.typography.bodySmall,
|
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Surface(
|
|
||||||
color = when (person.datenQuelle) {
|
|
||||||
DatenQuelleE.OEPS_ZNS -> MaterialTheme.colorScheme.primaryContainer
|
|
||||||
DatenQuelleE.MANUELL -> MaterialTheme.colorScheme.secondaryContainer
|
|
||||||
},
|
|
||||||
shape = MaterialTheme.shapes.small
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = when (person.datenQuelle) {
|
|
||||||
DatenQuelleE.OEPS_ZNS -> "OEPS"
|
|
||||||
DatenQuelleE.MANUELL -> "Manuell"
|
|
||||||
},
|
|
||||||
modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp),
|
|
||||||
style = MaterialTheme.typography.labelSmall,
|
|
||||||
color = when (person.datenQuelle) {
|
|
||||||
DatenQuelleE.OEPS_ZNS -> MaterialTheme.colorScheme.onPrimaryContainer
|
|
||||||
DatenQuelleE.MANUELL -> MaterialTheme.colorScheme.onSecondaryContainer
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
|
||||||
|
|
||||||
person.email?.let { email ->
|
|
||||||
Text(
|
|
||||||
text = "📧 $email",
|
|
||||||
style = MaterialTheme.typography.bodySmall,
|
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
person.telefon?.let { phone ->
|
|
||||||
Text(
|
|
||||||
text = "📞 $phone",
|
|
||||||
style = MaterialTheme.typography.bodySmall,
|
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (person.strasse != null && person.plz != null && person.ort != null) {
|
|
||||||
Text(
|
|
||||||
text = "📍 ${person.strasse}, ${person.plz} ${person.ort}",
|
|
||||||
style = MaterialTheme.typography.bodySmall,
|
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-181
@@ -1,181 +0,0 @@
|
|||||||
package at.mocode.client.web.viewmodel
|
|
||||||
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
import androidx.lifecycle.viewModelScope
|
|
||||||
import at.mocode.core.domain.model.DatenQuelleE
|
|
||||||
import at.mocode.core.domain.model.GeschlechtE
|
|
||||||
import at.mocode.members.application.usecase.CreatePersonUseCase
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.datetime.LocalDate
|
|
||||||
|
|
||||||
class CreatePersonViewModel(
|
|
||||||
private val createPersonUseCase: CreatePersonUseCase
|
|
||||||
) : ViewModel() {
|
|
||||||
|
|
||||||
// Form state
|
|
||||||
var nachname by mutableStateOf("")
|
|
||||||
private set
|
|
||||||
var vorname by mutableStateOf("")
|
|
||||||
private set
|
|
||||||
var titel by mutableStateOf("")
|
|
||||||
private set
|
|
||||||
var oepsSatzNr by mutableStateOf("")
|
|
||||||
private set
|
|
||||||
var geburtsdatum by mutableStateOf("")
|
|
||||||
private set
|
|
||||||
var geschlecht by mutableStateOf<GeschlechtE?>(null)
|
|
||||||
private set
|
|
||||||
var telefon by mutableStateOf("")
|
|
||||||
private set
|
|
||||||
var email by mutableStateOf("")
|
|
||||||
private set
|
|
||||||
var strasse by mutableStateOf("")
|
|
||||||
private set
|
|
||||||
var plz by mutableStateOf("")
|
|
||||||
private set
|
|
||||||
var ort by mutableStateOf("")
|
|
||||||
private set
|
|
||||||
var adresszusatz by mutableStateOf("")
|
|
||||||
private set
|
|
||||||
var feiId by mutableStateOf("")
|
|
||||||
private set
|
|
||||||
var mitgliedsNummer by mutableStateOf("")
|
|
||||||
private set
|
|
||||||
var notizen by mutableStateOf("")
|
|
||||||
private set
|
|
||||||
var istGesperrt by mutableStateOf(false)
|
|
||||||
private set
|
|
||||||
var sperrGrund by mutableStateOf("")
|
|
||||||
private set
|
|
||||||
|
|
||||||
// UI state
|
|
||||||
var isLoading by mutableStateOf(false)
|
|
||||||
private set
|
|
||||||
var errorMessage by mutableStateOf<String?>(null)
|
|
||||||
private set
|
|
||||||
var isSuccess by mutableStateOf(false)
|
|
||||||
private set
|
|
||||||
|
|
||||||
// Update methods
|
|
||||||
fun updateNachname(value: String) { nachname = value }
|
|
||||||
fun updateVorname(value: String) { vorname = value }
|
|
||||||
fun updateTitel(value: String) { titel = value }
|
|
||||||
fun updateOepsSatzNr(value: String) { oepsSatzNr = value }
|
|
||||||
fun updateGeburtsdatum(value: String) { geburtsdatum = value }
|
|
||||||
fun updateGeschlecht(value: GeschlechtE?) { geschlecht = value }
|
|
||||||
fun updateTelefon(value: String) { telefon = value }
|
|
||||||
fun updateEmail(value: String) { email = value }
|
|
||||||
fun updateStrasse(value: String) { strasse = value }
|
|
||||||
fun updatePlz(value: String) { plz = value }
|
|
||||||
fun updateOrt(value: String) { ort = value }
|
|
||||||
fun updateAdresszusatz(value: String) { adresszusatz = value }
|
|
||||||
fun updateFeiId(value: String) { feiId = value }
|
|
||||||
fun updateMitgliedsNummer(value: String) { mitgliedsNummer = value }
|
|
||||||
fun updateNotizen(value: String) { notizen = value }
|
|
||||||
fun updateIstGesperrt(value: Boolean) { istGesperrt = value }
|
|
||||||
fun updateSperrGrund(value: String) { sperrGrund = value }
|
|
||||||
|
|
||||||
fun clearError() {
|
|
||||||
errorMessage = null
|
|
||||||
}
|
|
||||||
|
|
||||||
fun createPerson() {
|
|
||||||
// Basic validation
|
|
||||||
when {
|
|
||||||
nachname.isBlank() -> {
|
|
||||||
errorMessage = "Nachname ist erforderlich"
|
|
||||||
return
|
|
||||||
}
|
|
||||||
vorname.isBlank() -> {
|
|
||||||
errorMessage = "Vorname ist erforderlich"
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
viewModelScope.launch {
|
|
||||||
isLoading = true
|
|
||||||
errorMessage = null
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Parse birthdate if provided
|
|
||||||
val parsedGeburtsdatum = if (geburtsdatum.isNotBlank()) {
|
|
||||||
try {
|
|
||||||
val parts = geburtsdatum.split("-")
|
|
||||||
if (parts.size == 3) {
|
|
||||||
LocalDate(parts[0].toInt(), parts[1].toInt(), parts[2].toInt())
|
|
||||||
} else {
|
|
||||||
errorMessage = "Ungültiges Datumsformat. Verwenden Sie YYYY-MM-DD"
|
|
||||||
isLoading = false
|
|
||||||
isSuccess = false
|
|
||||||
return@launch
|
|
||||||
}
|
|
||||||
} catch (_: Exception) {
|
|
||||||
errorMessage = "Ungültiges Datumsformat. Verwenden Sie YYYY-MM-DD"
|
|
||||||
isLoading = false
|
|
||||||
isSuccess = false
|
|
||||||
return@launch
|
|
||||||
}
|
|
||||||
} else null
|
|
||||||
|
|
||||||
val request = CreatePersonUseCase.CreatePersonRequest(
|
|
||||||
oepsSatzNr = oepsSatzNr.takeIf { it.isNotBlank() },
|
|
||||||
nachname = nachname,
|
|
||||||
vorname = vorname,
|
|
||||||
titel = titel.takeIf { it.isNotBlank() },
|
|
||||||
geburtsdatum = parsedGeburtsdatum,
|
|
||||||
geschlechtE = geschlecht,
|
|
||||||
telefon = telefon.takeIf { it.isNotBlank() },
|
|
||||||
email = email.takeIf { it.isNotBlank() },
|
|
||||||
strasse = strasse.takeIf { it.isNotBlank() },
|
|
||||||
plz = plz.takeIf { it.isNotBlank() },
|
|
||||||
ort = ort.takeIf { it.isNotBlank() },
|
|
||||||
adresszusatzZusatzinfo = adresszusatz.takeIf { it.isNotBlank() },
|
|
||||||
feiId = feiId.takeIf { it.isNotBlank() },
|
|
||||||
mitgliedsNummerBeiStammVerein = mitgliedsNummer.takeIf { it.isNotBlank() },
|
|
||||||
istGesperrt = istGesperrt,
|
|
||||||
sperrGrund = sperrGrund.takeIf { it.isNotBlank() },
|
|
||||||
datenQuelle = DatenQuelleE.MANUELL,
|
|
||||||
notizenIntern = notizen.takeIf { it.isNotBlank() }
|
|
||||||
)
|
|
||||||
|
|
||||||
val response = createPersonUseCase.execute(request)
|
|
||||||
|
|
||||||
if (response.success) {
|
|
||||||
isSuccess = true
|
|
||||||
} else {
|
|
||||||
errorMessage = response.error?.message ?: "Unbekannter Fehler beim Erstellen der Person"
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
errorMessage = "Fehler beim Erstellen der Person: ${e.message}"
|
|
||||||
} finally {
|
|
||||||
isLoading = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun resetForm() {
|
|
||||||
nachname = ""
|
|
||||||
vorname = ""
|
|
||||||
titel = ""
|
|
||||||
oepsSatzNr = ""
|
|
||||||
geburtsdatum = ""
|
|
||||||
geschlecht = null
|
|
||||||
telefon = ""
|
|
||||||
email = ""
|
|
||||||
strasse = ""
|
|
||||||
plz = ""
|
|
||||||
ort = ""
|
|
||||||
adresszusatz = ""
|
|
||||||
feiId = ""
|
|
||||||
mitgliedsNummer = ""
|
|
||||||
notizen = ""
|
|
||||||
istGesperrt = false
|
|
||||||
sperrGrund = ""
|
|
||||||
isLoading = false
|
|
||||||
errorMessage = null
|
|
||||||
isSuccess = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-48
@@ -1,48 +0,0 @@
|
|||||||
package at.mocode.client.web.viewmodel
|
|
||||||
|
|
||||||
import androidx.compose.runtime.*
|
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
import androidx.lifecycle.viewModelScope
|
|
||||||
import at.mocode.members.domain.model.DomPerson
|
|
||||||
import at.mocode.members.domain.repository.PersonRepository
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
class PersonListViewModel(
|
|
||||||
private val personRepository: PersonRepository
|
|
||||||
) : ViewModel() {
|
|
||||||
|
|
||||||
// UI state
|
|
||||||
var persons by mutableStateOf<List<DomPerson>>(emptyList())
|
|
||||||
private set
|
|
||||||
var isLoading by mutableStateOf(false)
|
|
||||||
private set
|
|
||||||
var errorMessage by mutableStateOf<String?>(null)
|
|
||||||
private set
|
|
||||||
|
|
||||||
init {
|
|
||||||
loadPersons()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun loadPersons() {
|
|
||||||
viewModelScope.launch {
|
|
||||||
isLoading = true
|
|
||||||
errorMessage = null
|
|
||||||
|
|
||||||
try {
|
|
||||||
persons = personRepository.findAllActive(limit = 100, offset = 0)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
errorMessage = "Fehler beim Laden der Personen: ${e.message}"
|
|
||||||
} finally {
|
|
||||||
isLoading = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun clearError() {
|
|
||||||
errorMessage = null
|
|
||||||
}
|
|
||||||
|
|
||||||
fun refreshPersons() {
|
|
||||||
loadPersons()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
package at.mocode.core.domain.event
|
package at.mocode.core.domain.event
|
||||||
|
|
||||||
import java.time.Instant
|
import com.benasher44.uuid.Uuid
|
||||||
import java.util.UUID
|
import com.benasher44.uuid.uuid4
|
||||||
|
import kotlinx.datetime.Clock
|
||||||
|
import kotlinx.datetime.Instant
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for all domain events in the system.
|
* Interface for all domain events in the system.
|
||||||
@@ -11,7 +13,7 @@ interface DomainEvent {
|
|||||||
/**
|
/**
|
||||||
* Unique identifier for this event instance.
|
* Unique identifier for this event instance.
|
||||||
*/
|
*/
|
||||||
val eventId: UUID
|
val eventId: Uuid
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Timestamp when the event occurred.
|
* Timestamp when the event occurred.
|
||||||
@@ -21,7 +23,7 @@ interface DomainEvent {
|
|||||||
/**
|
/**
|
||||||
* Identifier of the aggregate that the event belongs to.
|
* Identifier of the aggregate that the event belongs to.
|
||||||
*/
|
*/
|
||||||
val aggregateId: UUID
|
val aggregateId: Uuid
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Version of the aggregate after the event was applied.
|
* Version of the aggregate after the event was applied.
|
||||||
@@ -34,8 +36,8 @@ interface DomainEvent {
|
|||||||
* Provides default implementations for common properties.
|
* Provides default implementations for common properties.
|
||||||
*/
|
*/
|
||||||
abstract class BaseDomainEvent(
|
abstract class BaseDomainEvent(
|
||||||
override val eventId: UUID = UUID.randomUUID(),
|
override val eventId: Uuid = uuid4(),
|
||||||
override val timestamp: Instant = Instant.now(),
|
override val timestamp: Instant = Clock.System.now(),
|
||||||
override val aggregateId: UUID,
|
override val aggregateId: Uuid,
|
||||||
override val version: Long
|
override val version: Long
|
||||||
) : DomainEvent
|
) : DomainEvent
|
||||||
|
|||||||
+8
-30
@@ -1,51 +1,29 @@
|
|||||||
#Kotlin
|
# Kotlin Configuration
|
||||||
kotlin.code.style=official
|
kotlin.code.style=official
|
||||||
kotlin.daemon.jvmargs=-Xmx3072M -XX:+UseParallelGC -XX:MaxMetaspaceSize=1024M
|
kotlin.daemon.jvmargs=-Xmx3072M -XX:+UseParallelGC -XX:MaxMetaspaceSize=1024M
|
||||||
|
|
||||||
#Gradle
|
# Gradle Configuration
|
||||||
org.gradle.jvmargs=-Xmx3072M -Dfile.encoding=UTF-8 -XX:+UseParallelGC -XX:MaxMetaspaceSize=1024M -XX:+HeapDumpOnOutOfMemoryError
|
org.gradle.jvmargs=-Xmx3072M -Dfile.encoding=UTF-8 -XX:+UseParallelGC -XX:MaxMetaspaceSize=1024M -XX:+HeapDumpOnOutOfMemoryError
|
||||||
org.gradle.parallel=true
|
org.gradle.parallel=true
|
||||||
org.gradle.caching=true
|
org.gradle.caching=true
|
||||||
org.gradle.configureondemand=true
|
org.gradle.configureondemand=true
|
||||||
org.gradle.workers.max=8
|
org.gradle.workers.max=8
|
||||||
# Enable dependency verification for secure builds
|
org.gradle.vfs.watch=true
|
||||||
org.gradle.dependency.verification=lenient
|
|
||||||
|
|
||||||
# Enable dependency locking for reproducible builds
|
# Security and Reproducibility
|
||||||
|
org.gradle.dependency.verification=lenient
|
||||||
org.gradle.dependency.locking.enabled=true
|
org.gradle.dependency.locking.enabled=true
|
||||||
|
|
||||||
#Ktor
|
# Development
|
||||||
io.ktor.development=true
|
io.ktor.development=true
|
||||||
|
|
||||||
#IDE
|
# IDE Configuration
|
||||||
# kotlin.build.report.output=build_scan # Deaktiviert für sauberen Build-Process
|
|
||||||
kotlin.mpp.androidSourceSetLayoutVersion=2
|
kotlin.mpp.androidSourceSetLayoutVersion=2
|
||||||
org.jetbrains.kotlin.wasm.check.wasm.binary.format=false
|
org.jetbrains.kotlin.wasm.check.wasm.binary.format=false
|
||||||
kotlin.native.ignoreDisabledTargets=true
|
kotlin.native.ignoreDisabledTargets=true
|
||||||
|
|
||||||
#IntelliJ IDEA
|
|
||||||
idea.project.settings.delegate.build.run.actions.to.gradle=true
|
idea.project.settings.delegate.build.run.actions.to.gradle=true
|
||||||
|
|
||||||
# Optimierungen für Dependency Resolution (können bei Konflikten helfen)
|
# Build Reporting
|
||||||
# Abhängigkeits-Locking aktivieren (empfohlen für reproduzierbare Builds und zur Vermeidung unerwarteter transitive Abhängigkeitsänderungen)
|
|
||||||
# org.gradle.dependency.locking.enabled=true
|
|
||||||
|
|
||||||
# Strikte Abhängigkeitsauflösung erzwingen (kann helfen, subtile Konflikte aufzudecken, aber erfordert sorgfältige Konfiguration)
|
|
||||||
# configurations.all*.resolutionStrategy.failOnVersionConflict()
|
|
||||||
# configurations.all*.resolutionStrategy.preferProjectModules() # Bevorzuge Subprojekte gegenüber externen Abhängigkeiten gleicher Identität
|
|
||||||
|
|
||||||
# Optimierung für große Multi-Modul-Projekte
|
|
||||||
# Aktiviert die Konfiguration von Projekten parallel, aber verzögert die eigentliche Ausführung von Tasks so lange wie möglich
|
|
||||||
# org.gradle.configureondemand=true # Bereits aktiviert
|
|
||||||
# Nutze das File System Watching für schnellere inkrementelle Builds (Gradle 6.5+)
|
|
||||||
org.gradle.vfs.watch=true
|
|
||||||
# Configuration cache temporarily disabled due to serialization issues
|
|
||||||
# Will be re-enabled after fixing the issues
|
|
||||||
# org.gradle.unsafe.configuration-cache=true
|
|
||||||
# org.gradle.unsafe.configuration-cache-problems=warn
|
|
||||||
# org.gradle.unsafe.configuration-cache.max-problems=5
|
|
||||||
|
|
||||||
# Build-Reports minimieren für sauberen Build-Process
|
|
||||||
org.gradle.logging.level=lifecycle
|
org.gradle.logging.level=lifecycle
|
||||||
kotlin.build.report.single_file=false
|
kotlin.build.report.single_file=false
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user