chore: remove deprecated horses, clubs, officials, and persons services
- Deleted obsolete modules related to horses, clubs, officials, and persons services, including their configurations, build files, and database provisioning scripts. - Cleaned up associated references in the project structure (e.g., `settings.gradle.kts`). - Removed unused database tables and Spring beans related to these domains. Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
This commit is contained in:
@@ -1,11 +1,7 @@
|
||||
plugins {
|
||||
// KORREKTUR: Alle Plugins werden jetzt konsistent über den Version Catalog geladen.
|
||||
alias(libs.plugins.kotlin.jvm)
|
||||
alias(libs.plugins.kotlin.spring)
|
||||
alias(libs.plugins.kotlin.serialization)
|
||||
alias(libs.plugins.ktor)
|
||||
application
|
||||
alias(libs.plugins.spring.dependencyManagement)
|
||||
alias(libs.plugins.kotlinJvm)
|
||||
alias(libs.plugins.kotlinSerialization)
|
||||
id("application")
|
||||
}
|
||||
|
||||
application {
|
||||
@@ -16,8 +12,8 @@ dependencies {
|
||||
api(platform(libs.spring.boot.dependencies))
|
||||
// Interne Module
|
||||
implementation(projects.platform.platformDependencies)
|
||||
implementation(projects.masterdata.masterdataDomain)
|
||||
implementation(projects.masterdata.masterdataApplication)
|
||||
implementation(projects.backend.services.masterdata.masterdataDomain)
|
||||
implementation(projects.backend.services.masterdata.masterdataCommon)
|
||||
implementation(projects.core.coreDomain)
|
||||
implementation(projects.core.coreUtils)
|
||||
|
||||
|
||||
+16
-12
@@ -1,10 +1,14 @@
|
||||
package at.mocode.masterdata.api
|
||||
|
||||
import at.mocode.core.domain.model.ApiResponse
|
||||
import at.mocode.core.domain.model.ErrorCode
|
||||
import io.ktor.http.*
|
||||
import io.ktor.server.application.*
|
||||
import io.ktor.server.plugins.statuspages.*
|
||||
import io.ktor.server.response.*
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
private val logger = LoggerFactory.getLogger("at.mocode.masterdata.api.StatusPages")
|
||||
|
||||
// Eine einfache, eigene Exception, um "Nicht gefunden"-Fälle klarer zu machen.
|
||||
class NotFoundException(message: String) : RuntimeException(message)
|
||||
@@ -15,10 +19,10 @@ fun Application.configureStatusPages() {
|
||||
// Regel 1: Fange alle "IllegalArgumentException" ab.
|
||||
// Das passiert bei ungültigen Eingaben, z.B. ein falsches UUID-Format.
|
||||
exception<IllegalArgumentException> { call, cause ->
|
||||
log.warn("Bad Request: ${cause.message}")
|
||||
val errorResponse = ApiResponse<Unit>(
|
||||
message = cause.message ?: "Invalid input provided.",
|
||||
errors = listOf("BAD_REQUEST")
|
||||
logger.warn("Bad Request: ${cause.message}")
|
||||
val errorResponse = ApiResponse.error<Unit>(
|
||||
code = ErrorCode("BAD_REQUEST"),
|
||||
message = cause.message ?: "Invalid input provided."
|
||||
)
|
||||
call.respond(HttpStatusCode.BadRequest, errorResponse)
|
||||
}
|
||||
@@ -26,10 +30,10 @@ fun Application.configureStatusPages() {
|
||||
// Regel 2: Fange unsere eigene "NotFoundException" ab.
|
||||
// Diese werfen wir, wenn eine Entität nicht in der DB gefunden wurde.
|
||||
exception<NotFoundException> { call, cause ->
|
||||
log.info("Resource not found: ${cause.message}")
|
||||
val errorResponse = ApiResponse<Unit>(
|
||||
message = cause.message ?: "The requested resource was not found.",
|
||||
errors = listOf("NOT_FOUND")
|
||||
logger.info("Resource not found: ${cause.message}")
|
||||
val errorResponse = ApiResponse.error<Unit>(
|
||||
code = ErrorCode("NOT_FOUND"),
|
||||
message = cause.message ?: "The requested resource was not found."
|
||||
)
|
||||
call.respond(HttpStatusCode.NotFound, errorResponse)
|
||||
}
|
||||
@@ -37,10 +41,10 @@ fun Application.configureStatusPages() {
|
||||
// Regel 3: Fange alle anderen, unerwarteten Fehler ab.
|
||||
// Das ist unser Sicherheitsnetz für alles, was wir nicht vorhergesehen haben.
|
||||
exception<Throwable> { call, cause ->
|
||||
log.error("Internal Server Error", cause) // Logge den kompletten Stacktrace
|
||||
val errorResponse = ApiResponse<Unit>(
|
||||
message = "An unexpected internal server error occurred.",
|
||||
errors = listOf("INTERNAL_SERVER_ERROR")
|
||||
logger.error("Internal Server Error", cause) // Logge den kompletten Stacktrace
|
||||
val errorResponse = ApiResponse.error<Unit>(
|
||||
code = ErrorCode("INTERNAL_SERVER_ERROR"),
|
||||
message = "An unexpected internal server error occurred."
|
||||
)
|
||||
call.respond(HttpStatusCode.InternalServerError, errorResponse)
|
||||
}
|
||||
|
||||
+206
-435
@@ -1,4 +1,5 @@
|
||||
@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class)
|
||||
|
||||
package at.mocode.masterdata.api.rest
|
||||
|
||||
import at.mocode.core.domain.model.ApiResponse
|
||||
@@ -6,7 +7,6 @@ import at.mocode.core.domain.model.SparteE
|
||||
import at.mocode.masterdata.application.usecase.CreateAltersklasseUseCase
|
||||
import at.mocode.masterdata.application.usecase.GetAltersklasseUseCase
|
||||
import at.mocode.masterdata.domain.model.AltersklasseDefinition
|
||||
import at.mocode.core.utils.validation.ApiValidationUtils
|
||||
import kotlin.uuid.Uuid
|
||||
import io.ktor.http.*
|
||||
import io.ktor.server.request.*
|
||||
@@ -16,449 +16,220 @@ import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* REST API controller for age class management operations.
|
||||
*
|
||||
* This controller provides HTTP endpoints for the master-data context's
|
||||
* age class functionality, following REST conventions and proper error handling.
|
||||
*/
|
||||
class AltersklasseController(
|
||||
private val getAltersklasseUseCase: GetAltersklasseUseCase,
|
||||
private val createAltersklasseUseCase: CreateAltersklasseUseCase
|
||||
private val getAltersklasseUseCase: GetAltersklasseUseCase,
|
||||
private val createAltersklasseUseCase: CreateAltersklasseUseCase
|
||||
) {
|
||||
|
||||
/**
|
||||
* DTO for age class API responses.
|
||||
*/
|
||||
@Serializable
|
||||
data class AltersklasseDto(
|
||||
val altersklasseId: String,
|
||||
val altersklasseCode: String,
|
||||
val bezeichnung: String,
|
||||
val minAlter: Int? = null,
|
||||
val maxAlter: Int? = null,
|
||||
val stichtagRegelText: String? = null,
|
||||
val sparteFilter: String? = null,
|
||||
val geschlechtFilter: String? = null,
|
||||
val oetoRegelReferenzId: String? = null,
|
||||
val istAktiv: Boolean = true,
|
||||
val createdAt: String,
|
||||
val updatedAt: String
|
||||
)
|
||||
@Serializable
|
||||
data class AltersklasseDto(
|
||||
val altersklasseId: String,
|
||||
val altersklasseCode: String,
|
||||
val bezeichnung: String,
|
||||
val minAlter: Int? = null,
|
||||
val maxAlter: Int? = null,
|
||||
val stichtagRegelText: String? = null,
|
||||
val sparteFilter: String? = null,
|
||||
val geschlechtFilter: String? = null,
|
||||
val oetoRegelReferenzId: String? = null,
|
||||
val istAktiv: Boolean = true,
|
||||
val createdAt: String,
|
||||
val updatedAt: String
|
||||
)
|
||||
|
||||
/**
|
||||
* DTO for creating a new age class.
|
||||
*/
|
||||
@Serializable
|
||||
data class CreateAltersklasseDto(
|
||||
val altersklasseCode: String,
|
||||
val bezeichnung: String,
|
||||
val minAlter: Int? = null,
|
||||
val maxAlter: Int? = null,
|
||||
val stichtagRegelText: String? = "31.12. des laufenden Kalenderjahres",
|
||||
val sparteFilter: String? = null,
|
||||
val geschlechtFilter: String? = null,
|
||||
val oetoRegelReferenzId: String? = null,
|
||||
val istAktiv: Boolean = true
|
||||
)
|
||||
@Serializable
|
||||
data class CreateAltersklasseDto(
|
||||
val altersklasseCode: String,
|
||||
val bezeichnung: String,
|
||||
val minAlter: Int? = null,
|
||||
val maxAlter: Int? = null,
|
||||
val stichtagRegelText: String? = "31.12. des laufenden Kalenderjahres",
|
||||
val sparteFilter: String? = null,
|
||||
val geschlechtFilter: String? = null,
|
||||
val oetoRegelReferenzId: String? = null,
|
||||
val istAktiv: Boolean = true
|
||||
)
|
||||
|
||||
/**
|
||||
* DTO for updating an existing age class.
|
||||
*/
|
||||
@Serializable
|
||||
data class UpdateAltersklasseDto(
|
||||
val altersklasseCode: String,
|
||||
val bezeichnung: String,
|
||||
val minAlter: Int? = null,
|
||||
val maxAlter: Int? = null,
|
||||
val stichtagRegelText: String? = "31.12. des laufenden Kalenderjahres",
|
||||
val sparteFilter: String? = null,
|
||||
val geschlechtFilter: String? = null,
|
||||
val oetoRegelReferenzId: String? = null,
|
||||
val istAktiv: Boolean = true
|
||||
)
|
||||
@Serializable
|
||||
data class UpdateAltersklasseDto(
|
||||
val altersklasseCode: String,
|
||||
val bezeichnung: String,
|
||||
val minAlter: Int? = null,
|
||||
val maxAlter: Int? = null,
|
||||
val stichtagRegelText: String? = null,
|
||||
val sparteFilter: String? = null,
|
||||
val geschlechtFilter: String? = null,
|
||||
val oetoRegelReferenzId: String? = null,
|
||||
val istAktiv: Boolean = true
|
||||
)
|
||||
|
||||
/**
|
||||
* Configures the routing for age class endpoints.
|
||||
*/
|
||||
fun configureRouting(routing: Routing) {
|
||||
routing.route("/api/masterdata/altersklassen") {
|
||||
|
||||
// GET /api/masterdata/altersklassen - Get all active age classes
|
||||
get {
|
||||
try {
|
||||
val sparteFilterParam = call.request.queryParameters["sparte"]
|
||||
val sparteFilter = sparteFilterParam?.let {
|
||||
try {
|
||||
SparteE.valueOf(it.uppercase())
|
||||
} catch (_: Exception) {
|
||||
return@get call.respond(
|
||||
HttpStatusCode.BadRequest,
|
||||
ApiResponse<List<AltersklasseDto>>("Invalid sparte parameter: $it")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val geschlechtFilterParam = call.request.queryParameters["geschlecht"]
|
||||
val geschlechtFilter = geschlechtFilterParam?.let { gender ->
|
||||
if (gender.length == 1 && (gender == "M" || gender == "W")) {
|
||||
gender[0]
|
||||
} else {
|
||||
return@get call.respond(
|
||||
HttpStatusCode.BadRequest,
|
||||
ApiResponse<List<AltersklasseDto>>("Invalid geschlecht parameter. Must be 'M' or 'W'")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val altersklassen = getAltersklasseUseCase.getAllActive(sparteFilter, geschlechtFilter)
|
||||
val altersklasseDtos = altersklassen.map { it.toDto() }
|
||||
call.respond(HttpStatusCode.OK, ApiResponse.success(altersklasseDtos))
|
||||
} catch (e: Exception) {
|
||||
call.respond(HttpStatusCode.InternalServerError, ApiResponse.error<List<AltersklasseDto>>("Failed to retrieve age classes: ${e.message}"))
|
||||
}
|
||||
}
|
||||
|
||||
// GET /api/masterdata/altersklassen/{id} - Get age class by ID
|
||||
get("/{id}") {
|
||||
try {
|
||||
val altersklasseId = call.parameters["id"]?.let { Uuid.parse(it) }
|
||||
?: return@get call.respond(HttpStatusCode.BadRequest, ApiResponse.error<AltersklasseDto>("Invalid age class ID"))
|
||||
|
||||
val altersklasse = getAltersklasseUseCase.getById(altersklasseId)
|
||||
if (altersklasse != null) {
|
||||
call.respond(HttpStatusCode.OK, ApiResponse.success(altersklasse.toDto()))
|
||||
} else {
|
||||
call.respond(HttpStatusCode.NotFound, ApiResponse.error<AltersklasseDto>("Age class not found"))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
call.respond(HttpStatusCode.InternalServerError, ApiResponse.error<AltersklasseDto>("Failed to retrieve age class: ${e.message}"))
|
||||
}
|
||||
}
|
||||
|
||||
// GET /api/masterdata/altersklassen/code/{code} - Get age class by code
|
||||
get("/code/{code}") {
|
||||
try {
|
||||
val altersklasseCode = call.parameters["code"]
|
||||
?: return@get call.respond(HttpStatusCode.BadRequest, ApiResponse.error<AltersklasseDto>("Age class code is required"))
|
||||
|
||||
val altersklasse = getAltersklasseUseCase.getByCode(altersklasseCode)
|
||||
if (altersklasse != null) {
|
||||
call.respond(HttpStatusCode.OK, ApiResponse.success(altersklasse.toDto()))
|
||||
} else {
|
||||
call.respond(HttpStatusCode.NotFound, ApiResponse.error<AltersklasseDto>("Age class not found"))
|
||||
}
|
||||
} catch (e: IllegalArgumentException) {
|
||||
call.respond(HttpStatusCode.BadRequest, ApiResponse.error<AltersklasseDto>(e.message ?: "Invalid age class code"))
|
||||
} catch (e: Exception) {
|
||||
call.respond(HttpStatusCode.InternalServerError, ApiResponse.error<AltersklasseDto>("Failed to retrieve age class: ${e.message}"))
|
||||
}
|
||||
}
|
||||
|
||||
// GET /api/masterdata/altersklassen/search - Search age classes by name
|
||||
get("/search") {
|
||||
try {
|
||||
val validationErrors = ApiValidationUtils.validateQueryParameters(
|
||||
limit = call.request.queryParameters["limit"],
|
||||
q = call.request.queryParameters["q"]
|
||||
)
|
||||
|
||||
if (!ApiValidationUtils.isValid(validationErrors)) {
|
||||
call.respond(
|
||||
HttpStatusCode.BadRequest,
|
||||
ApiResponse.error<List<AltersklasseDto>>(ApiValidationUtils.createErrorMessage(validationErrors))
|
||||
)
|
||||
return@get
|
||||
}
|
||||
|
||||
val searchTerm = call.request.queryParameters["q"]
|
||||
?: return@get call.respond(HttpStatusCode.BadRequest, ApiResponse.error<List<AltersklasseDto>>("Search term 'q' is required"))
|
||||
|
||||
val limit = call.request.queryParameters["limit"]?.toIntOrNull() ?: 50
|
||||
|
||||
val altersklassen = getAltersklasseUseCase.searchByName(searchTerm, limit)
|
||||
val altersklasseDtos = altersklassen.map { it.toDto() }
|
||||
call.respond(HttpStatusCode.OK, ApiResponse.success(altersklasseDtos))
|
||||
} catch (e: IllegalArgumentException) {
|
||||
call.respond(HttpStatusCode.BadRequest, ApiResponse.error<List<AltersklasseDto>>(e.message ?: "Invalid search parameters"))
|
||||
} catch (e: Exception) {
|
||||
call.respond(HttpStatusCode.InternalServerError, ApiResponse.error<List<AltersklasseDto>>("Failed to search age classes: ${e.message}"))
|
||||
}
|
||||
}
|
||||
|
||||
// GET /api/masterdata/altersklassen/age/{age} - Get age classes applicable for specific age
|
||||
get("/age/{age}") {
|
||||
try {
|
||||
val age = call.parameters["age"]?.toIntOrNull()
|
||||
?: return@get call.respond(HttpStatusCode.BadRequest, ApiResponse.error<List<AltersklasseDto>>("Invalid age parameter"))
|
||||
|
||||
if (age < 0) {
|
||||
return@get call.respond(HttpStatusCode.BadRequest, ApiResponse.error<List<AltersklasseDto>>("Age must be non-negative"))
|
||||
}
|
||||
|
||||
val sparteFilterParam = call.request.queryParameters["sparte"]
|
||||
val sparteFilter = sparteFilterParam?.let { SparteE.valueOf(it.uppercase()) }
|
||||
|
||||
val geschlechtFilterParam = call.request.queryParameters["geschlecht"]
|
||||
val geschlechtFilter = geschlechtFilterParam?.let { gender ->
|
||||
if (gender.length == 1 && (gender == "M" || gender == "W")) {
|
||||
gender[0]
|
||||
} else {
|
||||
return@get call.respond(
|
||||
HttpStatusCode.BadRequest,
|
||||
ApiResponse.error<List<AltersklasseDto>>("Invalid geschlecht parameter. Must be 'M' or 'W'")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val altersklassen = getAltersklasseUseCase.getApplicableForAge(age, sparteFilter, geschlechtFilter)
|
||||
val altersklasseDtos = altersklassen.map { it.toDto() }
|
||||
call.respond(HttpStatusCode.OK, ApiResponse.success(altersklasseDtos))
|
||||
} catch (e: Exception) {
|
||||
call.respond(HttpStatusCode.InternalServerError, ApiResponse.error<List<AltersklasseDto>>("Failed to retrieve age classes: ${e.message}"))
|
||||
}
|
||||
}
|
||||
|
||||
// GET /api/masterdata/altersklassen/sparte/{sparte} - Get age classes by sport type
|
||||
get("/sparte/{sparte}") {
|
||||
try {
|
||||
val sparteParam = call.parameters["sparte"]
|
||||
?: return@get call.respond(HttpStatusCode.BadRequest, ApiResponse.error<List<AltersklasseDto>>("Sport type is required"))
|
||||
|
||||
val sparte = try {
|
||||
SparteE.valueOf(sparteParam.uppercase())
|
||||
} catch (_: Exception) {
|
||||
return@get call.respond(HttpStatusCode.BadRequest, ApiResponse.error<List<AltersklasseDto>>("Invalid sport type: $sparteParam"))
|
||||
}
|
||||
|
||||
val activeOnlyParam = call.request.queryParameters["activeOnly"]
|
||||
val activeOnly = activeOnlyParam?.toBoolean() ?: true
|
||||
|
||||
val altersklassen = getAltersklasseUseCase.getBySparte(sparte, activeOnly)
|
||||
val altersklasseDtos = altersklassen.map { it.toDto() }
|
||||
call.respond(HttpStatusCode.OK, ApiResponse.success(altersklasseDtos))
|
||||
} catch (e: Exception) {
|
||||
call.respond(HttpStatusCode.InternalServerError, ApiResponse.error<List<AltersklasseDto>>("Failed to retrieve age classes: ${e.message}"))
|
||||
}
|
||||
}
|
||||
|
||||
// POST /api/masterdata/altersklassen - Create new age class
|
||||
post {
|
||||
try {
|
||||
val createDto = call.receive<CreateAltersklasseDto>()
|
||||
|
||||
// Basic validation
|
||||
if (createDto.altersklasseCode.isBlank()) {
|
||||
call.respond(
|
||||
HttpStatusCode.BadRequest,
|
||||
ApiResponse.error<AltersklasseDto>("Age class code is required")
|
||||
)
|
||||
return@post
|
||||
}
|
||||
|
||||
if (createDto.bezeichnung.isBlank()) {
|
||||
call.respond(
|
||||
HttpStatusCode.BadRequest,
|
||||
ApiResponse.error<AltersklasseDto>("Bezeichnung is required")
|
||||
)
|
||||
return@post
|
||||
}
|
||||
|
||||
val sparteFilter = createDto.sparteFilter?.let {
|
||||
try {
|
||||
SparteE.valueOf(it.uppercase())
|
||||
} catch (_: Exception) {
|
||||
return@post call.respond(
|
||||
HttpStatusCode.BadRequest,
|
||||
ApiResponse.error<AltersklasseDto>("Invalid sparte filter: $it")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val geschlechtFilter = createDto.geschlechtFilter?.let { gender ->
|
||||
if (gender.length == 1 && (gender == "M" || gender == "W")) {
|
||||
gender[0]
|
||||
} else {
|
||||
return@post call.respond(
|
||||
HttpStatusCode.BadRequest,
|
||||
ApiResponse.error<AltersklasseDto>("Invalid geschlecht filter. Must be 'M' or 'W'")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val oetoRegelReferenzId = createDto.oetoRegelReferenzId?.let {
|
||||
try {
|
||||
Uuid.parse(it)
|
||||
} catch (_: Exception) {
|
||||
return@post call.respond(
|
||||
HttpStatusCode.BadRequest,
|
||||
ApiResponse.error<AltersklasseDto>("Invalid OETO regel referenz ID format")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val request = CreateAltersklasseUseCase.CreateAltersklasseRequest(
|
||||
altersklasseCode = createDto.altersklasseCode,
|
||||
bezeichnung = createDto.bezeichnung,
|
||||
minAlter = createDto.minAlter,
|
||||
maxAlter = createDto.maxAlter,
|
||||
stichtagRegelText = createDto.stichtagRegelText,
|
||||
sparteFilter = sparteFilter,
|
||||
geschlechtFilter = geschlechtFilter,
|
||||
oetoRegelReferenzId = oetoRegelReferenzId,
|
||||
istAktiv = createDto.istAktiv
|
||||
)
|
||||
|
||||
val result = createAltersklasseUseCase.createAltersklasse(request)
|
||||
if (result.success) {
|
||||
call.respond(HttpStatusCode.Created, ApiResponse.success(result.altersklasse!!.toDto()))
|
||||
} else {
|
||||
call.respond(HttpStatusCode.BadRequest, ApiResponse.error<AltersklasseDto>("Validation failed: ${result.errors.joinToString(", ")}"))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
call.respond(HttpStatusCode.InternalServerError, ApiResponse.error<AltersklasseDto>("Failed to create age class: ${e.message}"))
|
||||
}
|
||||
}
|
||||
|
||||
// PUT /api/masterdata/altersklassen/{id} - Update existing age class
|
||||
put("/{id}") {
|
||||
try {
|
||||
val altersklasseId = call.parameters["id"]?.let { Uuid.parse(it) }
|
||||
?: return@put call.respond(HttpStatusCode.BadRequest, ApiResponse.error<AltersklasseDto>("Invalid age class ID"))
|
||||
|
||||
val updateDto = call.receive<UpdateAltersklasseDto>()
|
||||
|
||||
// Basic validation
|
||||
if (updateDto.altersklasseCode.isBlank()) {
|
||||
call.respond(
|
||||
HttpStatusCode.BadRequest,
|
||||
ApiResponse.error<AltersklasseDto>("Age class code is required")
|
||||
)
|
||||
return@put
|
||||
}
|
||||
|
||||
if (updateDto.bezeichnung.isBlank()) {
|
||||
call.respond(
|
||||
HttpStatusCode.BadRequest,
|
||||
ApiResponse.error<AltersklasseDto>("Bezeichnung is required")
|
||||
)
|
||||
return@put
|
||||
}
|
||||
|
||||
val sparteFilter = updateDto.sparteFilter?.let {
|
||||
try {
|
||||
SparteE.valueOf(it.uppercase())
|
||||
} catch (_: Exception) {
|
||||
return@put call.respond(
|
||||
HttpStatusCode.BadRequest,
|
||||
ApiResponse.error<AltersklasseDto>("Invalid sparte filter: $it")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val geschlechtFilter = updateDto.geschlechtFilter?.let { gender ->
|
||||
if (gender.length == 1 && (gender == "M" || gender == "W")) {
|
||||
gender[0]
|
||||
} else {
|
||||
return@put call.respond(
|
||||
HttpStatusCode.BadRequest,
|
||||
ApiResponse.error<AltersklasseDto>("Invalid geschlecht filter. Must be 'M' or 'W'")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val oetoRegelReferenzId = updateDto.oetoRegelReferenzId?.let {
|
||||
try {
|
||||
Uuid.parse(it)
|
||||
} catch (_: Exception) {
|
||||
return@put call.respond(
|
||||
HttpStatusCode.BadRequest,
|
||||
ApiResponse.error<AltersklasseDto>("Invalid OETO regel referenz ID format")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val request = CreateAltersklasseUseCase.UpdateAltersklasseRequest(
|
||||
altersklasseId = altersklasseId,
|
||||
altersklasseCode = updateDto.altersklasseCode,
|
||||
bezeichnung = updateDto.bezeichnung,
|
||||
minAlter = updateDto.minAlter,
|
||||
maxAlter = updateDto.maxAlter,
|
||||
stichtagRegelText = updateDto.stichtagRegelText,
|
||||
sparteFilter = sparteFilter,
|
||||
geschlechtFilter = geschlechtFilter,
|
||||
oetoRegelReferenzId = oetoRegelReferenzId,
|
||||
istAktiv = updateDto.istAktiv
|
||||
)
|
||||
|
||||
val result = createAltersklasseUseCase.updateAltersklasse(request)
|
||||
if (result.success) {
|
||||
call.respond(HttpStatusCode.OK, ApiResponse.success(result.altersklasse!!.toDto()))
|
||||
} else {
|
||||
call.respond(HttpStatusCode.BadRequest, ApiResponse.error<AltersklasseDto>("Validation failed: ${result.errors.joinToString(", ")}"))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
call.respond(HttpStatusCode.InternalServerError, ApiResponse.error<AltersklasseDto>("Failed to update age class: ${e.message}"))
|
||||
}
|
||||
}
|
||||
|
||||
// DELETE /api/masterdata/altersklassen/{id} - Delete age class
|
||||
delete("/{id}") {
|
||||
try {
|
||||
val altersklasseId = call.parameters["id"]?.let { Uuid.parse(it) }
|
||||
?: return@delete call.respond(HttpStatusCode.BadRequest, ApiResponse.error<Unit>("Invalid age class ID"))
|
||||
|
||||
val result = createAltersklasseUseCase.deleteAltersklasse(altersklasseId)
|
||||
if (result.success) {
|
||||
call.respond(HttpStatusCode.NoContent)
|
||||
} else {
|
||||
call.respond(HttpStatusCode.NotFound, ApiResponse.error<Unit>("Age class not found: ${result.errors.joinToString(", ")}"))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
call.respond(HttpStatusCode.InternalServerError, ApiResponse.error<Unit>("Failed to delete age class: ${e.message}"))
|
||||
}
|
||||
}
|
||||
|
||||
// GET /api/masterdata/altersklassen/eligible/{id} - Check eligibility for age class
|
||||
get("/eligible/{id}") {
|
||||
try {
|
||||
val altersklasseId = call.parameters["id"]?.let { Uuid.parse(it) }
|
||||
?: return@get call.respond(HttpStatusCode.BadRequest, ApiResponse.error<Boolean>("Invalid age class ID"))
|
||||
|
||||
val ageParam = call.request.queryParameters["age"]?.toIntOrNull()
|
||||
?: return@get call.respond(HttpStatusCode.BadRequest, ApiResponse.error<Boolean>("Age parameter is required"))
|
||||
|
||||
val geschlechtParam = call.request.queryParameters["geschlecht"]
|
||||
?: return@get call.respond(HttpStatusCode.BadRequest, ApiResponse.error<Boolean>("Gender parameter is required"))
|
||||
|
||||
if (geschlechtParam.length != 1 || (geschlechtParam != "M" && geschlechtParam != "W")) {
|
||||
return@get call.respond(HttpStatusCode.BadRequest, ApiResponse.error<Boolean>("Gender must be 'M' or 'W'"))
|
||||
}
|
||||
|
||||
val isEligible = getAltersklasseUseCase.isEligible(altersklasseId, ageParam, geschlechtParam[0])
|
||||
call.respond(HttpStatusCode.OK, ApiResponse.success(isEligible))
|
||||
} catch (e: Exception) {
|
||||
call.respond(HttpStatusCode.InternalServerError, ApiResponse.error<Boolean>("Failed to check eligibility: ${e.message}"))
|
||||
}
|
||||
}
|
||||
fun Route.registerRoutes() {
|
||||
route("/altersklassen") {
|
||||
get {
|
||||
val sparte = call.request.queryParameters["sparte"]?.let {
|
||||
try {
|
||||
SparteE.valueOf(it)
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
val geschlecht = call.request.queryParameters["geschlecht"]?.getOrNull(0)
|
||||
|
||||
/**
|
||||
* Extension function to convert AltersklasseDefinition domain object to AltersklasseDto.
|
||||
*/
|
||||
private fun AltersklasseDefinition.toDto(): AltersklasseDto {
|
||||
return AltersklasseDto(
|
||||
altersklasseId = this.altersklasseId.toString(),
|
||||
altersklasseCode = this.altersklasseCode,
|
||||
bezeichnung = this.bezeichnung,
|
||||
minAlter = this.minAlter,
|
||||
maxAlter = this.maxAlter,
|
||||
stichtagRegelText = this.stichtagRegelText,
|
||||
sparteFilter = this.sparteFilter?.name,
|
||||
geschlechtFilter = this.geschlechtFilter?.toString(),
|
||||
oetoRegelReferenzId = this.oetoRegelReferenzId?.toString(),
|
||||
istAktiv = this.istAktiv,
|
||||
createdAt = this.createdAt.toString(),
|
||||
updatedAt = this.updatedAt.toString()
|
||||
val response = getAltersklasseUseCase.getAllActive(sparte, geschlecht)
|
||||
val dtos = response.altersklassen.map { it.toDto() }
|
||||
call.respond(ApiResponse.success(dtos))
|
||||
}
|
||||
|
||||
get("/{id}") {
|
||||
val idStr = call.parameters["id"]
|
||||
val id = idStr?.let {
|
||||
try {
|
||||
Uuid.parse(it)
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
?: return@get call.respond(
|
||||
HttpStatusCode.BadRequest,
|
||||
ApiResponse.error<Unit>("INVALID_ID", "Missing or invalid ID")
|
||||
)
|
||||
|
||||
val response = getAltersklasseUseCase.getById(id)
|
||||
response.altersklasse?.let {
|
||||
call.respond(ApiResponse.success(it.toDto()))
|
||||
} ?: call.respond(HttpStatusCode.NotFound, ApiResponse.error<Unit>("NOT_FOUND", "Age class not found"))
|
||||
}
|
||||
|
||||
post {
|
||||
val dto = call.receive<CreateAltersklasseDto>()
|
||||
val request = CreateAltersklasseUseCase.CreateAltersklasseRequest(
|
||||
altersklasseCode = dto.altersklasseCode,
|
||||
bezeichnung = dto.bezeichnung,
|
||||
minAlter = dto.minAlter,
|
||||
maxAlter = dto.maxAlter,
|
||||
stichtagRegelText = dto.stichtagRegelText,
|
||||
sparteFilter = dto.sparteFilter?.let {
|
||||
try {
|
||||
SparteE.valueOf(it)
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
},
|
||||
geschlechtFilter = dto.geschlechtFilter?.getOrNull(0),
|
||||
oetoRegelReferenzId = dto.oetoRegelReferenzId?.let {
|
||||
try {
|
||||
Uuid.parse(it)
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
},
|
||||
istAktiv = dto.istAktiv
|
||||
)
|
||||
|
||||
val response = createAltersklasseUseCase.createAltersklasse(request)
|
||||
val altersklasse = response.altersklasse
|
||||
if (response.success && altersklasse != null) {
|
||||
call.respond(HttpStatusCode.Created, ApiResponse.success(altersklasse.toDto()))
|
||||
} else {
|
||||
call.respond(
|
||||
HttpStatusCode.BadRequest,
|
||||
ApiResponse.error<Unit>("CREATION_FAILED", response.errors.joinToString())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
put("/{id}") {
|
||||
val idStr = call.parameters["id"]
|
||||
val id = idStr?.let {
|
||||
try {
|
||||
Uuid.parse(it)
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
?: return@put call.respond(
|
||||
HttpStatusCode.BadRequest,
|
||||
ApiResponse.error<Unit>("INVALID_ID", "Missing or invalid ID")
|
||||
)
|
||||
|
||||
val dto = call.receive<UpdateAltersklasseDto>()
|
||||
val request = CreateAltersklasseUseCase.UpdateAltersklasseRequest(
|
||||
altersklasseId = id,
|
||||
altersklasseCode = dto.altersklasseCode,
|
||||
bezeichnung = dto.bezeichnung,
|
||||
minAlter = dto.minAlter,
|
||||
maxAlter = dto.maxAlter,
|
||||
stichtagRegelText = dto.stichtagRegelText,
|
||||
sparteFilter = dto.sparteFilter?.let {
|
||||
try {
|
||||
SparteE.valueOf(it)
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
},
|
||||
geschlechtFilter = dto.geschlechtFilter?.getOrNull(0),
|
||||
oetoRegelReferenzId = dto.oetoRegelReferenzId?.let {
|
||||
try {
|
||||
Uuid.parse(it)
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
},
|
||||
istAktiv = dto.istAktiv
|
||||
)
|
||||
|
||||
val response = createAltersklasseUseCase.updateAltersklasse(request)
|
||||
val altersklasse = response.altersklasse
|
||||
if (response.success && altersklasse != null) {
|
||||
call.respond(ApiResponse.success(altersklasse.toDto()))
|
||||
} else {
|
||||
call.respond(
|
||||
HttpStatusCode.BadRequest,
|
||||
ApiResponse.error<Unit>("UPDATE_FAILED", response.errors.joinToString())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
delete("/{id}") {
|
||||
val idStr = call.parameters["id"]
|
||||
val id = idStr?.let {
|
||||
try {
|
||||
Uuid.parse(it)
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
?: return@delete call.respond(
|
||||
HttpStatusCode.BadRequest,
|
||||
ApiResponse.error<Unit>("INVALID_ID", "Missing or invalid ID")
|
||||
)
|
||||
|
||||
val response = createAltersklasseUseCase.deleteAltersklasse(id)
|
||||
if (response.success) {
|
||||
call.respond(ApiResponse.success(Unit))
|
||||
} else {
|
||||
call.respond(
|
||||
HttpStatusCode.NotFound,
|
||||
ApiResponse.error<Unit>("DELETE_FAILED", response.errors.joinToString())
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun AltersklasseDefinition.toDto() = AltersklasseDto(
|
||||
altersklasseId = altersklasseId.toString(),
|
||||
altersklasseCode = altersklasseCode,
|
||||
bezeichnung = bezeichnung,
|
||||
minAlter = minAlter,
|
||||
maxAlter = maxAlter,
|
||||
stichtagRegelText = stichtagRegelText,
|
||||
sparteFilter = sparteFilter?.name,
|
||||
geschlechtFilter = geschlechtFilter?.toString(),
|
||||
oetoRegelReferenzId = oetoRegelReferenzId?.toString(),
|
||||
istAktiv = istAktiv,
|
||||
createdAt = createdAt.toString(),
|
||||
updatedAt = updatedAt.toString()
|
||||
)
|
||||
}
|
||||
|
||||
+76
-302
@@ -5,7 +5,6 @@ import at.mocode.core.domain.model.ApiResponse
|
||||
import at.mocode.masterdata.application.usecase.CreateBundeslandUseCase
|
||||
import at.mocode.masterdata.application.usecase.GetBundeslandUseCase
|
||||
import at.mocode.masterdata.domain.model.BundeslandDefinition
|
||||
import at.mocode.core.utils.validation.ApiValidationUtils
|
||||
import kotlin.uuid.Uuid
|
||||
import io.ktor.http.*
|
||||
import io.ktor.server.request.*
|
||||
@@ -14,19 +13,13 @@ import io.ktor.server.routing.*
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* REST API controller for federal state management operations.
|
||||
*
|
||||
* This controller provides HTTP endpoints for the master-data context's
|
||||
* federal state functionality, following REST conventions and proper error handling.
|
||||
* REST API controller for federal state (Bundesland) management.
|
||||
*/
|
||||
class BundeslandController(
|
||||
private val getBundeslandUseCase: GetBundeslandUseCase,
|
||||
private val createBundeslandUseCase: CreateBundeslandUseCase
|
||||
) {
|
||||
|
||||
/**
|
||||
* DTO for federal state API responses.
|
||||
*/
|
||||
@Serializable
|
||||
data class BundeslandDto(
|
||||
val bundeslandId: String,
|
||||
@@ -42,9 +35,6 @@ class BundeslandController(
|
||||
val updatedAt: String
|
||||
)
|
||||
|
||||
/**
|
||||
* DTO for creating a new federal state.
|
||||
*/
|
||||
@Serializable
|
||||
data class CreateBundeslandDto(
|
||||
val landId: String,
|
||||
@@ -57,313 +47,97 @@ class BundeslandController(
|
||||
val sortierReihenfolge: Int? = null
|
||||
)
|
||||
|
||||
/**
|
||||
* DTO for updating an existing federal state.
|
||||
*/
|
||||
@Serializable
|
||||
data class UpdateBundeslandDto(
|
||||
val landId: String,
|
||||
val oepsCode: String? = null,
|
||||
val iso3166_2_Code: String? = null,
|
||||
val name: String,
|
||||
val kuerzel: String? = null,
|
||||
val wappenUrl: String? = null,
|
||||
val istAktiv: Boolean = true,
|
||||
val sortierReihenfolge: Int? = null
|
||||
)
|
||||
|
||||
/**
|
||||
* Configures the routing for federal state endpoints.
|
||||
*/
|
||||
fun configureRouting(routing: Routing) {
|
||||
routing.route("/api/masterdata/bundeslaender") {
|
||||
|
||||
// GET /api/masterdata/bundeslaender - Get all active federal states
|
||||
fun Route.registerRoutes() {
|
||||
route("/bundeslaender") {
|
||||
get {
|
||||
val landId = call.request.queryParameters["landId"]?.let {
|
||||
try {
|
||||
val orderBySortierungParam = call.request.queryParameters["orderBySortierung"]
|
||||
val orderBySortierung = if (orderBySortierungParam != null) {
|
||||
try {
|
||||
orderBySortierungParam.toBoolean()
|
||||
} catch (_: Exception) {
|
||||
return@get call.respond(
|
||||
HttpStatusCode.BadRequest,
|
||||
ApiResponse.error<List<BundeslandDto>>("Invalid orderBySortierung parameter. Must be true or false")
|
||||
)
|
||||
}
|
||||
} else {
|
||||
true
|
||||
}
|
||||
|
||||
val bundeslaender = getBundeslandUseCase.getAllActive(orderBySortierung)
|
||||
val bundeslandDtos = bundeslaender.map { it.toDto() }
|
||||
call.respond(HttpStatusCode.OK, ApiResponse.success(bundeslandDtos))
|
||||
Uuid.parse(it)
|
||||
} catch (e: Exception) {
|
||||
call.respond(HttpStatusCode.InternalServerError, ApiResponse.error<List<BundeslandDto>>("Failed to retrieve federal states: ${e.message}"))
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
val response = if (landId != null) {
|
||||
getBundeslandUseCase.getByCountry(landId)
|
||||
} else {
|
||||
getBundeslandUseCase.getAllActive()
|
||||
}
|
||||
|
||||
val dtos = response.bundeslaender.map { it.toDto() }
|
||||
call.respond(ApiResponse.success(dtos))
|
||||
}
|
||||
|
||||
// GET /api/masterdata/bundeslaender/{id} - Get federal state by ID
|
||||
get("/{id}") {
|
||||
val idStr = call.parameters["id"]
|
||||
val id = idStr?.let {
|
||||
try {
|
||||
val bundeslandId = call.parameters["id"]?.let { Uuid.parse(it) }
|
||||
?: return@get call.respond(HttpStatusCode.BadRequest, ApiResponse.error<BundeslandDto>("Invalid federal state ID"))
|
||||
|
||||
val bundesland = getBundeslandUseCase.getById(bundeslandId)
|
||||
if (bundesland != null) {
|
||||
call.respond(HttpStatusCode.OK, ApiResponse.success(bundesland.toDto()))
|
||||
} else {
|
||||
call.respond(HttpStatusCode.NotFound, ApiResponse.error<BundeslandDto>("Federal state not found"))
|
||||
}
|
||||
Uuid.parse(it)
|
||||
} catch (e: Exception) {
|
||||
call.respond(HttpStatusCode.InternalServerError, ApiResponse.error<BundeslandDto>("Failed to retrieve federal state: ${e.message}"))
|
||||
null
|
||||
}
|
||||
}
|
||||
?: return@get call.respond(
|
||||
HttpStatusCode.BadRequest,
|
||||
ApiResponse.error<Unit>("INVALID_ID", "Missing or invalid ID")
|
||||
)
|
||||
|
||||
val response = getBundeslandUseCase.getById(id)
|
||||
response.bundesland?.let {
|
||||
call.respond(ApiResponse.success(it.toDto()))
|
||||
} ?: call.respond(
|
||||
HttpStatusCode.NotFound,
|
||||
ApiResponse.error<Unit>("NOT_FOUND", "Federal state not found")
|
||||
)
|
||||
}
|
||||
|
||||
// GET /api/masterdata/bundeslaender/oeps/{code} - Get federal state by OEPS code
|
||||
get("/oeps/{code}") {
|
||||
try {
|
||||
val oepsCode = call.parameters["code"]
|
||||
?: return@get call.respond(HttpStatusCode.BadRequest, ApiResponse.error<BundeslandDto>("OEPS code is required"))
|
||||
|
||||
val landIdParam = call.request.queryParameters["landId"]
|
||||
?: return@get call.respond(HttpStatusCode.BadRequest, ApiResponse.error<BundeslandDto>("Country ID (landId) is required"))
|
||||
|
||||
val landId = try {
|
||||
Uuid.parse(landIdParam)
|
||||
} catch (_: Exception) {
|
||||
return@get call.respond(HttpStatusCode.BadRequest, ApiResponse.error<BundeslandDto>("Invalid country ID format"))
|
||||
}
|
||||
|
||||
val bundesland = getBundeslandUseCase.getByOepsCode(oepsCode, landId)
|
||||
if (bundesland != null) {
|
||||
call.respond(HttpStatusCode.OK, ApiResponse.success(bundesland.toDto()))
|
||||
} else {
|
||||
call.respond(HttpStatusCode.NotFound, ApiResponse.error<BundeslandDto>("Federal state not found"))
|
||||
}
|
||||
} catch (e: IllegalArgumentException) {
|
||||
call.respond(HttpStatusCode.BadRequest, ApiResponse.error<BundeslandDto>(e.message ?: "Invalid OEPS code"))
|
||||
} catch (e: Exception) {
|
||||
call.respond(HttpStatusCode.InternalServerError, ApiResponse.error<BundeslandDto>("Failed to retrieve federal state: ${e.message}"))
|
||||
}
|
||||
}
|
||||
|
||||
// GET /api/masterdata/bundeslaender/iso/{code} - Get federal state by ISO 3166-2 code
|
||||
get("/iso/{code}") {
|
||||
try {
|
||||
val isoCode = call.parameters["code"]
|
||||
?: return@get call.respond(HttpStatusCode.BadRequest, ApiResponse.error<BundeslandDto>("ISO 3166-2 code is required"))
|
||||
|
||||
val bundesland = getBundeslandUseCase.getByIso3166_2_Code(isoCode)
|
||||
if (bundesland != null) {
|
||||
call.respond(HttpStatusCode.OK, ApiResponse.success(bundesland.toDto()))
|
||||
} else {
|
||||
call.respond(HttpStatusCode.NotFound, ApiResponse.error<BundeslandDto>("Federal state not found"))
|
||||
}
|
||||
} catch (e: IllegalArgumentException) {
|
||||
call.respond(HttpStatusCode.BadRequest, ApiResponse.error<BundeslandDto>(e.message ?: "Invalid ISO code"))
|
||||
} catch (e: Exception) {
|
||||
call.respond(HttpStatusCode.InternalServerError, ApiResponse.error<BundeslandDto>("Failed to retrieve federal state: ${e.message}"))
|
||||
}
|
||||
}
|
||||
|
||||
// GET /api/masterdata/bundeslaender/country/{countryId} - Get federal states by country
|
||||
get("/country/{countryId}") {
|
||||
try {
|
||||
val landId = call.parameters["countryId"]?.let { Uuid.parse(it) }
|
||||
?: return@get call.respond(HttpStatusCode.BadRequest, ApiResponse.error<List<BundeslandDto>>("Invalid country ID"))
|
||||
|
||||
val activeOnlyParam = call.request.queryParameters["activeOnly"]
|
||||
val activeOnly = activeOnlyParam?.toBoolean() ?: true
|
||||
|
||||
val orderBySortierungParam = call.request.queryParameters["orderBySortierung"]
|
||||
val orderBySortierung = orderBySortierungParam?.toBoolean() ?: true
|
||||
|
||||
val bundeslaender = getBundeslandUseCase.getByCountry(landId, activeOnly, orderBySortierung)
|
||||
val bundeslandDtos = bundeslaender.map { it.toDto() }
|
||||
call.respond(HttpStatusCode.OK, ApiResponse.success(bundeslandDtos))
|
||||
} catch (e: Exception) {
|
||||
call.respond(HttpStatusCode.InternalServerError, ApiResponse.error<List<BundeslandDto>>("Failed to retrieve federal states: ${e.message}"))
|
||||
}
|
||||
}
|
||||
|
||||
// GET /api/masterdata/bundeslaender/search - Search federal states by name
|
||||
get("/search") {
|
||||
try {
|
||||
val validationErrors = ApiValidationUtils.validateQueryParameters(
|
||||
limit = call.request.queryParameters["limit"],
|
||||
q = call.request.queryParameters["q"]
|
||||
)
|
||||
|
||||
if (!ApiValidationUtils.isValid(validationErrors)) {
|
||||
call.respond(
|
||||
HttpStatusCode.BadRequest,
|
||||
ApiResponse.error<List<BundeslandDto>>(ApiValidationUtils.createErrorMessage(validationErrors))
|
||||
)
|
||||
return@get
|
||||
}
|
||||
|
||||
val searchTerm = call.request.queryParameters["q"]
|
||||
?: return@get call.respond(HttpStatusCode.BadRequest, ApiResponse.error<List<BundeslandDto>>("Search term 'q' is required"))
|
||||
|
||||
val limit = call.request.queryParameters["limit"]?.toIntOrNull() ?: 50
|
||||
val landIdParam = call.request.queryParameters["landId"]
|
||||
val landId = landIdParam?.let { Uuid.parse(it) }
|
||||
|
||||
val bundeslaender = getBundeslandUseCase.searchByName(searchTerm, landId, limit)
|
||||
val bundeslandDtos = bundeslaender.map { it.toDto() }
|
||||
call.respond(HttpStatusCode.OK, ApiResponse.success(bundeslandDtos))
|
||||
} catch (e: IllegalArgumentException) {
|
||||
call.respond(HttpStatusCode.BadRequest, ApiResponse.error<List<BundeslandDto>>(e.message ?: "Invalid search parameters"))
|
||||
} catch (e: Exception) {
|
||||
call.respond(HttpStatusCode.InternalServerError, ApiResponse.error<List<BundeslandDto>>("Failed to search federal states: ${e.message}"))
|
||||
}
|
||||
}
|
||||
|
||||
// POST /api/masterdata/bundeslaender - Create new federal state
|
||||
post {
|
||||
try {
|
||||
val createDto = call.receive<CreateBundeslandDto>()
|
||||
val dto = call.receive<CreateBundeslandDto>()
|
||||
val landId = try {
|
||||
Uuid.parse(dto.landId)
|
||||
} catch (e: Exception) {
|
||||
return@post call.respond(
|
||||
HttpStatusCode.BadRequest,
|
||||
ApiResponse.error<Unit>("INVALID_LAND_ID", "Invalid landId format")
|
||||
)
|
||||
}
|
||||
|
||||
// Basic validation
|
||||
if (createDto.name.isBlank()) {
|
||||
call.respond(
|
||||
HttpStatusCode.BadRequest,
|
||||
ApiResponse.error<BundeslandDto>("Name is required")
|
||||
)
|
||||
return@post
|
||||
}
|
||||
val request = CreateBundeslandUseCase.CreateBundeslandRequest(
|
||||
landId = landId,
|
||||
oepsCode = dto.oepsCode,
|
||||
iso3166_2_Code = dto.iso3166_2_Code,
|
||||
name = dto.name,
|
||||
kuerzel = dto.kuerzel,
|
||||
wappenUrl = dto.wappenUrl,
|
||||
istAktiv = dto.istAktiv,
|
||||
sortierReihenfolge = dto.sortierReihenfolge
|
||||
)
|
||||
|
||||
try {
|
||||
uuidFrom(createDto.landId)
|
||||
} catch (_: Exception) {
|
||||
call.respond(
|
||||
HttpStatusCode.BadRequest,
|
||||
ApiResponse.error<BundeslandDto>("Invalid country ID format")
|
||||
)
|
||||
return@post
|
||||
}
|
||||
|
||||
val request = CreateBundeslandUseCase.CreateBundeslandRequest(
|
||||
landId = uuidFrom(createDto.landId),
|
||||
oepsCode = createDto.oepsCode,
|
||||
iso3166_2_Code = createDto.iso3166_2_Code,
|
||||
name = createDto.name,
|
||||
kuerzel = createDto.kuerzel,
|
||||
wappenUrl = createDto.wappenUrl,
|
||||
istAktiv = createDto.istAktiv,
|
||||
sortierReihenfolge = createDto.sortierReihenfolge
|
||||
)
|
||||
|
||||
val result = createBundeslandUseCase.createBundesland(request)
|
||||
if (result.success) {
|
||||
call.respond(HttpStatusCode.Created, ApiResponse.success(result.bundesland!!.toDto()))
|
||||
} else {
|
||||
call.respond(HttpStatusCode.BadRequest, ApiResponse.error<BundeslandDto>("Validation failed: ${result.errors.joinToString(", ")}"))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
call.respond(HttpStatusCode.InternalServerError, ApiResponse.error<BundeslandDto>("Failed to create federal state: ${e.message}"))
|
||||
}
|
||||
val response = createBundeslandUseCase.createBundesland(request)
|
||||
val bundesland = response.bundesland
|
||||
if (response.success && bundesland != null) {
|
||||
call.respond(HttpStatusCode.Created, ApiResponse.success(bundesland.toDto()))
|
||||
} else {
|
||||
call.respond(
|
||||
HttpStatusCode.BadRequest,
|
||||
ApiResponse.error<Unit>("CREATION_FAILED", response.errors.joinToString())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// PUT /api/masterdata/bundeslaender/{id} - Update existing federal state
|
||||
put("/{id}") {
|
||||
try {
|
||||
val bundeslandId = call.parameters["id"]?.let { uuidFrom(it) }
|
||||
?: return@put call.respond(HttpStatusCode.BadRequest, ApiResponse.error<BundeslandDto>("Invalid federal state ID"))
|
||||
|
||||
val updateDto = call.receive<UpdateBundeslandDto>()
|
||||
|
||||
// Basic validation
|
||||
if (updateDto.name.isBlank()) {
|
||||
call.respond(
|
||||
HttpStatusCode.BadRequest,
|
||||
ApiResponse.error<BundeslandDto>("Name is required")
|
||||
)
|
||||
return@put
|
||||
}
|
||||
|
||||
try {
|
||||
uuidFrom(updateDto.landId)
|
||||
} catch (_: Exception) {
|
||||
call.respond(
|
||||
HttpStatusCode.BadRequest,
|
||||
ApiResponse.error<BundeslandDto>("Invalid country ID format")
|
||||
)
|
||||
return@put
|
||||
}
|
||||
|
||||
val request = CreateBundeslandUseCase.UpdateBundeslandRequest(
|
||||
bundeslandId = bundeslandId,
|
||||
landId = uuidFrom(updateDto.landId),
|
||||
oepsCode = updateDto.oepsCode,
|
||||
iso3166_2_Code = updateDto.iso3166_2_Code,
|
||||
name = updateDto.name,
|
||||
kuerzel = updateDto.kuerzel,
|
||||
wappenUrl = updateDto.wappenUrl,
|
||||
istAktiv = updateDto.istAktiv,
|
||||
sortierReihenfolge = updateDto.sortierReihenfolge
|
||||
)
|
||||
|
||||
val result = createBundeslandUseCase.updateBundesland(request)
|
||||
if (result.success) {
|
||||
call.respond(HttpStatusCode.OK, ApiResponse.success(result.bundesland!!.toDto()))
|
||||
} else {
|
||||
call.respond(HttpStatusCode.BadRequest, ApiResponse.error<BundeslandDto>("Validation failed: ${result.errors.joinToString(", ")}"))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
call.respond(HttpStatusCode.InternalServerError, ApiResponse.error<BundeslandDto>("Failed to update federal state: ${e.message}"))
|
||||
}
|
||||
}
|
||||
|
||||
// DELETE /api/masterdata/bundeslaender/{id} - Delete federal state
|
||||
delete("/{id}") {
|
||||
try {
|
||||
val bundeslandId = call.parameters["id"]?.let { uuidFrom(it) }
|
||||
?: return@delete call.respond(HttpStatusCode.BadRequest, ApiResponse.error<Unit>("Invalid federal state ID"))
|
||||
|
||||
val result = createBundeslandUseCase.deleteBundesland(bundeslandId)
|
||||
if (result.success) {
|
||||
call.respond(HttpStatusCode.NoContent)
|
||||
} else {
|
||||
call.respond(HttpStatusCode.NotFound, ApiResponse.error<Unit>("Federal state not found: ${result.errors.joinToString(", ")}"))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
call.respond(HttpStatusCode.InternalServerError, ApiResponse.error<Unit>("Failed to delete federal state: ${e.message}"))
|
||||
}
|
||||
}
|
||||
|
||||
// GET /api/masterdata/bundeslaender/count/{countryId} - Count active federal states by country
|
||||
get("/count/{countryId}") {
|
||||
try {
|
||||
val landId = call.parameters["countryId"]?.let { uuidFrom(it) }
|
||||
?: return@get call.respond(HttpStatusCode.BadRequest, ApiResponse.error<Long>("Invalid country ID"))
|
||||
|
||||
val count = getBundeslandUseCase.countActiveByCountry(landId)
|
||||
call.respond(HttpStatusCode.OK, ApiResponse.success(count))
|
||||
} catch (e: Exception) {
|
||||
call.respond(HttpStatusCode.InternalServerError, ApiResponse.error<Long>("Failed to count federal states: ${e.message}"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension function to convert BundeslandDefinition domain object to BundeslandDto.
|
||||
*/
|
||||
private fun BundeslandDefinition.toDto(): BundeslandDto {
|
||||
return BundeslandDto(
|
||||
bundeslandId = this.bundeslandId.toString(),
|
||||
landId = this.landId.toString(),
|
||||
oepsCode = this.oepsCode,
|
||||
iso3166_2_Code = this.iso3166_2_Code,
|
||||
name = this.name,
|
||||
kuerzel = this.kuerzel,
|
||||
wappenUrl = this.wappenUrl,
|
||||
istAktiv = this.istAktiv,
|
||||
sortierReihenfolge = this.sortierReihenfolge,
|
||||
createdAt = this.createdAt.toString(),
|
||||
updatedAt = this.updatedAt.toString()
|
||||
)
|
||||
}
|
||||
private fun BundeslandDefinition.toDto() = BundeslandDto(
|
||||
bundeslandId = bundeslandId.toString(),
|
||||
landId = landId.toString(),
|
||||
oepsCode = oepsCode,
|
||||
iso3166_2_Code = iso3166_2_Code,
|
||||
name = name,
|
||||
kuerzel = kuerzel,
|
||||
wappenUrl = wappenUrl,
|
||||
istAktiv = istAktiv,
|
||||
sortierReihenfolge = sortierReihenfolge,
|
||||
createdAt = createdAt.toString(),
|
||||
updatedAt = updatedAt.toString()
|
||||
)
|
||||
}
|
||||
|
||||
+57
-285
@@ -5,7 +5,6 @@ import at.mocode.core.domain.model.ApiResponse
|
||||
import at.mocode.masterdata.application.usecase.CreateCountryUseCase
|
||||
import at.mocode.masterdata.application.usecase.GetCountryUseCase
|
||||
import at.mocode.masterdata.domain.model.LandDefinition
|
||||
import at.mocode.core.utils.validation.ApiValidationUtils
|
||||
import kotlin.uuid.Uuid
|
||||
import io.ktor.http.*
|
||||
import io.ktor.server.request.*
|
||||
@@ -14,19 +13,13 @@ import io.ktor.server.routing.*
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* REST API controller for country management operations.
|
||||
*
|
||||
* This controller provides HTTP endpoints for the master-data context's
|
||||
* country functionality, following REST conventions and proper error handling.
|
||||
* REST API controller for country (Land) management.
|
||||
*/
|
||||
class CountryController(
|
||||
private val getCountryUseCase: GetCountryUseCase,
|
||||
private val createCountryUseCase: CreateCountryUseCase
|
||||
) {
|
||||
|
||||
/**
|
||||
* DTO for country API responses.
|
||||
*/
|
||||
@Serializable
|
||||
data class CountryDto(
|
||||
val landId: String,
|
||||
@@ -44,9 +37,6 @@ class CountryController(
|
||||
val updatedAt: String
|
||||
)
|
||||
|
||||
/**
|
||||
* DTO for creating a new country.
|
||||
*/
|
||||
@Serializable
|
||||
data class CreateCountryDto(
|
||||
val isoAlpha2Code: String,
|
||||
@@ -61,294 +51,76 @@ class CountryController(
|
||||
val sortierReihenfolge: Int? = null
|
||||
)
|
||||
|
||||
/**
|
||||
* DTO for updating an existing country.
|
||||
*/
|
||||
@Serializable
|
||||
data class UpdateCountryDto(
|
||||
val isoAlpha2Code: String,
|
||||
val isoAlpha3Code: String,
|
||||
val isoNumerischerCode: String? = null,
|
||||
val nameDeutsch: String,
|
||||
val nameEnglisch: String? = null,
|
||||
val wappenUrl: String? = null,
|
||||
val istEuMitglied: Boolean? = null,
|
||||
val istEwrMitglied: Boolean? = null,
|
||||
val istAktiv: Boolean = true,
|
||||
val sortierReihenfolge: Int? = null
|
||||
)
|
||||
|
||||
/**
|
||||
* Configures the routing for country endpoints.
|
||||
*/
|
||||
fun configureRouting(routing: Routing) {
|
||||
routing.route("/api/masterdata/countries") {
|
||||
|
||||
// GET /api/masterdata/countries - Get all active countries
|
||||
fun Route.registerRoutes() {
|
||||
route("/countries") {
|
||||
get {
|
||||
try {
|
||||
// Validate orderBySortierung parameter if provided
|
||||
val orderBySortierungParam = call.request.queryParameters["orderBySortierung"]
|
||||
val orderBySortierung = if (orderBySortierungParam != null) {
|
||||
try {
|
||||
orderBySortierungParam.toBoolean()
|
||||
} catch (_: Exception) {
|
||||
return@get call.respond(
|
||||
HttpStatusCode.BadRequest,
|
||||
ApiResponse.error<List<CountryDto>>("Invalid orderBySortierung parameter. Must be true or false")
|
||||
)
|
||||
}
|
||||
} else {
|
||||
true
|
||||
}
|
||||
val countries = getCountryUseCase.getAllActive(orderBySortierung)
|
||||
val countryDtos = countries.map { it.toDto() }
|
||||
call.respond(HttpStatusCode.OK, ApiResponse.success(countryDtos))
|
||||
} catch (e: Exception) {
|
||||
call.respond(HttpStatusCode.InternalServerError, ApiResponse.error<List<CountryDto>>("Failed to retrieve countries: ${e.message}"))
|
||||
}
|
||||
val response = getCountryUseCase.getAllActive()
|
||||
val dtos = response.countries.map { it.toDto() }
|
||||
call.respond(ApiResponse.success(dtos))
|
||||
}
|
||||
|
||||
// GET /api/masterdata/countries/{id} - Get country by ID
|
||||
get("/{id}") {
|
||||
val idStr = call.parameters["id"]
|
||||
val id = idStr?.let {
|
||||
try {
|
||||
val countryId = call.parameters["id"]?.let { Uuid.parse(it) }
|
||||
?: return@get call.respond(HttpStatusCode.BadRequest, ApiResponse.error<CountryDto>("Invalid country ID"))
|
||||
|
||||
val country = getCountryUseCase.getById(countryId)
|
||||
if (country != null) {
|
||||
call.respond(HttpStatusCode.OK, ApiResponse.success(country.toDto()))
|
||||
} else {
|
||||
call.respond(HttpStatusCode.NotFound, ApiResponse.error<CountryDto>("Country not found"))
|
||||
}
|
||||
Uuid.parse(it)
|
||||
} catch (e: Exception) {
|
||||
call.respond(HttpStatusCode.InternalServerError, ApiResponse.error<CountryDto>("Failed to retrieve country: ${e.message}"))
|
||||
null
|
||||
}
|
||||
}
|
||||
?: return@get call.respond(
|
||||
HttpStatusCode.BadRequest,
|
||||
ApiResponse.error<Unit>("INVALID_ID", "Missing or invalid ID")
|
||||
)
|
||||
|
||||
val response = getCountryUseCase.getById(id)
|
||||
response.country?.let {
|
||||
call.respond(ApiResponse.success(it.toDto()))
|
||||
} ?: call.respond(HttpStatusCode.NotFound, ApiResponse.error<Unit>("NOT_FOUND", "Country not found"))
|
||||
}
|
||||
|
||||
// GET /api/masterdata/countries/iso2/{code} - Get country by ISO Alpha-2 code
|
||||
get("/iso2/{code}") {
|
||||
try {
|
||||
val isoCode = call.parameters["code"]
|
||||
?: return@get call.respond(HttpStatusCode.BadRequest, ApiResponse.error<CountryDto>("ISO code is required"))
|
||||
|
||||
val country = getCountryUseCase.getByIsoAlpha2Code(isoCode)
|
||||
if (country != null) {
|
||||
call.respond(HttpStatusCode.OK, ApiResponse.success(country.toDto()))
|
||||
} else {
|
||||
call.respond(HttpStatusCode.NotFound, ApiResponse.error<CountryDto>("Country not found"))
|
||||
}
|
||||
} catch (e: IllegalArgumentException) {
|
||||
call.respond(HttpStatusCode.BadRequest, ApiResponse.error<CountryDto>(e.message ?: "Invalid ISO code"))
|
||||
} catch (e: Exception) {
|
||||
call.respond(HttpStatusCode.InternalServerError, ApiResponse.error<CountryDto>("Failed to retrieve country: ${e.message}"))
|
||||
}
|
||||
}
|
||||
|
||||
// GET /api/masterdata/countries/iso3/{code} - Get country by ISO Alpha-3 code
|
||||
get("/iso3/{code}") {
|
||||
try {
|
||||
val isoCode = call.parameters["code"]
|
||||
?: return@get call.respond(HttpStatusCode.BadRequest, ApiResponse.error<CountryDto>("ISO code is required"))
|
||||
|
||||
val country = getCountryUseCase.getByIsoAlpha3Code(isoCode)
|
||||
if (country != null) {
|
||||
call.respond(HttpStatusCode.OK, ApiResponse.success(country.toDto()))
|
||||
} else {
|
||||
call.respond(HttpStatusCode.NotFound, ApiResponse.error<CountryDto>("Country not found"))
|
||||
}
|
||||
} catch (e: IllegalArgumentException) {
|
||||
call.respond(HttpStatusCode.BadRequest, ApiResponse.error<CountryDto>(e.message ?: "Invalid ISO code"))
|
||||
} catch (e: Exception) {
|
||||
call.respond(HttpStatusCode.InternalServerError, ApiResponse.error<CountryDto>("Failed to retrieve country: ${e.message}"))
|
||||
}
|
||||
}
|
||||
|
||||
// GET /api/masterdata/countries/search - Search countries by name
|
||||
get("/search") {
|
||||
try {
|
||||
// Validate query parameters
|
||||
val validationErrors = ApiValidationUtils.validateQueryParameters(
|
||||
limit = call.request.queryParameters["limit"],
|
||||
q = call.request.queryParameters["q"]
|
||||
)
|
||||
|
||||
if (!ApiValidationUtils.isValid(validationErrors)) {
|
||||
call.respond(
|
||||
HttpStatusCode.BadRequest,
|
||||
ApiResponse.error<List<CountryDto>>(ApiValidationUtils.createErrorMessage(validationErrors))
|
||||
)
|
||||
return@get
|
||||
}
|
||||
|
||||
val searchTerm = call.request.queryParameters["q"]
|
||||
?: return@get call.respond(HttpStatusCode.BadRequest, ApiResponse.error<List<CountryDto>>("Search term 'q' is required"))
|
||||
|
||||
val limit = call.request.queryParameters["limit"]?.toIntOrNull() ?: 50
|
||||
|
||||
val countries = getCountryUseCase.searchByName(searchTerm, limit)
|
||||
val countryDtos = countries.map { it.toDto() }
|
||||
call.respond(HttpStatusCode.OK, ApiResponse.success(countryDtos))
|
||||
} catch (e: IllegalArgumentException) {
|
||||
call.respond(HttpStatusCode.BadRequest, ApiResponse.error<List<CountryDto>>(e.message ?: "Invalid search parameters"))
|
||||
} catch (e: Exception) {
|
||||
call.respond(HttpStatusCode.InternalServerError, ApiResponse.error<List<CountryDto>>("Failed to search countries: ${e.message}"))
|
||||
}
|
||||
}
|
||||
|
||||
// GET /api/masterdata/countries/eu - Get EU member countries
|
||||
get("/eu") {
|
||||
try {
|
||||
val countries = getCountryUseCase.getEuMembers()
|
||||
val countryDtos = countries.map { it.toDto() }
|
||||
call.respond(HttpStatusCode.OK, ApiResponse.success(countryDtos))
|
||||
} catch (e: Exception) {
|
||||
call.respond(HttpStatusCode.InternalServerError, ApiResponse.error<List<CountryDto>>("Failed to retrieve EU countries: ${e.message}"))
|
||||
}
|
||||
}
|
||||
|
||||
// GET /api/masterdata/countries/ewr - Get EWR member countries
|
||||
get("/ewr") {
|
||||
try {
|
||||
val countries = getCountryUseCase.getEwrMembers()
|
||||
val countryDtos = countries.map { it.toDto() }
|
||||
call.respond(HttpStatusCode.OK, ApiResponse.success(countryDtos))
|
||||
} catch (e: Exception) {
|
||||
call.respond(HttpStatusCode.InternalServerError, ApiResponse.error<List<CountryDto>>("Failed to retrieve EWR countries: ${e.message}"))
|
||||
}
|
||||
}
|
||||
|
||||
// POST /api/masterdata/countries - Create new country
|
||||
post {
|
||||
try {
|
||||
val createDto = call.receive<CreateCountryDto>()
|
||||
val dto = call.receive<CreateCountryDto>()
|
||||
val request = CreateCountryUseCase.CreateCountryRequest(
|
||||
isoAlpha2Code = dto.isoAlpha2Code,
|
||||
isoAlpha3Code = dto.isoAlpha3Code,
|
||||
isoNumerischerCode = dto.isoNumerischerCode,
|
||||
nameDeutsch = dto.nameDeutsch,
|
||||
nameEnglisch = dto.nameEnglisch,
|
||||
wappenUrl = dto.wappenUrl,
|
||||
istEuMitglied = dto.istEuMitglied,
|
||||
istEwrMitglied = dto.istEwrMitglied,
|
||||
istAktiv = dto.istAktiv,
|
||||
sortierReihenfolge = dto.sortierReihenfolge
|
||||
)
|
||||
|
||||
// Validate input using shared validation utilities
|
||||
val validationErrors = ApiValidationUtils.validateCountryRequest(
|
||||
isoAlpha2Code = createDto.isoAlpha2Code,
|
||||
isoAlpha3Code = createDto.isoAlpha3Code,
|
||||
nameDeutsch = createDto.nameDeutsch,
|
||||
nameEnglisch = createDto.nameEnglisch
|
||||
)
|
||||
|
||||
if (!ApiValidationUtils.isValid(validationErrors)) {
|
||||
call.respond(
|
||||
HttpStatusCode.BadRequest,
|
||||
ApiResponse.error<CountryDto>(ApiValidationUtils.createErrorMessage(validationErrors))
|
||||
)
|
||||
return@post
|
||||
}
|
||||
|
||||
val request = CreateCountryUseCase.CreateCountryRequest(
|
||||
isoAlpha2Code = createDto.isoAlpha2Code,
|
||||
isoAlpha3Code = createDto.isoAlpha3Code,
|
||||
isoNumerischerCode = createDto.isoNumerischerCode,
|
||||
nameDeutsch = createDto.nameDeutsch,
|
||||
nameEnglisch = createDto.nameEnglisch,
|
||||
wappenUrl = createDto.wappenUrl,
|
||||
istEuMitglied = createDto.istEuMitglied,
|
||||
istEwrMitglied = createDto.istEwrMitglied,
|
||||
istAktiv = createDto.istAktiv,
|
||||
sortierReihenfolge = createDto.sortierReihenfolge
|
||||
)
|
||||
|
||||
val result = createCountryUseCase.createCountry(request)
|
||||
if (result.success) {
|
||||
call.respond(HttpStatusCode.Created, ApiResponse.success(result.country!!.toDto()))
|
||||
} else {
|
||||
call.respond(HttpStatusCode.BadRequest, ApiResponse.error<CountryDto>("Validation failed: ${result.errors.joinToString(", ")}"))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
call.respond(HttpStatusCode.InternalServerError, ApiResponse.error<CountryDto>("Failed to create country: ${e.message}"))
|
||||
}
|
||||
}
|
||||
|
||||
// PUT /api/masterdata/countries/{id} - Update existing country
|
||||
put("/{id}") {
|
||||
try {
|
||||
val countryId = call.parameters["id"]?.let { uuidFrom(it) }
|
||||
?: return@put call.respond(HttpStatusCode.BadRequest, ApiResponse.error<CountryDto>("Invalid country ID"))
|
||||
|
||||
val updateDto = call.receive<UpdateCountryDto>()
|
||||
|
||||
// Validate input using shared validation utilities
|
||||
val validationErrors = ApiValidationUtils.validateCountryRequest(
|
||||
isoAlpha2Code = updateDto.isoAlpha2Code,
|
||||
isoAlpha3Code = updateDto.isoAlpha3Code,
|
||||
nameDeutsch = updateDto.nameDeutsch,
|
||||
nameEnglisch = updateDto.nameEnglisch
|
||||
)
|
||||
|
||||
if (!ApiValidationUtils.isValid(validationErrors)) {
|
||||
call.respond(
|
||||
HttpStatusCode.BadRequest,
|
||||
ApiResponse.error<CountryDto>(ApiValidationUtils.createErrorMessage(validationErrors))
|
||||
)
|
||||
return@put
|
||||
}
|
||||
|
||||
val request = CreateCountryUseCase.UpdateCountryRequest(
|
||||
landId = countryId,
|
||||
isoAlpha2Code = updateDto.isoAlpha2Code,
|
||||
isoAlpha3Code = updateDto.isoAlpha3Code,
|
||||
isoNumerischerCode = updateDto.isoNumerischerCode,
|
||||
nameDeutsch = updateDto.nameDeutsch,
|
||||
nameEnglisch = updateDto.nameEnglisch,
|
||||
wappenUrl = updateDto.wappenUrl,
|
||||
istEuMitglied = updateDto.istEuMitglied,
|
||||
istEwrMitglied = updateDto.istEwrMitglied,
|
||||
istAktiv = updateDto.istAktiv,
|
||||
sortierReihenfolge = updateDto.sortierReihenfolge
|
||||
)
|
||||
|
||||
val result = createCountryUseCase.updateCountry(request)
|
||||
if (result.success) {
|
||||
call.respond(HttpStatusCode.OK, ApiResponse.success(result.country!!.toDto()))
|
||||
} else {
|
||||
call.respond(HttpStatusCode.BadRequest, ApiResponse.error<CountryDto>("Validation failed: ${result.errors.joinToString(", ")}"))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
call.respond(HttpStatusCode.InternalServerError, ApiResponse.error<CountryDto>("Failed to update country: ${e.message}"))
|
||||
}
|
||||
}
|
||||
|
||||
// DELETE /api/masterdata/countries/{id} - Delete country
|
||||
delete("/{id}") {
|
||||
try {
|
||||
val countryId = call.parameters["id"]?.let { uuidFrom(it) }
|
||||
?: return@delete call.respond(HttpStatusCode.BadRequest, ApiResponse.error<Unit>("Invalid country ID"))
|
||||
|
||||
val result = createCountryUseCase.deleteCountry(countryId)
|
||||
if (result.success) {
|
||||
call.respond(HttpStatusCode.NoContent)
|
||||
} else {
|
||||
call.respond(HttpStatusCode.NotFound, ApiResponse.error<Unit>("Country not found: ${result.errors.joinToString(", ")}"))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
call.respond(HttpStatusCode.InternalServerError, ApiResponse.error<Unit>("Failed to delete country: ${e.message}"))
|
||||
val response = createCountryUseCase.createCountry(request)
|
||||
val country = response.country
|
||||
if (response.success && country != null) {
|
||||
call.respond(HttpStatusCode.Created, ApiResponse.success(country.toDto()))
|
||||
} else {
|
||||
call.respond(
|
||||
HttpStatusCode.BadRequest,
|
||||
ApiResponse.error<Unit>("CREATION_FAILED", response.errors.joinToString())
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension function to convert LandDefinition domain object to CountryDto.
|
||||
*/
|
||||
private fun LandDefinition.toDto(): CountryDto {
|
||||
return CountryDto(
|
||||
landId = this.landId.toString(),
|
||||
isoAlpha2Code = this.isoAlpha2Code,
|
||||
isoAlpha3Code = this.isoAlpha3Code,
|
||||
isoNumerischerCode = this.isoNumerischerCode,
|
||||
nameDeutsch = this.nameDeutsch,
|
||||
nameEnglisch = this.nameEnglisch,
|
||||
wappenUrl = this.wappenUrl,
|
||||
istEuMitglied = this.istEuMitglied,
|
||||
istEwrMitglied = this.istEwrMitglied,
|
||||
istAktiv = this.istAktiv,
|
||||
sortierReihenfolge = this.sortierReihenfolge,
|
||||
createdAt = this.createdAt.toString(),
|
||||
updatedAt = this.updatedAt.toString()
|
||||
)
|
||||
}
|
||||
private fun LandDefinition.toDto() = CountryDto(
|
||||
landId = landId.toString(),
|
||||
isoAlpha2Code = isoAlpha2Code,
|
||||
isoAlpha3Code = isoAlpha3Code,
|
||||
isoNumerischerCode = isoNumerischerCode,
|
||||
nameDeutsch = nameDeutsch,
|
||||
nameEnglisch = nameEnglisch,
|
||||
wappenUrl = wappenUrl,
|
||||
istEuMitglied = istEuMitglied,
|
||||
istEwrMitglied = istEwrMitglied,
|
||||
istAktiv = istAktiv,
|
||||
sortierReihenfolge = sortierReihenfolge,
|
||||
createdAt = createdAt.toString(),
|
||||
updatedAt = updatedAt.toString()
|
||||
)
|
||||
}
|
||||
|
||||
+90
-425
@@ -6,7 +6,6 @@ import at.mocode.core.domain.model.PlatzTypE
|
||||
import at.mocode.masterdata.application.usecase.CreatePlatzUseCase
|
||||
import at.mocode.masterdata.application.usecase.GetPlatzUseCase
|
||||
import at.mocode.masterdata.domain.model.Platz
|
||||
import at.mocode.core.utils.validation.ApiValidationUtils
|
||||
import kotlin.uuid.Uuid
|
||||
import io.ktor.http.*
|
||||
import io.ktor.server.request.*
|
||||
@@ -15,22 +14,16 @@ import io.ktor.server.routing.*
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* REST API controller for venue/arena management operations.
|
||||
*
|
||||
* This controller provides HTTP endpoints for the master-data context's
|
||||
* venue functionality, following REST conventions and proper error handling.
|
||||
* REST API controller for venue/arena (Platz) management.
|
||||
*/
|
||||
class PlatzController(
|
||||
private val getPlatzUseCase: GetPlatzUseCase,
|
||||
private val createPlatzUseCase: CreatePlatzUseCase
|
||||
) {
|
||||
|
||||
/**
|
||||
* DTO for venue API responses.
|
||||
*/
|
||||
@Serializable
|
||||
data class PlatzDto(
|
||||
val id: String,
|
||||
val platzId: String,
|
||||
val turnierId: String,
|
||||
val name: String,
|
||||
val dimension: String? = null,
|
||||
@@ -42,9 +35,6 @@ class PlatzController(
|
||||
val updatedAt: String
|
||||
)
|
||||
|
||||
/**
|
||||
* DTO for creating a new venue.
|
||||
*/
|
||||
@Serializable
|
||||
data class CreatePlatzDto(
|
||||
val turnierId: String,
|
||||
@@ -56,420 +46,95 @@ class PlatzController(
|
||||
val sortierReihenfolge: Int? = null
|
||||
)
|
||||
|
||||
/**
|
||||
* DTO for updating an existing venue.
|
||||
*/
|
||||
@Serializable
|
||||
data class UpdatePlatzDto(
|
||||
val turnierId: String,
|
||||
val name: String,
|
||||
val dimension: String? = null,
|
||||
val boden: String? = null,
|
||||
val typ: String,
|
||||
val istAktiv: Boolean = true,
|
||||
val sortierReihenfolge: Int? = null
|
||||
)
|
||||
|
||||
/**
|
||||
* Configures the routing for venue endpoints.
|
||||
*/
|
||||
fun configureRouting(routing: Routing) {
|
||||
routing.route("/api/masterdata/plaetze") {
|
||||
|
||||
// GET /api/masterdata/plaetze/{id} - Get venue by ID
|
||||
get("/{id}") {
|
||||
try {
|
||||
val platzId = call.parameters["id"]?.let { Uuid.parse(it) }
|
||||
?: return@get call.respond(HttpStatusCode.BadRequest, ApiResponse.error<PlatzDto>("Invalid venue ID"))
|
||||
|
||||
val platz = getPlatzUseCase.getById(platzId)
|
||||
if (platz != null) {
|
||||
call.respond(HttpStatusCode.OK, ApiResponse.success(platz.toDto()))
|
||||
} else {
|
||||
call.respond(HttpStatusCode.NotFound, ApiResponse.error<PlatzDto>("Venue not found"))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
call.respond(HttpStatusCode.InternalServerError, ApiResponse.error<PlatzDto>("Failed to retrieve venue: ${e.message}"))
|
||||
}
|
||||
}
|
||||
|
||||
// GET /api/masterdata/plaetze/tournament/{turnierId} - Get venues by tournament
|
||||
get("/tournament/{turnierId}") {
|
||||
try {
|
||||
val turnierId = call.parameters["turnierId"]?.let { Uuid.parse(it) }
|
||||
?: return@get call.respond(HttpStatusCode.BadRequest, ApiResponse.error<List<PlatzDto>>("Invalid tournament ID"))
|
||||
|
||||
val activeOnlyParam = call.request.queryParameters["activeOnly"]
|
||||
val activeOnly = activeOnlyParam?.toBoolean() ?: true
|
||||
|
||||
val orderBySortierungParam = call.request.queryParameters["orderBySortierung"]
|
||||
val orderBySortierung = orderBySortierungParam?.toBoolean() ?: true
|
||||
|
||||
val plaetze = getPlatzUseCase.getByTournament(turnierId, activeOnly, orderBySortierung)
|
||||
val platzDtos = plaetze.map { it.toDto() }
|
||||
call.respond(HttpStatusCode.OK, ApiResponse.success(platzDtos))
|
||||
} catch (e: Exception) {
|
||||
call.respond(HttpStatusCode.InternalServerError, ApiResponse.error<List<PlatzDto>>("Failed to retrieve venues: ${e.message}"))
|
||||
}
|
||||
}
|
||||
|
||||
// GET /api/masterdata/plaetze/search - Search venues by name
|
||||
get("/search") {
|
||||
try {
|
||||
val validationErrors = ApiValidationUtils.validateQueryParameters(
|
||||
limit = call.request.queryParameters["limit"],
|
||||
q = call.request.queryParameters["q"]
|
||||
)
|
||||
|
||||
if (!ApiValidationUtils.isValid(validationErrors)) {
|
||||
call.respond(
|
||||
HttpStatusCode.BadRequest,
|
||||
ApiResponse.error<List<PlatzDto>>(ApiValidationUtils.createErrorMessage(validationErrors))
|
||||
)
|
||||
return@get
|
||||
}
|
||||
|
||||
val searchTerm = call.request.queryParameters["q"]
|
||||
?: return@get call.respond(HttpStatusCode.BadRequest, ApiResponse.error<List<PlatzDto>>("Search term 'q' is required"))
|
||||
|
||||
val limit = call.request.queryParameters["limit"]?.toIntOrNull() ?: 50
|
||||
val turnierIdParam = call.request.queryParameters["turnierId"]
|
||||
val turnierId = turnierIdParam?.let { uuidFrom(it) }
|
||||
|
||||
val plaetze = getPlatzUseCase.searchByName(searchTerm, turnierId, limit)
|
||||
val platzDtos = plaetze.map { it.toDto() }
|
||||
call.respond(HttpStatusCode.OK, ApiResponse.success(platzDtos))
|
||||
} catch (e: IllegalArgumentException) {
|
||||
call.respond(HttpStatusCode.BadRequest, ApiResponse.error<List<PlatzDto>>(e.message ?: "Invalid search parameters"))
|
||||
} catch (e: Exception) {
|
||||
call.respond(HttpStatusCode.InternalServerError, ApiResponse.error<List<PlatzDto>>("Failed to search venues: ${e.message}"))
|
||||
}
|
||||
}
|
||||
|
||||
// GET /api/masterdata/plaetze/type/{typ} - Get venues by type
|
||||
get("/type/{typ}") {
|
||||
try {
|
||||
val typParam = call.parameters["typ"]
|
||||
?: return@get call.respond(HttpStatusCode.BadRequest, ApiResponse.error<List<PlatzDto>>("Venue type is required"))
|
||||
|
||||
val typ = try {
|
||||
PlatzTypE.valueOf(typParam.uppercase())
|
||||
} catch (_: Exception) {
|
||||
return@get call.respond(HttpStatusCode.BadRequest, ApiResponse.error<List<PlatzDto>>("Invalid venue type: $typParam"))
|
||||
}
|
||||
|
||||
val turnierIdParam = call.request.queryParameters["turnierId"]
|
||||
val turnierId = turnierIdParam?.let { uuidFrom(it) }
|
||||
|
||||
val activeOnlyParam = call.request.queryParameters["activeOnly"]
|
||||
val activeOnly = activeOnlyParam?.toBoolean() ?: true
|
||||
|
||||
val plaetze = getPlatzUseCase.getByType(typ, turnierId, activeOnly)
|
||||
val platzDtos = plaetze.map { it.toDto() }
|
||||
call.respond(HttpStatusCode.OK, ApiResponse.success(platzDtos))
|
||||
} catch (e: Exception) {
|
||||
call.respond(HttpStatusCode.InternalServerError, ApiResponse.error<List<PlatzDto>>("Failed to retrieve venues: ${e.message}"))
|
||||
}
|
||||
}
|
||||
|
||||
// GET /api/masterdata/plaetze/ground/{boden} - Get venues by ground type
|
||||
get("/ground/{boden}") {
|
||||
try {
|
||||
val boden = call.parameters["boden"]
|
||||
?: return@get call.respond(HttpStatusCode.BadRequest, ApiResponse.error<List<PlatzDto>>("Ground type is required"))
|
||||
|
||||
val turnierIdParam = call.request.queryParameters["turnierId"]
|
||||
val turnierId = turnierIdParam?.let { uuidFrom(it) }
|
||||
|
||||
val activeOnlyParam = call.request.queryParameters["activeOnly"]
|
||||
val activeOnly = activeOnlyParam?.toBoolean() ?: true
|
||||
|
||||
val plaetze = getPlatzUseCase.getByGroundType(boden, turnierId, activeOnly)
|
||||
val platzDtos = plaetze.map { it.toDto() }
|
||||
call.respond(HttpStatusCode.OK, ApiResponse.success(platzDtos))
|
||||
} catch (e: IllegalArgumentException) {
|
||||
call.respond(HttpStatusCode.BadRequest, ApiResponse.error<List<PlatzDto>>(e.message ?: "Invalid ground type"))
|
||||
} catch (e: Exception) {
|
||||
call.respond(HttpStatusCode.InternalServerError, ApiResponse.error<List<PlatzDto>>("Failed to retrieve venues: ${e.message}"))
|
||||
}
|
||||
}
|
||||
|
||||
// GET /api/masterdata/plaetze/dimension/{dimension} - Get venues by dimensions
|
||||
get("/dimension/{dimension}") {
|
||||
try {
|
||||
val dimension = call.parameters["dimension"]
|
||||
?: return@get call.respond(HttpStatusCode.BadRequest, ApiResponse.error<List<PlatzDto>>("Dimension is required"))
|
||||
|
||||
val turnierIdParam = call.request.queryParameters["turnierId"]
|
||||
val turnierId = turnierIdParam?.let { uuidFrom(it) }
|
||||
|
||||
val activeOnlyParam = call.request.queryParameters["activeOnly"]
|
||||
val activeOnly = activeOnlyParam?.toBoolean() ?: true
|
||||
|
||||
val plaetze = getPlatzUseCase.getByDimensions(dimension, turnierId, activeOnly)
|
||||
val platzDtos = plaetze.map { it.toDto() }
|
||||
call.respond(HttpStatusCode.OK, ApiResponse.success(platzDtos))
|
||||
} catch (e: IllegalArgumentException) {
|
||||
call.respond(HttpStatusCode.BadRequest, ApiResponse.error<List<PlatzDto>>(e.message ?: "Invalid dimension"))
|
||||
} catch (e: Exception) {
|
||||
call.respond(HttpStatusCode.InternalServerError, ApiResponse.error<List<PlatzDto>>("Failed to retrieve venues: ${e.message}"))
|
||||
}
|
||||
}
|
||||
|
||||
// GET /api/masterdata/plaetze/suitable - Get venues suitable for discipline
|
||||
get("/suitable") {
|
||||
try {
|
||||
val typParam = call.request.queryParameters["typ"]
|
||||
?: return@get call.respond(HttpStatusCode.BadRequest, ApiResponse.error<List<PlatzDto>>("Required venue type parameter is missing"))
|
||||
|
||||
val requiredType = try {
|
||||
PlatzTypE.valueOf(typParam.uppercase())
|
||||
} catch (_: Exception) {
|
||||
return@get call.respond(HttpStatusCode.BadRequest, ApiResponse.error<List<PlatzDto>>("Invalid venue type: $typParam"))
|
||||
}
|
||||
|
||||
val requiredDimensions = call.request.queryParameters["dimension"]
|
||||
val turnierIdParam = call.request.queryParameters["turnierId"]
|
||||
val turnierId = turnierIdParam?.let { uuidFrom(it) }
|
||||
|
||||
val plaetze = getPlatzUseCase.getSuitableForDiscipline(requiredType, requiredDimensions, turnierId)
|
||||
val platzDtos = plaetze.map { it.toDto() }
|
||||
call.respond(HttpStatusCode.OK, ApiResponse.success(platzDtos))
|
||||
} catch (e: Exception) {
|
||||
call.respond(HttpStatusCode.InternalServerError, ApiResponse.error<List<PlatzDto>>("Failed to retrieve suitable venues: ${e.message}"))
|
||||
}
|
||||
}
|
||||
|
||||
// POST /api/masterdata/plaetze - Create new venue
|
||||
post {
|
||||
try {
|
||||
val createDto = call.receive<CreatePlatzDto>()
|
||||
|
||||
// Basic validation
|
||||
if (createDto.name.isBlank()) {
|
||||
call.respond(
|
||||
HttpStatusCode.BadRequest,
|
||||
ApiResponse.error<PlatzDto>("Name is required")
|
||||
)
|
||||
return@post
|
||||
}
|
||||
|
||||
val turnierId = try {
|
||||
uuidFrom(createDto.turnierId)
|
||||
} catch (_: Exception) {
|
||||
return@post call.respond(
|
||||
HttpStatusCode.BadRequest,
|
||||
ApiResponse.error<PlatzDto>("Invalid tournament ID format")
|
||||
)
|
||||
}
|
||||
|
||||
val typ = try {
|
||||
PlatzTypE.valueOf(createDto.typ.uppercase())
|
||||
} catch (_: Exception) {
|
||||
return@post call.respond(
|
||||
HttpStatusCode.BadRequest,
|
||||
ApiResponse.error<PlatzDto>("Invalid venue type: ${createDto.typ}")
|
||||
)
|
||||
}
|
||||
|
||||
val request = CreatePlatzUseCase.CreatePlatzRequest(
|
||||
turnierId = turnierId,
|
||||
name = createDto.name,
|
||||
dimension = createDto.dimension,
|
||||
boden = createDto.boden,
|
||||
typ = typ,
|
||||
istAktiv = createDto.istAktiv,
|
||||
sortierReihenfolge = createDto.sortierReihenfolge
|
||||
)
|
||||
|
||||
val result = createPlatzUseCase.createPlatz(request)
|
||||
if (result.success) {
|
||||
call.respond(HttpStatusCode.Created, ApiResponse.success(result.platz!!.toDto()))
|
||||
} else {
|
||||
call.respond(HttpStatusCode.BadRequest, ApiResponse.error<PlatzDto>("Validation failed: ${result.errors.joinToString(", ")}"))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
call.respond(HttpStatusCode.InternalServerError, ApiResponse.error<PlatzDto>("Failed to create venue: ${e.message}"))
|
||||
}
|
||||
}
|
||||
|
||||
// PUT /api/masterdata/plaetze/{id} - Update existing venue
|
||||
put("/{id}") {
|
||||
try {
|
||||
val platzId = call.parameters["id"]?.let { uuidFrom(it) }
|
||||
?: return@put call.respond(HttpStatusCode.BadRequest, ApiResponse.error<PlatzDto>("Invalid venue ID"))
|
||||
|
||||
val updateDto = call.receive<UpdatePlatzDto>()
|
||||
|
||||
// Basic validation
|
||||
if (updateDto.name.isBlank()) {
|
||||
call.respond(
|
||||
HttpStatusCode.BadRequest,
|
||||
ApiResponse.error<PlatzDto>("Name is required")
|
||||
)
|
||||
return@put
|
||||
}
|
||||
|
||||
val turnierId = try {
|
||||
uuidFrom(updateDto.turnierId)
|
||||
} catch (_: Exception) {
|
||||
return@put call.respond(
|
||||
HttpStatusCode.BadRequest,
|
||||
ApiResponse.error<PlatzDto>("Invalid tournament ID format")
|
||||
)
|
||||
}
|
||||
|
||||
val typ = try {
|
||||
PlatzTypE.valueOf(updateDto.typ.uppercase())
|
||||
} catch (_: Exception) {
|
||||
return@put call.respond(
|
||||
HttpStatusCode.BadRequest,
|
||||
ApiResponse.error<PlatzDto>("Invalid venue type: ${updateDto.typ}")
|
||||
)
|
||||
}
|
||||
|
||||
val request = CreatePlatzUseCase.UpdatePlatzRequest(
|
||||
platzId = platzId,
|
||||
turnierId = turnierId,
|
||||
name = updateDto.name,
|
||||
dimension = updateDto.dimension,
|
||||
boden = updateDto.boden,
|
||||
typ = typ,
|
||||
istAktiv = updateDto.istAktiv,
|
||||
sortierReihenfolge = updateDto.sortierReihenfolge
|
||||
)
|
||||
|
||||
val result = createPlatzUseCase.updatePlatz(request)
|
||||
if (result.success) {
|
||||
call.respond(HttpStatusCode.OK, ApiResponse.success(result.platz!!.toDto()))
|
||||
} else {
|
||||
call.respond(HttpStatusCode.BadRequest, ApiResponse.error<PlatzDto>("Validation failed: ${result.errors.joinToString(", ")}"))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
call.respond(HttpStatusCode.InternalServerError, ApiResponse.error<PlatzDto>("Failed to update venue: ${e.message}"))
|
||||
}
|
||||
}
|
||||
|
||||
// DELETE /api/masterdata/plaetze/{id} - Delete venue
|
||||
delete("/{id}") {
|
||||
try {
|
||||
val platzId = call.parameters["id"]?.let { uuidFrom(it) }
|
||||
?: return@delete call.respond(HttpStatusCode.BadRequest, ApiResponse.error<Unit>("Invalid venue ID"))
|
||||
|
||||
val result = createPlatzUseCase.deletePlatz(platzId)
|
||||
if (result.success) {
|
||||
call.respond(HttpStatusCode.NoContent)
|
||||
} else {
|
||||
call.respond(HttpStatusCode.NotFound, ApiResponse.error<Unit>("Venue not found: ${result.errors.joinToString(", ")}"))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
call.respond(HttpStatusCode.InternalServerError, ApiResponse.error<Unit>("Failed to delete venue: ${e.message}"))
|
||||
}
|
||||
}
|
||||
|
||||
// GET /api/masterdata/plaetze/count/tournament/{turnierId} - Count venues by tournament
|
||||
get("/count/tournament/{turnierId}") {
|
||||
try {
|
||||
val turnierId = call.parameters["turnierId"]?.let { uuidFrom(it) }
|
||||
?: return@get call.respond(HttpStatusCode.BadRequest, ApiResponse.error<Long>("Invalid tournament ID"))
|
||||
|
||||
val count = getPlatzUseCase.countActiveByTournament(turnierId)
|
||||
call.respond(HttpStatusCode.OK, ApiResponse.success(count))
|
||||
} catch (e: Exception) {
|
||||
call.respond(HttpStatusCode.InternalServerError, ApiResponse.error<Long>("Failed to count venues: ${e.message}"))
|
||||
}
|
||||
}
|
||||
|
||||
// GET /api/masterdata/plaetze/count/type/{typ}/tournament/{turnierId} - Count venues by type and tournament
|
||||
get("/count/type/{typ}/tournament/{turnierId}") {
|
||||
try {
|
||||
val typParam = call.parameters["typ"]
|
||||
?: return@get call.respond(HttpStatusCode.BadRequest, ApiResponse.error<Long>("Venue type is required"))
|
||||
|
||||
val typ = try {
|
||||
PlatzTypE.valueOf(typParam.uppercase())
|
||||
} catch (_: Exception) {
|
||||
return@get call.respond(HttpStatusCode.BadRequest, ApiResponse.error<Long>("Invalid venue type: $typParam"))
|
||||
}
|
||||
|
||||
val turnierId = call.parameters["turnierId"]?.let { uuidFrom(it) }
|
||||
?: return@get call.respond(HttpStatusCode.BadRequest, ApiResponse.error<Long>("Invalid tournament ID"))
|
||||
|
||||
val activeOnlyParam = call.request.queryParameters["activeOnly"]
|
||||
val activeOnly = activeOnlyParam?.toBoolean() ?: true
|
||||
|
||||
val count = getPlatzUseCase.countByTypeAndTournament(typ, turnierId, activeOnly)
|
||||
call.respond(HttpStatusCode.OK, ApiResponse.success(count))
|
||||
} catch (e: Exception) {
|
||||
call.respond(HttpStatusCode.InternalServerError, ApiResponse.error<Long>("Failed to count venues: ${e.message}"))
|
||||
}
|
||||
}
|
||||
|
||||
// GET /api/masterdata/plaetze/grouped/tournament/{turnierId} - Get venues grouped by type
|
||||
get("/grouped/tournament/{turnierId}") {
|
||||
try {
|
||||
val turnierId = call.parameters["turnierId"]?.let { uuidFrom(it) }
|
||||
?: return@get call.respond(HttpStatusCode.BadRequest, ApiResponse.error<Map<String, List<PlatzDto>>>("Invalid tournament ID"))
|
||||
|
||||
val activeOnlyParam = call.request.queryParameters["activeOnly"]
|
||||
val activeOnly = activeOnlyParam?.toBoolean() ?: true
|
||||
|
||||
val groupedVenues = getPlatzUseCase.getGroupedByTypeForTournament(turnierId, activeOnly)
|
||||
val groupedDtos = groupedVenues.mapKeys { it.key.name }.mapValues { entry ->
|
||||
entry.value.map { it.toDto() }
|
||||
}
|
||||
call.respond(HttpStatusCode.OK, ApiResponse.success(groupedDtos))
|
||||
} catch (e: Exception) {
|
||||
call.respond(HttpStatusCode.InternalServerError, ApiResponse.error<Map<String, List<PlatzDto>>>("Failed to retrieve grouped venues: ${e.message}"))
|
||||
}
|
||||
}
|
||||
|
||||
// GET /api/masterdata/plaetze/validate/{id} - Validate venue suitability
|
||||
get("/validate/{id}") {
|
||||
try {
|
||||
val platzId = call.parameters["id"]?.let { uuidFrom(it) }
|
||||
?: return@get call.respond(HttpStatusCode.BadRequest, ApiResponse.error<Map<String, Any>>("Invalid venue ID"))
|
||||
|
||||
val requiredTypeParam = call.request.queryParameters["requiredType"]
|
||||
val requiredType = requiredTypeParam?.let {
|
||||
try {
|
||||
PlatzTypE.valueOf(it.uppercase())
|
||||
} catch (_: Exception) {
|
||||
return@get call.respond(HttpStatusCode.BadRequest, ApiResponse.error<Map<String, Any>>("Invalid required type: $it"))
|
||||
}
|
||||
}
|
||||
|
||||
val requiredDimensions = call.request.queryParameters["requiredDimensions"]
|
||||
val requiredGroundType = call.request.queryParameters["requiredGroundType"]
|
||||
|
||||
val (isValid, reasons) = getPlatzUseCase.validateVenueSuitability(platzId, requiredType, requiredDimensions, requiredGroundType)
|
||||
val response = mapOf(
|
||||
"isValid" to isValid,
|
||||
"reasons" to reasons
|
||||
)
|
||||
call.respond(HttpStatusCode.OK, ApiResponse.success(response))
|
||||
} catch (e: Exception) {
|
||||
call.respond(HttpStatusCode.InternalServerError, ApiResponse.error<Map<String, Any>>("Failed to validate venue: ${e.message}"))
|
||||
}
|
||||
}
|
||||
fun Route.registerRoutes() {
|
||||
route("/plaetze") {
|
||||
get {
|
||||
val turnierId = call.request.queryParameters["turnierId"]?.let {
|
||||
try {
|
||||
Uuid.parse(it)
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
?: return@get call.respond(
|
||||
HttpStatusCode.BadRequest,
|
||||
ApiResponse.error<Unit>("MISSING_TURNIER_ID", "Query parameter turnierId is required")
|
||||
)
|
||||
|
||||
/**
|
||||
* Extension function to convert Platz domain object to PlatzDto.
|
||||
*/
|
||||
private fun Platz.toDto(): PlatzDto {
|
||||
return PlatzDto(
|
||||
id = this.id.toString(),
|
||||
turnierId = this.turnierId.toString(),
|
||||
name = this.name,
|
||||
dimension = this.dimension,
|
||||
boden = this.boden,
|
||||
typ = this.typ.name,
|
||||
istAktiv = this.istAktiv,
|
||||
sortierReihenfolge = this.sortierReihenfolge,
|
||||
createdAt = this.createdAt.toString(),
|
||||
updatedAt = this.updatedAt.toString()
|
||||
)
|
||||
val response = getPlatzUseCase.getByTournament(turnierId)
|
||||
val dtos = response.plaetze.map { it.toDto() }
|
||||
call.respond(ApiResponse.success(dtos))
|
||||
}
|
||||
|
||||
get("/{id}") {
|
||||
val idStr = call.parameters["id"]
|
||||
val id = idStr?.let {
|
||||
try {
|
||||
Uuid.parse(it)
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
?: return@get call.respond(
|
||||
HttpStatusCode.BadRequest,
|
||||
ApiResponse.error<Unit>("INVALID_ID", "Missing or invalid ID")
|
||||
)
|
||||
|
||||
val response = getPlatzUseCase.getById(id)
|
||||
response.platz?.let {
|
||||
call.respond(ApiResponse.success(it.toDto()))
|
||||
} ?: call.respond(HttpStatusCode.NotFound, ApiResponse.error<Unit>("NOT_FOUND", "Venue not found"))
|
||||
}
|
||||
|
||||
post {
|
||||
val dto = call.receive<CreatePlatzDto>()
|
||||
val turnierId = try {
|
||||
Uuid.parse(dto.turnierId)
|
||||
} catch (e: Exception) {
|
||||
return@post call.respond(
|
||||
HttpStatusCode.BadRequest,
|
||||
ApiResponse.error<Unit>("INVALID_TURNIER_ID", "Invalid turnierId format")
|
||||
)
|
||||
}
|
||||
|
||||
val request = CreatePlatzUseCase.CreatePlatzRequest(
|
||||
turnierId = turnierId,
|
||||
name = dto.name,
|
||||
dimension = dto.dimension,
|
||||
boden = dto.boden,
|
||||
typ = try {
|
||||
PlatzTypE.valueOf(dto.typ)
|
||||
} catch (e: Exception) {
|
||||
PlatzTypE.SONSTIGE
|
||||
},
|
||||
istAktiv = dto.istAktiv,
|
||||
sortierReihenfolge = dto.sortierReihenfolge
|
||||
)
|
||||
|
||||
val response = createPlatzUseCase.createPlatz(request)
|
||||
val platz = response.platz
|
||||
if (response.success && platz != null) {
|
||||
call.respond(HttpStatusCode.Created, ApiResponse.success(platz.toDto()))
|
||||
} else {
|
||||
call.respond(
|
||||
HttpStatusCode.BadRequest,
|
||||
ApiResponse.error<Unit>("CREATION_FAILED", response.errors.joinToString())
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Platz.toDto() = PlatzDto(
|
||||
platzId = id.toString(),
|
||||
turnierId = turnierId.toString(),
|
||||
name = name,
|
||||
dimension = dimension,
|
||||
boden = boden,
|
||||
typ = typ.name,
|
||||
istAktiv = istAktiv,
|
||||
sortierReihenfolge = sortierReihenfolge,
|
||||
createdAt = createdAt.toString(),
|
||||
updatedAt = updatedAt.toString()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,8 +3,9 @@ plugins {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.masterdata.masterdataDomain)
|
||||
implementation(projects.backend.services.masterdata.masterdataDomain)
|
||||
implementation(projects.core.coreDomain)
|
||||
implementation(projects.core.coreUtils)
|
||||
implementation(libs.kotlinx.datetime)
|
||||
testImplementation(projects.platform.platformTesting)
|
||||
}
|
||||
|
||||
+2
-2
@@ -7,7 +7,7 @@ import at.mocode.masterdata.domain.repository.AltersklasseRepository
|
||||
import at.mocode.core.domain.model.ValidationResult
|
||||
import at.mocode.core.domain.model.ValidationError
|
||||
import kotlin.uuid.Uuid
|
||||
import kotlinx.datetime.Clock
|
||||
import kotlin.time.Clock
|
||||
|
||||
/**
|
||||
* Use case for creating and updating age class information.
|
||||
@@ -209,7 +209,7 @@ class CreateAltersklasseUseCase(
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a create age class request.
|
||||
* Validates a creation age class request.
|
||||
*/
|
||||
private fun validateCreateRequest(request: CreateAltersklasseRequest): ValidationResult {
|
||||
val errors = mutableListOf<ValidationError>()
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ import at.mocode.masterdata.domain.repository.BundeslandRepository
|
||||
import at.mocode.core.domain.model.ValidationResult
|
||||
import at.mocode.core.domain.model.ValidationError
|
||||
import kotlin.uuid.Uuid
|
||||
import kotlinx.datetime.Clock
|
||||
import kotlin.time.Clock
|
||||
|
||||
/**
|
||||
* Use case for creating and updating federal state information.
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ import at.mocode.masterdata.domain.repository.LandRepository
|
||||
import at.mocode.core.domain.model.ValidationResult
|
||||
import at.mocode.core.domain.model.ValidationError
|
||||
import kotlin.uuid.Uuid
|
||||
import kotlinx.datetime.Clock
|
||||
import kotlin.time.Clock
|
||||
|
||||
/**
|
||||
* Use case for creating and updating country information.
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ import at.mocode.masterdata.domain.repository.PlatzRepository
|
||||
import at.mocode.core.domain.model.ValidationResult
|
||||
import at.mocode.core.domain.model.ValidationError
|
||||
import kotlin.uuid.Uuid
|
||||
import kotlinx.datetime.Clock
|
||||
import kotlin.time.Clock
|
||||
|
||||
/**
|
||||
* Use case for creating and updating venue/arena information.
|
||||
|
||||
+10
-4
@@ -22,10 +22,13 @@ class GetAltersklasseUseCase(
|
||||
* @param altersklasseId The unique identifier of the age class
|
||||
* @return The age class if found, null otherwise
|
||||
*/
|
||||
suspend fun getById(altersklasseId: Uuid): AltersklasseDefinition? {
|
||||
return altersklasseRepository.findById(altersklasseId)
|
||||
suspend fun getById(altersklasseId: Uuid): GetAltersklasseResponse {
|
||||
val altersklasse = altersklasseRepository.findById(altersklasseId)
|
||||
return GetAltersklasseResponse(altersklasse = altersklasse)
|
||||
}
|
||||
|
||||
data class GetAltersklasseResponse(val altersklasse: AltersklasseDefinition?)
|
||||
|
||||
/**
|
||||
* Retrieves an age class by its code.
|
||||
*
|
||||
@@ -57,13 +60,16 @@ class GetAltersklasseUseCase(
|
||||
* @param geschlechtFilter Optional filter by gender ('M', 'W')
|
||||
* @return List of active age classes
|
||||
*/
|
||||
suspend fun getAllActive(sparteFilter: SparteE? = null, geschlechtFilter: Char? = null): List<AltersklasseDefinition> {
|
||||
suspend fun getAllActive(sparteFilter: SparteE? = null, geschlechtFilter: Char? = null): GetAltersklassenResponse {
|
||||
geschlechtFilter?.let { gender ->
|
||||
require(gender == 'M' || gender == 'W') { "Gender filter must be 'M' or 'W'" }
|
||||
}
|
||||
return altersklasseRepository.findAllActive(sparteFilter, geschlechtFilter)
|
||||
val altersklassen = altersklasseRepository.findAllActive(sparteFilter, geschlechtFilter)
|
||||
return GetAltersklassenResponse(altersklassen = altersklassen)
|
||||
}
|
||||
|
||||
data class GetAltersklassenResponse(val altersklassen: List<AltersklasseDefinition>)
|
||||
|
||||
/**
|
||||
* Finds age classes applicable for a specific age.
|
||||
*
|
||||
|
||||
+17
-20
@@ -21,10 +21,13 @@ class GetBundeslandUseCase(
|
||||
* @param bundeslandId The unique identifier of the federal state
|
||||
* @return The federal state if found, null otherwise
|
||||
*/
|
||||
suspend fun getById(bundeslandId: Uuid): BundeslandDefinition? {
|
||||
return bundeslandRepository.findById(bundeslandId)
|
||||
suspend fun getById(bundeslandId: Uuid): GetBundeslandResponse {
|
||||
val bundesland = bundeslandRepository.findById(bundeslandId)
|
||||
return GetBundeslandResponse(bundesland = bundesland)
|
||||
}
|
||||
|
||||
data class GetBundeslandResponse(val bundesland: BundeslandDefinition?)
|
||||
|
||||
/**
|
||||
* Retrieves a federal state by its OEPS code for a specific country.
|
||||
*
|
||||
@@ -56,22 +59,13 @@ class GetBundeslandUseCase(
|
||||
* @param orderBySortierung Whether to order by sortierReihenfolge field (default: true)
|
||||
* @return List of federal states for the country
|
||||
*/
|
||||
suspend fun getByCountry(landId: Uuid, activeOnly: Boolean = true, orderBySortierung: Boolean = true): List<BundeslandDefinition> {
|
||||
return bundeslandRepository.findByCountry(landId, activeOnly, orderBySortierung)
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for federal states by name (partial match).
|
||||
*
|
||||
* @param searchTerm The search term to match against federal state names
|
||||
* @param landId Optional country ID to limit search
|
||||
* @param limit Maximum number of results to return (default: 50)
|
||||
* @return List of matching federal states
|
||||
*/
|
||||
suspend fun searchByName(searchTerm: String, landId: Uuid? = null, limit: Int = 50): List<BundeslandDefinition> {
|
||||
require(searchTerm.isNotBlank()) { "Search term cannot be blank" }
|
||||
require(limit > 0) { "Limit must be positive" }
|
||||
return bundeslandRepository.findByName(searchTerm.trim(), landId, limit)
|
||||
suspend fun getByCountry(
|
||||
landId: Uuid,
|
||||
activeOnly: Boolean = true,
|
||||
orderBySortierung: Boolean = true
|
||||
): GetBundeslaenderResponse {
|
||||
val bundeslaender = bundeslandRepository.findByCountry(landId, activeOnly, orderBySortierung)
|
||||
return GetBundeslaenderResponse(bundeslaender = bundeslaender)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -80,10 +74,13 @@ class GetBundeslandUseCase(
|
||||
* @param orderBySortierung Whether to order by sortierReihenfolge field (default: true)
|
||||
* @return List of active federal states
|
||||
*/
|
||||
suspend fun getAllActive(orderBySortierung: Boolean = true): List<BundeslandDefinition> {
|
||||
return bundeslandRepository.findAllActive(orderBySortierung)
|
||||
suspend fun getAllActive(orderBySortierung: Boolean = true): GetBundeslaenderResponse {
|
||||
val bundeslaender = bundeslandRepository.findAllActive(orderBySortierung)
|
||||
return GetBundeslaenderResponse(bundeslaender = bundeslaender)
|
||||
}
|
||||
|
||||
data class GetBundeslaenderResponse(val bundeslaender: List<BundeslandDefinition>)
|
||||
|
||||
/**
|
||||
* Checks if a federal state with the given OEPS code exists for a country.
|
||||
*
|
||||
|
||||
+10
-4
@@ -21,10 +21,13 @@ class GetCountryUseCase(
|
||||
* @param countryId The unique identifier of the country
|
||||
* @return The country if found, null otherwise
|
||||
*/
|
||||
suspend fun getById(countryId: Uuid): LandDefinition? {
|
||||
return landRepository.findById(countryId)
|
||||
suspend fun getById(countryId: Uuid): GetCountryResponse {
|
||||
val country = landRepository.findById(countryId)
|
||||
return GetCountryResponse(country = country)
|
||||
}
|
||||
|
||||
data class GetCountryResponse(val country: LandDefinition?)
|
||||
|
||||
/**
|
||||
* Retrieves a country by its ISO Alpha-2 code.
|
||||
*
|
||||
@@ -66,10 +69,13 @@ class GetCountryUseCase(
|
||||
* @param orderBySortierung Whether to order by sortierReihenfolge field (default: true)
|
||||
* @return List of active countries
|
||||
*/
|
||||
suspend fun getAllActive(orderBySortierung: Boolean = true): List<LandDefinition> {
|
||||
return landRepository.findAllActive(orderBySortierung)
|
||||
suspend fun getAllActive(orderBySortierung: Boolean = true): GetCountriesResponse {
|
||||
val countries = landRepository.findAllActive(orderBySortierung)
|
||||
return GetCountriesResponse(countries = countries)
|
||||
}
|
||||
|
||||
data class GetCountriesResponse(val countries: List<LandDefinition>)
|
||||
|
||||
/**
|
||||
* Retrieves all EU member countries.
|
||||
*
|
||||
|
||||
+14
-4
@@ -22,10 +22,13 @@ class GetPlatzUseCase(
|
||||
* @param platzId The unique identifier of the venue
|
||||
* @return The venue if found, null otherwise
|
||||
*/
|
||||
suspend fun getById(platzId: Uuid): Platz? {
|
||||
return platzRepository.findById(platzId)
|
||||
suspend fun getById(platzId: Uuid): GetPlatzResponse {
|
||||
val platz = platzRepository.findById(platzId)
|
||||
return GetPlatzResponse(platz = platz)
|
||||
}
|
||||
|
||||
data class GetPlatzResponse(val platz: Platz?)
|
||||
|
||||
/**
|
||||
* Retrieves all venues for a specific tournament.
|
||||
*
|
||||
@@ -34,10 +37,17 @@ class GetPlatzUseCase(
|
||||
* @param orderBySortierung Whether to order by sortierReihenfolge field (default: true)
|
||||
* @return List of venues for the tournament
|
||||
*/
|
||||
suspend fun getByTournament(turnierId: Uuid, activeOnly: Boolean = true, orderBySortierung: Boolean = true): List<Platz> {
|
||||
return platzRepository.findByTournament(turnierId, activeOnly, orderBySortierung)
|
||||
suspend fun getByTournament(
|
||||
turnierId: Uuid,
|
||||
activeOnly: Boolean = true,
|
||||
orderBySortierung: Boolean = true
|
||||
): GetPlaetzeResponse {
|
||||
val plaetze = platzRepository.findByTournament(turnierId, activeOnly, orderBySortierung)
|
||||
return GetPlaetzeResponse(plaetze = plaetze)
|
||||
}
|
||||
|
||||
data class GetPlaetzeResponse(val plaetze: List<Platz>)
|
||||
|
||||
/**
|
||||
* Searches for venues by name (partial match).
|
||||
*
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
plugins {
|
||||
alias(libs.plugins.kotlin.multiplatform)
|
||||
alias(libs.plugins.kotlin.serialization)
|
||||
alias(libs.plugins.kotlinMultiplatform)
|
||||
alias(libs.plugins.kotlinSerialization)
|
||||
}
|
||||
|
||||
kotlin {
|
||||
@@ -20,6 +20,10 @@ kotlin {
|
||||
val commonTest by getting {
|
||||
dependencies {
|
||||
implementation(kotlin("test"))
|
||||
}
|
||||
}
|
||||
val jvmTest by getting {
|
||||
dependencies {
|
||||
implementation(projects.platform.platformTesting)
|
||||
}
|
||||
}
|
||||
|
||||
+8
-9
@@ -1,13 +1,12 @@
|
||||
@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class)
|
||||
package at.mocode.masterdata.domain.model
|
||||
|
||||
import at.mocode.core.domain.model.SparteE // Optional, falls Altersklassen stark spartenspezifisch sind
|
||||
import at.mocode.core.domain.serialization.KotlinInstantSerializer
|
||||
import at.mocode.core.domain.model.SparteE
|
||||
import at.mocode.core.domain.serialization.InstantSerializer
|
||||
import at.mocode.core.domain.serialization.UuidSerializer
|
||||
import kotlin.uuid.Uuid
|
||||
import kotlinx.datetime.Clock
|
||||
import kotlinx.datetime.Instant
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlin.time.Instant
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
/**
|
||||
* Definiert eine spezifische Altersklasse für Teilnehmer (Reiter, Fahrer, Voltigierer)
|
||||
@@ -52,8 +51,8 @@ data class AltersklasseDefinition(
|
||||
|
||||
var istAktiv: Boolean = true,
|
||||
|
||||
@Serializable(with = KotlinInstantSerializer::class)
|
||||
val createdAt: Instant = Clock.System.now(),
|
||||
@Serializable(with = KotlinInstantSerializer::class)
|
||||
var updatedAt: Instant = Clock.System.now()
|
||||
@Serializable(with = InstantSerializer::class)
|
||||
val createdAt: Instant,
|
||||
@Serializable(with = InstantSerializer::class)
|
||||
var updatedAt: Instant
|
||||
)
|
||||
+7
-8
@@ -1,12 +1,11 @@
|
||||
@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class)
|
||||
package at.mocode.masterdata.domain.model
|
||||
|
||||
import at.mocode.core.domain.serialization.KotlinInstantSerializer
|
||||
import at.mocode.core.domain.serialization.InstantSerializer
|
||||
import at.mocode.core.domain.serialization.UuidSerializer
|
||||
import kotlin.uuid.Uuid
|
||||
import kotlinx.datetime.Clock
|
||||
import kotlinx.datetime.Instant
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlin.time.Instant
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
/**
|
||||
* Definiert ein Bundesland oder eine vergleichbare subnationale Verwaltungseinheit.
|
||||
@@ -44,8 +43,8 @@ data class BundeslandDefinition(
|
||||
var istAktiv: Boolean = true,
|
||||
var sortierReihenfolge: Int? = null,
|
||||
|
||||
@Serializable(with = KotlinInstantSerializer::class)
|
||||
val createdAt: Instant = Clock.System.now(),
|
||||
@Serializable(with = KotlinInstantSerializer::class)
|
||||
var updatedAt: Instant = Clock.System.now()
|
||||
@Serializable(with = InstantSerializer::class)
|
||||
val createdAt: Instant,
|
||||
@Serializable(with = InstantSerializer::class)
|
||||
var updatedAt: Instant
|
||||
)
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class)
|
||||
|
||||
package at.mocode.masterdata.domain.model
|
||||
|
||||
import at.mocode.core.domain.model.DatenQuelleE
|
||||
import at.mocode.core.domain.model.FunktionaerRolleE
|
||||
import at.mocode.core.domain.model.RichterQualifikationE
|
||||
import at.mocode.core.domain.model.SparteE
|
||||
import at.mocode.core.domain.serialization.InstantSerializer
|
||||
import at.mocode.core.domain.serialization.UuidSerializer
|
||||
import kotlinx.datetime.LocalDate
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlin.time.Clock
|
||||
import kotlin.time.Instant
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
/**
|
||||
* Domain-Modell für einen Funktionär im actor-context.
|
||||
*
|
||||
* Repräsentiert eine Person mit einer definierten Rolle bei Turnieren (Richter, TBA,
|
||||
* Parcoursbauer, etc.). Die Qualifikation wird gegen `RICHT01.DAT` aus dem ZNS geprüft.
|
||||
*
|
||||
* Aggregate Root des `officials`-Bounded Context.
|
||||
*
|
||||
* @property funktionaerId Eindeutige interne ID (UUID).
|
||||
* @property richterNummer ÖPS-Funktionärsnummer aus ZNS (RICHT01.dat), 6-stellig.
|
||||
* @property vorname Vorname der Person.
|
||||
* @property nachname Nachname der Person.
|
||||
* @property geburtsdatum Geburtsdatum (optional, für Altersklassen-Prüfung).
|
||||
* @property rollen Menge der Rollen, die diese Person ausüben darf (TBA, Richter, ...).
|
||||
* @property richterQualifikation Qualifikationsstufe als Richter (GA, G1–G3, International).
|
||||
* @property qualifiziertFuerSparten Sparten, für die eine Richter-Qualifikation vorliegt.
|
||||
* @property email E-Mail-Adresse für Kommunikation.
|
||||
* @property telefon Telefonnummer.
|
||||
* @property vereinsNummer Vereinsnummer des Heimvereins (Referenz auf DomVerein).
|
||||
* @property istAktiv Ob der Funktionär aktuell aktiv/einsatzbereit ist.
|
||||
* @property bemerkungen Interne Notizen.
|
||||
* @property datenQuelle Herkunft des Datensatzes (ZNS-Import oder manuell).
|
||||
* @property createdAt Erstellungszeitpunkt.
|
||||
* @property updatedAt Letzter Änderungszeitpunkt.
|
||||
*/
|
||||
@Serializable
|
||||
data class DomFunktionaer(
|
||||
@Serializable(with = UuidSerializer::class)
|
||||
val funktionaerId: Uuid = Uuid.random(),
|
||||
|
||||
// Identifikation
|
||||
val richterNummer: String? = null,
|
||||
|
||||
// Persönliche Daten
|
||||
var vorname: String,
|
||||
var nachname: String,
|
||||
var geburtsdatum: LocalDate? = null,
|
||||
|
||||
// Qualifikation & Rollen
|
||||
var rollen: Set<FunktionaerRolleE> = emptySet(),
|
||||
var richterQualifikation: RichterQualifikationE? = null,
|
||||
var qualifiziertFuerSparten: Set<SparteE> = emptySet(),
|
||||
|
||||
// Kontakt
|
||||
var email: String? = null,
|
||||
var telefon: String? = null,
|
||||
|
||||
// Vereinszugehörigkeit
|
||||
var vereinsNummer: String? = null,
|
||||
|
||||
// Status & Verwaltung
|
||||
var istAktiv: Boolean = true,
|
||||
var bemerkungen: String? = null,
|
||||
var datenQuelle: DatenQuelleE = DatenQuelleE.IMPORT_ZNS,
|
||||
|
||||
// Audit
|
||||
@Serializable(with = InstantSerializer::class)
|
||||
val createdAt: Instant = Clock.System.now(),
|
||||
@Serializable(with = InstantSerializer::class)
|
||||
var updatedAt: Instant = Clock.System.now()
|
||||
) {
|
||||
/**
|
||||
* Gibt den vollständigen Anzeigenamen zurück.
|
||||
*/
|
||||
fun getDisplayName(): String = "$vorname $nachname"
|
||||
|
||||
/**
|
||||
* Gibt den Anzeigenamen mit Funktionärsnummer zurück (falls vorhanden).
|
||||
*/
|
||||
fun getDisplayNameWithNummer(): String =
|
||||
richterNummer?.let { "${getDisplayName()} ($it)" } ?: getDisplayName()
|
||||
|
||||
/**
|
||||
* Prüft, ob der Funktionär als Richter für eine bestimmte Sparte qualifiziert ist.
|
||||
*/
|
||||
fun istRichterFuerSparte(sparte: SparteE): Boolean =
|
||||
rollen.contains(FunktionaerRolleE.RICHTER) && qualifiziertFuerSparten.contains(sparte)
|
||||
|
||||
/**
|
||||
* Prüft, ob der Funktionär die Rolle TBA ausüben darf.
|
||||
*/
|
||||
fun istTba(): Boolean = rollen.contains(FunktionaerRolleE.TBA)
|
||||
|
||||
/**
|
||||
* Validiert die Pflichtfelder für den Turniereinsatz.
|
||||
* Gibt eine Liste von Warnungen zurück (kein harter Fehler – Override-Event möglich).
|
||||
*/
|
||||
fun validateFuerTurniereinsatz(rolle: FunktionaerRolleE, sparte: SparteE? = null): List<String> {
|
||||
val warnings = mutableListOf<String>()
|
||||
|
||||
if (!istAktiv) {
|
||||
warnings.add("Funktionär ${getDisplayName()} ist nicht aktiv.")
|
||||
}
|
||||
|
||||
if (!rollen.contains(rolle)) {
|
||||
warnings.add("Funktionär ${getDisplayName()} hat keine Qualifikation für Rolle $rolle.")
|
||||
}
|
||||
|
||||
if (rolle == FunktionaerRolleE.RICHTER && sparte != null && !istRichterFuerSparte(sparte)) {
|
||||
warnings.add("Funktionär ${getDisplayName()} ist nicht als Richter für Sparte $sparte qualifiziert.")
|
||||
}
|
||||
|
||||
return warnings
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt eine Kopie mit aktualisiertem Zeitstempel.
|
||||
*/
|
||||
fun withUpdatedTimestamp(): DomFunktionaer = this.copy(updatedAt = Clock.System.now())
|
||||
}
|
||||
+175
@@ -0,0 +1,175 @@
|
||||
@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class)
|
||||
|
||||
package at.mocode.masterdata.domain.model
|
||||
|
||||
import at.mocode.core.domain.model.DatenQuelleE
|
||||
import at.mocode.core.domain.model.PferdeGeschlechtE
|
||||
import at.mocode.core.domain.serialization.InstantSerializer
|
||||
import at.mocode.core.domain.serialization.UuidSerializer
|
||||
import kotlinx.datetime.LocalDate
|
||||
import kotlinx.datetime.number
|
||||
import kotlinx.datetime.todayIn
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlin.time.Clock
|
||||
import kotlin.time.Instant
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
/**
|
||||
* Domain model representing a horse in the registry system.
|
||||
*
|
||||
* This entity contains all essential information about a horse including
|
||||
* identification, ownership, breeding information, and administrative data.
|
||||
* It serves as the core aggregate root for the horse-registry bounded context.
|
||||
*
|
||||
* @property pferdId Unique internal identifier for this horse (UUID).
|
||||
* @property pferdeName Name of the horse.
|
||||
* @property geschlecht Gender of the horse (Hengst, Stute, Wallach).
|
||||
* @property geburtsdatum Birthdate of the horse.
|
||||
* @property rasse Breed of the horse.
|
||||
* @property farbe Color/coat of the horse.
|
||||
* @property besitzerId ID of the current owner (Person from member-management context).
|
||||
* @property verantwortlichePersonId ID of the responsible person (trainer, rider, etc.).
|
||||
* @property zuechterName Name of the breeder.
|
||||
* @property zuchtbuchNummer Studbook number if registered.
|
||||
* @property lebensnummer Life number (unique identification number).
|
||||
* @property chipNummer Microchip number for identification.
|
||||
* @property passNummer Passport number.
|
||||
* @property oepsNummer OEPS (Austrian Equestrian Federation) number.
|
||||
* @property feiNummer FEI (International Equestrian Federation) number.
|
||||
* @property vaterName Name of the sire (father).
|
||||
* @property mutterName Name of the dam (mother).
|
||||
* @property mutterVaterName Name of the maternal grandsire.
|
||||
* @property stockmass Height of the horse in cm.
|
||||
* @property istAktiv Whether the horse is currently active in the system.
|
||||
* @property bemerkungen Additional notes or comments.
|
||||
* @property datenQuelle Source of the data (manual entry, import, etc.).
|
||||
* @property createdAt Timestamp when this record was created.
|
||||
* @property updatedAt Timestamp when this record was last updated.
|
||||
*/
|
||||
@Serializable
|
||||
data class DomPferd(
|
||||
@Serializable(with = UuidSerializer::class)
|
||||
val pferdId: Uuid = Uuid.random(),
|
||||
|
||||
// Basic Information
|
||||
var pferdeName: String,
|
||||
var geschlecht: PferdeGeschlechtE,
|
||||
var geburtsdatum: LocalDate? = null,
|
||||
var rasse: String? = null,
|
||||
var farbe: String? = null,
|
||||
|
||||
// Ownership and Responsibility
|
||||
@Serializable(with = UuidSerializer::class)
|
||||
var besitzerId: Uuid? = null,
|
||||
@Serializable(with = UuidSerializer::class)
|
||||
var verantwortlichePersonId: Uuid? = null,
|
||||
|
||||
// Breeding Information
|
||||
var zuechterName: String? = null,
|
||||
var zuchtbuchNummer: String? = null,
|
||||
|
||||
// Identification Numbers
|
||||
var lebensnummer: String? = null,
|
||||
var chipNummer: String? = null,
|
||||
var passNummer: String? = null,
|
||||
var oepsNummer: String? = null,
|
||||
var feiNummer: String? = null,
|
||||
|
||||
// Pedigree Information
|
||||
var vaterName: String? = null,
|
||||
var mutterName: String? = null,
|
||||
var mutterVaterName: String? = null,
|
||||
|
||||
// Physical Characteristics
|
||||
var stockmass: Int? = null, // Height in cm
|
||||
|
||||
// Status and Administrative
|
||||
var istAktiv: Boolean = true,
|
||||
var bemerkungen: String? = null,
|
||||
var datenQuelle: DatenQuelleE = DatenQuelleE.MANUELL,
|
||||
|
||||
// Audit Fields
|
||||
@Serializable(with = InstantSerializer::class)
|
||||
val createdAt: Instant = Clock.System.now(),
|
||||
@Serializable(with = InstantSerializer::class)
|
||||
var updatedAt: Instant = Clock.System.now()
|
||||
) {
|
||||
/**
|
||||
* Returns the display name for the horse, combining name and birth year if available.
|
||||
*/
|
||||
fun getDisplayName(): String {
|
||||
return geburtsdatum?.let { birthDate ->
|
||||
"$pferdeName (${birthDate.year})"
|
||||
} ?: pferdeName
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the horse has complete identification information.
|
||||
*/
|
||||
fun hasCompleteIdentification(): Boolean {
|
||||
return !lebensnummer.isNullOrBlank() ||
|
||||
!chipNummer.isNullOrBlank() ||
|
||||
!passNummer.isNullOrBlank()
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the horse is registered with OEPS.
|
||||
*/
|
||||
fun isOepsRegistered(): Boolean {
|
||||
return !oepsNummer.isNullOrBlank()
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the horse is registered with FEI.
|
||||
*/
|
||||
fun isFeiRegistered(): Boolean {
|
||||
return !feiNummer.isNullOrBlank()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the age of the horse in years, or null if birth date is unknown.
|
||||
*/
|
||||
fun getAge(): Int? {
|
||||
return geburtsdatum?.let { birthDate ->
|
||||
val today = Clock.System.todayIn(kotlinx.datetime.TimeZone.currentSystemDefault())
|
||||
var age = today.year - birthDate.year
|
||||
|
||||
// Check if a birthday has occurred this year
|
||||
if (today.month.number < birthDate.month.number ||
|
||||
(today.month.number == birthDate.month.number && today.day < birthDate.day)
|
||||
) {
|
||||
age--
|
||||
}
|
||||
|
||||
age
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that required fields are present for horse registration.
|
||||
*/
|
||||
fun validateForRegistration(): List<String> {
|
||||
val errors = mutableListOf<String>()
|
||||
|
||||
if (pferdeName.isBlank()) {
|
||||
errors.add("Horse name is required")
|
||||
}
|
||||
|
||||
if (!hasCompleteIdentification()) {
|
||||
errors.add("At least one identification number (life number, chip number, or passport number) is required")
|
||||
}
|
||||
|
||||
if (besitzerId == null) {
|
||||
errors.add("Owner is required")
|
||||
}
|
||||
|
||||
return errors
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a copy of this horse with an updated timestamp.
|
||||
*/
|
||||
fun withUpdatedTimestamp(): DomPferd {
|
||||
return this.copy(updatedAt = Clock.System.now())
|
||||
}
|
||||
}
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class)
|
||||
|
||||
package at.mocode.masterdata.domain.model
|
||||
|
||||
import at.mocode.core.domain.model.DatenQuelleE
|
||||
import at.mocode.core.domain.model.LizenzKlasseE
|
||||
import at.mocode.core.domain.model.SparteE
|
||||
import at.mocode.core.domain.serialization.InstantSerializer
|
||||
import at.mocode.core.domain.serialization.LocalDateSerializer
|
||||
import at.mocode.core.domain.serialization.UuidSerializer
|
||||
import kotlinx.datetime.LocalDate
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlin.time.Clock
|
||||
import kotlin.time.Instant
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
/**
|
||||
* Domain model representing a rider (Reiter) in the actor-context.
|
||||
*
|
||||
* A rider is a specialization of a person with additional equestrian-specific
|
||||
* attributes such as license, start card, and competition eligibility.
|
||||
* Data is primarily sourced from the OEPS ZNS (LIZENZ01.DAT).
|
||||
*
|
||||
* Key rules (ÖTO):
|
||||
* - A rider requires an active Startkarte (annual fee paid) to compete nationally.
|
||||
* - LizenzKlasse determines which competition classes the rider may enter.
|
||||
* - Satznummer (6-digit) is the primary key for ZNS data exchange.
|
||||
* - Kopfnummer is NOT a unique identifier – it can change.
|
||||
*
|
||||
* @property reiterId Unique internal identifier (UUID).
|
||||
* @property personId Reference to the base DomPerson record (UUID).
|
||||
* @property satznummer 6-digit ZNS primary key for data exchange. Primary key for ZNS.
|
||||
* @property lizenzNummer OEPS license number (from ZNS LIZENZ01.DAT).
|
||||
* @property lizenzKlasse License class determining competition eligibility (e.g. R1, RD2).
|
||||
* @property lizenzSparten Disciplines for which the license is valid.
|
||||
* @property startkartAktiv Whether the annual start card fee has been paid.
|
||||
* @property startkartSaison Season year for which the start card is valid (e.g. 2026).
|
||||
* @property feiId FEI international rider ID (optional).
|
||||
* @property nation Nation code (e.g. AUT).
|
||||
* @property geburtsdatum Date of birth (for age class validation).
|
||||
* @property vereinsNummer Club number (OEPS).
|
||||
* @property vereinsName Club name.
|
||||
* @property istGastreiter Whether the rider is a guest rider (foreign nationality, not in Austrian club).
|
||||
* @property istAktiv Whether the rider is currently active in the system.
|
||||
* @property datenQuelle Source of the data.
|
||||
* @property createdAt Timestamp when this record was created.
|
||||
* @property updatedAt Timestamp when this record was last updated.
|
||||
*/
|
||||
@Serializable
|
||||
data class DomReiter(
|
||||
@Serializable(with = UuidSerializer::class)
|
||||
val reiterId: Uuid = Uuid.random(),
|
||||
|
||||
// Reference to base person
|
||||
@Serializable(with = UuidSerializer::class)
|
||||
val personId: Uuid,
|
||||
|
||||
// ZNS Identification
|
||||
val satznummer: String,
|
||||
val lizenzNummer: String? = null,
|
||||
|
||||
// License & Eligibility
|
||||
val lizenzKlasse: LizenzKlasseE = LizenzKlasseE.LIZENZFREI,
|
||||
val lizenzSparten: List<SparteE> = emptyList(),
|
||||
|
||||
// Start Card (Startkarte) – annual fee proof
|
||||
val startkartAktiv: Boolean = false,
|
||||
val startkartSaison: Int? = null,
|
||||
|
||||
// International
|
||||
val feiId: String? = null,
|
||||
val nation: String? = null,
|
||||
|
||||
// Personal Data (denormalized from DomPerson for performance)
|
||||
val nachname: String,
|
||||
val vorname: String,
|
||||
@Serializable(with = LocalDateSerializer::class)
|
||||
val geburtsdatum: LocalDate? = null,
|
||||
|
||||
// Club Affiliation
|
||||
val vereinsNummer: String? = null,
|
||||
val vereinsName: String? = null,
|
||||
|
||||
// Status
|
||||
val istGastreiter: Boolean = false,
|
||||
val istAktiv: Boolean = true,
|
||||
val datenQuelle: DatenQuelleE = DatenQuelleE.IMPORT_ZNS,
|
||||
|
||||
// Audit Fields
|
||||
@Serializable(with = InstantSerializer::class)
|
||||
val createdAt: Instant = Clock.System.now(),
|
||||
@Serializable(with = InstantSerializer::class)
|
||||
var updatedAt: Instant = Clock.System.now()
|
||||
) {
|
||||
/**
|
||||
* Returns the display name of the rider.
|
||||
*/
|
||||
fun getDisplayName(): String = "$vorname $nachname"
|
||||
|
||||
/**
|
||||
* Checks if the rider is eligible to compete nationally.
|
||||
* Requires an active start card (Startkarte).
|
||||
*/
|
||||
fun isStartberechtigt(): Boolean = istAktiv && startkartAktiv
|
||||
|
||||
/**
|
||||
* Checks if the rider holds a license for the given discipline.
|
||||
*/
|
||||
fun hasLizenzForSparte(sparte: SparteE): Boolean =
|
||||
lizenzKlasse == LizenzKlasseE.LIZENZFREI || lizenzSparten.contains(sparte)
|
||||
|
||||
/**
|
||||
* Validates the rider for competition entry.
|
||||
* Returns a list of warning messages (never hard errors – TBA has final say).
|
||||
*/
|
||||
fun validateForNennung(sparte: SparteE): List<String> {
|
||||
val warnings = mutableListOf<String>()
|
||||
|
||||
if (!istAktiv) {
|
||||
warnings.add("Reiter ${getDisplayName()} ist nicht aktiv")
|
||||
}
|
||||
if (!startkartAktiv) {
|
||||
warnings.add("Reiter ${getDisplayName()} hat keine aktive Startkarte für Saison $startkartSaison")
|
||||
}
|
||||
if (!hasLizenzForSparte(sparte)) {
|
||||
warnings.add("Reiter ${getDisplayName()} hat keine Lizenz für Sparte $sparte (Lizenzklasse: $lizenzKlasse)")
|
||||
}
|
||||
|
||||
return warnings
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a copy of this rider with an updated timestamp.
|
||||
*/
|
||||
fun withUpdatedTimestamp(): DomReiter = this.copy(updatedAt = Clock.System.now())
|
||||
}
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class)
|
||||
|
||||
package at.mocode.masterdata.domain.model
|
||||
|
||||
import at.mocode.core.domain.model.DatenQuelleE
|
||||
import at.mocode.core.domain.serialization.InstantSerializer
|
||||
import at.mocode.core.domain.serialization.UuidSerializer
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlin.time.Clock
|
||||
import kotlin.time.Instant
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
/**
|
||||
* Domain-Modell für einen Verein im actor-context.
|
||||
*
|
||||
* Repräsentiert einen OEPS-Mitgliedsverein, der als Veranstalter von Turnieren
|
||||
* und als Heimverein von Reitern und Funktionären fungiert.
|
||||
* Daten werden primär aus dem ZNS (VEREIN01.dat) importiert.
|
||||
*
|
||||
* Aggregate Root des `clubs`-Bounded Context.
|
||||
*
|
||||
* @property vereinId Eindeutige interne ID (UUID).
|
||||
* @property vereinsNummer ÖPS-Vereinsnummer aus ZNS (VEREIN01.dat), 4-stellig. Primärschlüssel für ZNS-Datenaustausch.
|
||||
* @property name Offizieller Vereinsname.
|
||||
* @property kurzname Kurzbezeichnung des Vereins (optional).
|
||||
* @property bundesland Bundesland, in dem der Verein ansässig ist.
|
||||
* @property ort Ort / Stadt des Vereinssitzes.
|
||||
* @property plz Postleitzahl.
|
||||
* @property strasse Straße und Hausnummer.
|
||||
* @property email Offizielle E-Mail-Adresse des Vereins.
|
||||
* @property telefon Telefonnummer des Vereins.
|
||||
* @property website Website-URL des Vereins.
|
||||
* @property oepsRegionNummer Regionsnummer beim OEPS (Landesverband).
|
||||
* @property istVeranstalter Ob der Verein als Veranstalter von Turnieren zugelassen ist.
|
||||
* @property istAktiv Ob der Verein aktuell aktiv ist.
|
||||
* @property bemerkungen Interne Notizen.
|
||||
* @property datenQuelle Herkunft des Datensatzes (ZNS-Import oder manuell).
|
||||
* @property createdAt Erstellungszeitpunkt.
|
||||
* @property updatedAt Letzter Änderungszeitpunkt.
|
||||
*/
|
||||
@Serializable
|
||||
data class DomVerein(
|
||||
@Serializable(with = UuidSerializer::class)
|
||||
val vereinId: Uuid = Uuid.random(),
|
||||
|
||||
// Identifikation
|
||||
val vereinsNummer: String,
|
||||
|
||||
// Stammdaten
|
||||
var name: String,
|
||||
var kurzname: String? = null,
|
||||
|
||||
// Adresse
|
||||
var bundesland: String? = null,
|
||||
var ort: String? = null,
|
||||
var plz: String? = null,
|
||||
var strasse: String? = null,
|
||||
|
||||
// Kontakt
|
||||
var email: String? = null,
|
||||
var telefon: String? = null,
|
||||
var website: String? = null,
|
||||
|
||||
// OEPS-Verwaltung
|
||||
var oepsRegionNummer: String? = null,
|
||||
var istVeranstalter: Boolean = false,
|
||||
|
||||
// Status & Verwaltung
|
||||
var istAktiv: Boolean = true,
|
||||
var bemerkungen: String? = null,
|
||||
var datenQuelle: DatenQuelleE = DatenQuelleE.IMPORT_ZNS,
|
||||
|
||||
// Audit
|
||||
@Serializable(with = InstantSerializer::class)
|
||||
val createdAt: Instant = Clock.System.now(),
|
||||
@Serializable(with = InstantSerializer::class)
|
||||
var updatedAt: Instant = Clock.System.now()
|
||||
) {
|
||||
/**
|
||||
* Gibt den Anzeigenamen zurück – Kurzname bevorzugt, sonst vollständiger Name.
|
||||
*/
|
||||
fun getDisplayName(): String = kurzname ?: name
|
||||
|
||||
/**
|
||||
* Gibt den vollständigen Anzeigenamen mit Vereinsnummer zurück.
|
||||
*/
|
||||
fun getDisplayNameWithNummer(): String = "${getDisplayName()} ($vereinsNummer)"
|
||||
|
||||
/**
|
||||
* Prüft, ob vollständige Adressdaten vorhanden sind.
|
||||
*/
|
||||
fun hasCompleteAddress(): Boolean =
|
||||
!ort.isNullOrBlank() && !plz.isNullOrBlank() && !strasse.isNullOrBlank()
|
||||
|
||||
/**
|
||||
* Validiert den Verein für den Einsatz als Veranstalter.
|
||||
* Gibt Warnungen zurück (kein harter Fehler – Override-Event möglich).
|
||||
*/
|
||||
fun validateFuerVeranstaltung(): List<String> {
|
||||
val warnings = mutableListOf<String>()
|
||||
|
||||
if (!istAktiv) {
|
||||
warnings.add("Verein ${getDisplayName()} ist nicht aktiv.")
|
||||
}
|
||||
|
||||
if (!istVeranstalter) {
|
||||
warnings.add("Verein ${getDisplayName()} ist nicht als Veranstalter zugelassen.")
|
||||
}
|
||||
|
||||
if (!hasCompleteAddress()) {
|
||||
warnings.add("Verein ${getDisplayName()} hat keine vollständige Adresse hinterlegt.")
|
||||
}
|
||||
|
||||
return warnings
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt eine Kopie mit aktualisiertem Zeitstempel.
|
||||
*/
|
||||
fun withUpdatedTimestamp(): DomVerein = this.copy(updatedAt = Clock.System.now())
|
||||
}
|
||||
+7
-8
@@ -1,12 +1,11 @@
|
||||
@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class)
|
||||
package at.mocode.masterdata.domain.model
|
||||
|
||||
import at.mocode.core.domain.serialization.KotlinInstantSerializer
|
||||
import at.mocode.core.domain.serialization.InstantSerializer
|
||||
import at.mocode.core.domain.serialization.UuidSerializer
|
||||
import kotlin.uuid.Uuid
|
||||
import kotlinx.datetime.Clock
|
||||
import kotlinx.datetime.Instant
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlin.time.Instant
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
/**
|
||||
* Definiert ein Land/eine Nation mit seinen offiziellen Codes und Bezeichnungen.
|
||||
@@ -44,8 +43,8 @@ data class LandDefinition(
|
||||
var istAktiv: Boolean = true,
|
||||
var sortierReihenfolge: Int? = null,
|
||||
|
||||
@Serializable(with = KotlinInstantSerializer::class)
|
||||
val createdAt: Instant = Clock.System.now(),
|
||||
@Serializable(with = KotlinInstantSerializer::class)
|
||||
var updatedAt: Instant = Clock.System.now()
|
||||
@Serializable(with = InstantSerializer::class)
|
||||
val createdAt: Instant,
|
||||
@Serializable(with = InstantSerializer::class)
|
||||
var updatedAt: Instant
|
||||
)
|
||||
+7
-8
@@ -2,12 +2,11 @@
|
||||
package at.mocode.masterdata.domain.model
|
||||
|
||||
import at.mocode.core.domain.model.PlatzTypE
|
||||
import at.mocode.core.domain.serialization.KotlinInstantSerializer
|
||||
import at.mocode.core.domain.serialization.InstantSerializer
|
||||
import at.mocode.core.domain.serialization.UuidSerializer
|
||||
import kotlin.uuid.Uuid
|
||||
import kotlinx.datetime.Clock
|
||||
import kotlinx.datetime.Instant
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlin.time.Instant
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
/**
|
||||
* Definiert einen Turnierplatz oder eine Wettkampfstätte.
|
||||
@@ -41,8 +40,8 @@ data class Platz(
|
||||
var istAktiv: Boolean = true,
|
||||
var sortierReihenfolge: Int? = null,
|
||||
|
||||
@Serializable(with = KotlinInstantSerializer::class)
|
||||
val createdAt: Instant = Clock.System.now(),
|
||||
@Serializable(with = KotlinInstantSerializer::class)
|
||||
var updatedAt: Instant = Clock.System.now()
|
||||
@Serializable(with = InstantSerializer::class)
|
||||
val createdAt: Instant,
|
||||
@Serializable(with = InstantSerializer::class)
|
||||
var updatedAt: Instant
|
||||
)
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class)
|
||||
|
||||
package at.mocode.masterdata.domain.repository
|
||||
|
||||
import at.mocode.core.domain.model.SparteE
|
||||
import at.mocode.masterdata.domain.model.AltersklasseDefinition
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
/**
|
||||
* Repository interface for AltersklasseDefinition (Age Class) domain operations.
|
||||
*
|
||||
* This interface defines the contract for age class data access operations
|
||||
* without depending on specific implementation details (database, etc.).
|
||||
* Following the hexagonal architecture pattern, this interface belongs
|
||||
* to the domain layer and will be implemented in the infrastructure layer.
|
||||
*/
|
||||
interface AltersklasseRepository {
|
||||
|
||||
/**
|
||||
* Finds an age class by its unique ID.
|
||||
*
|
||||
* @param id The unique identifier of the age class
|
||||
* @return The age class if found, null otherwise
|
||||
*/
|
||||
suspend fun findById(id: Uuid): AltersklasseDefinition?
|
||||
|
||||
/**
|
||||
* Finds an age class by its code.
|
||||
*
|
||||
* @param altersklasseCode The age class code (e.g., "JGD_U16", "JUN_U18")
|
||||
* @return The age class if found, null otherwise
|
||||
*/
|
||||
suspend fun findByCode(altersklasseCode: String): AltersklasseDefinition?
|
||||
|
||||
/**
|
||||
* Finds age classes by name (partial match).
|
||||
*
|
||||
* @param searchTerm The search term to match against age class names
|
||||
* @param limit Maximum number of results to return
|
||||
* @return List of matching age classes
|
||||
*/
|
||||
suspend fun findByName(searchTerm: String, limit: Int = 50): List<AltersklasseDefinition>
|
||||
|
||||
/**
|
||||
* Finds all active age classes.
|
||||
*
|
||||
* @param sparteFilter Optional filter by sport type
|
||||
* @param geschlechtFilter Optional filter by gender ('M', 'W')
|
||||
* @return List of active age classes
|
||||
*/
|
||||
suspend fun findAllActive(sparteFilter: SparteE? = null, geschlechtFilter: Char? = null): List<AltersklasseDefinition>
|
||||
|
||||
/**
|
||||
* Finds age classes applicable for a specific age.
|
||||
*
|
||||
* @param age The age to check
|
||||
* @param sparteFilter Optional filter by sport type
|
||||
* @param geschlechtFilter Optional filter by gender ('M', 'W')
|
||||
* @return List of applicable age classes
|
||||
*/
|
||||
suspend fun findApplicableForAge(
|
||||
age: Int,
|
||||
sparteFilter: SparteE? = null,
|
||||
geschlechtFilter: Char? = null
|
||||
): List<AltersklasseDefinition>
|
||||
|
||||
/**
|
||||
* Finds age classes by sport type.
|
||||
*
|
||||
* @param sparte The sport type
|
||||
* @param activeOnly Whether to return only active age classes
|
||||
* @return List of age classes for the sport type
|
||||
*/
|
||||
suspend fun findBySparte(sparte: SparteE, activeOnly: Boolean = true): List<AltersklasseDefinition>
|
||||
|
||||
/**
|
||||
* Finds age classes by gender filter.
|
||||
*
|
||||
* @param geschlecht The gender ('M', 'W')
|
||||
* @param activeOnly Whether to return only active age classes
|
||||
* @return List of age classes for the gender
|
||||
*/
|
||||
suspend fun findByGeschlecht(geschlecht: Char, activeOnly: Boolean = true): List<AltersklasseDefinition>
|
||||
|
||||
/**
|
||||
* Finds age classes by age range.
|
||||
*
|
||||
* @param minAge Minimum age (inclusive)
|
||||
* @param maxAge Maximum age (inclusive)
|
||||
* @param activeOnly Whether to return only active age classes
|
||||
* @return List of age classes within the age range
|
||||
*/
|
||||
suspend fun findByAgeRange(minAge: Int?, maxAge: Int?, activeOnly: Boolean = true): List<AltersklasseDefinition>
|
||||
|
||||
/**
|
||||
* Finds age classes by OETO rule reference.
|
||||
*
|
||||
* @param oetoRegelReferenzId The OETO rule reference ID
|
||||
* @return List of age classes linked to the rule
|
||||
*/
|
||||
suspend fun findByOetoRegelReferenz(oetoRegelReferenzId: Uuid): List<AltersklasseDefinition>
|
||||
|
||||
/**
|
||||
* Saves an age class (create or update).
|
||||
*
|
||||
* @param altersklasse The age class to save
|
||||
* @return The saved age class with updated timestamps
|
||||
*/
|
||||
suspend fun save(altersklasse: AltersklasseDefinition): AltersklasseDefinition
|
||||
|
||||
/**
|
||||
* Deletes an age class by ID.
|
||||
*
|
||||
* @param id The unique identifier of the age class to delete
|
||||
* @return true if the age class was deleted, false if not found
|
||||
*/
|
||||
suspend fun delete(id: Uuid): Boolean
|
||||
|
||||
/**
|
||||
* Checks if an age class with the given code exists.
|
||||
*
|
||||
* @param altersklasseCode The age class code to check
|
||||
* @return true if an age class with this code exists, false otherwise
|
||||
*/
|
||||
suspend fun existsByCode(altersklasseCode: String): Boolean
|
||||
|
||||
/**
|
||||
* Counts the total number of active age classes.
|
||||
*
|
||||
* @param sparteFilter Optional filter by sport type
|
||||
* @return The total count of active age classes
|
||||
*/
|
||||
suspend fun countActive(sparteFilter: SparteE? = null): Long
|
||||
|
||||
/**
|
||||
* Validates if a person with given age and gender can participate in an age class.
|
||||
*
|
||||
* @param altersklasseId The age class ID
|
||||
* @param age The person's age
|
||||
* @param geschlecht The person's gender ('M', 'W')
|
||||
* @return true if the person can participate, false otherwise
|
||||
*/
|
||||
suspend fun isEligible(altersklasseId: Uuid, age: Int, geschlecht: Char): Boolean
|
||||
}
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class)
|
||||
|
||||
package at.mocode.masterdata.domain.repository
|
||||
|
||||
import at.mocode.core.domain.model.FunktionaerRolleE
|
||||
import at.mocode.core.domain.model.RichterQualifikationE
|
||||
import at.mocode.core.domain.model.SparteE
|
||||
import at.mocode.masterdata.domain.model.DomFunktionaer
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
/**
|
||||
* Repository-Interface für DomFunktionaer (Funktionär) Domain-Operationen.
|
||||
*
|
||||
* Definiert den Vertrag für Datenzugriffs-Operationen ohne Abhängigkeit
|
||||
* von konkreten Implementierungsdetails (Datenbank, etc.).
|
||||
*/
|
||||
interface FunktionaerRepository {
|
||||
|
||||
/**
|
||||
* Sucht einen Funktionär anhand seiner eindeutigen ID.
|
||||
*/
|
||||
suspend fun findById(id: Uuid): DomFunktionaer?
|
||||
|
||||
/**
|
||||
* Sucht einen Funktionär anhand seiner Richternummer.
|
||||
*/
|
||||
suspend fun findByRichterNummer(richterNummer: String): DomFunktionaer?
|
||||
|
||||
/**
|
||||
* Sucht Funktionäre anhand von Vor- und/oder Nachname (Teilübereinstimmung).
|
||||
*/
|
||||
suspend fun findByName(searchTerm: String, limit: Int = 50): List<DomFunktionaer>
|
||||
|
||||
/**
|
||||
* Sucht alle Funktionäre mit einer bestimmten Rolle.
|
||||
*/
|
||||
suspend fun findByRolle(rolle: FunktionaerRolleE, activeOnly: Boolean = true): List<DomFunktionaer>
|
||||
|
||||
/**
|
||||
* Sucht alle Richter mit einer bestimmten Qualifikation.
|
||||
*/
|
||||
suspend fun findByRichterQualifikation(
|
||||
qualifikation: RichterQualifikationE,
|
||||
activeOnly: Boolean = true
|
||||
): List<DomFunktionaer>
|
||||
|
||||
/**
|
||||
* Sucht alle Funktionäre, die für eine bestimmte Sparte qualifiziert sind.
|
||||
*/
|
||||
suspend fun findBySparte(sparte: SparteE, activeOnly: Boolean = true): List<DomFunktionaer>
|
||||
|
||||
/**
|
||||
* Sucht alle Funktionäre eines bestimmten Vereins.
|
||||
*/
|
||||
suspend fun findByVereinsNummer(vereinsNummer: String, activeOnly: Boolean = true): List<DomFunktionaer>
|
||||
|
||||
/**
|
||||
* Gibt alle aktiven Funktionäre zurück (paginiert).
|
||||
*/
|
||||
suspend fun findAllActive(limit: Int = 100, offset: Int = 0): List<DomFunktionaer>
|
||||
|
||||
/**
|
||||
* Gibt alle Funktionäre zurück (paginiert).
|
||||
*/
|
||||
suspend fun findAll(limit: Int = 100, offset: Int = 0): List<DomFunktionaer>
|
||||
|
||||
/**
|
||||
* Speichert einen Funktionär (Insert oder Update).
|
||||
*/
|
||||
suspend fun save(funktionaer: DomFunktionaer): DomFunktionaer
|
||||
|
||||
/**
|
||||
* Löscht einen Funktionär anhand seiner ID.
|
||||
*
|
||||
* @return true wenn gelöscht, false wenn nicht gefunden
|
||||
*/
|
||||
suspend fun delete(id: Uuid): Boolean
|
||||
|
||||
/**
|
||||
* Zählt alle aktiven Funktionäre.
|
||||
*/
|
||||
suspend fun countActive(): Long
|
||||
|
||||
/**
|
||||
* Zählt alle Richter (Rolle = RICHTER) mit einer bestimmten Qualifikation.
|
||||
*/
|
||||
suspend fun countByRichterQualifikation(qualifikation: RichterQualifikationE, activeOnly: Boolean = true): Long
|
||||
|
||||
/**
|
||||
* Prüft ob ein Funktionär mit der gegebenen Richternummer bereits existiert.
|
||||
*/
|
||||
suspend fun existsByRichterNummer(richterNummer: String): Boolean
|
||||
}
|
||||
+248
@@ -0,0 +1,248 @@
|
||||
@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class)
|
||||
|
||||
package at.mocode.masterdata.domain.repository
|
||||
|
||||
import at.mocode.masterdata.domain.model.DomPferd
|
||||
import at.mocode.core.domain.model.PferdeGeschlechtE
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
/**
|
||||
* Repository interface for DomPferd (Horse) domain operations.
|
||||
*
|
||||
* This interface defines the contract for horse data access operations
|
||||
* without depending on specific implementation details (database, etc.).
|
||||
* Following the hexagonal architecture pattern, this interface belongs
|
||||
* to the domain layer and will be implemented in the infrastructure layer.
|
||||
*/
|
||||
interface HorseRepository {
|
||||
|
||||
/**
|
||||
* Finds a horse by its unique ID.
|
||||
*
|
||||
* @param id The unique identifier of the horse
|
||||
* @return The horse if found, null otherwise
|
||||
*/
|
||||
suspend fun findById(id: Uuid): DomPferd?
|
||||
|
||||
/**
|
||||
* Finds a horse by its life number (Lebensnummer).
|
||||
*
|
||||
* @param lebensnummer The life number to search for
|
||||
* @return The horse if found, null otherwise
|
||||
*/
|
||||
suspend fun findByLebensnummer(lebensnummer: String): DomPferd?
|
||||
|
||||
/**
|
||||
* Finds a horse by its chip number.
|
||||
*
|
||||
* @param chipNummer The chip number to search for
|
||||
* @return The horse if found, null otherwise
|
||||
*/
|
||||
suspend fun findByChipNummer(chipNummer: String): DomPferd?
|
||||
|
||||
/**
|
||||
* Finds a horse by its passport number.
|
||||
*
|
||||
* @param passNummer The passport number to search for
|
||||
* @return The horse if found, null otherwise
|
||||
*/
|
||||
suspend fun findByPassNummer(passNummer: String): DomPferd?
|
||||
|
||||
/**
|
||||
* Finds a horse by its OEPS number.
|
||||
*
|
||||
* @param oepsNummer The OEPS number to search for
|
||||
* @return The horse if found, null otherwise
|
||||
*/
|
||||
suspend fun findByOepsNummer(oepsNummer: String): DomPferd?
|
||||
|
||||
/**
|
||||
* Finds a horse by its FEI number.
|
||||
*
|
||||
* @param feiNummer The FEI number to search for
|
||||
* @return The horse if found, null otherwise
|
||||
*/
|
||||
suspend fun findByFeiNummer(feiNummer: String): DomPferd?
|
||||
|
||||
/**
|
||||
* Finds horses by name (partial match).
|
||||
*
|
||||
* @param searchTerm The search term to match against horse names
|
||||
* @param limit Maximum number of results to return
|
||||
* @return List of matching horses
|
||||
*/
|
||||
suspend fun findByName(searchTerm: String, limit: Int = 50): List<DomPferd>
|
||||
|
||||
/**
|
||||
* Finds all horses owned by a specific person.
|
||||
*
|
||||
* @param ownerId The ID of the owner (from member-management context)
|
||||
* @param activeOnly Whether to return only active horses
|
||||
* @return List of horses owned by the person
|
||||
*/
|
||||
suspend fun findByOwnerId(ownerId: Uuid, activeOnly: Boolean = true): List<DomPferd>
|
||||
|
||||
/**
|
||||
* Finds all horses for which a person is responsible.
|
||||
*
|
||||
* @param responsiblePersonId The ID of the responsible person
|
||||
* @param activeOnly Whether to return only active horses
|
||||
* @return List of horses for which the person is responsible
|
||||
*/
|
||||
suspend fun findByResponsiblePersonId(responsiblePersonId: Uuid, activeOnly: Boolean = true): List<DomPferd>
|
||||
|
||||
/**
|
||||
* Finds horses by gender.
|
||||
*
|
||||
* @param geschlecht The gender to filter by
|
||||
* @param activeOnly Whether to return only active horses
|
||||
* @param limit Maximum number of results to return
|
||||
* @return List of horses with the specified gender
|
||||
*/
|
||||
suspend fun findByGeschlecht(
|
||||
geschlecht: PferdeGeschlechtE,
|
||||
activeOnly: Boolean = true,
|
||||
limit: Int = 100
|
||||
): List<DomPferd>
|
||||
|
||||
/**
|
||||
* Finds horses by breed.
|
||||
*
|
||||
* @param rasse The breed to filter by
|
||||
* @param activeOnly Whether to return only active horses
|
||||
* @param limit Maximum number of results to return
|
||||
* @return List of horses of the specified breed
|
||||
*/
|
||||
suspend fun findByRasse(rasse: String, activeOnly: Boolean = true, limit: Int = 100): List<DomPferd>
|
||||
|
||||
/**
|
||||
* Finds horses by birth year.
|
||||
*
|
||||
* @param birthYear The birth year to filter by
|
||||
* @param activeOnly Whether to return only active horses
|
||||
* @return List of horses born in the specified year
|
||||
*/
|
||||
suspend fun findByBirthYear(birthYear: Int, activeOnly: Boolean = true): List<DomPferd>
|
||||
|
||||
/**
|
||||
* Finds horses by birth year range.
|
||||
*
|
||||
* @param fromYear The start year (inclusive)
|
||||
* @param toYear The end year (inclusive)
|
||||
* @param activeOnly Whether to return only active horses
|
||||
* @return List of horses born within the specified year range
|
||||
*/
|
||||
suspend fun findByBirthYearRange(fromYear: Int, toYear: Int, activeOnly: Boolean = true): List<DomPferd>
|
||||
|
||||
/**
|
||||
* Finds all active horses.
|
||||
*
|
||||
* @param limit Maximum number of results to return
|
||||
* @return List of active horses
|
||||
*/
|
||||
suspend fun findAllActive(limit: Int = 1000): List<DomPferd>
|
||||
|
||||
/**
|
||||
* Finds horses with OEPS registration.
|
||||
*
|
||||
* @param activeOnly Whether to return only active horses
|
||||
* @return List of OEPS registered horses
|
||||
*/
|
||||
suspend fun findOepsRegistered(activeOnly: Boolean = true): List<DomPferd>
|
||||
|
||||
/**
|
||||
* Finds horses with FEI registration.
|
||||
*
|
||||
* @param activeOnly Whether to return only active horses
|
||||
* @return List of FEI registered horses
|
||||
*/
|
||||
suspend fun findFeiRegistered(activeOnly: Boolean = true): List<DomPferd>
|
||||
|
||||
/**
|
||||
* Saves a horse (create or update).
|
||||
*
|
||||
* @param horse The horse to save
|
||||
* @return The saved horse with updated timestamps
|
||||
*/
|
||||
suspend fun save(horse: DomPferd): DomPferd
|
||||
|
||||
/**
|
||||
* Deletes a horse by ID.
|
||||
*
|
||||
* @param id The unique identifier of the horse to delete
|
||||
* @return true if the horse was deleted, false if not found
|
||||
*/
|
||||
suspend fun delete(id: Uuid): Boolean
|
||||
|
||||
/**
|
||||
* Checks if a horse with the given life number exists.
|
||||
*
|
||||
* @param lebensnummer The life number to check
|
||||
* @return true if a horse with this life number exists, false otherwise
|
||||
*/
|
||||
suspend fun existsByLebensnummer(lebensnummer: String): Boolean
|
||||
|
||||
/**
|
||||
* Checks if a horse with the given chip number exists.
|
||||
*
|
||||
* @param chipNummer The chip number to check
|
||||
* @return true if a horse with this chip number exists, false otherwise
|
||||
*/
|
||||
suspend fun existsByChipNummer(chipNummer: String): Boolean
|
||||
|
||||
/**
|
||||
* Checks if a horse with the given passport number exists.
|
||||
*
|
||||
* @param passNummer The passport number to check
|
||||
* @return true if a horse with this passport number exists, false otherwise
|
||||
*/
|
||||
suspend fun existsByPassNummer(passNummer: String): Boolean
|
||||
|
||||
/**
|
||||
* Checks if a horse with the given OEPS number exists.
|
||||
*
|
||||
* @param oepsNummer The OEPS number to check
|
||||
* @return true if a horse with this OEPS number exists, false otherwise
|
||||
*/
|
||||
suspend fun existsByOepsNummer(oepsNummer: String): Boolean
|
||||
|
||||
/**
|
||||
* Checks if a horse with the given FEI number exists.
|
||||
*
|
||||
* @param feiNummer The FEI number to check
|
||||
* @return true if a horse with this FEI number exists, false otherwise
|
||||
*/
|
||||
suspend fun existsByFeiNummer(feiNummer: String): Boolean
|
||||
|
||||
/**
|
||||
* Counts the total number of active horses.
|
||||
*
|
||||
* @return The total count of active horses
|
||||
*/
|
||||
suspend fun countActive(): Long
|
||||
|
||||
/**
|
||||
* Counts horses by owner.
|
||||
*
|
||||
* @param ownerId The ID of the owner
|
||||
* @param activeOnly Whether to count only active horses
|
||||
* @return The count of horses owned by the person
|
||||
*/
|
||||
suspend fun countByOwnerId(ownerId: Uuid, activeOnly: Boolean = true): Long
|
||||
|
||||
/**
|
||||
* Counts horses with OEPS registration.
|
||||
*
|
||||
* @param activeOnly Whether to count only active horses
|
||||
* @return The count of OEPS registered horses
|
||||
*/
|
||||
suspend fun countOepsRegistered(activeOnly: Boolean = true): Long
|
||||
|
||||
/**
|
||||
* Counts horses with FEI registration.
|
||||
*
|
||||
* @param activeOnly Whether to count only active horses
|
||||
* @return The count of FEI registered horses
|
||||
*/
|
||||
suspend fun countFeiRegistered(activeOnly: Boolean = true): Long
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class)
|
||||
|
||||
package at.mocode.masterdata.domain.repository
|
||||
|
||||
import at.mocode.core.domain.model.LizenzKlasseE
|
||||
import at.mocode.core.domain.model.SparteE
|
||||
import at.mocode.masterdata.domain.model.DomReiter
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
/**
|
||||
* Repository-Interface für DomReiter (Reiter) Domain-Operationen.
|
||||
*
|
||||
* Definiert den Vertrag für Datenzugriffs-Operationen ohne Abhängigkeit
|
||||
* von konkreten Implementierungsdetails (Datenbank, etc.).
|
||||
*/
|
||||
interface ReiterRepository {
|
||||
|
||||
/**
|
||||
* Sucht einen Reiter anhand seiner eindeutigen ID.
|
||||
*/
|
||||
suspend fun findById(id: Uuid): DomReiter?
|
||||
|
||||
/**
|
||||
* Sucht einen Reiter anhand seiner Satznummer (OEPS-Mitgliedsnummer).
|
||||
*/
|
||||
suspend fun findBySatznummer(satznummer: String): DomReiter?
|
||||
|
||||
/**
|
||||
* Sucht einen Reiter anhand seiner FEI-ID.
|
||||
*/
|
||||
suspend fun findByFeiId(feiId: String): DomReiter?
|
||||
|
||||
/**
|
||||
* Sucht Reiter anhand von Vor- und/oder Nachname (Teilübereinstimmung).
|
||||
*/
|
||||
suspend fun findByName(searchTerm: String, limit: Int = 50): List<DomReiter>
|
||||
|
||||
/**
|
||||
* Sucht alle Reiter eines bestimmten Vereins.
|
||||
*/
|
||||
suspend fun findByVereinsNummer(vereinsNummer: String, activeOnly: Boolean = true): List<DomReiter>
|
||||
|
||||
/**
|
||||
* Sucht alle Reiter mit einer bestimmten Lizenzklasse.
|
||||
*/
|
||||
suspend fun findByLizenzKlasse(lizenzKlasse: LizenzKlasseE, activeOnly: Boolean = true): List<DomReiter>
|
||||
|
||||
/**
|
||||
* Sucht alle Reiter, die für eine bestimmte Sparte lizenziert sind.
|
||||
*/
|
||||
suspend fun findBySparte(sparte: SparteE, activeOnly: Boolean = true): List<DomReiter>
|
||||
|
||||
/**
|
||||
* Sucht alle Gastreiter.
|
||||
*/
|
||||
suspend fun findGastreiter(activeOnly: Boolean = true): List<DomReiter>
|
||||
|
||||
/**
|
||||
* Gibt alle aktiven Reiter zurück (paginiert).
|
||||
*/
|
||||
suspend fun findAllActive(limit: Int = 100, offset: Int = 0): List<DomReiter>
|
||||
|
||||
/**
|
||||
* Gibt alle Reiter zurück (paginiert).
|
||||
*/
|
||||
suspend fun findAll(limit: Int = 100, offset: Int = 0): List<DomReiter>
|
||||
|
||||
/**
|
||||
* Speichert einen Reiter (Insert oder Update).
|
||||
*/
|
||||
suspend fun save(reiter: DomReiter): DomReiter
|
||||
|
||||
/**
|
||||
* Löscht einen Reiter anhand seiner ID.
|
||||
*
|
||||
* @return true wenn gelöscht, false wenn nicht gefunden
|
||||
*/
|
||||
suspend fun delete(id: Uuid): Boolean
|
||||
|
||||
/**
|
||||
* Zählt alle aktiven Reiter.
|
||||
*/
|
||||
suspend fun countActive(): Long
|
||||
|
||||
/**
|
||||
* Prüft ob ein Reiter mit der gegebenen Satznummer bereits existiert.
|
||||
*/
|
||||
suspend fun existsBySatznummer(satznummer: String): Boolean
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class)
|
||||
|
||||
package at.mocode.masterdata.domain.repository
|
||||
|
||||
import at.mocode.masterdata.domain.model.DomVerein
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
/**
|
||||
* Repository-Interface für DomVerein (Verein) Domain-Operationen.
|
||||
*
|
||||
* Definiert den Vertrag für Datenzugriffs-Operationen ohne Abhängigkeit
|
||||
* von konkreten Implementierungsdetails (Datenbank etc.).
|
||||
*/
|
||||
interface VereinRepository {
|
||||
|
||||
/**
|
||||
* Sucht einen Verein anhand seiner eindeutigen ID.
|
||||
*/
|
||||
suspend fun findById(id: Uuid): DomVerein?
|
||||
|
||||
/**
|
||||
* Sucht einen Verein anhand seiner OEPS-Vereinsnummer.
|
||||
*/
|
||||
suspend fun findByVereinsNummer(vereinsNummer: String): DomVerein?
|
||||
|
||||
/**
|
||||
* Sucht Vereine anhand des Namens (Teilübereinstimmung).
|
||||
*/
|
||||
suspend fun findByName(searchTerm: String, limit: Int = 50): List<DomVerein>
|
||||
|
||||
/**
|
||||
* Sucht alle Vereine eines Bundeslandes.
|
||||
*/
|
||||
suspend fun findByBundesland(bundesland: String, activeOnly: Boolean = true): List<DomVerein>
|
||||
|
||||
/**
|
||||
* Sucht alle Vereine, die als Veranstalter markiert sind.
|
||||
*/
|
||||
suspend fun findVeranstalter(activeOnly: Boolean = true): List<DomVerein>
|
||||
|
||||
/**
|
||||
* Gibt alle aktiven Vereine zurück (paginiert).
|
||||
*/
|
||||
suspend fun findAllActive(limit: Int = 100, offset: Int = 0): List<DomVerein>
|
||||
|
||||
/**
|
||||
* Gibt alle Vereine zurück (paginiert).
|
||||
*/
|
||||
suspend fun findAll(limit: Int = 100, offset: Int = 0): List<DomVerein>
|
||||
|
||||
/**
|
||||
* Speichert einen Verein (Insert oder Update).
|
||||
*/
|
||||
suspend fun save(verein: DomVerein): DomVerein
|
||||
|
||||
/**
|
||||
* Löscht einen Verein anhand seiner ID.
|
||||
*
|
||||
* @return true wenn gelöscht, false wenn nicht gefunden
|
||||
*/
|
||||
suspend fun delete(id: Uuid): Boolean
|
||||
|
||||
/**
|
||||
* Zählt alle aktiven Vereine.
|
||||
*/
|
||||
suspend fun countActive(): Long
|
||||
|
||||
/**
|
||||
* Prüft ob ein Verein mit der gegebenen Vereinsnummer bereits existiert.
|
||||
*/
|
||||
suspend fun existsByVereinsNummer(vereinsNummer: String): Boolean
|
||||
}
|
||||
-139
@@ -1,139 +0,0 @@
|
||||
@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class)
|
||||
package at.mocode.masterdata.domain.repository
|
||||
|
||||
import at.mocode.core.domain.model.SparteE
|
||||
import at.mocode.masterdata.domain.model.AltersklasseDefinition
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
/**
|
||||
* Repository interface for AltersklasseDefinition (Age Class) domain operations.
|
||||
*
|
||||
* This interface defines the contract for age class data access operations
|
||||
* without depending on specific implementation details (database, etc.).
|
||||
* Following the hexagonal architecture pattern, this interface belongs
|
||||
* to the domain layer and will be implemented in the infrastructure layer.
|
||||
*/
|
||||
interface AltersklasseRepository {
|
||||
|
||||
/**
|
||||
* Finds an age class by its unique ID.
|
||||
*
|
||||
* @param id The unique identifier of the age class
|
||||
* @return The age class if found, null otherwise
|
||||
*/
|
||||
suspend fun findById(id: Uuid): AltersklasseDefinition?
|
||||
|
||||
/**
|
||||
* Finds an age class by its code.
|
||||
*
|
||||
* @param altersklasseCode The age class code (e.g., "JGD_U16", "JUN_U18")
|
||||
* @return The age class if found, null otherwise
|
||||
*/
|
||||
suspend fun findByCode(altersklasseCode: String): AltersklasseDefinition?
|
||||
|
||||
/**
|
||||
* Finds age classes by name (partial match).
|
||||
*
|
||||
* @param searchTerm The search term to match against age class names
|
||||
* @param limit Maximum number of results to return
|
||||
* @return List of matching age classes
|
||||
*/
|
||||
suspend fun findByName(searchTerm: String, limit: Int = 50): List<AltersklasseDefinition>
|
||||
|
||||
/**
|
||||
* Finds all active age classes.
|
||||
*
|
||||
* @param sparteFilter Optional filter by sport type
|
||||
* @param geschlechtFilter Optional filter by gender ('M', 'W')
|
||||
* @return List of active age classes
|
||||
*/
|
||||
suspend fun findAllActive(sparteFilter: SparteE? = null, geschlechtFilter: Char? = null): List<AltersklasseDefinition>
|
||||
|
||||
/**
|
||||
* Finds age classes applicable for a specific age.
|
||||
*
|
||||
* @param age The age to check
|
||||
* @param sparteFilter Optional filter by sport type
|
||||
* @param geschlechtFilter Optional filter by gender ('M', 'W')
|
||||
* @return List of applicable age classes
|
||||
*/
|
||||
suspend fun findApplicableForAge(age: Int, sparteFilter: SparteE? = null, geschlechtFilter: Char? = null): List<AltersklasseDefinition>
|
||||
|
||||
/**
|
||||
* Finds age classes by sport type.
|
||||
*
|
||||
* @param sparte The sport type
|
||||
* @param activeOnly Whether to return only active age classes
|
||||
* @return List of age classes for the sport type
|
||||
*/
|
||||
suspend fun findBySparte(sparte: SparteE, activeOnly: Boolean = true): List<AltersklasseDefinition>
|
||||
|
||||
/**
|
||||
* Finds age classes by gender filter.
|
||||
*
|
||||
* @param geschlecht The gender ('M', 'W')
|
||||
* @param activeOnly Whether to return only active age classes
|
||||
* @return List of age classes for the gender
|
||||
*/
|
||||
suspend fun findByGeschlecht(geschlecht: Char, activeOnly: Boolean = true): List<AltersklasseDefinition>
|
||||
|
||||
/**
|
||||
* Finds age classes by age range.
|
||||
*
|
||||
* @param minAge Minimum age (inclusive)
|
||||
* @param maxAge Maximum age (inclusive)
|
||||
* @param activeOnly Whether to return only active age classes
|
||||
* @return List of age classes within the age range
|
||||
*/
|
||||
suspend fun findByAgeRange(minAge: Int?, maxAge: Int?, activeOnly: Boolean = true): List<AltersklasseDefinition>
|
||||
|
||||
/**
|
||||
* Finds age classes by OETO rule reference.
|
||||
*
|
||||
* @param oetoRegelReferenzId The OETO rule reference ID
|
||||
* @return List of age classes linked to the rule
|
||||
*/
|
||||
suspend fun findByOetoRegelReferenz(oetoRegelReferenzId: Uuid): List<AltersklasseDefinition>
|
||||
|
||||
/**
|
||||
* Saves an age class (create or update).
|
||||
*
|
||||
* @param altersklasse The age class to save
|
||||
* @return The saved age class with updated timestamps
|
||||
*/
|
||||
suspend fun save(altersklasse: AltersklasseDefinition): AltersklasseDefinition
|
||||
|
||||
/**
|
||||
* Deletes an age class by ID.
|
||||
*
|
||||
* @param id The unique identifier of the age class to delete
|
||||
* @return true if the age class was deleted, false if not found
|
||||
*/
|
||||
suspend fun delete(id: Uuid): Boolean
|
||||
|
||||
/**
|
||||
* Checks if an age class with the given code exists.
|
||||
*
|
||||
* @param altersklasseCode The age class code to check
|
||||
* @return true if an age class with this code exists, false otherwise
|
||||
*/
|
||||
suspend fun existsByCode(altersklasseCode: String): Boolean
|
||||
|
||||
/**
|
||||
* Counts the total number of active age classes.
|
||||
*
|
||||
* @param sparteFilter Optional filter by sport type
|
||||
* @return The total count of active age classes
|
||||
*/
|
||||
suspend fun countActive(sparteFilter: SparteE? = null): Long
|
||||
|
||||
/**
|
||||
* Validates if a person with given age and gender can participate in an age class.
|
||||
*
|
||||
* @param altersklasseId The age class ID
|
||||
* @param age The person's age
|
||||
* @param geschlecht The person's gender ('M', 'W')
|
||||
* @return true if the person can participate, false otherwise
|
||||
*/
|
||||
suspend fun isEligible(altersklasseId: Uuid, age: Int, geschlecht: Char): Boolean
|
||||
}
|
||||
@@ -1,27 +1,26 @@
|
||||
plugins {
|
||||
alias(libs.plugins.kotlin.jvm)
|
||||
alias(libs.plugins.kotlin.spring)
|
||||
|
||||
// KORREKTUR: Dieses Plugin ist entscheidend. Es schaltet den `springBoot`-Block
|
||||
// und alle Spring-Boot-spezifischen Gradle-Tasks frei.
|
||||
alias(libs.plugins.spring.boot)
|
||||
|
||||
// Dependency Management für konsistente Spring-Versionen
|
||||
kotlin("jvm")
|
||||
alias(libs.plugins.spring.boot) apply false
|
||||
alias(libs.plugins.spring.dependencyManagement)
|
||||
alias(libs.plugins.kotlinSpring)
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.platform.platformDependencies)
|
||||
|
||||
implementation(projects.masterdata.masterdataDomain)
|
||||
implementation(projects.masterdata.masterdataApplication)
|
||||
implementation(projects.backend.services.masterdata.masterdataDomain)
|
||||
implementation(projects.core.coreDomain)
|
||||
implementation(projects.core.coreUtils)
|
||||
implementation(projects.infrastructure.cache.cacheApi)
|
||||
implementation(projects.infrastructure.eventStore.eventStoreApi)
|
||||
implementation(projects.infrastructure.messaging.messagingClient)
|
||||
implementation(projects.backend.infrastructure.cache.cacheApi)
|
||||
implementation(projects.backend.infrastructure.eventStore.eventStoreApi)
|
||||
implementation(projects.backend.infrastructure.messaging.messagingClient)
|
||||
|
||||
// Exposed
|
||||
implementation(libs.exposed.core)
|
||||
implementation(libs.exposed.dao)
|
||||
implementation(libs.exposed.jdbc)
|
||||
implementation(libs.exposed.kotlin.datetime)
|
||||
|
||||
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
|
||||
implementation("org.postgresql:postgresql")
|
||||
|
||||
testImplementation(projects.platform.platformTesting)
|
||||
|
||||
+173
-193
@@ -1,240 +1,220 @@
|
||||
@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class)
|
||||
|
||||
package at.mocode.masterdata.infrastructure.persistence
|
||||
|
||||
import at.mocode.core.domain.model.SparteE
|
||||
import at.mocode.core.utils.database.DatabaseFactory
|
||||
import at.mocode.masterdata.domain.model.AltersklasseDefinition
|
||||
import at.mocode.masterdata.domain.repository.AltersklasseRepository
|
||||
import at.mocode.core.utils.database.DatabaseFactory
|
||||
import org.jetbrains.exposed.v1.core.*
|
||||
import org.jetbrains.exposed.v1.jdbc.*
|
||||
import kotlin.uuid.Uuid
|
||||
import kotlinx.datetime.Clock
|
||||
import kotlinx.datetime.TimeZone
|
||||
import kotlinx.datetime.toInstant
|
||||
import kotlinx.datetime.toLocalDateTime
|
||||
import org.jetbrains.exposed.sql.*
|
||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
||||
|
||||
/**
|
||||
* Implementierung des AltersklasseRepository für die Datenbankzugriffe.
|
||||
*
|
||||
* Diese Implementierung verwendet Exposed SQL für den Datenbankzugriff
|
||||
* und mappt zwischen der AltersklasseDefinition Domain-Entität und der AltersklasseTable.
|
||||
*/
|
||||
class AltersklasseRepositoryImpl : AltersklasseRepository {
|
||||
|
||||
/**
|
||||
* Konvertiert eine Datenbankzeile in ein Domain-Objekt.
|
||||
*/
|
||||
private fun rowToAltersklasseDefinition(row: ResultRow): AltersklasseDefinition {
|
||||
return AltersklasseDefinition(
|
||||
altersklasseId = row[AltersklasseTable.id],
|
||||
altersklasseCode = row[AltersklasseTable.altersklasseCode],
|
||||
bezeichnung = row[AltersklasseTable.bezeichnung],
|
||||
minAlter = row[AltersklasseTable.minAlter],
|
||||
maxAlter = row[AltersklasseTable.maxAlter],
|
||||
stichtagRegelText = row[AltersklasseTable.stichtagRegelText],
|
||||
sparteFilter = row[AltersklasseTable.sparteFilter]?.let { SparteE.valueOf(it) },
|
||||
geschlechtFilter = row[AltersklasseTable.geschlechtFilter],
|
||||
oetoRegelReferenzId = row[AltersklasseTable.oetoRegelReferenzId],
|
||||
istAktiv = row[AltersklasseTable.istAktiv],
|
||||
createdAt = row[AltersklasseTable.createdAt].toInstant(TimeZone.UTC),
|
||||
updatedAt = row[AltersklasseTable.updatedAt].toInstant(TimeZone.UTC)
|
||||
)
|
||||
private fun rowToAltersklasseDefinition(row: ResultRow): AltersklasseDefinition {
|
||||
return AltersklasseDefinition(
|
||||
altersklasseId = row[AltersklasseTable.id],
|
||||
altersklasseCode = row[AltersklasseTable.altersklasseCode],
|
||||
bezeichnung = row[AltersklasseTable.bezeichnung],
|
||||
minAlter = row[AltersklasseTable.minAlter],
|
||||
maxAlter = row[AltersklasseTable.maxAlter],
|
||||
stichtagRegelText = row[AltersklasseTable.stichtagRegelText],
|
||||
sparteFilter = row[AltersklasseTable.sparteFilter]?.let { SparteE.valueOf(it) },
|
||||
geschlechtFilter = row[AltersklasseTable.geschlechtFilter],
|
||||
oetoRegelReferenzId = row[AltersklasseTable.oetoRegelReferenzId],
|
||||
istAktiv = row[AltersklasseTable.istAktiv],
|
||||
createdAt = row[AltersklasseTable.createdAt],
|
||||
updatedAt = row[AltersklasseTable.updatedAt]
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun findById(id: Uuid): AltersklasseDefinition? = DatabaseFactory.dbQuery {
|
||||
AltersklasseTable.selectAll().where { AltersklasseTable.id eq id }
|
||||
.map(::rowToAltersklasseDefinition)
|
||||
.singleOrNull()
|
||||
}
|
||||
|
||||
override suspend fun findByCode(altersklasseCode: String): AltersklasseDefinition? = DatabaseFactory.dbQuery {
|
||||
AltersklasseTable.selectAll().where { AltersklasseTable.altersklasseCode eq altersklasseCode }
|
||||
.map(::rowToAltersklasseDefinition)
|
||||
.singleOrNull()
|
||||
}
|
||||
|
||||
override suspend fun findByName(searchTerm: String, limit: Int): List<AltersklasseDefinition> =
|
||||
DatabaseFactory.dbQuery {
|
||||
val pattern = "%$searchTerm%"
|
||||
AltersklasseTable.selectAll().where { AltersklasseTable.bezeichnung like pattern }
|
||||
.limit(limit)
|
||||
.map(::rowToAltersklasseDefinition)
|
||||
}
|
||||
|
||||
override suspend fun findById(id: Uuid): AltersklasseDefinition? = DatabaseFactory.dbQuery {
|
||||
AltersklasseTable.selectAll().where { AltersklasseTable.id eq id }
|
||||
.map(::rowToAltersklasseDefinition)
|
||||
.singleOrNull()
|
||||
}
|
||||
override suspend fun findAllActive(sparteFilter: SparteE?, geschlechtFilter: Char?): List<AltersklasseDefinition> =
|
||||
DatabaseFactory.dbQuery {
|
||||
val query = AltersklasseTable.selectAll().where { AltersklasseTable.istAktiv eq true }
|
||||
|
||||
override suspend fun findByCode(altersklasseCode: String): AltersklasseDefinition? = DatabaseFactory.dbQuery {
|
||||
AltersklasseTable.selectAll().where { AltersklasseTable.altersklasseCode eq altersklasseCode }
|
||||
.map(::rowToAltersklasseDefinition)
|
||||
.singleOrNull()
|
||||
}
|
||||
|
||||
override suspend fun findByName(searchTerm: String, limit: Int): List<AltersklasseDefinition> = DatabaseFactory.dbQuery {
|
||||
val pattern = "%$searchTerm%"
|
||||
AltersklasseTable.selectAll().where { AltersklasseTable.bezeichnung like pattern }
|
||||
.limit(limit)
|
||||
.map(::rowToAltersklasseDefinition)
|
||||
}
|
||||
|
||||
override suspend fun findAllActive(sparteFilter: SparteE?, geschlechtFilter: Char?): List<AltersklasseDefinition> = DatabaseFactory.dbQuery {
|
||||
val query = AltersklasseTable.selectAll().where { AltersklasseTable.istAktiv eq true }
|
||||
|
||||
sparteFilter?.let { sparte ->
|
||||
query.andWhere {
|
||||
(AltersklasseTable.sparteFilter eq sparte.name) or (AltersklasseTable.sparteFilter.isNull())
|
||||
}
|
||||
}
|
||||
|
||||
geschlechtFilter?.let { geschlecht ->
|
||||
query.andWhere {
|
||||
(AltersklasseTable.geschlechtFilter eq geschlecht) or (AltersklasseTable.geschlechtFilter.isNull())
|
||||
}
|
||||
}
|
||||
|
||||
query.orderBy(AltersklasseTable.bezeichnung to SortOrder.ASC)
|
||||
.map(::rowToAltersklasseDefinition)
|
||||
}
|
||||
|
||||
override suspend fun findApplicableForAge(age: Int, sparteFilter: SparteE?, geschlechtFilter: Char?): List<AltersklasseDefinition> = DatabaseFactory.dbQuery {
|
||||
val query = AltersklasseTable.selectAll().where { AltersklasseTable.istAktiv eq true }
|
||||
|
||||
// Age range filter
|
||||
sparteFilter?.let { sparte ->
|
||||
query.andWhere {
|
||||
(AltersklasseTable.minAlter.isNull() or (AltersklasseTable.minAlter lessEq age)) and
|
||||
(AltersklasseTable.maxAlter.isNull() or (AltersklasseTable.maxAlter greaterEq age))
|
||||
(AltersklasseTable.sparteFilter eq sparte.name) or (AltersklasseTable.sparteFilter.isNull())
|
||||
}
|
||||
}
|
||||
|
||||
sparteFilter?.let { sparte ->
|
||||
query.andWhere {
|
||||
(AltersklasseTable.sparteFilter eq sparte.name) or (AltersklasseTable.sparteFilter.isNull())
|
||||
}
|
||||
geschlechtFilter?.let { geschlecht ->
|
||||
query.andWhere {
|
||||
(AltersklasseTable.geschlechtFilter eq geschlecht) or (AltersklasseTable.geschlechtFilter.isNull())
|
||||
}
|
||||
}
|
||||
|
||||
geschlechtFilter?.let { geschlecht ->
|
||||
query.andWhere {
|
||||
(AltersklasseTable.geschlechtFilter eq geschlecht) or (AltersklasseTable.geschlechtFilter.isNull())
|
||||
}
|
||||
}
|
||||
|
||||
query.orderBy(AltersklasseTable.bezeichnung to SortOrder.ASC)
|
||||
.map(::rowToAltersklasseDefinition)
|
||||
query.orderBy(AltersklasseTable.bezeichnung to SortOrder.ASC)
|
||||
.map(::rowToAltersklasseDefinition)
|
||||
}
|
||||
|
||||
override suspend fun findBySparte(sparte: SparteE, activeOnly: Boolean): List<AltersklasseDefinition> = DatabaseFactory.dbQuery {
|
||||
val query = AltersklasseTable.selectAll().where {
|
||||
(AltersklasseTable.sparteFilter eq sparte.name) or (AltersklasseTable.sparteFilter.isNull())
|
||||
}
|
||||
override suspend fun findApplicableForAge(
|
||||
age: Int,
|
||||
sparteFilter: SparteE?,
|
||||
geschlechtFilter: Char?
|
||||
): List<AltersklasseDefinition> = DatabaseFactory.dbQuery {
|
||||
val query = AltersklasseTable.selectAll().where { AltersklasseTable.istAktiv eq true }
|
||||
|
||||
if (activeOnly) {
|
||||
query.andWhere { AltersklasseTable.istAktiv eq true }
|
||||
}
|
||||
|
||||
query.orderBy(AltersklasseTable.bezeichnung to SortOrder.ASC)
|
||||
.map(::rowToAltersklasseDefinition)
|
||||
query.andWhere {
|
||||
(AltersklasseTable.minAlter.isNull() or (AltersklasseTable.minAlter lessEq age)) and
|
||||
(AltersklasseTable.maxAlter.isNull() or (AltersklasseTable.maxAlter greaterEq age))
|
||||
}
|
||||
|
||||
override suspend fun findByGeschlecht(geschlecht: Char, activeOnly: Boolean): List<AltersklasseDefinition> = DatabaseFactory.dbQuery {
|
||||
val query = AltersklasseTable.selectAll().where {
|
||||
(AltersklasseTable.geschlechtFilter eq geschlecht) or (AltersklasseTable.geschlechtFilter.isNull())
|
||||
}
|
||||
|
||||
if (activeOnly) {
|
||||
query.andWhere { AltersklasseTable.istAktiv eq true }
|
||||
}
|
||||
|
||||
query.orderBy(AltersklasseTable.bezeichnung to SortOrder.ASC)
|
||||
.map(::rowToAltersklasseDefinition)
|
||||
sparteFilter?.let { sparte ->
|
||||
query.andWhere {
|
||||
(AltersklasseTable.sparteFilter eq sparte.name) or (AltersklasseTable.sparteFilter.isNull())
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun findByAgeRange(minAge: Int?, maxAge: Int?, activeOnly: Boolean): List<AltersklasseDefinition> = DatabaseFactory.dbQuery {
|
||||
val query = AltersklasseTable.selectAll()
|
||||
|
||||
minAge?.let { min ->
|
||||
query.andWhere {
|
||||
(AltersklasseTable.maxAlter.isNull()) or (AltersklasseTable.maxAlter greaterEq min)
|
||||
}
|
||||
}
|
||||
|
||||
maxAge?.let { max ->
|
||||
query.andWhere {
|
||||
(AltersklasseTable.minAlter.isNull()) or (AltersklasseTable.minAlter lessEq max)
|
||||
}
|
||||
}
|
||||
|
||||
if (activeOnly) {
|
||||
query.andWhere { AltersklasseTable.istAktiv eq true }
|
||||
}
|
||||
|
||||
query.orderBy(AltersklasseTable.bezeichnung to SortOrder.ASC)
|
||||
.map(::rowToAltersklasseDefinition)
|
||||
geschlechtFilter?.let { geschlecht ->
|
||||
query.andWhere {
|
||||
(AltersklasseTable.geschlechtFilter eq geschlecht) or (AltersklasseTable.geschlechtFilter.isNull())
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun findByOetoRegelReferenz(oetoRegelReferenzId: Uuid): List<AltersklasseDefinition> = DatabaseFactory.dbQuery {
|
||||
AltersklasseTable.selectAll().where { AltersklasseTable.oetoRegelReferenzId eq oetoRegelReferenzId }
|
||||
.orderBy(AltersklasseTable.bezeichnung to SortOrder.ASC)
|
||||
.map(::rowToAltersklasseDefinition)
|
||||
query.orderBy(AltersklasseTable.bezeichnung to SortOrder.ASC)
|
||||
.map(::rowToAltersklasseDefinition)
|
||||
}
|
||||
|
||||
override suspend fun findBySparte(sparte: SparteE, activeOnly: Boolean): List<AltersklasseDefinition> =
|
||||
DatabaseFactory.dbQuery {
|
||||
val query = AltersklasseTable.selectAll().where {
|
||||
(AltersklasseTable.sparteFilter eq sparte.name) or (AltersklasseTable.sparteFilter.isNull())
|
||||
}
|
||||
|
||||
if (activeOnly) {
|
||||
query.andWhere { AltersklasseTable.istAktiv eq true }
|
||||
}
|
||||
|
||||
query.orderBy(AltersklasseTable.bezeichnung to SortOrder.ASC)
|
||||
.map(::rowToAltersklasseDefinition)
|
||||
}
|
||||
|
||||
override suspend fun save(altersklasse: AltersklasseDefinition): AltersklasseDefinition = DatabaseFactory.dbQuery {
|
||||
val now = Clock.System.now()
|
||||
val existingAltersklasse = AltersklasseTable.selectAll().where { AltersklasseTable.id eq altersklasse.altersklasseId }.singleOrNull()
|
||||
override suspend fun findByGeschlecht(geschlecht: Char, activeOnly: Boolean): List<AltersklasseDefinition> =
|
||||
DatabaseFactory.dbQuery {
|
||||
val query = AltersklasseTable.selectAll().where {
|
||||
(AltersklasseTable.geschlechtFilter eq geschlecht) or (AltersklasseTable.geschlechtFilter.isNull())
|
||||
}
|
||||
|
||||
if (existingAltersklasse == null) {
|
||||
// Insert a new age class
|
||||
AltersklasseTable.insert { stmt ->
|
||||
stmt[id] = altersklasse.altersklasseId
|
||||
stmt[altersklasseCode] = altersklasse.altersklasseCode
|
||||
stmt[bezeichnung] = altersklasse.bezeichnung
|
||||
stmt[minAlter] = altersklasse.minAlter
|
||||
stmt[maxAlter] = altersklasse.maxAlter
|
||||
stmt[stichtagRegelText] = altersklasse.stichtagRegelText
|
||||
stmt[sparteFilter] = altersklasse.sparteFilter?.name
|
||||
stmt[geschlechtFilter] = altersklasse.geschlechtFilter
|
||||
stmt[oetoRegelReferenzId] = altersklasse.oetoRegelReferenzId
|
||||
stmt[istAktiv] = altersklasse.istAktiv
|
||||
stmt[createdAt] = altersklasse.createdAt.toLocalDateTime(TimeZone.UTC)
|
||||
stmt[updatedAt] = now.toLocalDateTime(TimeZone.UTC)
|
||||
}
|
||||
} else {
|
||||
// Update existing age class
|
||||
AltersklasseTable.update({ AltersklasseTable.id eq altersklasse.altersklasseId }) { stmt ->
|
||||
stmt[altersklasseCode] = altersklasse.altersklasseCode
|
||||
stmt[bezeichnung] = altersklasse.bezeichnung
|
||||
stmt[minAlter] = altersklasse.minAlter
|
||||
stmt[maxAlter] = altersklasse.maxAlter
|
||||
stmt[stichtagRegelText] = altersklasse.stichtagRegelText
|
||||
stmt[sparteFilter] = altersklasse.sparteFilter?.name
|
||||
stmt[geschlechtFilter] = altersklasse.geschlechtFilter
|
||||
stmt[oetoRegelReferenzId] = altersklasse.oetoRegelReferenzId
|
||||
stmt[istAktiv] = altersklasse.istAktiv
|
||||
stmt[updatedAt] = now.toLocalDateTime(TimeZone.UTC)
|
||||
}
|
||||
}
|
||||
if (activeOnly) {
|
||||
query.andWhere { AltersklasseTable.istAktiv eq true }
|
||||
}
|
||||
|
||||
altersklasse.copy(updatedAt = now)
|
||||
query.orderBy(AltersklasseTable.bezeichnung to SortOrder.ASC)
|
||||
.map(::rowToAltersklasseDefinition)
|
||||
}
|
||||
|
||||
override suspend fun delete(id: Uuid): Boolean = DatabaseFactory.dbQuery {
|
||||
AltersklasseTable.deleteWhere { AltersklasseTable.id eq id } > 0
|
||||
override suspend fun findByAgeRange(minAge: Int?, maxAge: Int?, activeOnly: Boolean): List<AltersklasseDefinition> =
|
||||
DatabaseFactory.dbQuery {
|
||||
val query = AltersklasseTable.selectAll()
|
||||
|
||||
if (minAge != null) {
|
||||
query.andWhere { AltersklasseTable.minAlter greaterEq minAge }
|
||||
}
|
||||
if (maxAge != null) {
|
||||
query.andWhere { AltersklasseTable.maxAlter lessEq maxAge }
|
||||
}
|
||||
if (activeOnly) {
|
||||
query.andWhere { AltersklasseTable.istAktiv eq true }
|
||||
}
|
||||
|
||||
query.orderBy(AltersklasseTable.bezeichnung to SortOrder.ASC)
|
||||
.map(::rowToAltersklasseDefinition)
|
||||
}
|
||||
|
||||
override suspend fun existsByCode(altersklasseCode: String): Boolean = DatabaseFactory.dbQuery {
|
||||
AltersklasseTable.selectAll().where { AltersklasseTable.altersklasseCode eq altersklasseCode }
|
||||
.count() > 0
|
||||
override suspend fun findByOetoRegelReferenz(oetoRegelReferenzId: Uuid): List<AltersklasseDefinition> =
|
||||
DatabaseFactory.dbQuery {
|
||||
AltersklasseTable.selectAll().where { AltersklasseTable.oetoRegelReferenzId eq oetoRegelReferenzId }
|
||||
.map(::rowToAltersklasseDefinition)
|
||||
}
|
||||
|
||||
override suspend fun countActive(sparteFilter: SparteE?): Long = DatabaseFactory.dbQuery {
|
||||
val query = AltersklasseTable.selectAll().where { AltersklasseTable.istAktiv eq true }
|
||||
override suspend fun save(altersklasse: AltersklasseDefinition): AltersklasseDefinition = DatabaseFactory.dbQuery {
|
||||
val exists = AltersklasseTable.selectAll().where { AltersklasseTable.id eq altersklasse.altersklasseId }.any()
|
||||
|
||||
sparteFilter?.let { sparte ->
|
||||
query.andWhere {
|
||||
(AltersklasseTable.sparteFilter eq sparte.name) or (AltersklasseTable.sparteFilter.isNull())
|
||||
}
|
||||
}
|
||||
|
||||
query.count()
|
||||
if (exists) {
|
||||
AltersklasseTable.update({ AltersklasseTable.id eq altersklasse.altersklasseId }) {
|
||||
it[altersklasseCode] = altersklasse.altersklasseCode
|
||||
it[bezeichnung] = altersklasse.bezeichnung
|
||||
it[minAlter] = altersklasse.minAlter
|
||||
it[maxAlter] = altersklasse.maxAlter
|
||||
it[stichtagRegelText] = altersklasse.stichtagRegelText
|
||||
it[sparteFilter] = altersklasse.sparteFilter?.name
|
||||
it[geschlechtFilter] = altersklasse.geschlechtFilter
|
||||
it[oetoRegelReferenzId] = altersklasse.oetoRegelReferenzId
|
||||
it[istAktiv] = altersklasse.istAktiv
|
||||
it[updatedAt] = altersklasse.updatedAt
|
||||
}
|
||||
altersklasse
|
||||
} else {
|
||||
AltersklasseTable.insert {
|
||||
it[id] = altersklasse.altersklasseId
|
||||
it[altersklasseCode] = altersklasse.altersklasseCode
|
||||
it[bezeichnung] = altersklasse.bezeichnung
|
||||
it[minAlter] = altersklasse.minAlter
|
||||
it[maxAlter] = altersklasse.maxAlter
|
||||
it[stichtagRegelText] = altersklasse.stichtagRegelText
|
||||
it[sparteFilter] = altersklasse.sparteFilter?.name
|
||||
it[geschlechtFilter] = altersklasse.geschlechtFilter
|
||||
it[oetoRegelReferenzId] = altersklasse.oetoRegelReferenzId
|
||||
it[istAktiv] = altersklasse.istAktiv
|
||||
it[createdAt] = altersklasse.createdAt
|
||||
it[updatedAt] = altersklasse.updatedAt
|
||||
}
|
||||
altersklasse
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun isEligible(altersklasseId: Uuid, age: Int, geschlecht: Char): Boolean = DatabaseFactory.dbQuery {
|
||||
val altersklasse = AltersklasseTable.selectAll().where {
|
||||
(AltersklasseTable.id eq altersklasseId) and (AltersklasseTable.istAktiv eq true)
|
||||
}.singleOrNull()
|
||||
override suspend fun delete(id: Uuid): Boolean = DatabaseFactory.dbQuery {
|
||||
AltersklasseTable.deleteWhere { AltersklasseTable.id eq id } > 0
|
||||
}
|
||||
|
||||
if (altersklasse == null) return@dbQuery false
|
||||
override suspend fun existsByCode(altersklasseCode: String): Boolean = DatabaseFactory.dbQuery {
|
||||
AltersklasseTable.selectAll().where { AltersklasseTable.altersklasseCode eq altersklasseCode }.any()
|
||||
}
|
||||
|
||||
// Check age eligibility
|
||||
val minAlter = altersklasse[AltersklasseTable.minAlter]
|
||||
val maxAlter = altersklasse[AltersklasseTable.maxAlter]
|
||||
val ageEligible = (minAlter == null || age >= minAlter) && (maxAlter == null || age <= maxAlter)
|
||||
|
||||
// Check gender eligibility
|
||||
val geschlechtFilter = altersklasse[AltersklasseTable.geschlechtFilter]
|
||||
val genderEligible = geschlechtFilter == null || geschlechtFilter == geschlecht
|
||||
|
||||
ageEligible && genderEligible
|
||||
override suspend fun countActive(sparteFilter: SparteE?): Long = DatabaseFactory.dbQuery {
|
||||
val query = AltersklasseTable.selectAll().where { AltersklasseTable.istAktiv eq true }
|
||||
sparteFilter?.let { sparte ->
|
||||
query.andWhere { (AltersklasseTable.sparteFilter eq sparte.name) or (AltersklasseTable.sparteFilter.isNull()) }
|
||||
}
|
||||
query.count()
|
||||
}
|
||||
|
||||
override suspend fun isEligible(altersklasseId: Uuid, age: Int, geschlecht: Char): Boolean = DatabaseFactory.dbQuery {
|
||||
AltersklasseTable.selectAll().where { AltersklasseTable.id eq altersklasseId }
|
||||
.map {
|
||||
val min = it[AltersklasseTable.minAlter]
|
||||
val max = it[AltersklasseTable.maxAlter]
|
||||
val g = it[AltersklasseTable.geschlechtFilter]
|
||||
|
||||
val ageOk = (min == null || age >= min) && (max == null || age <= max)
|
||||
val geschlechtOk = (g == null || g == geschlecht)
|
||||
|
||||
ageOk && geschlechtOk
|
||||
}.singleOrNull() ?: false
|
||||
}
|
||||
}
|
||||
|
||||
+25
-24
@@ -1,37 +1,38 @@
|
||||
@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class)
|
||||
|
||||
package at.mocode.masterdata.infrastructure.persistence
|
||||
|
||||
import org.jetbrains.exposed.v1.core.Table
|
||||
import org.jetbrains.exposed.v1.core.kotlin.datetime.datetime
|
||||
import org.jetbrains.exposed.v1.core.kotlin.datetime.CurrentDateTime
|
||||
import org.jetbrains.exposed.v1.core.javaUUID
|
||||
import org.jetbrains.exposed.v1.datetime.CurrentTimestamp
|
||||
import org.jetbrains.exposed.v1.datetime.timestamp
|
||||
|
||||
/**
|
||||
* Exposed-Tabellendefinition für die Altersklasse-Entität (Altersklassendefinitionen).
|
||||
* Exposed-Tabellendefinition für die Altersklasse-Entität (Altersklassendefinition).
|
||||
*
|
||||
* Diese Tabelle speichert alle Informationen zu Altersklassen für Teilnehmer
|
||||
* entsprechend der AltersklasseDefinition Domain-Entität.
|
||||
*/
|
||||
object AltersklasseTable : Table("altersklasse") {
|
||||
val id = javaUUID("id").autoGenerate()
|
||||
val altersklasseCode = varchar("altersklasse_code", 50).uniqueIndex()
|
||||
val bezeichnung = varchar("bezeichnung", 200)
|
||||
val minAlter = integer("min_alter").nullable()
|
||||
val maxAlter = integer("max_alter").nullable()
|
||||
val stichtagRegelText = varchar("stichtag_regel_text", 500).nullable()
|
||||
val sparteFilter = varchar("sparte_filter", 50).nullable() // Enum as string
|
||||
val geschlechtFilter = char("geschlecht_filter").nullable()
|
||||
val oetoRegelReferenzId = javaUUID("oeto_regel_referenz_id").nullable()
|
||||
val istAktiv = bool("ist_aktiv").default(true)
|
||||
val createdAt = datetime("created_at").defaultExpression(CurrentDateTime)
|
||||
val updatedAt = datetime("updated_at").defaultExpression(CurrentDateTime)
|
||||
val id = uuid("id")
|
||||
val altersklasseCode = varchar("altersklasse_code", 50).uniqueIndex()
|
||||
val bezeichnung = varchar("bezeichnung", 200)
|
||||
val minAlter = integer("min_alter").nullable()
|
||||
val maxAlter = integer("max_alter").nullable()
|
||||
val stichtagRegelText = varchar("stichtag_regel_text", 500).nullable()
|
||||
val sparteFilter = varchar("sparte_filter", 50).nullable() // Enum as string
|
||||
val geschlechtFilter = char("geschlecht_filter").nullable()
|
||||
val oetoRegelReferenzId = uuid("oeto_regel_referenz_id").nullable()
|
||||
val istAktiv = bool("ist_aktiv").default(true)
|
||||
val createdAt = timestamp("created_at").defaultExpression(CurrentTimestamp)
|
||||
val updatedAt = timestamp("updated_at").defaultExpression(CurrentTimestamp)
|
||||
|
||||
override val primaryKey = PrimaryKey(id)
|
||||
override val primaryKey = PrimaryKey(id)
|
||||
|
||||
init {
|
||||
// Index for performance on common queries
|
||||
index(customIndexName = "idx_altersklasse_aktiv", columns = arrayOf(istAktiv))
|
||||
index(customIndexName = "idx_altersklasse_sparte", columns = arrayOf(sparteFilter))
|
||||
index(customIndexName = "idx_altersklasse_geschlecht", columns = arrayOf(geschlechtFilter))
|
||||
index(customIndexName = "idx_altersklasse_alter", columns = arrayOf(minAlter, maxAlter))
|
||||
}
|
||||
init {
|
||||
// Index for performance on common queries
|
||||
index(customIndexName = "idx_altersklasse_aktiv", columns = arrayOf(istAktiv))
|
||||
index(customIndexName = "idx_altersklasse_sparte", columns = arrayOf(sparteFilter))
|
||||
index(customIndexName = "idx_altersklasse_geschlecht", columns = arrayOf(geschlechtFilter))
|
||||
index(customIndexName = "idx_altersklasse_alter", columns = arrayOf(minAlter, maxAlter))
|
||||
}
|
||||
}
|
||||
|
||||
+114
-136
@@ -1,158 +1,136 @@
|
||||
@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class)
|
||||
|
||||
package at.mocode.masterdata.infrastructure.persistence
|
||||
|
||||
import at.mocode.core.utils.database.DatabaseFactory
|
||||
import at.mocode.masterdata.domain.model.BundeslandDefinition
|
||||
import at.mocode.masterdata.domain.repository.BundeslandRepository
|
||||
import at.mocode.core.utils.database.DatabaseFactory
|
||||
import org.jetbrains.exposed.v1.core.*
|
||||
import org.jetbrains.exposed.v1.jdbc.*
|
||||
import kotlin.uuid.Uuid
|
||||
import kotlinx.datetime.Clock
|
||||
import kotlinx.datetime.TimeZone
|
||||
import kotlinx.datetime.toInstant
|
||||
import kotlinx.datetime.toLocalDateTime
|
||||
import org.jetbrains.exposed.sql.*
|
||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
||||
|
||||
/**
|
||||
* Implementierung des BundeslandRepository für die Datenbankzugriffe.
|
||||
*
|
||||
* Diese Implementierung verwendet Exposed SQL für den Datenbankzugriff
|
||||
* und mappt zwischen der BundeslandDefinition Domain-Entität und der BundeslandTable.
|
||||
*/
|
||||
class BundeslandRepositoryImpl : BundeslandRepository {
|
||||
|
||||
/**
|
||||
* Konvertiert eine Datenbankzeile in ein Domain-Objekt.
|
||||
*/
|
||||
private fun rowToBundeslandDefinition(row: ResultRow): BundeslandDefinition {
|
||||
return BundeslandDefinition(
|
||||
bundeslandId = row[BundeslandTable.id],
|
||||
landId = row[BundeslandTable.landId],
|
||||
oepsCode = row[BundeslandTable.oepsCode],
|
||||
iso3166_2_Code = row[BundeslandTable.iso3166_2_Code],
|
||||
name = row[BundeslandTable.name],
|
||||
kuerzel = row[BundeslandTable.kuerzel],
|
||||
wappenUrl = row[BundeslandTable.wappenUrl],
|
||||
istAktiv = row[BundeslandTable.istAktiv],
|
||||
sortierReihenfolge = row[BundeslandTable.sortierReihenfolge],
|
||||
createdAt = row[BundeslandTable.createdAt].toInstant(TimeZone.UTC),
|
||||
updatedAt = row[BundeslandTable.updatedAt].toInstant(TimeZone.UTC)
|
||||
)
|
||||
private fun rowToBundeslandDefinition(row: ResultRow): BundeslandDefinition {
|
||||
return BundeslandDefinition(
|
||||
bundeslandId = row[BundeslandTable.id],
|
||||
landId = row[BundeslandTable.landId],
|
||||
oepsCode = row[BundeslandTable.oepsCode],
|
||||
iso3166_2_Code = row[BundeslandTable.iso3166_2_Code],
|
||||
name = row[BundeslandTable.name],
|
||||
kuerzel = row[BundeslandTable.kuerzel],
|
||||
wappenUrl = row[BundeslandTable.wappenUrl],
|
||||
istAktiv = row[BundeslandTable.istAktiv],
|
||||
sortierReihenfolge = row[BundeslandTable.sortierReihenfolge],
|
||||
createdAt = row[BundeslandTable.createdAt],
|
||||
updatedAt = row[BundeslandTable.updatedAt]
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun findById(id: Uuid): BundeslandDefinition? = DatabaseFactory.dbQuery {
|
||||
BundeslandTable.selectAll().where { BundeslandTable.id eq id }
|
||||
.map(::rowToBundeslandDefinition)
|
||||
.singleOrNull()
|
||||
}
|
||||
|
||||
override suspend fun findByOepsCode(oepsCode: String, landId: Uuid): BundeslandDefinition? = DatabaseFactory.dbQuery {
|
||||
BundeslandTable.selectAll().where { (BundeslandTable.oepsCode eq oepsCode) and (BundeslandTable.landId eq landId) }
|
||||
.map(::rowToBundeslandDefinition)
|
||||
.singleOrNull()
|
||||
}
|
||||
|
||||
override suspend fun findByIso3166_2_Code(iso3166_2_Code: String): BundeslandDefinition? = DatabaseFactory.dbQuery {
|
||||
BundeslandTable.selectAll().where { BundeslandTable.iso3166_2_Code eq iso3166_2_Code }
|
||||
.map(::rowToBundeslandDefinition)
|
||||
.singleOrNull()
|
||||
}
|
||||
|
||||
override suspend fun findByCountry(
|
||||
landId: Uuid,
|
||||
activeOnly: Boolean,
|
||||
orderBySortierung: Boolean
|
||||
): List<BundeslandDefinition> = DatabaseFactory.dbQuery {
|
||||
val query = BundeslandTable.selectAll().where { BundeslandTable.landId eq landId }
|
||||
if (activeOnly) {
|
||||
query.andWhere { BundeslandTable.istAktiv eq true }
|
||||
}
|
||||
if (orderBySortierung) {
|
||||
query.orderBy(BundeslandTable.sortierReihenfolge to SortOrder.ASC, BundeslandTable.name to SortOrder.ASC)
|
||||
} else {
|
||||
query.orderBy(BundeslandTable.name to SortOrder.ASC)
|
||||
}
|
||||
query.map(::rowToBundeslandDefinition)
|
||||
}
|
||||
|
||||
override suspend fun findByName(searchTerm: String, landId: Uuid?, limit: Int): List<BundeslandDefinition> =
|
||||
DatabaseFactory.dbQuery {
|
||||
val pattern = "%$searchTerm%"
|
||||
val query = BundeslandTable.selectAll().where { BundeslandTable.name like pattern }
|
||||
landId?.let { query.andWhere { BundeslandTable.landId eq it } }
|
||||
query.limit(limit).map(::rowToBundeslandDefinition)
|
||||
}
|
||||
|
||||
override suspend fun findById(id: Uuid): BundeslandDefinition? = DatabaseFactory.dbQuery {
|
||||
BundeslandTable.selectAll().where { BundeslandTable.id eq id }
|
||||
.map(::rowToBundeslandDefinition)
|
||||
.singleOrNull()
|
||||
override suspend fun findAllActive(orderBySortierung: Boolean): List<BundeslandDefinition> = DatabaseFactory.dbQuery {
|
||||
val query = BundeslandTable.selectAll().where { BundeslandTable.istAktiv eq true }
|
||||
if (orderBySortierung) {
|
||||
query.orderBy(BundeslandTable.sortierReihenfolge to SortOrder.ASC, BundeslandTable.name to SortOrder.ASC)
|
||||
} else {
|
||||
query.orderBy(BundeslandTable.name to SortOrder.ASC)
|
||||
}
|
||||
query.map(::rowToBundeslandDefinition)
|
||||
}
|
||||
|
||||
override suspend fun findByOepsCode(oepsCode: String, landId: Uuid): BundeslandDefinition? = DatabaseFactory.dbQuery {
|
||||
BundeslandTable.selectAll().where {
|
||||
(BundeslandTable.oepsCode eq oepsCode) and (BundeslandTable.landId eq landId)
|
||||
}
|
||||
.map(::rowToBundeslandDefinition)
|
||||
.singleOrNull()
|
||||
override suspend fun save(bundesland: BundeslandDefinition): BundeslandDefinition = DatabaseFactory.dbQuery {
|
||||
val exists = BundeslandTable.selectAll().where { BundeslandTable.id eq bundesland.bundeslandId }.any()
|
||||
if (exists) {
|
||||
BundeslandTable.update({ BundeslandTable.id eq bundesland.bundeslandId }) {
|
||||
it[landId] = bundesland.landId
|
||||
it[oepsCode] = bundesland.oepsCode
|
||||
it[iso3166_2_Code] = bundesland.iso3166_2_Code
|
||||
it[name] = bundesland.name
|
||||
it[kuerzel] = bundesland.kuerzel
|
||||
it[wappenUrl] = bundesland.wappenUrl
|
||||
it[istAktiv] = bundesland.istAktiv
|
||||
it[sortierReihenfolge] = bundesland.sortierReihenfolge
|
||||
it[updatedAt] = bundesland.updatedAt
|
||||
}
|
||||
bundesland
|
||||
} else {
|
||||
BundeslandTable.insert {
|
||||
it[id] = bundesland.bundeslandId
|
||||
it[landId] = bundesland.landId
|
||||
it[oepsCode] = bundesland.oepsCode
|
||||
it[iso3166_2_Code] = bundesland.iso3166_2_Code
|
||||
it[name] = bundesland.name
|
||||
it[kuerzel] = bundesland.kuerzel
|
||||
it[wappenUrl] = bundesland.wappenUrl
|
||||
it[istAktiv] = bundesland.istAktiv
|
||||
it[sortierReihenfolge] = bundesland.sortierReihenfolge
|
||||
it[createdAt] = bundesland.createdAt
|
||||
it[updatedAt] = bundesland.updatedAt
|
||||
}
|
||||
bundesland
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun findByIso3166_2_Code(iso3166_2_Code: String): BundeslandDefinition? = DatabaseFactory.dbQuery {
|
||||
BundeslandTable.selectAll().where { BundeslandTable.iso3166_2_Code eq iso3166_2_Code }
|
||||
.map(::rowToBundeslandDefinition)
|
||||
.singleOrNull()
|
||||
}
|
||||
override suspend fun delete(id: Uuid): Boolean = DatabaseFactory.dbQuery {
|
||||
BundeslandTable.deleteWhere { BundeslandTable.id eq id } > 0
|
||||
}
|
||||
|
||||
override suspend fun findByCountry(landId: Uuid, activeOnly: Boolean, orderBySortierung: Boolean): List<BundeslandDefinition> = DatabaseFactory.dbQuery {
|
||||
val query = BundeslandTable.selectAll().where { BundeslandTable.landId eq landId }
|
||||
override suspend fun existsByOepsCode(oepsCode: String, landId: Uuid): Boolean = DatabaseFactory.dbQuery {
|
||||
BundeslandTable.selectAll().where { (BundeslandTable.oepsCode eq oepsCode) and (BundeslandTable.landId eq landId) }
|
||||
.any()
|
||||
}
|
||||
|
||||
if (activeOnly) {
|
||||
query.andWhere { BundeslandTable.istAktiv eq true }
|
||||
}
|
||||
override suspend fun existsByIso3166_2_Code(iso3166_2_Code: String): Boolean = DatabaseFactory.dbQuery {
|
||||
BundeslandTable.selectAll().where { BundeslandTable.iso3166_2_Code eq iso3166_2_Code }.any()
|
||||
}
|
||||
|
||||
if (orderBySortierung) {
|
||||
query.orderBy(BundeslandTable.sortierReihenfolge to SortOrder.ASC, BundeslandTable.name to SortOrder.ASC)
|
||||
} else {
|
||||
query.orderBy(BundeslandTable.name to SortOrder.ASC)
|
||||
}
|
||||
|
||||
query.map(::rowToBundeslandDefinition)
|
||||
}
|
||||
|
||||
override suspend fun findByName(searchTerm: String, landId: Uuid?, limit: Int): List<BundeslandDefinition> = DatabaseFactory.dbQuery {
|
||||
val pattern = "%$searchTerm%"
|
||||
val query = BundeslandTable.selectAll().where { BundeslandTable.name like pattern }
|
||||
|
||||
landId?.let {
|
||||
query.andWhere { BundeslandTable.landId eq it }
|
||||
}
|
||||
|
||||
query.limit(limit).map(::rowToBundeslandDefinition)
|
||||
}
|
||||
|
||||
override suspend fun findAllActive(orderBySortierung: Boolean): List<BundeslandDefinition> = DatabaseFactory.dbQuery {
|
||||
val query = BundeslandTable.selectAll().where { BundeslandTable.istAktiv eq true }
|
||||
|
||||
if (orderBySortierung) {
|
||||
query.orderBy(BundeslandTable.sortierReihenfolge to SortOrder.ASC, BundeslandTable.name to SortOrder.ASC)
|
||||
} else {
|
||||
query.orderBy(BundeslandTable.name to SortOrder.ASC)
|
||||
}
|
||||
|
||||
query.map(::rowToBundeslandDefinition)
|
||||
}
|
||||
|
||||
override suspend fun save(bundesland: BundeslandDefinition): BundeslandDefinition = DatabaseFactory.dbQuery {
|
||||
val now = Clock.System.now()
|
||||
val existingBundesland = BundeslandTable.selectAll().where { BundeslandTable.id eq bundesland.bundeslandId }.singleOrNull()
|
||||
|
||||
if (existingBundesland == null) {
|
||||
// Insert a new federal state
|
||||
BundeslandTable.insert { stmt ->
|
||||
stmt[id] = bundesland.bundeslandId
|
||||
stmt[landId] = bundesland.landId
|
||||
stmt[oepsCode] = bundesland.oepsCode
|
||||
stmt[iso3166_2_Code] = bundesland.iso3166_2_Code
|
||||
stmt[name] = bundesland.name
|
||||
stmt[kuerzel] = bundesland.kuerzel
|
||||
stmt[wappenUrl] = bundesland.wappenUrl
|
||||
stmt[istAktiv] = bundesland.istAktiv
|
||||
stmt[sortierReihenfolge] = bundesland.sortierReihenfolge
|
||||
stmt[createdAt] = bundesland.createdAt.toLocalDateTime(TimeZone.UTC)
|
||||
stmt[updatedAt] = now.toLocalDateTime(TimeZone.UTC)
|
||||
}
|
||||
} else {
|
||||
// Update existing federal state
|
||||
BundeslandTable.update({ BundeslandTable.id eq bundesland.bundeslandId }) { stmt ->
|
||||
stmt[landId] = bundesland.landId
|
||||
stmt[oepsCode] = bundesland.oepsCode
|
||||
stmt[iso3166_2_Code] = bundesland.iso3166_2_Code
|
||||
stmt[name] = bundesland.name
|
||||
stmt[kuerzel] = bundesland.kuerzel
|
||||
stmt[wappenUrl] = bundesland.wappenUrl
|
||||
stmt[istAktiv] = bundesland.istAktiv
|
||||
stmt[sortierReihenfolge] = bundesland.sortierReihenfolge
|
||||
stmt[updatedAt] = now.toLocalDateTime(TimeZone.UTC)
|
||||
}
|
||||
}
|
||||
|
||||
bundesland.copy(updatedAt = now)
|
||||
}
|
||||
|
||||
override suspend fun delete(id: Uuid): Boolean = DatabaseFactory.dbQuery {
|
||||
BundeslandTable.deleteWhere { BundeslandTable.id eq id } > 0
|
||||
}
|
||||
|
||||
override suspend fun existsByOepsCode(oepsCode: String, landId: Uuid): Boolean = DatabaseFactory.dbQuery {
|
||||
BundeslandTable.selectAll().where {
|
||||
(BundeslandTable.oepsCode eq oepsCode) and (BundeslandTable.landId eq landId)
|
||||
}.count() > 0
|
||||
}
|
||||
|
||||
override suspend fun existsByIso3166_2_Code(iso3166_2_Code: String): Boolean = DatabaseFactory.dbQuery {
|
||||
BundeslandTable.selectAll().where { BundeslandTable.iso3166_2_Code eq iso3166_2_Code }
|
||||
.count() > 0
|
||||
}
|
||||
|
||||
override suspend fun countActiveByCountry(landId: Uuid): Long = DatabaseFactory.dbQuery {
|
||||
BundeslandTable.selectAll().where {
|
||||
(BundeslandTable.landId eq landId) and (BundeslandTable.istAktiv eq true)
|
||||
}.count()
|
||||
}
|
||||
override suspend fun countActiveByCountry(landId: Uuid): Long = DatabaseFactory.dbQuery {
|
||||
BundeslandTable.selectAll().where { (BundeslandTable.landId eq landId) and (BundeslandTable.istAktiv eq true) }
|
||||
.count()
|
||||
}
|
||||
}
|
||||
|
||||
+21
-25
@@ -1,35 +1,31 @@
|
||||
@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class)
|
||||
|
||||
package at.mocode.masterdata.infrastructure.persistence
|
||||
|
||||
import org.jetbrains.exposed.v1.core.Table
|
||||
import org.jetbrains.exposed.v1.core.kotlin.datetime.datetime
|
||||
import org.jetbrains.exposed.v1.core.kotlin.datetime.CurrentDateTime
|
||||
import org.jetbrains.exposed.v1.core.javaUUID
|
||||
import org.jetbrains.exposed.v1.datetime.CurrentTimestamp
|
||||
import org.jetbrains.exposed.v1.datetime.timestamp
|
||||
|
||||
/**
|
||||
* Exposed-Tabellendefinition für die Bundesland-Entität (Bundesländer/Regionen).
|
||||
*
|
||||
* Diese Tabelle speichert alle Informationen zu Bundesländern und subnationalen
|
||||
* Verwaltungseinheiten entsprechend der BundeslandDefinition Domain-Entität.
|
||||
* Exposed-Tabellendefinition für die Bundesland-Entität.
|
||||
*/
|
||||
object BundeslandTable : Table("bundesland") {
|
||||
val id = javaUUID("id").autoGenerate()
|
||||
val landId = javaUUID("land_id").references(LandTable.id)
|
||||
val oepsCode = varchar("oeps_code", 10).nullable()
|
||||
val iso3166_2_Code = varchar("iso_3166_2_code", 10).nullable()
|
||||
val name = varchar("name", 100)
|
||||
val kuerzel = varchar("kuerzel", 10).nullable()
|
||||
val wappenUrl = varchar("wappen_url", 500).nullable()
|
||||
val istAktiv = bool("ist_aktiv").default(true)
|
||||
val sortierReihenfolge = integer("sortier_reihenfolge").nullable()
|
||||
val createdAt = datetime("created_at").defaultExpression(CurrentDateTime)
|
||||
val updatedAt = datetime("updated_at").defaultExpression(CurrentDateTime)
|
||||
val id = uuid("bundesland_id")
|
||||
val landId = uuid("land_id")
|
||||
val oepsCode = varchar("oeps_code", 10).nullable()
|
||||
val iso3166_2_Code = varchar("iso_3166_2_code", 10).nullable()
|
||||
val name = varchar("name", 100)
|
||||
val kuerzel = varchar("kuerzel", 10).nullable()
|
||||
val wappenUrl = varchar("wappen_url", 255).nullable()
|
||||
val istAktiv = bool("ist_aktiv").default(true)
|
||||
val sortierReihenfolge = integer("sortier_reihenfolge").nullable()
|
||||
val createdAt = timestamp("created_at").defaultExpression(CurrentTimestamp)
|
||||
val updatedAt = timestamp("updated_at").defaultExpression(CurrentTimestamp)
|
||||
|
||||
override val primaryKey = PrimaryKey(id)
|
||||
override val primaryKey = PrimaryKey(id)
|
||||
|
||||
init {
|
||||
// Unique constraint for OEPS code per country
|
||||
uniqueIndex("uk_bundesland_oeps_land", oepsCode, landId)
|
||||
// Unique constraint for ISO 3166-2 code globally
|
||||
uniqueIndex("uk_bundesland_iso3166_2", iso3166_2_Code)
|
||||
}
|
||||
init {
|
||||
uniqueIndex("idx_bundesland_oeps", oepsCode, landId)
|
||||
uniqueIndex("idx_bundesland_iso", iso3166_2_Code)
|
||||
}
|
||||
}
|
||||
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class)
|
||||
|
||||
package at.mocode.masterdata.infrastructure.persistence
|
||||
|
||||
import at.mocode.core.domain.model.DatenQuelleE
|
||||
import at.mocode.core.domain.model.FunktionaerRolleE
|
||||
import at.mocode.core.domain.model.RichterQualifikationE
|
||||
import at.mocode.core.domain.model.SparteE
|
||||
import at.mocode.core.utils.database.DatabaseFactory
|
||||
import at.mocode.masterdata.domain.model.DomFunktionaer
|
||||
import at.mocode.masterdata.domain.repository.FunktionaerRepository
|
||||
import org.jetbrains.exposed.v1.core.ResultRow
|
||||
import org.jetbrains.exposed.v1.core.eq
|
||||
import org.jetbrains.exposed.v1.core.like
|
||||
import org.jetbrains.exposed.v1.core.or
|
||||
import org.jetbrains.exposed.v1.jdbc.*
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
/**
|
||||
* Exposed-basierte Implementierung des Funktionaer-Repositorys.
|
||||
*/
|
||||
class ExposedFunktionaerRepository : FunktionaerRepository {
|
||||
|
||||
private fun rowToDomFunktionaer(row: ResultRow): DomFunktionaer {
|
||||
return DomFunktionaer(
|
||||
funktionaerId = row[FunktionaerTable.id],
|
||||
richterNummer = row[FunktionaerTable.richterNummer],
|
||||
vorname = row[FunktionaerTable.vorname],
|
||||
nachname = row[FunktionaerTable.nachname],
|
||||
geburtsdatum = row[FunktionaerTable.geburtsdatum],
|
||||
email = row[FunktionaerTable.email],
|
||||
telefon = row[FunktionaerTable.telefon],
|
||||
vereinsNummer = row[FunktionaerTable.vereinsNummer],
|
||||
istAktiv = row[FunktionaerTable.istAktiv],
|
||||
bemerkungen = row[FunktionaerTable.bemerkungen],
|
||||
datenQuelle = DatenQuelleE.valueOf(row[FunktionaerTable.datenQuelle]),
|
||||
createdAt = row[FunktionaerTable.createdAt],
|
||||
updatedAt = row[FunktionaerTable.updatedAt]
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun findById(id: Uuid): DomFunktionaer? = DatabaseFactory.dbQuery {
|
||||
FunktionaerTable.selectAll().where { FunktionaerTable.id eq id }
|
||||
.map(::rowToDomFunktionaer)
|
||||
.singleOrNull()
|
||||
}
|
||||
|
||||
override suspend fun findByRichterNummer(richterNummer: String): DomFunktionaer? = DatabaseFactory.dbQuery {
|
||||
FunktionaerTable.selectAll().where { FunktionaerTable.richterNummer eq richterNummer }
|
||||
.map(::rowToDomFunktionaer)
|
||||
.singleOrNull()
|
||||
}
|
||||
|
||||
override suspend fun findByName(searchTerm: String, limit: Int): List<DomFunktionaer> = DatabaseFactory.dbQuery {
|
||||
val pattern = "%$searchTerm%"
|
||||
FunktionaerTable.selectAll()
|
||||
.where { (FunktionaerTable.nachname like pattern) or (FunktionaerTable.vorname like pattern) }
|
||||
.limit(limit)
|
||||
.map(::rowToDomFunktionaer)
|
||||
}
|
||||
|
||||
override suspend fun findByRolle(rolle: FunktionaerRolleE, activeOnly: Boolean): List<DomFunktionaer> =
|
||||
DatabaseFactory.dbQuery {
|
||||
// Rolle wird aktuell nicht in FunktionaerTable gespeichert.
|
||||
// Falls benötigt, muss die Tabelle erweitert werden.
|
||||
emptyList()
|
||||
}
|
||||
|
||||
override suspend fun findByRichterQualifikation(
|
||||
qualifikation: RichterQualifikationE,
|
||||
activeOnly: Boolean
|
||||
): List<DomFunktionaer> = DatabaseFactory.dbQuery {
|
||||
// Qualifikationen werden aktuell nicht in FunktionaerTable gespeichert.
|
||||
emptyList()
|
||||
}
|
||||
|
||||
override suspend fun findBySparte(sparte: SparteE, activeOnly: Boolean): List<DomFunktionaer> =
|
||||
DatabaseFactory.dbQuery {
|
||||
emptyList()
|
||||
}
|
||||
|
||||
override suspend fun findByVereinsNummer(vereinsNummer: String, activeOnly: Boolean): List<DomFunktionaer> =
|
||||
DatabaseFactory.dbQuery {
|
||||
val query = FunktionaerTable.selectAll().where { FunktionaerTable.vereinsNummer eq vereinsNummer }
|
||||
if (activeOnly) {
|
||||
query.andWhere { FunktionaerTable.istAktiv eq true }
|
||||
}
|
||||
query.map(::rowToDomFunktionaer)
|
||||
}
|
||||
|
||||
override suspend fun findAllActive(limit: Int, offset: Int): List<DomFunktionaer> = DatabaseFactory.dbQuery {
|
||||
FunktionaerTable.selectAll().where { FunktionaerTable.istAktiv eq true }
|
||||
.limit(limit).offset(offset.toLong())
|
||||
.map(::rowToDomFunktionaer)
|
||||
}
|
||||
|
||||
override suspend fun findAll(limit: Int, offset: Int): List<DomFunktionaer> = DatabaseFactory.dbQuery {
|
||||
FunktionaerTable.selectAll()
|
||||
.limit(limit).offset(offset.toLong())
|
||||
.map(::rowToDomFunktionaer)
|
||||
}
|
||||
|
||||
override suspend fun save(funktionaer: DomFunktionaer): DomFunktionaer = DatabaseFactory.dbQuery {
|
||||
val exists = FunktionaerTable.selectAll().where { FunktionaerTable.id eq funktionaer.funktionaerId }.any()
|
||||
if (exists) {
|
||||
FunktionaerTable.update({ FunktionaerTable.id eq funktionaer.funktionaerId }) {
|
||||
it[richterNummer] = funktionaer.richterNummer
|
||||
it[vorname] = funktionaer.vorname
|
||||
it[nachname] = funktionaer.nachname
|
||||
it[geburtsdatum] = funktionaer.geburtsdatum
|
||||
it[email] = funktionaer.email
|
||||
it[telefon] = funktionaer.telefon
|
||||
it[vereinsNummer] = funktionaer.vereinsNummer
|
||||
it[istAktiv] = funktionaer.istAktiv
|
||||
it[bemerkungen] = funktionaer.bemerkungen
|
||||
it[datenQuelle] = funktionaer.datenQuelle.name
|
||||
it[updatedAt] = funktionaer.updatedAt
|
||||
}
|
||||
funktionaer
|
||||
} else {
|
||||
FunktionaerTable.insert {
|
||||
it[id] = funktionaer.funktionaerId
|
||||
it[richterNummer] = funktionaer.richterNummer
|
||||
it[vorname] = funktionaer.vorname
|
||||
it[nachname] = funktionaer.nachname
|
||||
it[geburtsdatum] = funktionaer.geburtsdatum
|
||||
it[email] = funktionaer.email
|
||||
it[telefon] = funktionaer.telefon
|
||||
it[vereinsNummer] = funktionaer.vereinsNummer
|
||||
it[istAktiv] = funktionaer.istAktiv
|
||||
it[bemerkungen] = funktionaer.bemerkungen
|
||||
it[datenQuelle] = funktionaer.datenQuelle.name
|
||||
it[createdAt] = funktionaer.createdAt
|
||||
it[updatedAt] = funktionaer.updatedAt
|
||||
}
|
||||
funktionaer
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun delete(id: Uuid): Boolean = DatabaseFactory.dbQuery {
|
||||
FunktionaerTable.deleteWhere { FunktionaerTable.id eq id } > 0
|
||||
}
|
||||
|
||||
override suspend fun countActive(): Long = DatabaseFactory.dbQuery {
|
||||
FunktionaerTable.selectAll().where { FunktionaerTable.istAktiv eq true }.count()
|
||||
}
|
||||
|
||||
override suspend fun countByRichterQualifikation(qualifikation: RichterQualifikationE, activeOnly: Boolean): Long =
|
||||
DatabaseFactory.dbQuery {
|
||||
// Aktuell keine Qualifikations-Speicherung
|
||||
0L
|
||||
}
|
||||
|
||||
override suspend fun existsByRichterNummer(richterNummer: String): Boolean = DatabaseFactory.dbQuery {
|
||||
FunktionaerTable.selectAll().where { FunktionaerTable.richterNummer eq richterNummer }.any()
|
||||
}
|
||||
}
|
||||
+177
@@ -0,0 +1,177 @@
|
||||
@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class)
|
||||
|
||||
package at.mocode.masterdata.infrastructure.persistence
|
||||
|
||||
import at.mocode.core.domain.model.DatenQuelleE
|
||||
import at.mocode.core.domain.model.LizenzKlasseE
|
||||
import at.mocode.core.domain.model.SparteE
|
||||
import at.mocode.core.utils.database.DatabaseFactory
|
||||
import at.mocode.masterdata.domain.model.DomReiter
|
||||
import at.mocode.masterdata.domain.repository.ReiterRepository
|
||||
import org.jetbrains.exposed.v1.core.ResultRow
|
||||
import org.jetbrains.exposed.v1.core.eq
|
||||
import org.jetbrains.exposed.v1.core.like
|
||||
import org.jetbrains.exposed.v1.core.or
|
||||
import org.jetbrains.exposed.v1.jdbc.*
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
/**
|
||||
* Exposed-basierte Implementierung des Reiter-Repositorys.
|
||||
*/
|
||||
class ExposedReiterRepository : ReiterRepository {
|
||||
|
||||
private fun rowToDomReiter(row: ResultRow): DomReiter {
|
||||
return DomReiter(
|
||||
reiterId = row[ReiterTable.id],
|
||||
personId = row[ReiterTable.personId],
|
||||
satznummer = row[ReiterTable.satznummer],
|
||||
nachname = row[ReiterTable.nachname],
|
||||
vorname = row[ReiterTable.vorname],
|
||||
geburtsdatum = row[ReiterTable.geburtsdatum],
|
||||
lizenzNummer = row[ReiterTable.lizenzNummer],
|
||||
lizenzKlasse = LizenzKlasseE.valueOf(row[ReiterTable.lizenzKlasse]),
|
||||
startkartAktiv = row[ReiterTable.startkartAktiv],
|
||||
startkartSaison = row[ReiterTable.startkartSaison],
|
||||
feiId = row[ReiterTable.feiId],
|
||||
nation = row[ReiterTable.nation],
|
||||
vereinsNummer = row[ReiterTable.vereinsNummer],
|
||||
vereinsName = row[ReiterTable.vereinsName],
|
||||
istGastreiter = row[ReiterTable.istGastreiter],
|
||||
istAktiv = row[ReiterTable.istAktiv],
|
||||
datenQuelle = DatenQuelleE.valueOf(row[ReiterTable.datenQuelle]),
|
||||
createdAt = row[ReiterTable.createdAt],
|
||||
updatedAt = row[ReiterTable.updatedAt]
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun findById(id: Uuid): DomReiter? = DatabaseFactory.dbQuery {
|
||||
ReiterTable.selectAll().where { ReiterTable.id eq id }
|
||||
.map(::rowToDomReiter)
|
||||
.singleOrNull()
|
||||
}
|
||||
|
||||
override suspend fun findBySatznummer(satznummer: String): DomReiter? = DatabaseFactory.dbQuery {
|
||||
ReiterTable.selectAll().where { ReiterTable.satznummer eq satznummer }
|
||||
.map(::rowToDomReiter)
|
||||
.singleOrNull()
|
||||
}
|
||||
|
||||
override suspend fun findByFeiId(feiId: String): DomReiter? = DatabaseFactory.dbQuery {
|
||||
ReiterTable.selectAll().where { ReiterTable.feiId eq feiId }
|
||||
.map(::rowToDomReiter)
|
||||
.singleOrNull()
|
||||
}
|
||||
|
||||
override suspend fun findByName(searchTerm: String, limit: Int): List<DomReiter> = DatabaseFactory.dbQuery {
|
||||
val pattern = "%$searchTerm%"
|
||||
ReiterTable.selectAll().where { (ReiterTable.nachname like pattern) or (ReiterTable.vorname like pattern) }
|
||||
.limit(limit)
|
||||
.map(::rowToDomReiter)
|
||||
}
|
||||
|
||||
override suspend fun findByVereinsNummer(vereinsNummer: String, activeOnly: Boolean): List<DomReiter> =
|
||||
DatabaseFactory.dbQuery {
|
||||
val query = ReiterTable.selectAll().where { ReiterTable.vereinsNummer eq vereinsNummer }
|
||||
if (activeOnly) {
|
||||
query.andWhere { ReiterTable.istAktiv eq true }
|
||||
}
|
||||
query.map(::rowToDomReiter)
|
||||
}
|
||||
|
||||
override suspend fun findByLizenzKlasse(lizenzKlasse: LizenzKlasseE, activeOnly: Boolean): List<DomReiter> =
|
||||
DatabaseFactory.dbQuery {
|
||||
val query = ReiterTable.selectAll().where { ReiterTable.lizenzKlasse eq lizenzKlasse.name }
|
||||
if (activeOnly) {
|
||||
query.andWhere { ReiterTable.istAktiv eq true }
|
||||
}
|
||||
query.map(::rowToDomReiter)
|
||||
}
|
||||
|
||||
override suspend fun findBySparte(sparte: SparteE, activeOnly: Boolean): List<DomReiter> = DatabaseFactory.dbQuery {
|
||||
// Da wir in ReiterTable keinen sparteFilter haben, müssen wir ggf. über eine andere Tabelle gehen
|
||||
// oder die Logik anpassen. Fürs erste geben wir eine leere Liste zurück oder suchen nach Name in Lizenz?
|
||||
// TODO: Implementierung prüfen, falls Sparten-Lizenzierung in eigener Tabelle liegt.
|
||||
emptyList()
|
||||
}
|
||||
|
||||
override suspend fun findGastreiter(activeOnly: Boolean): List<DomReiter> = DatabaseFactory.dbQuery {
|
||||
val query = ReiterTable.selectAll().where { ReiterTable.istGastreiter eq true }
|
||||
if (activeOnly) {
|
||||
query.andWhere { ReiterTable.istAktiv eq true }
|
||||
}
|
||||
query.map(::rowToDomReiter)
|
||||
}
|
||||
|
||||
override suspend fun findAllActive(limit: Int, offset: Int): List<DomReiter> = DatabaseFactory.dbQuery {
|
||||
ReiterTable.selectAll().where { ReiterTable.istAktiv eq true }
|
||||
.limit(limit).offset(offset.toLong())
|
||||
.map(::rowToDomReiter)
|
||||
}
|
||||
|
||||
override suspend fun findAll(limit: Int, offset: Int): List<DomReiter> = DatabaseFactory.dbQuery {
|
||||
ReiterTable.selectAll()
|
||||
.limit(limit).offset(offset.toLong())
|
||||
.map(::rowToDomReiter)
|
||||
}
|
||||
|
||||
override suspend fun save(reiter: DomReiter): DomReiter = DatabaseFactory.dbQuery {
|
||||
val exists = ReiterTable.selectAll().where { ReiterTable.id eq reiter.reiterId }.any()
|
||||
if (exists) {
|
||||
ReiterTable.update({ ReiterTable.id eq reiter.reiterId }) {
|
||||
it[personId] = reiter.personId
|
||||
it[satznummer] = reiter.satznummer
|
||||
it[nachname] = reiter.nachname
|
||||
it[vorname] = reiter.vorname
|
||||
it[geburtsdatum] = reiter.geburtsdatum
|
||||
it[lizenzNummer] = reiter.lizenzNummer
|
||||
it[lizenzKlasse] = reiter.lizenzKlasse.name
|
||||
it[startkartAktiv] = reiter.startkartAktiv
|
||||
it[startkartSaison] = reiter.startkartSaison
|
||||
it[feiId] = reiter.feiId
|
||||
it[nation] = reiter.nation
|
||||
it[vereinsNummer] = reiter.vereinsNummer
|
||||
it[vereinsName] = reiter.vereinsName
|
||||
it[istGastreiter] = reiter.istGastreiter
|
||||
it[istAktiv] = reiter.istAktiv
|
||||
it[datenQuelle] = reiter.datenQuelle.name
|
||||
it[updatedAt] = reiter.updatedAt
|
||||
}
|
||||
reiter
|
||||
} else {
|
||||
ReiterTable.insert {
|
||||
it[id] = reiter.reiterId
|
||||
it[personId] = reiter.personId
|
||||
it[satznummer] = reiter.satznummer
|
||||
it[nachname] = reiter.nachname
|
||||
it[vorname] = reiter.vorname
|
||||
it[geburtsdatum] = reiter.geburtsdatum
|
||||
it[lizenzNummer] = reiter.lizenzNummer
|
||||
it[lizenzKlasse] = reiter.lizenzKlasse.name
|
||||
it[startkartAktiv] = reiter.startkartAktiv
|
||||
it[startkartSaison] = reiter.startkartSaison
|
||||
it[feiId] = reiter.feiId
|
||||
it[nation] = reiter.nation
|
||||
it[vereinsNummer] = reiter.vereinsNummer
|
||||
it[vereinsName] = reiter.vereinsName
|
||||
it[istGastreiter] = reiter.istGastreiter
|
||||
it[istAktiv] = reiter.istAktiv
|
||||
it[datenQuelle] = reiter.datenQuelle.name
|
||||
it[createdAt] = reiter.createdAt
|
||||
it[updatedAt] = reiter.updatedAt
|
||||
}
|
||||
reiter
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun delete(id: Uuid): Boolean = DatabaseFactory.dbQuery {
|
||||
ReiterTable.deleteWhere { ReiterTable.id eq id } > 0
|
||||
}
|
||||
|
||||
override suspend fun countActive(): Long = DatabaseFactory.dbQuery {
|
||||
ReiterTable.selectAll().where { ReiterTable.istAktiv eq true }.count()
|
||||
}
|
||||
|
||||
override suspend fun existsBySatznummer(satznummer: String): Boolean = DatabaseFactory.dbQuery {
|
||||
ReiterTable.selectAll().where { ReiterTable.satznummer eq satznummer }.any()
|
||||
}
|
||||
}
|
||||
+154
@@ -0,0 +1,154 @@
|
||||
@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class)
|
||||
|
||||
package at.mocode.masterdata.infrastructure.persistence
|
||||
|
||||
import at.mocode.core.domain.model.DatenQuelleE
|
||||
import at.mocode.core.utils.database.DatabaseFactory
|
||||
import at.mocode.masterdata.domain.model.DomVerein
|
||||
import at.mocode.masterdata.domain.repository.VereinRepository
|
||||
import org.jetbrains.exposed.v1.core.ResultRow
|
||||
import org.jetbrains.exposed.v1.core.or
|
||||
import org.jetbrains.exposed.v1.jdbc.deleteWhere
|
||||
import org.jetbrains.exposed.v1.jdbc.insert
|
||||
import org.jetbrains.exposed.v1.jdbc.selectAll
|
||||
import org.jetbrains.exposed.v1.jdbc.update
|
||||
import org.jetbrains.exposed.v1.core.eq
|
||||
import org.jetbrains.exposed.v1.core.like
|
||||
import org.jetbrains.exposed.v1.jdbc.andWhere
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
/**
|
||||
* Exposed-basierte Implementierung des Verein-Repositorys.
|
||||
*/
|
||||
class ExposedVereinRepository : VereinRepository {
|
||||
|
||||
private fun rowToDomVerein(row: ResultRow): DomVerein {
|
||||
return DomVerein(
|
||||
vereinId = row[VereinTable.id],
|
||||
vereinsNummer = row[VereinTable.vereinsNummer],
|
||||
name = row[VereinTable.name],
|
||||
kurzname = row[VereinTable.kurzname],
|
||||
bundesland = row[VereinTable.bundesland],
|
||||
ort = row[VereinTable.ort],
|
||||
plz = row[VereinTable.plz],
|
||||
strasse = row[VereinTable.strasse],
|
||||
email = row[VereinTable.email],
|
||||
telefon = row[VereinTable.telefon],
|
||||
website = row[VereinTable.website],
|
||||
oepsRegionNummer = row[VereinTable.oepsRegionNummer],
|
||||
istVeranstalter = row[VereinTable.istVeranstalter],
|
||||
istAktiv = row[VereinTable.istAktiv],
|
||||
bemerkungen = row[VereinTable.bemerkungen],
|
||||
datenQuelle = DatenQuelleE.valueOf(row[VereinTable.datenQuelle]),
|
||||
createdAt = row[VereinTable.createdAt],
|
||||
updatedAt = row[VereinTable.updatedAt]
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun findById(id: Uuid): DomVerein? = DatabaseFactory.dbQuery {
|
||||
VereinTable.selectAll().where { VereinTable.id eq id }
|
||||
.map(::rowToDomVerein)
|
||||
.singleOrNull()
|
||||
}
|
||||
|
||||
override suspend fun findByVereinsNummer(vereinsNummer: String): DomVerein? = DatabaseFactory.dbQuery {
|
||||
VereinTable.selectAll().where { VereinTable.vereinsNummer eq vereinsNummer }
|
||||
.map(::rowToDomVerein)
|
||||
.singleOrNull()
|
||||
}
|
||||
|
||||
override suspend fun findByName(searchTerm: String, limit: Int): List<DomVerein> = DatabaseFactory.dbQuery {
|
||||
val pattern = "%$searchTerm%"
|
||||
VereinTable.selectAll().where { (VereinTable.name like pattern) or (VereinTable.kurzname like pattern) }
|
||||
.limit(limit)
|
||||
.map(::rowToDomVerein)
|
||||
}
|
||||
|
||||
override suspend fun findByBundesland(bundesland: String, activeOnly: Boolean): List<DomVerein> =
|
||||
DatabaseFactory.dbQuery {
|
||||
val query = VereinTable.selectAll().where { VereinTable.bundesland eq bundesland }
|
||||
if (activeOnly) {
|
||||
query.andWhere { VereinTable.istAktiv eq true }
|
||||
}
|
||||
query.map(::rowToDomVerein)
|
||||
}
|
||||
|
||||
override suspend fun findVeranstalter(activeOnly: Boolean): List<DomVerein> = DatabaseFactory.dbQuery {
|
||||
val query = VereinTable.selectAll().where { VereinTable.istVeranstalter eq true }
|
||||
if (activeOnly) {
|
||||
query.andWhere { VereinTable.istAktiv eq true }
|
||||
}
|
||||
query.map(::rowToDomVerein)
|
||||
}
|
||||
|
||||
override suspend fun findAllActive(limit: Int, offset: Int): List<DomVerein> = DatabaseFactory.dbQuery {
|
||||
VereinTable.selectAll().where { VereinTable.istAktiv eq true }
|
||||
.limit(limit).offset(offset.toLong())
|
||||
.map(::rowToDomVerein)
|
||||
}
|
||||
|
||||
override suspend fun findAll(limit: Int, offset: Int): List<DomVerein> = DatabaseFactory.dbQuery {
|
||||
VereinTable.selectAll()
|
||||
.limit(limit).offset(offset.toLong())
|
||||
.map(::rowToDomVerein)
|
||||
}
|
||||
|
||||
override suspend fun save(verein: DomVerein): DomVerein = DatabaseFactory.dbQuery {
|
||||
val exists = VereinTable.selectAll().where { VereinTable.id eq verein.vereinId }.any()
|
||||
if (exists) {
|
||||
VereinTable.update({ VereinTable.id eq verein.vereinId }) {
|
||||
it[vereinsNummer] = verein.vereinsNummer
|
||||
it[name] = verein.name
|
||||
it[kurzname] = verein.kurzname
|
||||
it[bundesland] = verein.bundesland
|
||||
it[ort] = verein.ort
|
||||
it[plz] = verein.plz
|
||||
it[strasse] = verein.strasse
|
||||
it[email] = verein.email
|
||||
it[telefon] = verein.telefon
|
||||
it[website] = verein.website
|
||||
it[oepsRegionNummer] = verein.oepsRegionNummer
|
||||
it[istVeranstalter] = verein.istVeranstalter
|
||||
it[istAktiv] = verein.istAktiv
|
||||
it[bemerkungen] = verein.bemerkungen
|
||||
it[datenQuelle] = verein.datenQuelle.name
|
||||
it[updatedAt] = verein.updatedAt
|
||||
}
|
||||
verein
|
||||
} else {
|
||||
VereinTable.insert {
|
||||
it[id] = verein.vereinId
|
||||
it[vereinsNummer] = verein.vereinsNummer
|
||||
it[name] = verein.name
|
||||
it[kurzname] = verein.kurzname
|
||||
it[bundesland] = verein.bundesland
|
||||
it[ort] = verein.ort
|
||||
it[plz] = verein.plz
|
||||
it[strasse] = verein.strasse
|
||||
it[email] = verein.email
|
||||
it[telefon] = verein.telefon
|
||||
it[website] = verein.website
|
||||
it[oepsRegionNummer] = verein.oepsRegionNummer
|
||||
it[istVeranstalter] = verein.istVeranstalter
|
||||
it[istAktiv] = verein.istAktiv
|
||||
it[bemerkungen] = verein.bemerkungen
|
||||
it[datenQuelle] = verein.datenQuelle.name
|
||||
it[createdAt] = verein.createdAt
|
||||
it[updatedAt] = verein.updatedAt
|
||||
}
|
||||
verein
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun delete(id: Uuid): Boolean = DatabaseFactory.dbQuery {
|
||||
VereinTable.deleteWhere { VereinTable.id eq id } > 0
|
||||
}
|
||||
|
||||
override suspend fun countActive(): Long = DatabaseFactory.dbQuery {
|
||||
VereinTable.selectAll().where { VereinTable.istAktiv eq true }.count()
|
||||
}
|
||||
|
||||
override suspend fun existsByVereinsNummer(vereinsNummer: String): Boolean = DatabaseFactory.dbQuery {
|
||||
VereinTable.selectAll().where { VereinTable.vereinsNummer eq vereinsNummer }.any()
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class)
|
||||
|
||||
package at.mocode.masterdata.infrastructure.persistence
|
||||
|
||||
import org.jetbrains.exposed.v1.core.Table
|
||||
import org.jetbrains.exposed.v1.datetime.CurrentTimestamp
|
||||
import org.jetbrains.exposed.v1.datetime.date
|
||||
import org.jetbrains.exposed.v1.datetime.timestamp
|
||||
|
||||
|
||||
/**
|
||||
* Exposed-Tabellendefinition für die Funktionär-Entität.
|
||||
*/
|
||||
object FunktionaerTable : Table("funktionaer") {
|
||||
val id = uuid("funktionaer_id")
|
||||
val richterNummer = varchar("richter_nummer", 10).nullable().uniqueIndex()
|
||||
val vorname = varchar("vorname", 100)
|
||||
val nachname = varchar("nachname", 100)
|
||||
val geburtsdatum = date("geburtsdatum").nullable()
|
||||
val email = varchar("email", 200).nullable()
|
||||
val telefon = varchar("telefon", 50).nullable()
|
||||
val vereinsNummer = varchar("vereins_nummer", 10).nullable()
|
||||
val istAktiv = bool("ist_aktiv").default(true)
|
||||
val bemerkungen = text("bemerkungen").nullable()
|
||||
val datenQuelle = varchar("daten_quelle", 50)
|
||||
val createdAt = timestamp("created_at").defaultExpression(CurrentTimestamp)
|
||||
val updatedAt = timestamp("updated_at").defaultExpression(CurrentTimestamp)
|
||||
|
||||
override val primaryKey = PrimaryKey(id)
|
||||
}
|
||||
+286
@@ -0,0 +1,286 @@
|
||||
@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class)
|
||||
|
||||
package at.mocode.masterdata.infrastructure.persistence
|
||||
|
||||
import at.mocode.core.domain.model.DatenQuelleE
|
||||
import at.mocode.core.domain.model.PferdeGeschlechtE
|
||||
import at.mocode.core.utils.database.DatabaseFactory
|
||||
import at.mocode.masterdata.domain.model.DomPferd
|
||||
import at.mocode.masterdata.domain.repository.HorseRepository
|
||||
import org.jetbrains.exposed.v1.core.*
|
||||
import org.jetbrains.exposed.v1.jdbc.*
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
/**
|
||||
* Exposed-basierte Implementierung des Horse-Repositorys.
|
||||
*/
|
||||
class HorseRepositoryImpl : HorseRepository {
|
||||
|
||||
private fun rowToDomPferd(row: ResultRow): DomPferd {
|
||||
return DomPferd(
|
||||
pferdId = row[HorseTable.id],
|
||||
pferdeName = row[HorseTable.pferdeName],
|
||||
geschlecht = PferdeGeschlechtE.valueOf(row[HorseTable.geschlecht]),
|
||||
geburtsdatum = row[HorseTable.geburtsdatum],
|
||||
rasse = row[HorseTable.rasse],
|
||||
farbe = row[HorseTable.farbe],
|
||||
besitzerId = row[HorseTable.besitzerId],
|
||||
verantwortlichePersonId = row[HorseTable.verantwortlichePersonId],
|
||||
zuechterName = row[HorseTable.zuechterName],
|
||||
zuchtbuchNummer = row[HorseTable.zuchtbuchNummer],
|
||||
lebensnummer = row[HorseTable.lebensnummer],
|
||||
chipNummer = row[HorseTable.chipNummer],
|
||||
passNummer = row[HorseTable.passNummer],
|
||||
oepsNummer = row[HorseTable.oepsNummer],
|
||||
feiNummer = row[HorseTable.feiNummer],
|
||||
vaterName = row[HorseTable.vaterName],
|
||||
mutterName = row[HorseTable.mutterName],
|
||||
mutterVaterName = row[HorseTable.mutterVaterName],
|
||||
stockmass = row[HorseTable.stockmass],
|
||||
istAktiv = row[HorseTable.istAktiv],
|
||||
bemerkungen = row[HorseTable.bemerkungen],
|
||||
datenQuelle = DatenQuelleE.valueOf(row[HorseTable.datenQuelle]),
|
||||
createdAt = row[HorseTable.createdAt],
|
||||
updatedAt = row[HorseTable.updatedAt]
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun findById(id: Uuid): DomPferd? = DatabaseFactory.dbQuery {
|
||||
HorseTable.selectAll().where { HorseTable.id eq id }
|
||||
.map(::rowToDomPferd)
|
||||
.singleOrNull()
|
||||
}
|
||||
|
||||
override suspend fun findByLebensnummer(lebensnummer: String): DomPferd? = DatabaseFactory.dbQuery {
|
||||
HorseTable.selectAll().where { HorseTable.lebensnummer eq lebensnummer }
|
||||
.map(::rowToDomPferd)
|
||||
.singleOrNull()
|
||||
}
|
||||
|
||||
override suspend fun findByChipNummer(chipNummer: String): DomPferd? = DatabaseFactory.dbQuery {
|
||||
HorseTable.selectAll().where { HorseTable.chipNummer eq chipNummer }
|
||||
.map(::rowToDomPferd)
|
||||
.singleOrNull()
|
||||
}
|
||||
|
||||
override suspend fun findByPassNummer(passNummer: String): DomPferd? = DatabaseFactory.dbQuery {
|
||||
HorseTable.selectAll().where { HorseTable.passNummer eq passNummer }
|
||||
.map(::rowToDomPferd)
|
||||
.singleOrNull()
|
||||
}
|
||||
|
||||
override suspend fun findByOepsNummer(oepsNummer: String): DomPferd? = DatabaseFactory.dbQuery {
|
||||
HorseTable.selectAll().where { HorseTable.oepsNummer eq oepsNummer }
|
||||
.map(::rowToDomPferd)
|
||||
.singleOrNull()
|
||||
}
|
||||
|
||||
override suspend fun findByFeiNummer(feiNummer: String): DomPferd? = DatabaseFactory.dbQuery {
|
||||
HorseTable.selectAll().where { HorseTable.feiNummer eq feiNummer }
|
||||
.map(::rowToDomPferd)
|
||||
.singleOrNull()
|
||||
}
|
||||
|
||||
override suspend fun findByName(searchTerm: String, limit: Int): List<DomPferd> = DatabaseFactory.dbQuery {
|
||||
val pattern = "%$searchTerm%"
|
||||
HorseTable.selectAll().where { HorseTable.pferdeName like pattern }
|
||||
.limit(limit)
|
||||
.map(::rowToDomPferd)
|
||||
}
|
||||
|
||||
override suspend fun findByOwnerId(ownerId: Uuid, activeOnly: Boolean): List<DomPferd> = DatabaseFactory.dbQuery {
|
||||
val query = HorseTable.selectAll().where { HorseTable.besitzerId eq ownerId }
|
||||
if (activeOnly) {
|
||||
query.andWhere { HorseTable.istAktiv eq true }
|
||||
}
|
||||
query.map(::rowToDomPferd)
|
||||
}
|
||||
|
||||
override suspend fun findByResponsiblePersonId(responsiblePersonId: Uuid, activeOnly: Boolean): List<DomPferd> =
|
||||
DatabaseFactory.dbQuery {
|
||||
val query = HorseTable.selectAll().where { HorseTable.verantwortlichePersonId eq responsiblePersonId }
|
||||
if (activeOnly) {
|
||||
query.andWhere { HorseTable.istAktiv eq true }
|
||||
}
|
||||
query.map(::rowToDomPferd)
|
||||
}
|
||||
|
||||
override suspend fun findByGeschlecht(
|
||||
geschlecht: PferdeGeschlechtE,
|
||||
activeOnly: Boolean,
|
||||
limit: Int
|
||||
): List<DomPferd> = DatabaseFactory.dbQuery {
|
||||
val query = HorseTable.selectAll().where { HorseTable.geschlecht eq geschlecht.name }
|
||||
if (activeOnly) {
|
||||
query.andWhere { HorseTable.istAktiv eq true }
|
||||
}
|
||||
query.limit(limit).map(::rowToDomPferd)
|
||||
}
|
||||
|
||||
override suspend fun findByRasse(rasse: String, activeOnly: Boolean, limit: Int): List<DomPferd> =
|
||||
DatabaseFactory.dbQuery {
|
||||
val query = HorseTable.selectAll().where { HorseTable.rasse eq rasse }
|
||||
if (activeOnly) {
|
||||
query.andWhere { HorseTable.istAktiv eq true }
|
||||
}
|
||||
query.limit(limit).map(::rowToDomPferd)
|
||||
}
|
||||
|
||||
override suspend fun findByBirthYear(birthYear: Int, activeOnly: Boolean): List<DomPferd> = DatabaseFactory.dbQuery {
|
||||
// In Exposed v1 gibt es kein directes year() für date Spalten ohne extra Extension.
|
||||
// Wir suchen im Datumsbereich.
|
||||
val startDate = kotlinx.datetime.LocalDate(birthYear, 1, 1)
|
||||
val endDate = kotlinx.datetime.LocalDate(birthYear, 12, 31)
|
||||
val query = HorseTable.selectAll()
|
||||
.where { (HorseTable.geburtsdatum greaterEq startDate) and (HorseTable.geburtsdatum lessEq endDate) }
|
||||
if (activeOnly) {
|
||||
query.andWhere { HorseTable.istAktiv eq true }
|
||||
}
|
||||
query.map(::rowToDomPferd)
|
||||
}
|
||||
|
||||
override suspend fun findByBirthYearRange(fromYear: Int, toYear: Int, activeOnly: Boolean): List<DomPferd> =
|
||||
DatabaseFactory.dbQuery {
|
||||
val startDate = kotlinx.datetime.LocalDate(fromYear, 1, 1)
|
||||
val endDate = kotlinx.datetime.LocalDate(toYear, 12, 31)
|
||||
val query = HorseTable.selectAll()
|
||||
.where { (HorseTable.geburtsdatum greaterEq startDate) and (HorseTable.geburtsdatum lessEq endDate) }
|
||||
if (activeOnly) {
|
||||
query.andWhere { HorseTable.istAktiv eq true }
|
||||
}
|
||||
query.map(::rowToDomPferd)
|
||||
}
|
||||
|
||||
override suspend fun findAllActive(limit: Int): List<DomPferd> = DatabaseFactory.dbQuery {
|
||||
HorseTable.selectAll().where { HorseTable.istAktiv eq true }
|
||||
.limit(limit)
|
||||
.map(::rowToDomPferd)
|
||||
}
|
||||
|
||||
override suspend fun findOepsRegistered(activeOnly: Boolean): List<DomPferd> = DatabaseFactory.dbQuery {
|
||||
val query = HorseTable.selectAll().where { HorseTable.oepsNummer.isNotNull() }
|
||||
if (activeOnly) {
|
||||
query.andWhere { HorseTable.istAktiv eq true }
|
||||
}
|
||||
query.map(::rowToDomPferd)
|
||||
}
|
||||
|
||||
override suspend fun findFeiRegistered(activeOnly: Boolean): List<DomPferd> = DatabaseFactory.dbQuery {
|
||||
val query = HorseTable.selectAll().where { HorseTable.feiNummer.isNotNull() }
|
||||
if (activeOnly) {
|
||||
query.andWhere { HorseTable.istAktiv eq true }
|
||||
}
|
||||
query.map(::rowToDomPferd)
|
||||
}
|
||||
|
||||
override suspend fun save(horse: DomPferd): DomPferd = DatabaseFactory.dbQuery {
|
||||
val exists = HorseTable.selectAll().where { HorseTable.id eq horse.pferdId }.any()
|
||||
if (exists) {
|
||||
HorseTable.update({ HorseTable.id eq horse.pferdId }) {
|
||||
it[pferdeName] = horse.pferdeName
|
||||
it[geschlecht] = horse.geschlecht.name
|
||||
it[geburtsdatum] = horse.geburtsdatum
|
||||
it[rasse] = horse.rasse
|
||||
it[farbe] = horse.farbe
|
||||
it[besitzerId] = horse.besitzerId
|
||||
it[verantwortlichePersonId] = horse.verantwortlichePersonId
|
||||
it[zuechterName] = horse.zuechterName
|
||||
it[zuchtbuchNummer] = horse.zuchtbuchNummer
|
||||
it[lebensnummer] = horse.lebensnummer
|
||||
it[chipNummer] = horse.chipNummer
|
||||
it[passNummer] = horse.passNummer
|
||||
it[oepsNummer] = horse.oepsNummer
|
||||
it[feiNummer] = horse.feiNummer
|
||||
it[vaterName] = horse.vaterName
|
||||
it[mutterName] = horse.mutterName
|
||||
it[mutterVaterName] = horse.mutterVaterName
|
||||
it[stockmass] = horse.stockmass
|
||||
it[istAktiv] = horse.istAktiv
|
||||
it[bemerkungen] = horse.bemerkungen
|
||||
it[datenQuelle] = horse.datenQuelle.name
|
||||
it[updatedAt] = horse.updatedAt
|
||||
}
|
||||
horse
|
||||
} else {
|
||||
HorseTable.insert {
|
||||
it[id] = horse.pferdId
|
||||
it[pferdeName] = horse.pferdeName
|
||||
it[geschlecht] = horse.geschlecht.name
|
||||
it[geburtsdatum] = horse.geburtsdatum
|
||||
it[rasse] = horse.rasse
|
||||
it[farbe] = horse.farbe
|
||||
it[besitzerId] = horse.besitzerId
|
||||
it[verantwortlichePersonId] = horse.verantwortlichePersonId
|
||||
it[zuechterName] = horse.zuechterName
|
||||
it[zuchtbuchNummer] = horse.zuchtbuchNummer
|
||||
it[lebensnummer] = horse.lebensnummer
|
||||
it[chipNummer] = horse.chipNummer
|
||||
it[passNummer] = horse.passNummer
|
||||
it[oepsNummer] = horse.oepsNummer
|
||||
it[feiNummer] = horse.feiNummer
|
||||
it[vaterName] = horse.vaterName
|
||||
it[mutterName] = horse.mutterName
|
||||
it[mutterVaterName] = horse.mutterVaterName
|
||||
it[stockmass] = horse.stockmass
|
||||
it[istAktiv] = horse.istAktiv
|
||||
it[bemerkungen] = horse.bemerkungen
|
||||
it[datenQuelle] = horse.datenQuelle.name
|
||||
it[createdAt] = horse.createdAt
|
||||
it[updatedAt] = horse.updatedAt
|
||||
}
|
||||
horse
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun delete(id: Uuid): Boolean = DatabaseFactory.dbQuery {
|
||||
HorseTable.deleteWhere { HorseTable.id eq id } > 0
|
||||
}
|
||||
|
||||
override suspend fun existsByLebensnummer(lebensnummer: String): Boolean = DatabaseFactory.dbQuery {
|
||||
HorseTable.selectAll().where { HorseTable.lebensnummer eq lebensnummer }.any()
|
||||
}
|
||||
|
||||
override suspend fun existsByChipNummer(chipNummer: String): Boolean = DatabaseFactory.dbQuery {
|
||||
HorseTable.selectAll().where { HorseTable.chipNummer eq chipNummer }.any()
|
||||
}
|
||||
|
||||
override suspend fun existsByPassNummer(passNummer: String): Boolean = DatabaseFactory.dbQuery {
|
||||
HorseTable.selectAll().where { HorseTable.passNummer eq passNummer }.any()
|
||||
}
|
||||
|
||||
override suspend fun existsByOepsNummer(oepsNummer: String): Boolean = DatabaseFactory.dbQuery {
|
||||
HorseTable.selectAll().where { HorseTable.oepsNummer eq oepsNummer }.any()
|
||||
}
|
||||
|
||||
override suspend fun existsByFeiNummer(feiNummer: String): Boolean = DatabaseFactory.dbQuery {
|
||||
HorseTable.selectAll().where { HorseTable.feiNummer eq feiNummer }.any()
|
||||
}
|
||||
|
||||
override suspend fun countActive(): Long = DatabaseFactory.dbQuery {
|
||||
HorseTable.selectAll().where { HorseTable.istAktiv eq true }.count()
|
||||
}
|
||||
|
||||
override suspend fun countByOwnerId(ownerId: Uuid, activeOnly: Boolean): Long = DatabaseFactory.dbQuery {
|
||||
val query = HorseTable.selectAll().where { HorseTable.besitzerId eq ownerId }
|
||||
if (activeOnly) {
|
||||
query.andWhere { HorseTable.istAktiv eq true }
|
||||
}
|
||||
query.count()
|
||||
}
|
||||
|
||||
override suspend fun countOepsRegistered(activeOnly: Boolean): Long = DatabaseFactory.dbQuery {
|
||||
val query = HorseTable.selectAll().where { HorseTable.oepsNummer.isNotNull() }
|
||||
if (activeOnly) {
|
||||
query.andWhere { HorseTable.istAktiv eq true }
|
||||
}
|
||||
query.count()
|
||||
}
|
||||
|
||||
override suspend fun countFeiRegistered(activeOnly: Boolean): Long = DatabaseFactory.dbQuery {
|
||||
val query = HorseTable.selectAll().where { HorseTable.feiNummer.isNotNull() }
|
||||
if (activeOnly) {
|
||||
query.andWhere { HorseTable.istAktiv eq true }
|
||||
}
|
||||
query.count()
|
||||
}
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class)
|
||||
|
||||
package at.mocode.masterdata.infrastructure.persistence
|
||||
|
||||
import org.jetbrains.exposed.v1.core.Table
|
||||
import org.jetbrains.exposed.v1.datetime.CurrentTimestamp
|
||||
import org.jetbrains.exposed.v1.datetime.date
|
||||
import org.jetbrains.exposed.v1.datetime.timestamp
|
||||
|
||||
/**
|
||||
* Exposed-Tabellendefinition für die Pferd-Entität.
|
||||
*/
|
||||
object HorseTable : Table("horse") {
|
||||
val id = uuid("horse_id")
|
||||
val pferdeName = varchar("pferde_name", 200)
|
||||
val geschlecht = varchar("geschlecht", 20)
|
||||
val geburtsdatum = date("geburtsdatum").nullable()
|
||||
val rasse = varchar("rasse", 100).nullable()
|
||||
val farbe = varchar("farbe", 100).nullable()
|
||||
val besitzerId = uuid("besitzer_id").nullable()
|
||||
val verantwortlichePersonId = uuid("verantwortliche_person_id").nullable()
|
||||
val zuechterName = varchar("zuechter_name", 200).nullable()
|
||||
val zuchtbuchNummer = varchar("zuchtbuch_nummer", 50).nullable()
|
||||
val lebensnummer = varchar("lebensnummer", 50).nullable()
|
||||
val chipNummer = varchar("chip_nummer", 50).nullable()
|
||||
val passNummer = varchar("pass_nummer", 50).nullable()
|
||||
val oepsNummer = varchar("oeps_nummer", 50).nullable()
|
||||
val feiNummer = varchar("fei_nummer", 50).nullable()
|
||||
val vaterName = varchar("vater_name", 200).nullable()
|
||||
val mutterName = varchar("mutter_name", 200).nullable()
|
||||
val mutterVaterName = varchar("mutter_vater_name", 200).nullable()
|
||||
val stockmass = integer("stockmass").nullable()
|
||||
val istAktiv = bool("ist_aktiv").default(true)
|
||||
val bemerkungen = text("bemerkungen").nullable()
|
||||
val datenQuelle = varchar("daten_quelle", 50)
|
||||
val createdAt = timestamp("created_at").defaultExpression(CurrentTimestamp)
|
||||
val updatedAt = timestamp("updated_at").defaultExpression(CurrentTimestamp)
|
||||
|
||||
override val primaryKey = PrimaryKey(id)
|
||||
|
||||
init {
|
||||
index("idx_horse_lebensnummer", isUnique = false, lebensnummer)
|
||||
index("idx_horse_name", isUnique = false, pferdeName)
|
||||
}
|
||||
}
|
||||
+58
-71
@@ -1,28 +1,25 @@
|
||||
@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class)
|
||||
package at.mocode.masterdata.infrastructure.persistence
|
||||
|
||||
import at.mocode.core.utils.database.DatabaseFactory
|
||||
import at.mocode.masterdata.domain.model.LandDefinition
|
||||
import at.mocode.masterdata.domain.repository.LandRepository
|
||||
import at.mocode.core.utils.database.DatabaseFactory
|
||||
import org.jetbrains.exposed.v1.core.ResultRow
|
||||
import org.jetbrains.exposed.v1.core.SortOrder
|
||||
import org.jetbrains.exposed.v1.core.or
|
||||
import org.jetbrains.exposed.v1.jdbc.deleteWhere
|
||||
import org.jetbrains.exposed.v1.jdbc.insert
|
||||
import org.jetbrains.exposed.v1.jdbc.selectAll
|
||||
import org.jetbrains.exposed.v1.jdbc.update
|
||||
import org.jetbrains.exposed.v1.core.eq
|
||||
import org.jetbrains.exposed.v1.core.like
|
||||
import kotlin.uuid.Uuid
|
||||
import kotlinx.datetime.Clock
|
||||
import kotlinx.datetime.TimeZone
|
||||
import kotlinx.datetime.toInstant
|
||||
import kotlinx.datetime.toLocalDateTime
|
||||
import org.jetbrains.exposed.sql.*
|
||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
||||
|
||||
/**
|
||||
* Implementierung des LandRepository für die Datenbankzugriffe.
|
||||
*
|
||||
* Diese Implementierung verwendet Exposed SQL für den Datenbankzugriff
|
||||
* und mappt zwischen der LandDefinition Domain-Entität und der LandTable.
|
||||
*/
|
||||
class LandRepositoryImpl : LandRepository {
|
||||
|
||||
/**
|
||||
* Konvertiert eine Datenbankzeile in ein Domain-Objekt.
|
||||
*/
|
||||
private fun rowToLandDefinition(row: ResultRow): LandDefinition {
|
||||
return LandDefinition(
|
||||
landId = row[LandTable.id],
|
||||
@@ -36,8 +33,8 @@ class LandRepositoryImpl : LandRepository {
|
||||
istEwrMitglied = row[LandTable.istEwrMitglied],
|
||||
istAktiv = row[LandTable.istAktiv],
|
||||
sortierReihenfolge = row[LandTable.sortierReihenfolge],
|
||||
createdAt = row[LandTable.createdAt].toInstant(TimeZone.UTC),
|
||||
updatedAt = row[LandTable.updatedAt].toInstant(TimeZone.UTC)
|
||||
createdAt = row[LandTable.createdAt],
|
||||
updatedAt = row[LandTable.updatedAt]
|
||||
)
|
||||
}
|
||||
|
||||
@@ -48,90 +45,82 @@ class LandRepositoryImpl : LandRepository {
|
||||
}
|
||||
|
||||
override suspend fun findByIsoAlpha2Code(isoAlpha2Code: String): LandDefinition? = DatabaseFactory.dbQuery {
|
||||
LandTable.selectAll().where { LandTable.isoAlpha2Code eq isoAlpha2Code }
|
||||
LandTable.selectAll().where { LandTable.isoAlpha2Code eq isoAlpha2Code.uppercase() }
|
||||
.map(::rowToLandDefinition)
|
||||
.singleOrNull()
|
||||
}
|
||||
|
||||
override suspend fun findByIsoAlpha3Code(isoAlpha3Code: String): LandDefinition? = DatabaseFactory.dbQuery {
|
||||
LandTable.selectAll().where { LandTable.isoAlpha3Code eq isoAlpha3Code }
|
||||
LandTable.selectAll().where { LandTable.isoAlpha3Code eq isoAlpha3Code.uppercase() }
|
||||
.map(::rowToLandDefinition)
|
||||
.singleOrNull()
|
||||
}
|
||||
|
||||
override suspend fun findByName(searchTerm: String, limit: Int): List<LandDefinition> = DatabaseFactory.dbQuery {
|
||||
val pattern = "%$searchTerm%"
|
||||
LandTable.selectAll().where {
|
||||
(LandTable.nameDeutsch like pattern) or
|
||||
(LandTable.nameEnglisch like pattern)
|
||||
}
|
||||
.limit(limit)
|
||||
.map(::rowToLandDefinition)
|
||||
}
|
||||
|
||||
override suspend fun findAllActive(orderBySortierung: Boolean): List<LandDefinition> = DatabaseFactory.dbQuery {
|
||||
val query = LandTable.selectAll().where { LandTable.istAktiv eq true }
|
||||
|
||||
if (orderBySortierung) {
|
||||
query.orderBy(LandTable.sortierReihenfolge to SortOrder.ASC, LandTable.nameDeutsch to SortOrder.ASC)
|
||||
} else {
|
||||
query.orderBy(LandTable.nameDeutsch to SortOrder.ASC)
|
||||
}
|
||||
|
||||
query.map(::rowToLandDefinition)
|
||||
}
|
||||
|
||||
override suspend fun findByName(searchTerm: String, limit: Int): List<LandDefinition> = DatabaseFactory.dbQuery {
|
||||
val pattern = "%$searchTerm%"
|
||||
LandTable.selectAll()
|
||||
.where { (LandTable.nameDeutsch like pattern) or (LandTable.nameEnglisch like pattern) }
|
||||
.limit(limit)
|
||||
.map(::rowToLandDefinition)
|
||||
}
|
||||
|
||||
override suspend fun findEuMembers(): List<LandDefinition> = DatabaseFactory.dbQuery {
|
||||
LandTable.selectAll().where { (LandTable.istEuMitglied eq true) and (LandTable.istAktiv eq true) }
|
||||
.orderBy(LandTable.sortierReihenfolge to SortOrder.ASC, LandTable.nameDeutsch to SortOrder.ASC)
|
||||
LandTable.selectAll().where { LandTable.istEuMitglied eq true }
|
||||
.orderBy(LandTable.nameDeutsch to SortOrder.ASC)
|
||||
.map(::rowToLandDefinition)
|
||||
}
|
||||
|
||||
override suspend fun findEwrMembers(): List<LandDefinition> = DatabaseFactory.dbQuery {
|
||||
LandTable.selectAll().where { (LandTable.istEwrMitglied eq true) and (LandTable.istAktiv eq true) }
|
||||
.orderBy(LandTable.sortierReihenfolge to SortOrder.ASC, LandTable.nameDeutsch to SortOrder.ASC)
|
||||
LandTable.selectAll().where { LandTable.istEwrMitglied eq true }
|
||||
.orderBy(LandTable.nameDeutsch to SortOrder.ASC)
|
||||
.map(::rowToLandDefinition)
|
||||
}
|
||||
|
||||
override suspend fun save(land: LandDefinition): LandDefinition = DatabaseFactory.dbQuery {
|
||||
val now = Clock.System.now()
|
||||
val existingLand = LandTable.selectAll().where { LandTable.id eq land.landId }.singleOrNull()
|
||||
|
||||
if (existingLand == null) {
|
||||
// Insert a new country
|
||||
LandTable.insert { stmt ->
|
||||
stmt[id] = land.landId
|
||||
stmt[isoAlpha2Code] = land.isoAlpha2Code
|
||||
stmt[isoAlpha3Code] = land.isoAlpha3Code
|
||||
stmt[isoNumerischerCode] = land.isoNumerischerCode
|
||||
stmt[nameDeutsch] = land.nameDeutsch
|
||||
stmt[nameEnglisch] = land.nameEnglisch
|
||||
stmt[wappenUrl] = land.wappenUrl
|
||||
stmt[istEuMitglied] = land.istEuMitglied
|
||||
stmt[istEwrMitglied] = land.istEwrMitglied
|
||||
stmt[istAktiv] = land.istAktiv
|
||||
stmt[sortierReihenfolge] = land.sortierReihenfolge
|
||||
stmt[createdAt] = land.createdAt.toLocalDateTime(TimeZone.UTC)
|
||||
stmt[updatedAt] = now.toLocalDateTime(TimeZone.UTC)
|
||||
val exists = LandTable.selectAll().where { LandTable.id eq land.landId }.any()
|
||||
if (exists) {
|
||||
LandTable.update({ LandTable.id eq land.landId }) {
|
||||
it[isoAlpha2Code] = land.isoAlpha2Code.uppercase()
|
||||
it[isoAlpha3Code] = land.isoAlpha3Code.uppercase()
|
||||
it[isoNumerischerCode] = land.isoNumerischerCode
|
||||
it[nameDeutsch] = land.nameDeutsch
|
||||
it[nameEnglisch] = land.nameEnglisch
|
||||
it[wappenUrl] = land.wappenUrl
|
||||
it[istEuMitglied] = land.istEuMitglied
|
||||
it[istEwrMitglied] = land.istEwrMitglied
|
||||
it[istAktiv] = land.istAktiv
|
||||
it[sortierReihenfolge] = land.sortierReihenfolge
|
||||
it[updatedAt] = land.updatedAt
|
||||
}
|
||||
land
|
||||
} else {
|
||||
// Update existing country
|
||||
LandTable.update({ LandTable.id eq land.landId }) { stmt ->
|
||||
stmt[isoAlpha2Code] = land.isoAlpha2Code
|
||||
stmt[isoAlpha3Code] = land.isoAlpha3Code
|
||||
stmt[isoNumerischerCode] = land.isoNumerischerCode
|
||||
stmt[nameDeutsch] = land.nameDeutsch
|
||||
stmt[nameEnglisch] = land.nameEnglisch
|
||||
stmt[wappenUrl] = land.wappenUrl
|
||||
stmt[istEuMitglied] = land.istEuMitglied
|
||||
stmt[istEwrMitglied] = land.istEwrMitglied
|
||||
stmt[istAktiv] = land.istAktiv
|
||||
stmt[sortierReihenfolge] = land.sortierReihenfolge
|
||||
stmt[updatedAt] = now.toLocalDateTime(TimeZone.UTC)
|
||||
LandTable.insert {
|
||||
it[id] = land.landId
|
||||
it[isoAlpha2Code] = land.isoAlpha2Code.uppercase()
|
||||
it[isoAlpha3Code] = land.isoAlpha3Code.uppercase()
|
||||
it[isoNumerischerCode] = land.isoNumerischerCode
|
||||
it[nameDeutsch] = land.nameDeutsch
|
||||
it[nameEnglisch] = land.nameEnglisch
|
||||
it[wappenUrl] = land.wappenUrl
|
||||
it[istEuMitglied] = land.istEuMitglied
|
||||
it[istEwrMitglied] = land.istEwrMitglied
|
||||
it[istAktiv] = land.istAktiv
|
||||
it[sortierReihenfolge] = land.sortierReihenfolge
|
||||
it[createdAt] = land.createdAt
|
||||
it[updatedAt] = land.updatedAt
|
||||
}
|
||||
land
|
||||
}
|
||||
|
||||
land.copy(updatedAt = now)
|
||||
}
|
||||
|
||||
override suspend fun delete(id: Uuid): Boolean = DatabaseFactory.dbQuery {
|
||||
@@ -139,13 +128,11 @@ class LandRepositoryImpl : LandRepository {
|
||||
}
|
||||
|
||||
override suspend fun existsByIsoAlpha2Code(isoAlpha2Code: String): Boolean = DatabaseFactory.dbQuery {
|
||||
LandTable.selectAll().where { LandTable.isoAlpha2Code eq isoAlpha2Code }
|
||||
.count() > 0
|
||||
LandTable.selectAll().where { LandTable.isoAlpha2Code eq isoAlpha2Code.uppercase() }.any()
|
||||
}
|
||||
|
||||
override suspend fun existsByIsoAlpha3Code(isoAlpha3Code: String): Boolean = DatabaseFactory.dbQuery {
|
||||
LandTable.selectAll().where { LandTable.isoAlpha3Code eq isoAlpha3Code }
|
||||
.count() > 0
|
||||
LandTable.selectAll().where { LandTable.isoAlpha3Code eq isoAlpha3Code.uppercase() }.any()
|
||||
}
|
||||
|
||||
override suspend fun countActive(): Long = DatabaseFactory.dbQuery {
|
||||
|
||||
+19
-21
@@ -1,30 +1,28 @@
|
||||
@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class)
|
||||
|
||||
package at.mocode.masterdata.infrastructure.persistence
|
||||
|
||||
import org.jetbrains.exposed.v1.core.Table
|
||||
import org.jetbrains.exposed.v1.core.kotlin.datetime.datetime
|
||||
import org.jetbrains.exposed.v1.core.kotlin.datetime.CurrentDateTime
|
||||
import org.jetbrains.exposed.v1.core.javaUUID
|
||||
import org.jetbrains.exposed.v1.datetime.CurrentTimestamp
|
||||
import org.jetbrains.exposed.v1.datetime.timestamp
|
||||
|
||||
/**
|
||||
* Exposed-Tabellendefinition für die Land-Entität (Länderstammdaten).
|
||||
*
|
||||
* Diese Tabelle speichert alle Informationen zu Ländern/Nationen entsprechend
|
||||
* der LandDefinition Domain-Entität.
|
||||
* Exposed-Tabellendefinition für die Land-Entität.
|
||||
*/
|
||||
object LandTable : Table("land") {
|
||||
val id = javaUUID("id").autoGenerate()
|
||||
val isoAlpha2Code = varchar("iso_alpha2_code", 2).uniqueIndex()
|
||||
val isoAlpha3Code = varchar("iso_alpha3_code", 3).uniqueIndex()
|
||||
val isoNumerischerCode = varchar("iso_numerischer_code", 3).nullable()
|
||||
val nameDeutsch = varchar("name_deutsch", 100)
|
||||
val nameEnglisch = varchar("name_englisch", 100).nullable()
|
||||
val wappenUrl = varchar("wappen_url", 500).nullable()
|
||||
val istEuMitglied = bool("ist_eu_mitglied").nullable()
|
||||
val istEwrMitglied = bool("ist_ewr_mitglied").nullable()
|
||||
val istAktiv = bool("ist_aktiv").default(true)
|
||||
val sortierReihenfolge = integer("sortier_reihenfolge").nullable()
|
||||
val createdAt = datetime("created_at").defaultExpression(CurrentDateTime)
|
||||
val updatedAt = datetime("updated_at").defaultExpression(CurrentDateTime)
|
||||
val id = uuid("land_id")
|
||||
val isoAlpha2Code = varchar("iso_alpha2_code", 2).uniqueIndex()
|
||||
val isoAlpha3Code = varchar("iso_alpha3_code", 3).uniqueIndex()
|
||||
val isoNumerischerCode = varchar("iso_numerischer_code", 3).nullable()
|
||||
val nameDeutsch = varchar("name_deutsch", 100)
|
||||
val nameEnglisch = varchar("name_englisch", 100).nullable()
|
||||
val wappenUrl = varchar("wappen_url", 255).nullable()
|
||||
val istEuMitglied = bool("ist_eu_mitglied").nullable()
|
||||
val istEwrMitglied = bool("ist_ewr_mitglied").nullable()
|
||||
val istAktiv = bool("ist_aktiv").default(true)
|
||||
val sortierReihenfolge = integer("sortier_reihenfolge").nullable()
|
||||
val createdAt = timestamp("created_at").defaultExpression(CurrentTimestamp)
|
||||
val updatedAt = timestamp("updated_at").defaultExpression(CurrentTimestamp)
|
||||
|
||||
override val primaryKey = PrimaryKey(id)
|
||||
override val primaryKey = PrimaryKey(id)
|
||||
}
|
||||
|
||||
+61
-121
@@ -2,28 +2,26 @@
|
||||
package at.mocode.masterdata.infrastructure.persistence
|
||||
|
||||
import at.mocode.core.domain.model.PlatzTypE
|
||||
import at.mocode.core.utils.database.DatabaseFactory
|
||||
import at.mocode.masterdata.domain.model.Platz
|
||||
import at.mocode.masterdata.domain.repository.PlatzRepository
|
||||
import at.mocode.core.utils.database.DatabaseFactory
|
||||
import org.jetbrains.exposed.v1.core.ResultRow
|
||||
import org.jetbrains.exposed.v1.core.SortOrder
|
||||
import org.jetbrains.exposed.v1.core.and
|
||||
import org.jetbrains.exposed.v1.core.eq
|
||||
import org.jetbrains.exposed.v1.core.like
|
||||
import org.jetbrains.exposed.v1.jdbc.andWhere
|
||||
import org.jetbrains.exposed.v1.jdbc.deleteWhere
|
||||
import org.jetbrains.exposed.v1.jdbc.insert
|
||||
import org.jetbrains.exposed.v1.jdbc.selectAll
|
||||
import org.jetbrains.exposed.v1.jdbc.update
|
||||
import kotlin.uuid.Uuid
|
||||
import kotlinx.datetime.Clock
|
||||
import kotlinx.datetime.TimeZone
|
||||
import kotlinx.datetime.toInstant
|
||||
import kotlinx.datetime.toLocalDateTime
|
||||
import org.jetbrains.exposed.sql.*
|
||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
||||
|
||||
/**
|
||||
* Implementierung des PlatzRepository für die Datenbankzugriffe.
|
||||
*
|
||||
* Diese Implementierung verwendet Exposed SQL für den Datenbankzugriff
|
||||
* und mappt zwischen der Platz Domain-Entität und der PlatzTable.
|
||||
* Exposed-basierte Implementierung des Platz-Repositorys.
|
||||
*/
|
||||
class PlatzRepositoryImpl : PlatzRepository {
|
||||
|
||||
/**
|
||||
* Konvertiert eine Datenbankzeile in ein Domain-Objekt.
|
||||
*/
|
||||
private fun rowToPlatz(row: ResultRow): Platz {
|
||||
return Platz(
|
||||
id = row[PlatzTable.id],
|
||||
@@ -34,8 +32,8 @@ class PlatzRepositoryImpl : PlatzRepository {
|
||||
typ = PlatzTypE.valueOf(row[PlatzTable.typ]),
|
||||
istAktiv = row[PlatzTable.istAktiv],
|
||||
sortierReihenfolge = row[PlatzTable.sortierReihenfolge],
|
||||
createdAt = row[PlatzTable.createdAt].toInstant(TimeZone.UTC),
|
||||
updatedAt = row[PlatzTable.updatedAt].toInstant(TimeZone.UTC)
|
||||
createdAt = row[PlatzTable.createdAt],
|
||||
updatedAt = row[PlatzTable.updatedAt]
|
||||
)
|
||||
}
|
||||
|
||||
@@ -47,144 +45,102 @@ class PlatzRepositoryImpl : PlatzRepository {
|
||||
|
||||
override suspend fun findByTournament(turnierId: Uuid, activeOnly: Boolean, orderBySortierung: Boolean): List<Platz> = DatabaseFactory.dbQuery {
|
||||
val query = PlatzTable.selectAll().where { PlatzTable.turnierId eq turnierId }
|
||||
|
||||
if (activeOnly) {
|
||||
query.andWhere { PlatzTable.istAktiv eq true }
|
||||
}
|
||||
|
||||
if (orderBySortierung) {
|
||||
query.orderBy(PlatzTable.sortierReihenfolge to SortOrder.ASC, PlatzTable.name to SortOrder.ASC)
|
||||
} else {
|
||||
query.orderBy(PlatzTable.name to SortOrder.ASC)
|
||||
}
|
||||
|
||||
query.map(::rowToPlatz)
|
||||
}
|
||||
|
||||
override suspend fun findByName(searchTerm: String, turnierId: Uuid?, limit: Int): List<Platz> = DatabaseFactory.dbQuery {
|
||||
val pattern = "%$searchTerm%"
|
||||
val query = PlatzTable.selectAll().where { PlatzTable.name like pattern }
|
||||
|
||||
turnierId?.let {
|
||||
query.andWhere { PlatzTable.turnierId eq it }
|
||||
}
|
||||
|
||||
query.limit(limit)
|
||||
.orderBy(PlatzTable.name to SortOrder.ASC)
|
||||
.map(::rowToPlatz)
|
||||
turnierId?.let { tid -> query.andWhere { PlatzTable.turnierId eq tid } }
|
||||
query.limit(limit).map(::rowToPlatz)
|
||||
}
|
||||
|
||||
override suspend fun findByType(typ: PlatzTypE, turnierId: Uuid?, activeOnly: Boolean): List<Platz> = DatabaseFactory.dbQuery {
|
||||
val query = PlatzTable.selectAll().where { PlatzTable.typ eq typ.name }
|
||||
|
||||
turnierId?.let {
|
||||
query.andWhere { PlatzTable.turnierId eq it }
|
||||
}
|
||||
|
||||
turnierId?.let { tid -> query.andWhere { PlatzTable.turnierId eq tid } }
|
||||
if (activeOnly) {
|
||||
query.andWhere { PlatzTable.istAktiv eq true }
|
||||
}
|
||||
|
||||
query.orderBy(PlatzTable.name to SortOrder.ASC)
|
||||
.map(::rowToPlatz)
|
||||
query.map(::rowToPlatz)
|
||||
}
|
||||
|
||||
override suspend fun findByGroundType(boden: String, turnierId: Uuid?, activeOnly: Boolean): List<Platz> = DatabaseFactory.dbQuery {
|
||||
val query = PlatzTable.selectAll().where { PlatzTable.boden eq boden }
|
||||
|
||||
turnierId?.let {
|
||||
query.andWhere { PlatzTable.turnierId eq it }
|
||||
}
|
||||
|
||||
turnierId?.let { tid -> query.andWhere { PlatzTable.turnierId eq tid } }
|
||||
if (activeOnly) {
|
||||
query.andWhere { PlatzTable.istAktiv eq true }
|
||||
}
|
||||
|
||||
query.orderBy(PlatzTable.name to SortOrder.ASC)
|
||||
.map(::rowToPlatz)
|
||||
query.map(::rowToPlatz)
|
||||
}
|
||||
|
||||
override suspend fun findByDimensions(dimension: String, turnierId: Uuid?, activeOnly: Boolean): List<Platz> = DatabaseFactory.dbQuery {
|
||||
val query = PlatzTable.selectAll().where { PlatzTable.dimension eq dimension }
|
||||
|
||||
turnierId?.let {
|
||||
query.andWhere { PlatzTable.turnierId eq it }
|
||||
}
|
||||
|
||||
turnierId?.let { tid -> query.andWhere { PlatzTable.turnierId eq tid } }
|
||||
if (activeOnly) {
|
||||
query.andWhere { PlatzTable.istAktiv eq true }
|
||||
}
|
||||
|
||||
query.orderBy(PlatzTable.name to SortOrder.ASC)
|
||||
.map(::rowToPlatz)
|
||||
query.map(::rowToPlatz)
|
||||
}
|
||||
|
||||
override suspend fun findAllActive(orderBySortierung: Boolean): List<Platz> = DatabaseFactory.dbQuery {
|
||||
val query = PlatzTable.selectAll().where { PlatzTable.istAktiv eq true }
|
||||
|
||||
if (orderBySortierung) {
|
||||
query.orderBy(PlatzTable.sortierReihenfolge to SortOrder.ASC, PlatzTable.name to SortOrder.ASC)
|
||||
} else {
|
||||
query.orderBy(PlatzTable.name to SortOrder.ASC)
|
||||
}
|
||||
|
||||
query.map(::rowToPlatz)
|
||||
}
|
||||
|
||||
override suspend fun findSuitableForDiscipline(
|
||||
requiredType: PlatzTypE,
|
||||
requiredDimensions: String?,
|
||||
turnierId: Uuid?
|
||||
): List<Platz> = DatabaseFactory.dbQuery {
|
||||
val query = PlatzTable.selectAll().where {
|
||||
(PlatzTable.typ eq requiredType.name) and (PlatzTable.istAktiv eq true)
|
||||
}
|
||||
|
||||
requiredDimensions?.let { dimensions ->
|
||||
query.andWhere { PlatzTable.dimension eq dimensions }
|
||||
}
|
||||
|
||||
turnierId?.let {
|
||||
query.andWhere { PlatzTable.turnierId eq it }
|
||||
}
|
||||
|
||||
query.orderBy(PlatzTable.sortierReihenfolge to SortOrder.ASC, PlatzTable.name to SortOrder.ASC)
|
||||
.map(::rowToPlatz)
|
||||
override suspend fun findSuitableForDiscipline(
|
||||
requiredType: PlatzTypE,
|
||||
requiredDimensions: String?,
|
||||
turnierId: Uuid?
|
||||
): List<Platz> = DatabaseFactory.dbQuery {
|
||||
val query = PlatzTable.selectAll().where { PlatzTable.typ eq requiredType.name }
|
||||
requiredDimensions?.let { dim -> query.andWhere { PlatzTable.dimension eq dim } }
|
||||
turnierId?.let { tid -> query.andWhere { PlatzTable.turnierId eq tid } }
|
||||
query.andWhere { PlatzTable.istAktiv eq true }
|
||||
query.map(::rowToPlatz)
|
||||
}
|
||||
|
||||
override suspend fun save(platz: Platz): Platz = DatabaseFactory.dbQuery {
|
||||
val now = Clock.System.now()
|
||||
val existingPlatz = PlatzTable.selectAll().where { PlatzTable.id eq platz.id }.singleOrNull()
|
||||
|
||||
if (existingPlatz == null) {
|
||||
// Insert a new venue
|
||||
PlatzTable.insert { stmt ->
|
||||
stmt[id] = platz.id
|
||||
stmt[turnierId] = platz.turnierId
|
||||
stmt[name] = platz.name
|
||||
stmt[dimension] = platz.dimension
|
||||
stmt[boden] = platz.boden
|
||||
stmt[typ] = platz.typ.name
|
||||
stmt[istAktiv] = platz.istAktiv
|
||||
stmt[sortierReihenfolge] = platz.sortierReihenfolge
|
||||
stmt[createdAt] = platz.createdAt.toLocalDateTime(TimeZone.UTC)
|
||||
stmt[updatedAt] = now.toLocalDateTime(TimeZone.UTC)
|
||||
val exists = PlatzTable.selectAll().where { PlatzTable.id eq platz.id }.any()
|
||||
if (exists) {
|
||||
PlatzTable.update({ PlatzTable.id eq platz.id }) {
|
||||
it[turnierId] = platz.turnierId
|
||||
it[name] = platz.name
|
||||
it[dimension] = platz.dimension
|
||||
it[boden] = platz.boden
|
||||
it[typ] = platz.typ.name
|
||||
it[istAktiv] = platz.istAktiv
|
||||
it[sortierReihenfolge] = platz.sortierReihenfolge
|
||||
it[updatedAt] = platz.updatedAt
|
||||
}
|
||||
platz
|
||||
} else {
|
||||
// Update existing venue
|
||||
PlatzTable.update({ PlatzTable.id eq platz.id }) { stmt ->
|
||||
stmt[turnierId] = platz.turnierId
|
||||
stmt[name] = platz.name
|
||||
stmt[dimension] = platz.dimension
|
||||
stmt[boden] = platz.boden
|
||||
stmt[typ] = platz.typ.name
|
||||
stmt[istAktiv] = platz.istAktiv
|
||||
stmt[sortierReihenfolge] = platz.sortierReihenfolge
|
||||
stmt[updatedAt] = now.toLocalDateTime(TimeZone.UTC)
|
||||
PlatzTable.insert {
|
||||
it[id] = platz.id
|
||||
it[turnierId] = platz.turnierId
|
||||
it[name] = platz.name
|
||||
it[dimension] = platz.dimension
|
||||
it[boden] = platz.boden
|
||||
it[typ] = platz.typ.name
|
||||
it[istAktiv] = platz.istAktiv
|
||||
it[sortierReihenfolge] = platz.sortierReihenfolge
|
||||
it[createdAt] = platz.createdAt
|
||||
it[updatedAt] = platz.updatedAt
|
||||
}
|
||||
platz
|
||||
}
|
||||
|
||||
platz.copy(updatedAt = now)
|
||||
}
|
||||
|
||||
override suspend fun delete(id: Uuid): Boolean = DatabaseFactory.dbQuery {
|
||||
@@ -192,40 +148,24 @@ class PlatzRepositoryImpl : PlatzRepository {
|
||||
}
|
||||
|
||||
override suspend fun existsByNameAndTournament(name: String, turnierId: Uuid): Boolean = DatabaseFactory.dbQuery {
|
||||
PlatzTable.selectAll().where {
|
||||
(PlatzTable.name eq name) and (PlatzTable.turnierId eq turnierId)
|
||||
}.count() > 0
|
||||
PlatzTable.selectAll().where { (PlatzTable.name eq name) and (PlatzTable.turnierId eq turnierId) }.any()
|
||||
}
|
||||
|
||||
override suspend fun countActiveByTournament(turnierId: Uuid): Long = DatabaseFactory.dbQuery {
|
||||
PlatzTable.selectAll().where {
|
||||
(PlatzTable.turnierId eq turnierId) and (PlatzTable.istAktiv eq true)
|
||||
}.count()
|
||||
PlatzTable.selectAll().where { (PlatzTable.turnierId eq turnierId) and (PlatzTable.istAktiv eq true) }.count()
|
||||
}
|
||||
|
||||
override suspend fun countByTypeAndTournament(typ: PlatzTypE, turnierId: Uuid, activeOnly: Boolean): Long = DatabaseFactory.dbQuery {
|
||||
val query = PlatzTable.selectAll().where {
|
||||
(PlatzTable.typ eq typ.name) and (PlatzTable.turnierId eq turnierId)
|
||||
}
|
||||
|
||||
val query = PlatzTable.selectAll().where { (PlatzTable.turnierId eq turnierId) and (PlatzTable.typ eq typ.name) }
|
||||
if (activeOnly) {
|
||||
query.andWhere { PlatzTable.istAktiv eq true }
|
||||
}
|
||||
|
||||
query.count()
|
||||
}
|
||||
|
||||
override suspend fun findAvailableForTimeSlot(turnierId: Uuid, startTime: String?, endTime: String?): List<Platz> = DatabaseFactory.dbQuery {
|
||||
// For now, this returns all active venues for the tournament
|
||||
// This can be extended when venue scheduling functionality is implemented
|
||||
val query = PlatzTable.selectAll().where {
|
||||
(PlatzTable.turnierId eq turnierId) and (PlatzTable.istAktiv eq true)
|
||||
}
|
||||
|
||||
// TODO: Add time slot availability logic when scheduling is implemented
|
||||
// This would involve joining with a scheduling/booking table to check availability
|
||||
|
||||
query.orderBy(PlatzTable.sortierReihenfolge to SortOrder.ASC, PlatzTable.name to SortOrder.ASC)
|
||||
// Derzeit gibt die Methode einfach alle aktiven Plätze des Turniers zurück.
|
||||
PlatzTable.selectAll().where { (PlatzTable.turnierId eq turnierId) and (PlatzTable.istAktiv eq true) }
|
||||
.map(::rowToPlatz)
|
||||
}
|
||||
}
|
||||
|
||||
+19
-28
@@ -1,38 +1,29 @@
|
||||
@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class)
|
||||
|
||||
package at.mocode.masterdata.infrastructure.persistence
|
||||
|
||||
import org.jetbrains.exposed.v1.core.Table
|
||||
import org.jetbrains.exposed.v1.core.kotlin.datetime.datetime
|
||||
import org.jetbrains.exposed.v1.core.kotlin.datetime.CurrentDateTime
|
||||
import org.jetbrains.exposed.v1.core.javaUUID
|
||||
import org.jetbrains.exposed.v1.datetime.CurrentTimestamp
|
||||
import org.jetbrains.exposed.v1.datetime.timestamp
|
||||
|
||||
/**
|
||||
* Exposed-Tabellendefinition für die Platz-Entität (Turnierplätze/Wettkampfstätten).
|
||||
*
|
||||
* Diese Tabelle speichert alle Informationen zu Plätzen und Arenen
|
||||
* entsprechend der Platz Domain-Entität.
|
||||
* Exposed-Tabellendefinition für die Platz-Entität.
|
||||
*/
|
||||
object PlatzTable : Table("platz") {
|
||||
val id = javaUUID("id").autoGenerate()
|
||||
val turnierId = javaUUID("turnier_id") // Foreign key to tournament (not enforced here as tournament might be in different module)
|
||||
val name = varchar("name", 200)
|
||||
val dimension = varchar("dimension", 50).nullable()
|
||||
val boden = varchar("boden", 100).nullable()
|
||||
val typ = varchar("typ", 50) // Enum as string
|
||||
val istAktiv = bool("ist_aktiv").default(true)
|
||||
val sortierReihenfolge = integer("sortier_reihenfolge").nullable()
|
||||
val createdAt = datetime("created_at").defaultExpression(CurrentDateTime)
|
||||
val updatedAt = datetime("updated_at").defaultExpression(CurrentDateTime)
|
||||
val id = uuid("platz_id")
|
||||
val turnierId = uuid("turnier_id")
|
||||
val name = varchar("name", 100)
|
||||
val dimension = varchar("dimension", 50).nullable()
|
||||
val boden = varchar("boden", 50).nullable()
|
||||
val typ = varchar("typ", 50)
|
||||
val istAktiv = bool("ist_aktiv").default(true)
|
||||
val sortierReihenfolge = integer("sortier_reihenfolge").nullable()
|
||||
val createdAt = timestamp("created_at").defaultExpression(CurrentTimestamp)
|
||||
val updatedAt = timestamp("updated_at").defaultExpression(CurrentTimestamp)
|
||||
|
||||
override val primaryKey = PrimaryKey(id)
|
||||
override val primaryKey = PrimaryKey(id)
|
||||
|
||||
init {
|
||||
// Index for performance on common queries
|
||||
index(customIndexName = "idx_platz_turnier", columns = arrayOf(turnierId))
|
||||
index(customIndexName = "idx_platz_aktiv", columns = arrayOf(istAktiv))
|
||||
index(customIndexName = "idx_platz_typ", columns = arrayOf(typ))
|
||||
index(customIndexName = "idx_platz_turnier_aktiv", columns = arrayOf(turnierId, istAktiv))
|
||||
|
||||
// Unique constraint for name per tournament
|
||||
uniqueIndex("uk_platz_name_turnier", name, turnierId)
|
||||
}
|
||||
init {
|
||||
index("idx_platz_turnier", isUnique = false, turnierId)
|
||||
}
|
||||
}
|
||||
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class)
|
||||
|
||||
package at.mocode.masterdata.infrastructure.persistence
|
||||
|
||||
import org.jetbrains.exposed.v1.core.Table
|
||||
import org.jetbrains.exposed.v1.datetime.CurrentTimestamp
|
||||
import org.jetbrains.exposed.v1.datetime.date
|
||||
import org.jetbrains.exposed.v1.datetime.timestamp
|
||||
|
||||
/**
|
||||
* Exposed-Tabellendefinition für die Reiter-Entität.
|
||||
*/
|
||||
object ReiterTable : Table("reiter") {
|
||||
val id = uuid("reiter_id")
|
||||
val personId = uuid("person_id")
|
||||
val satznummer = varchar("satznummer", 10).uniqueIndex()
|
||||
val lizenzNummer = varchar("lizenz_nummer", 20).nullable()
|
||||
val lizenzKlasse = varchar("lizenz_klasse", 20)
|
||||
val startkartAktiv = bool("startkart_aktiv").default(false)
|
||||
val startkartSaison = integer("startkart_saison").nullable()
|
||||
val feiId = varchar("fei_id", 20).nullable()
|
||||
val nation = varchar("nation", 3).nullable()
|
||||
val nachname = varchar("nachname", 100)
|
||||
val vorname = varchar("vorname", 100)
|
||||
val geburtsdatum = date("geburtsdatum").nullable()
|
||||
val vereinsNummer = varchar("vereins_nummer", 10).nullable()
|
||||
val vereinsName = varchar("vereins_name", 200).nullable()
|
||||
val istGastreiter = bool("ist_gastreiter").default(false)
|
||||
val istAktiv = bool("ist_aktiv").default(true)
|
||||
val datenQuelle = varchar("daten_quelle", 50)
|
||||
val createdAt = timestamp("created_at").defaultExpression(CurrentTimestamp)
|
||||
val updatedAt = timestamp("updated_at").defaultExpression(CurrentTimestamp)
|
||||
|
||||
override val primaryKey = PrimaryKey(id)
|
||||
|
||||
init {
|
||||
index("idx_reiter_satznummer", isUnique = true, satznummer)
|
||||
index("idx_reiter_name", isUnique = false, nachname, vorname)
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class)
|
||||
|
||||
package at.mocode.masterdata.infrastructure.persistence
|
||||
|
||||
import org.jetbrains.exposed.v1.core.Table
|
||||
import org.jetbrains.exposed.v1.datetime.CurrentTimestamp
|
||||
import org.jetbrains.exposed.v1.datetime.timestamp
|
||||
|
||||
/**
|
||||
* Exposed-Tabellendefinition für die Verein-Entität.
|
||||
*/
|
||||
object VereinTable : Table("verein") {
|
||||
val id = uuid("verein_id")
|
||||
val vereinsNummer = varchar("vereins_nummer", 10).uniqueIndex()
|
||||
val name = varchar("name", 200)
|
||||
val kurzname = varchar("kurzname", 100).nullable()
|
||||
val bundesland = varchar("bundesland", 100).nullable()
|
||||
val ort = varchar("ort", 100).nullable()
|
||||
val plz = varchar("plz", 10).nullable()
|
||||
val strasse = varchar("strasse", 200).nullable()
|
||||
val email = varchar("email", 200).nullable()
|
||||
val telefon = varchar("telefon", 50).nullable()
|
||||
val website = varchar("website", 255).nullable()
|
||||
val oepsRegionNummer = varchar("oeps_region_nummer", 10).nullable()
|
||||
val istVeranstalter = bool("ist_veranstalter").default(false)
|
||||
val istAktiv = bool("ist_aktiv").default(true)
|
||||
val bemerkungen = text("bemerkungen").nullable()
|
||||
val datenQuelle = varchar("daten_quelle", 50)
|
||||
val createdAt = timestamp("created_at").defaultExpression(CurrentTimestamp)
|
||||
val updatedAt = timestamp("updated_at").defaultExpression(CurrentTimestamp)
|
||||
|
||||
override val primaryKey = PrimaryKey(id)
|
||||
}
|
||||
@@ -1,57 +1,53 @@
|
||||
plugins {
|
||||
alias(libs.plugins.kotlin.jvm)
|
||||
alias(libs.plugins.kotlin.spring)
|
||||
|
||||
// KORREKTUR: Dieses Plugin ist entscheidend. Es schaltet den `springBoot`-Block
|
||||
// und alle Spring-Boot-spezifischen Gradle-Tasks frei.
|
||||
alias(libs.plugins.spring.boot)
|
||||
|
||||
// Dependency Management für konsistente Spring-Versionen
|
||||
alias(libs.plugins.spring.dependencyManagement)
|
||||
alias(libs.plugins.kotlinJvm)
|
||||
alias(libs.plugins.spring.boot)
|
||||
alias(libs.plugins.spring.dependencyManagement)
|
||||
alias(libs.plugins.kotlinSpring)
|
||||
}
|
||||
|
||||
// Dieser Block funktioniert jetzt, weil das `springBoot`-Plugin oben aktiviert ist.
|
||||
springBoot {
|
||||
mainClass.set("at.mocode.masterdata.service.MasterdataServiceApplicationKt")
|
||||
mainClass.set("at.mocode.masterdata.service.MasterdataServiceApplicationKt")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// Interne Module
|
||||
implementation(projects.platform.platformDependencies)
|
||||
implementation(projects.core.coreUtils)
|
||||
implementation(projects.masterdata.masterdataDomain)
|
||||
implementation(projects.masterdata.masterdataApplication)
|
||||
implementation(projects.masterdata.masterdataInfrastructure)
|
||||
implementation(projects.masterdata.masterdataApi)
|
||||
// Interne Module
|
||||
implementation(projects.platform.platformDependencies)
|
||||
implementation(projects.core.coreUtils)
|
||||
implementation(projects.backend.services.masterdata.masterdataDomain)
|
||||
implementation(projects.backend.services.masterdata.masterdataInfrastructure)
|
||||
implementation(projects.backend.services.masterdata.masterdataCommon)
|
||||
implementation(projects.backend.services.masterdata.masterdataApi)
|
||||
|
||||
// Infrastruktur-Clients
|
||||
implementation(projects.infrastructure.cache.redisCache)
|
||||
implementation(projects.infrastructure.messaging.messagingClient)
|
||||
implementation(projects.infrastructure.monitoring.monitoringClient)
|
||||
// Infrastruktur-Clients
|
||||
implementation(projects.backend.infrastructure.cache.valkeyCache)
|
||||
implementation(projects.backend.infrastructure.messaging.messagingClient)
|
||||
implementation(projects.backend.infrastructure.monitoring.monitoringClient)
|
||||
|
||||
// KORREKTUR: Alle externen Abhängigkeiten werden jetzt über den Version Catalog bezogen.
|
||||
// KORREKTUR: Alle externen Abhängigkeiten werden jetzt über den Version Catalog bezogen.
|
||||
|
||||
// Spring Boot Starters
|
||||
implementation(libs.spring.boot.starter.web)
|
||||
implementation(libs.spring.boot.starter.validation)
|
||||
implementation(libs.spring.boot.starter.actuator)
|
||||
//implementation(libs.springdoc.openapi.starter.webmvc.ui)
|
||||
// Spring Boot Starters
|
||||
implementation(libs.spring.boot.starter.web)
|
||||
implementation(libs.spring.boot.starter.validation)
|
||||
implementation(libs.spring.boot.starter.actuator)
|
||||
//implementation(libs.springdoc.openapi.starter.webmvc.ui)
|
||||
|
||||
// Datenbank-Abhängigkeiten
|
||||
implementation(libs.exposed.core)
|
||||
implementation(libs.exposed.dao)
|
||||
implementation(libs.exposed.jdbc)
|
||||
implementation(libs.exposed.kotlin.datetime)
|
||||
implementation(libs.hikari.cp)
|
||||
runtimeOnly(libs.postgresql.driver)
|
||||
testRuntimeOnly(libs.h2.driver)
|
||||
// Datenbank-Abhängigkeiten
|
||||
implementation(libs.exposed.core)
|
||||
implementation(libs.exposed.dao)
|
||||
implementation(libs.exposed.jdbc)
|
||||
implementation(libs.exposed.kotlin.datetime)
|
||||
implementation(libs.hikari.cp)
|
||||
// implementation(libs.firebase.database.ktx) // Firebase removed
|
||||
runtimeOnly(libs.postgresql.driver)
|
||||
testRuntimeOnly(libs.h2.driver)
|
||||
|
||||
// Testing
|
||||
testImplementation(projects.platform.platformTesting)
|
||||
testImplementation(libs.spring.boot.starter.test)
|
||||
testImplementation(libs.logback.classic) // SLF4J provider for tests
|
||||
// Testing
|
||||
testImplementation(projects.platform.platformTesting)
|
||||
testImplementation(libs.spring.boot.starter.test)
|
||||
testImplementation(libs.logback.classic) // SLF4J provider for tests
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
useJUnitPlatform()
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
+2
-15
@@ -1,7 +1,5 @@
|
||||
package at.mocode.masterdata.service
|
||||
|
||||
import at.mocode.core.utils.config.AppConfig
|
||||
import at.mocode.core.utils.database.DatabaseFactory
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication
|
||||
import org.springframework.boot.runApplication
|
||||
|
||||
@@ -14,17 +12,6 @@ import org.springframework.boot.runApplication
|
||||
class MasterdataServiceApplication
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
// 1. Lade die Konfiguration explizit, genau einmal beim Start.
|
||||
val appConfig = AppConfig.load()
|
||||
println("Konfiguration für Umgebung '${appConfig.environment}' geladen.")
|
||||
|
||||
// 2. Initialisiere die Datenbank mit der geladenen Konfiguration.
|
||||
// Flyway-Migrationen werden hier automatisch ausgeführt.
|
||||
DatabaseFactory.init(appConfig.database)
|
||||
println("Datenbank initialisiert und migriert.")
|
||||
|
||||
// 3. Starte die Spring Boot / Ktor Anwendung.
|
||||
// Der appConfig-Wert kann hier an die Anwendung übergeben werden,
|
||||
// um ihn später per Dependency Injection zu nutzen.
|
||||
runApplication<MasterdataServiceApplication>(*args)
|
||||
// Starte die Spring Boot Anwendung.
|
||||
runApplication<MasterdataServiceApplication>(*args)
|
||||
}
|
||||
|
||||
+129
-109
@@ -17,103 +17,123 @@ import org.springframework.context.annotation.Profile
|
||||
@Configuration
|
||||
class MasterdataConfiguration {
|
||||
|
||||
// Repository Implementations
|
||||
@Bean
|
||||
fun landRepository(): LandRepository {
|
||||
return LandRepositoryImpl()
|
||||
}
|
||||
// Repository Implementations
|
||||
@Bean
|
||||
fun landRepository(): LandRepository {
|
||||
return LandRepositoryImpl()
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun bundeslandRepository(): BundeslandRepository {
|
||||
return BundeslandRepositoryImpl()
|
||||
}
|
||||
@Bean
|
||||
fun bundeslandRepository(): BundeslandRepository {
|
||||
return BundeslandRepositoryImpl()
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun altersklasseRepository(): AltersklasseRepository {
|
||||
return AltersklasseRepositoryImpl()
|
||||
}
|
||||
@Bean
|
||||
fun altersklasseRepository(): AltersklasseRepository {
|
||||
return AltersklasseRepositoryImpl()
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun platzRepository(): PlatzRepository {
|
||||
return PlatzRepositoryImpl()
|
||||
}
|
||||
@Bean
|
||||
fun platzRepository(): PlatzRepository {
|
||||
return PlatzRepositoryImpl()
|
||||
}
|
||||
|
||||
// Use Cases - Country/Land
|
||||
@Bean
|
||||
fun getCountryUseCase(landRepository: LandRepository): GetCountryUseCase {
|
||||
return GetCountryUseCase(landRepository)
|
||||
}
|
||||
@Bean
|
||||
fun reiterRepository(): ReiterRepository {
|
||||
return ExposedReiterRepository()
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun createCountryUseCase(landRepository: LandRepository): CreateCountryUseCase {
|
||||
return CreateCountryUseCase(landRepository)
|
||||
}
|
||||
@Bean
|
||||
fun vereinRepository(): VereinRepository {
|
||||
return ExposedVereinRepository()
|
||||
}
|
||||
|
||||
// Use Cases - Federal State/Bundesland
|
||||
@Bean
|
||||
fun getBundeslandUseCase(bundeslandRepository: BundeslandRepository): GetBundeslandUseCase {
|
||||
return GetBundeslandUseCase(bundeslandRepository)
|
||||
}
|
||||
@Bean
|
||||
fun horseRepository(): HorseRepository {
|
||||
return HorseRepositoryImpl()
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun createBundeslandUseCase(bundeslandRepository: BundeslandRepository): CreateBundeslandUseCase {
|
||||
return CreateBundeslandUseCase(bundeslandRepository)
|
||||
}
|
||||
@Bean
|
||||
fun funktionaerRepository(): FunktionaerRepository {
|
||||
return ExposedFunktionaerRepository()
|
||||
}
|
||||
|
||||
// Use Cases - Age Class/Altersklasse
|
||||
@Bean
|
||||
fun getAltersklasseUseCase(altersklasseRepository: AltersklasseRepository): GetAltersklasseUseCase {
|
||||
return GetAltersklasseUseCase(altersklasseRepository)
|
||||
}
|
||||
// Use Cases - Country/Land
|
||||
@Bean
|
||||
fun getCountryUseCase(landRepository: LandRepository): GetCountryUseCase {
|
||||
return GetCountryUseCase(landRepository)
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun createAltersklasseUseCase(altersklasseRepository: AltersklasseRepository): CreateAltersklasseUseCase {
|
||||
return CreateAltersklasseUseCase(altersklasseRepository)
|
||||
}
|
||||
@Bean
|
||||
fun createCountryUseCase(landRepository: LandRepository): CreateCountryUseCase {
|
||||
return CreateCountryUseCase(landRepository)
|
||||
}
|
||||
|
||||
// Use Cases - Venue/Platz
|
||||
@Bean
|
||||
fun getPlatzUseCase(platzRepository: PlatzRepository): GetPlatzUseCase {
|
||||
return GetPlatzUseCase(platzRepository)
|
||||
}
|
||||
// Use Cases - Federal State/Bundesland
|
||||
@Bean
|
||||
fun getBundeslandUseCase(bundeslandRepository: BundeslandRepository): GetBundeslandUseCase {
|
||||
return GetBundeslandUseCase(bundeslandRepository)
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun createPlatzUseCase(platzRepository: PlatzRepository): CreatePlatzUseCase {
|
||||
return CreatePlatzUseCase(platzRepository)
|
||||
}
|
||||
@Bean
|
||||
fun createBundeslandUseCase(bundeslandRepository: BundeslandRepository): CreateBundeslandUseCase {
|
||||
return CreateBundeslandUseCase(bundeslandRepository)
|
||||
}
|
||||
|
||||
// API Controllers
|
||||
@Bean
|
||||
fun countryController(
|
||||
getCountryUseCase: GetCountryUseCase,
|
||||
createCountryUseCase: CreateCountryUseCase
|
||||
): CountryController {
|
||||
return CountryController(getCountryUseCase, createCountryUseCase)
|
||||
}
|
||||
// Use Cases - Age Class/Altersklasse
|
||||
@Bean
|
||||
fun getAltersklasseUseCase(altersklasseRepository: AltersklasseRepository): GetAltersklasseUseCase {
|
||||
return GetAltersklasseUseCase(altersklasseRepository)
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun bundeslandController(
|
||||
getBundeslandUseCase: GetBundeslandUseCase,
|
||||
createBundeslandUseCase: CreateBundeslandUseCase
|
||||
): BundeslandController {
|
||||
return BundeslandController(getBundeslandUseCase, createBundeslandUseCase)
|
||||
}
|
||||
@Bean
|
||||
fun createAltersklasseUseCase(altersklasseRepository: AltersklasseRepository): CreateAltersklasseUseCase {
|
||||
return CreateAltersklasseUseCase(altersklasseRepository)
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun altersklasseController(
|
||||
getAltersklasseUseCase: GetAltersklasseUseCase,
|
||||
createAltersklasseUseCase: CreateAltersklasseUseCase
|
||||
): AltersklasseController {
|
||||
return AltersklasseController(getAltersklasseUseCase, createAltersklasseUseCase)
|
||||
}
|
||||
// Use Cases - Venue/Platz
|
||||
@Bean
|
||||
fun getPlatzUseCase(platzRepository: PlatzRepository): GetPlatzUseCase {
|
||||
return GetPlatzUseCase(platzRepository)
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun platzController(
|
||||
getPlatzUseCase: GetPlatzUseCase,
|
||||
createPlatzUseCase: CreatePlatzUseCase
|
||||
): PlatzController {
|
||||
return PlatzController(getPlatzUseCase, createPlatzUseCase)
|
||||
}
|
||||
@Bean
|
||||
fun createPlatzUseCase(platzRepository: PlatzRepository): CreatePlatzUseCase {
|
||||
return CreatePlatzUseCase(platzRepository)
|
||||
}
|
||||
|
||||
// API Controllers
|
||||
@Bean
|
||||
fun countryController(
|
||||
getCountryUseCase: GetCountryUseCase,
|
||||
createCountryUseCase: CreateCountryUseCase
|
||||
): CountryController {
|
||||
return CountryController(getCountryUseCase, createCountryUseCase)
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun bundeslandController(
|
||||
getBundeslandUseCase: GetBundeslandUseCase,
|
||||
createBundeslandUseCase: CreateBundeslandUseCase
|
||||
): BundeslandController {
|
||||
return BundeslandController(getBundeslandUseCase, createBundeslandUseCase)
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun altersklasseController(
|
||||
getAltersklasseUseCase: GetAltersklasseUseCase,
|
||||
createAltersklasseUseCase: CreateAltersklasseUseCase
|
||||
): AltersklasseController {
|
||||
return AltersklasseController(getAltersklasseUseCase, createAltersklasseUseCase)
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun platzController(
|
||||
getPlatzUseCase: GetPlatzUseCase,
|
||||
createPlatzUseCase: CreatePlatzUseCase
|
||||
): PlatzController {
|
||||
return PlatzController(getPlatzUseCase, createPlatzUseCase)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -122,33 +142,33 @@ class MasterdataConfiguration {
|
||||
@Configuration
|
||||
class DatabaseConfiguration {
|
||||
|
||||
/**
|
||||
* Development database configuration.
|
||||
*/
|
||||
@Configuration
|
||||
@Profile("dev", "development")
|
||||
class DevelopmentDatabaseConfig {
|
||||
// Development-specific database configuration
|
||||
// This would typically include H2 or local PostgreSQL setup
|
||||
}
|
||||
/**
|
||||
* Development database configuration.
|
||||
*/
|
||||
@Configuration
|
||||
@Profile("dev", "development")
|
||||
class DevelopmentDatabaseConfig {
|
||||
// Development-specific database configuration
|
||||
// This would typically include H2 or local PostgreSQL setup
|
||||
}
|
||||
|
||||
/**
|
||||
* Production database configuration.
|
||||
*/
|
||||
@Configuration
|
||||
@Profile("prod", "production")
|
||||
class ProductionDatabaseConfig {
|
||||
// Production-specific database configuration
|
||||
// This would include production PostgreSQL setup with connection pooling
|
||||
}
|
||||
/**
|
||||
* Production database configuration.
|
||||
*/
|
||||
@Configuration
|
||||
@Profile("prod", "production")
|
||||
class ProductionDatabaseConfig {
|
||||
// Production-specific database configuration
|
||||
// This would include production PostgreSQL setup with connection pooling
|
||||
}
|
||||
|
||||
/**
|
||||
* Test database configuration.
|
||||
*/
|
||||
@Configuration
|
||||
@Profile("test")
|
||||
class TestDatabaseConfig {
|
||||
// Test-specific database configuration
|
||||
// This would typically include in-memory H2 database
|
||||
}
|
||||
/**
|
||||
* Test database configuration.
|
||||
*/
|
||||
@Configuration
|
||||
@Profile("test")
|
||||
class TestDatabaseConfig {
|
||||
// Test-specific database configuration
|
||||
// This would typically include in-memory H2 database
|
||||
}
|
||||
}
|
||||
|
||||
+53
-81
@@ -1,18 +1,17 @@
|
||||
package at.mocode.masterdata.service.config
|
||||
|
||||
import at.mocode.core.utils.database.DatabaseConfig
|
||||
import at.mocode.core.utils.database.DatabaseFactory
|
||||
import at.mocode.masterdata.infrastructure.persistence.LandTable
|
||||
import at.mocode.masterdata.infrastructure.persistence.BundeslandTable
|
||||
|
||||
import at.mocode.masterdata.infrastructure.persistence.AltersklasseTable
|
||||
import at.mocode.masterdata.infrastructure.persistence.BundeslandTable
|
||||
import at.mocode.masterdata.infrastructure.persistence.LandTable
|
||||
import at.mocode.masterdata.infrastructure.persistence.PlatzTable
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.context.annotation.Profile
|
||||
import jakarta.annotation.PostConstruct
|
||||
import jakarta.annotation.PreDestroy
|
||||
import org.jetbrains.exposed.v1.jdbc.SchemaUtils
|
||||
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.jetbrains.exposed.sql.SchemaUtils
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.context.annotation.Profile
|
||||
|
||||
/**
|
||||
* Database configuration for the Masterdata Service.
|
||||
@@ -24,40 +23,33 @@ import org.jetbrains.exposed.sql.transactions.transaction
|
||||
@Profile("!test")
|
||||
class MasterdataDatabaseConfiguration {
|
||||
|
||||
private val log = LoggerFactory.getLogger(MasterdataDatabaseConfiguration::class.java)
|
||||
private val log = LoggerFactory.getLogger(MasterdataDatabaseConfiguration::class.java)
|
||||
|
||||
@PostConstruct
|
||||
fun initializeDatabase() {
|
||||
log.info("Initializing database schema for Masterdata Service...")
|
||||
@PostConstruct
|
||||
fun initializeDatabase() {
|
||||
log.info("Initializing database schema for Masterdata Service...")
|
||||
|
||||
try {
|
||||
// Database connection is already initialized by the gateway
|
||||
// Only initialize the schema for this service
|
||||
transaction {
|
||||
SchemaUtils.create(
|
||||
LandTable,
|
||||
BundeslandTable,
|
||||
AltersklasseTable,
|
||||
PlatzTable
|
||||
)
|
||||
log.info("Masterdata database schema initialized successfully")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
log.error("Failed to initialize database schema", e)
|
||||
throw e
|
||||
}
|
||||
try {
|
||||
// Database connection should be initialized by Spring Boot
|
||||
transaction {
|
||||
SchemaUtils.create(
|
||||
LandTable,
|
||||
BundeslandTable,
|
||||
AltersklasseTable,
|
||||
PlatzTable
|
||||
)
|
||||
log.info("Masterdata database schema initialized successfully")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
log.error("Failed to initialize database schema", e)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
fun closeDatabase() {
|
||||
log.info("Closing database connection for Masterdata Service...")
|
||||
try {
|
||||
DatabaseFactory.close()
|
||||
log.info("Database connection closed successfully")
|
||||
} catch (e: Exception) {
|
||||
log.error("Error closing database connection", e)
|
||||
}
|
||||
}
|
||||
@PreDestroy
|
||||
fun closeDatabase() {
|
||||
log.info("Closing Masterdata Service database configuration...")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -67,51 +59,31 @@ class MasterdataDatabaseConfiguration {
|
||||
@Profile("test")
|
||||
class MasterdataTestDatabaseConfiguration {
|
||||
|
||||
private val log = LoggerFactory.getLogger(MasterdataTestDatabaseConfiguration::class.java)
|
||||
private val log = LoggerFactory.getLogger(MasterdataTestDatabaseConfiguration::class.java)
|
||||
|
||||
@PostConstruct
|
||||
fun initializeTestDatabase() {
|
||||
log.info("Initializing test database connection for Masterdata Service...")
|
||||
@PostConstruct
|
||||
fun initializeTestDatabase() {
|
||||
log.info("Initializing test database schema for Masterdata Service...")
|
||||
|
||||
try {
|
||||
// Use H2 in-memory database for tests
|
||||
val testConfig = DatabaseConfig(
|
||||
jdbcUrl = "jdbc:h2:mem:masterdata_test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE",
|
||||
username = "sa",
|
||||
password = "",
|
||||
driverClassName = "org.h2.Driver",
|
||||
maxPoolSize = 5,
|
||||
minPoolSize = 1,
|
||||
autoMigrate = true
|
||||
)
|
||||
|
||||
DatabaseFactory.init(testConfig)
|
||||
log.info("Test database connection initialized successfully")
|
||||
|
||||
// Initialize database schema for tests
|
||||
transaction {
|
||||
SchemaUtils.create(
|
||||
LandTable,
|
||||
BundeslandTable,
|
||||
AltersklasseTable,
|
||||
PlatzTable
|
||||
)
|
||||
log.info("Test masterdata database schema initialized successfully")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
log.error("Failed to initialize test database connection", e)
|
||||
throw e
|
||||
}
|
||||
try {
|
||||
// Initialize database schema for tests
|
||||
transaction {
|
||||
SchemaUtils.create(
|
||||
LandTable,
|
||||
BundeslandTable,
|
||||
AltersklasseTable,
|
||||
PlatzTable
|
||||
)
|
||||
log.info("Test masterdata database schema initialized successfully")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
log.error("Failed to initialize test database schema", e)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
fun closeTestDatabase() {
|
||||
log.info("Closing test database connection for Masterdata Service...")
|
||||
try {
|
||||
DatabaseFactory.close()
|
||||
log.info("Test database connection closed successfully")
|
||||
} catch (e: Exception) {
|
||||
log.error("Error closing test database connection", e)
|
||||
}
|
||||
}
|
||||
@PreDestroy
|
||||
fun closeTestDatabase() {
|
||||
log.info("Closing test database configuration for Masterdata Service...")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user