docs: Migrationsplan für Projekt-Restrukturierung hinzugefügt
- Detaillierter Plan zur Migration von alter zu neuer Modulstruktur - Umfasst Überführung von shared-kernel zu core-Modulen - Definiert Migration von Fachdomänen zu bounded contexts: * master-data → masterdata-Module * member-management → members-Module * horse-registry → horses-Module * event-management → events-Module - Beschreibt Verlagerung von api-gateway zu infrastructure/gateway - Strukturiert nach Domain-driven Design Prinzipien - Berücksichtigt Clean Architecture Layering (domain, application, infrastructure, api)
This commit is contained in:
+390
@@ -0,0 +1,390 @@
|
||||
package at.mocode.masterdata.application.usecase
|
||||
|
||||
import at.mocode.core.domain.model.SparteE
|
||||
import at.mocode.masterdata.domain.model.AltersklasseDefinition
|
||||
import at.mocode.masterdata.domain.repository.AltersklasseRepository
|
||||
import at.mocode.core.utils.validation.ValidationResult
|
||||
import at.mocode.core.utils.validation.ValidationError
|
||||
import com.benasher44.uuid.Uuid
|
||||
import kotlinx.datetime.Clock
|
||||
|
||||
/**
|
||||
* Use case for creating and updating age class information.
|
||||
*
|
||||
* This use case encapsulates the business logic for age class management
|
||||
* including validation, duplicate checking, and persistence.
|
||||
*/
|
||||
class CreateAltersklasseUseCase(
|
||||
private val altersklasseRepository: AltersklasseRepository
|
||||
) {
|
||||
|
||||
/**
|
||||
* Request data for creating a new age class.
|
||||
*/
|
||||
data class CreateAltersklasseRequest(
|
||||
val altersklasseCode: String,
|
||||
val bezeichnung: String,
|
||||
val minAlter: Int? = null,
|
||||
val maxAlter: Int? = null,
|
||||
val stichtagRegelText: String? = "31.12. des laufenden Kalenderjahres",
|
||||
val sparteFilter: SparteE? = null,
|
||||
val geschlechtFilter: Char? = null,
|
||||
val oetoRegelReferenzId: Uuid? = null,
|
||||
val istAktiv: Boolean = true
|
||||
)
|
||||
|
||||
/**
|
||||
* Request data for updating an existing age class.
|
||||
*/
|
||||
data class UpdateAltersklasseRequest(
|
||||
val altersklasseId: Uuid,
|
||||
val altersklasseCode: String,
|
||||
val bezeichnung: String,
|
||||
val minAlter: Int? = null,
|
||||
val maxAlter: Int? = null,
|
||||
val stichtagRegelText: String? = "31.12. des laufenden Kalenderjahres",
|
||||
val sparteFilter: SparteE? = null,
|
||||
val geschlechtFilter: Char? = null,
|
||||
val oetoRegelReferenzId: Uuid? = null,
|
||||
val istAktiv: Boolean = true
|
||||
)
|
||||
|
||||
/**
|
||||
* Response data for age class creation.
|
||||
*/
|
||||
data class CreateAltersklasseResponse(
|
||||
val altersklasse: AltersklasseDefinition?,
|
||||
val success: Boolean,
|
||||
val errors: List<String> = emptyList()
|
||||
)
|
||||
|
||||
/**
|
||||
* Response data for age class update.
|
||||
*/
|
||||
data class UpdateAltersklasseResponse(
|
||||
val altersklasse: AltersklasseDefinition?,
|
||||
val success: Boolean,
|
||||
val errors: List<String> = emptyList()
|
||||
)
|
||||
|
||||
/**
|
||||
* Response data for age class deletion.
|
||||
*/
|
||||
data class DeleteAltersklasseResponse(
|
||||
val success: Boolean,
|
||||
val errors: List<String> = emptyList()
|
||||
)
|
||||
|
||||
/**
|
||||
* Creates a new age class after validation.
|
||||
*
|
||||
* @param request The age class creation request
|
||||
* @return CreateAltersklasseResponse with the created age class or validation errors
|
||||
*/
|
||||
suspend fun createAltersklasse(request: CreateAltersklasseRequest): CreateAltersklasseResponse {
|
||||
// Validate the request
|
||||
val validationResult = validateCreateRequest(request)
|
||||
if (!validationResult.isValid()) {
|
||||
val errors = (validationResult as ValidationResult.Invalid).errors.map { it.message }
|
||||
return CreateAltersklasseResponse(
|
||||
altersklasse = null,
|
||||
success = false,
|
||||
errors = errors
|
||||
)
|
||||
}
|
||||
|
||||
// Check for duplicates
|
||||
val duplicateCheck = checkForDuplicates(request.altersklasseCode)
|
||||
if (!duplicateCheck.isValid()) {
|
||||
val errors = (duplicateCheck as ValidationResult.Invalid).errors.map { it.message }
|
||||
return CreateAltersklasseResponse(
|
||||
altersklasse = null,
|
||||
success = false,
|
||||
errors = errors
|
||||
)
|
||||
}
|
||||
|
||||
// Create the domain object
|
||||
val now = Clock.System.now()
|
||||
val altersklasse = AltersklasseDefinition(
|
||||
altersklasseCode = request.altersklasseCode.trim().uppercase(),
|
||||
bezeichnung = request.bezeichnung.trim(),
|
||||
minAlter = request.minAlter,
|
||||
maxAlter = request.maxAlter,
|
||||
stichtagRegelText = request.stichtagRegelText?.trim(),
|
||||
sparteFilter = request.sparteFilter,
|
||||
geschlechtFilter = request.geschlechtFilter,
|
||||
oetoRegelReferenzId = request.oetoRegelReferenzId,
|
||||
istAktiv = request.istAktiv,
|
||||
createdAt = now,
|
||||
updatedAt = now
|
||||
)
|
||||
|
||||
// Save to repository
|
||||
val savedAltersklasse = altersklasseRepository.save(altersklasse)
|
||||
return CreateAltersklasseResponse(
|
||||
altersklasse = savedAltersklasse,
|
||||
success = true
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an existing age class after validation.
|
||||
*
|
||||
* @param request The age class update request
|
||||
* @return UpdateAltersklasseResponse containing the updated age class or validation errors
|
||||
*/
|
||||
suspend fun updateAltersklasse(request: UpdateAltersklasseRequest): UpdateAltersklasseResponse {
|
||||
// Check if age class exists
|
||||
val existingAltersklasse = altersklasseRepository.findById(request.altersklasseId)
|
||||
if (existingAltersklasse == null) {
|
||||
return UpdateAltersklasseResponse(
|
||||
altersklasse = null,
|
||||
success = false,
|
||||
errors = listOf("Age class with ID ${request.altersklasseId} not found")
|
||||
)
|
||||
}
|
||||
|
||||
// Validate the request
|
||||
val validationResult = validateUpdateRequest(request)
|
||||
if (!validationResult.isValid()) {
|
||||
val errors = (validationResult as ValidationResult.Invalid).errors.map { it.message }
|
||||
return UpdateAltersklasseResponse(
|
||||
altersklasse = null,
|
||||
success = false,
|
||||
errors = errors
|
||||
)
|
||||
}
|
||||
|
||||
// Check for duplicates (excluding current age class)
|
||||
val duplicateCheck = checkForDuplicatesExcluding(request.altersklasseCode, request.altersklasseId)
|
||||
if (!duplicateCheck.isValid()) {
|
||||
val errors = (duplicateCheck as ValidationResult.Invalid).errors.map { it.message }
|
||||
return UpdateAltersklasseResponse(
|
||||
altersklasse = null,
|
||||
success = false,
|
||||
errors = errors
|
||||
)
|
||||
}
|
||||
|
||||
// Update the domain object
|
||||
val updatedAltersklasse = existingAltersklasse.copy(
|
||||
altersklasseCode = request.altersklasseCode.trim().uppercase(),
|
||||
bezeichnung = request.bezeichnung.trim(),
|
||||
minAlter = request.minAlter,
|
||||
maxAlter = request.maxAlter,
|
||||
stichtagRegelText = request.stichtagRegelText?.trim(),
|
||||
sparteFilter = request.sparteFilter,
|
||||
geschlechtFilter = request.geschlechtFilter,
|
||||
oetoRegelReferenzId = request.oetoRegelReferenzId,
|
||||
istAktiv = request.istAktiv,
|
||||
updatedAt = Clock.System.now()
|
||||
)
|
||||
|
||||
// Save to repository
|
||||
val savedAltersklasse = altersklasseRepository.save(updatedAltersklasse)
|
||||
return UpdateAltersklasseResponse(
|
||||
altersklasse = savedAltersklasse,
|
||||
success = true
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes an age class by ID.
|
||||
*
|
||||
* @param altersklasseId The unique identifier of the age class to delete
|
||||
* @return DeleteAltersklasseResponse indicating success or failure
|
||||
*/
|
||||
suspend fun deleteAltersklasse(altersklasseId: Uuid): DeleteAltersklasseResponse {
|
||||
val deleted = altersklasseRepository.delete(altersklasseId)
|
||||
return if (deleted) {
|
||||
DeleteAltersklasseResponse(success = true)
|
||||
} else {
|
||||
DeleteAltersklasseResponse(
|
||||
success = false,
|
||||
errors = listOf("Age class with ID $altersklasseId not found or could not be deleted")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a create age class request.
|
||||
*/
|
||||
private fun validateCreateRequest(request: CreateAltersklasseRequest): ValidationResult {
|
||||
val errors = mutableListOf<ValidationError>()
|
||||
|
||||
// Age class code validation
|
||||
if (request.altersklasseCode.isBlank()) {
|
||||
errors.add(ValidationError("altersklasseCode", "Age class code is required", "REQUIRED"))
|
||||
} else if (request.altersklasseCode.length > 50) {
|
||||
errors.add(ValidationError("altersklasseCode", "Age class code must not exceed 50 characters", "MAX_LENGTH"))
|
||||
} else if (!request.altersklasseCode.matches(Regex("^[A-Z0-9_]+$"))) {
|
||||
errors.add(ValidationError("altersklasseCode", "Age class code must contain only uppercase letters, numbers, and underscores", "INVALID_FORMAT"))
|
||||
}
|
||||
|
||||
// Bezeichnung validation
|
||||
if (request.bezeichnung.isBlank()) {
|
||||
errors.add(ValidationError("bezeichnung", "Bezeichnung is required", "REQUIRED"))
|
||||
} else if (request.bezeichnung.length > 200) {
|
||||
errors.add(ValidationError("bezeichnung", "Bezeichnung must not exceed 200 characters", "MAX_LENGTH"))
|
||||
}
|
||||
|
||||
// Age range validation
|
||||
request.minAlter?.let { min ->
|
||||
if (min < 0) {
|
||||
errors.add(ValidationError("minAlter", "Minimum age must be non-negative", "INVALID_VALUE"))
|
||||
}
|
||||
}
|
||||
|
||||
request.maxAlter?.let { max ->
|
||||
if (max < 0) {
|
||||
errors.add(ValidationError("maxAlter", "Maximum age must be non-negative", "INVALID_VALUE"))
|
||||
}
|
||||
request.minAlter?.let { min ->
|
||||
if (max < min) {
|
||||
errors.add(ValidationError("maxAlter", "Maximum age must be greater than or equal to minimum age", "INVALID_RANGE"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stichtag regel text validation
|
||||
request.stichtagRegelText?.let { text ->
|
||||
if (text.length > 500) {
|
||||
errors.add(ValidationError("stichtagRegelText", "Stichtag regel text must not exceed 500 characters", "MAX_LENGTH"))
|
||||
}
|
||||
}
|
||||
|
||||
// Gender filter validation
|
||||
request.geschlechtFilter?.let { gender ->
|
||||
if (gender != 'M' && gender != 'W') {
|
||||
errors.add(ValidationError("geschlechtFilter", "Gender filter must be 'M' or 'W'", "INVALID_VALUE"))
|
||||
}
|
||||
}
|
||||
|
||||
return if (errors.isEmpty()) {
|
||||
ValidationResult.Valid
|
||||
} else {
|
||||
ValidationResult.Invalid(errors)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates an update age class request.
|
||||
*/
|
||||
private fun validateUpdateRequest(request: UpdateAltersklasseRequest): ValidationResult {
|
||||
// Use the same validation logic as create request
|
||||
val createRequest = CreateAltersklasseRequest(
|
||||
altersklasseCode = request.altersklasseCode,
|
||||
bezeichnung = request.bezeichnung,
|
||||
minAlter = request.minAlter,
|
||||
maxAlter = request.maxAlter,
|
||||
stichtagRegelText = request.stichtagRegelText,
|
||||
sparteFilter = request.sparteFilter,
|
||||
geschlechtFilter = request.geschlechtFilter,
|
||||
oetoRegelReferenzId = request.oetoRegelReferenzId,
|
||||
istAktiv = request.istAktiv
|
||||
)
|
||||
return validateCreateRequest(createRequest)
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for duplicate age class codes.
|
||||
*/
|
||||
private suspend fun checkForDuplicates(altersklasseCode: String): ValidationResult {
|
||||
val errors = mutableListOf<ValidationError>()
|
||||
|
||||
if (altersklasseRepository.existsByCode(altersklasseCode.trim().uppercase())) {
|
||||
errors.add(ValidationError("altersklasseCode", "Age class with code '${altersklasseCode.uppercase()}' already exists", "DUPLICATE"))
|
||||
}
|
||||
|
||||
return if (errors.isEmpty()) {
|
||||
ValidationResult.Valid
|
||||
} else {
|
||||
ValidationResult.Invalid(errors)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for duplicate age class codes excluding a specific age class ID.
|
||||
*/
|
||||
private suspend fun checkForDuplicatesExcluding(altersklasseCode: String, excludeId: Uuid): ValidationResult {
|
||||
val errors = mutableListOf<ValidationError>()
|
||||
|
||||
// Check code
|
||||
val existing = altersklasseRepository.findByCode(altersklasseCode.trim().uppercase())
|
||||
if (existing != null && existing.altersklasseId != excludeId) {
|
||||
errors.add(ValidationError("altersklasseCode", "Age class with code '${altersklasseCode.uppercase()}' already exists", "DUPLICATE"))
|
||||
}
|
||||
|
||||
return if (errors.isEmpty()) {
|
||||
ValidationResult.Valid
|
||||
} else {
|
||||
ValidationResult.Invalid(errors)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates age eligibility for a specific age class and participant.
|
||||
* This is a business logic method that can be used by other parts of the application.
|
||||
*
|
||||
* @param altersklasseId The age class ID
|
||||
* @param participantAge The participant's age
|
||||
* @param participantGender The participant's gender ('M', 'W')
|
||||
* @param participantSparte The participant's sport type
|
||||
* @return ValidationResult indicating eligibility or reasons for ineligibility
|
||||
*/
|
||||
suspend fun validateEligibility(
|
||||
altersklasseId: Uuid,
|
||||
participantAge: Int,
|
||||
participantGender: Char,
|
||||
participantSparte: SparteE
|
||||
): ValidationResult {
|
||||
val errors = mutableListOf<ValidationError>()
|
||||
|
||||
// Get the age class
|
||||
val altersklasse = altersklasseRepository.findById(altersklasseId)
|
||||
if (altersklasse == null) {
|
||||
errors.add(ValidationError("altersklasseId", "Age class not found", "NOT_FOUND"))
|
||||
return ValidationResult.Invalid(errors)
|
||||
}
|
||||
|
||||
// Check if age class is active
|
||||
if (!altersklasse.istAktiv) {
|
||||
errors.add(ValidationError("altersklasse", "Age class is not active", "INACTIVE"))
|
||||
}
|
||||
|
||||
// Check age eligibility
|
||||
altersklasse.minAlter?.let { min ->
|
||||
if (participantAge < min) {
|
||||
errors.add(ValidationError("age", "Participant is too young for this age class (minimum age: $min)", "AGE_TOO_LOW"))
|
||||
}
|
||||
}
|
||||
|
||||
altersklasse.maxAlter?.let { max ->
|
||||
if (participantAge > max) {
|
||||
errors.add(ValidationError("age", "Participant is too old for this age class (maximum age: $max)", "AGE_TOO_HIGH"))
|
||||
}
|
||||
}
|
||||
|
||||
// Check gender eligibility
|
||||
altersklasse.geschlechtFilter?.let { requiredGender ->
|
||||
if (participantGender != requiredGender) {
|
||||
val genderName = if (requiredGender == 'M') "male" else "female"
|
||||
errors.add(ValidationError("gender", "This age class is only for $genderName participants", "GENDER_MISMATCH"))
|
||||
}
|
||||
}
|
||||
|
||||
// Check sport eligibility
|
||||
altersklasse.sparteFilter?.let { requiredSparte ->
|
||||
if (participantSparte != requiredSparte) {
|
||||
errors.add(ValidationError("sparte", "This age class is only for ${requiredSparte.name} sport", "SPORT_MISMATCH"))
|
||||
}
|
||||
}
|
||||
|
||||
return if (errors.isEmpty()) {
|
||||
ValidationResult.Valid
|
||||
} else {
|
||||
ValidationResult.Invalid(errors)
|
||||
}
|
||||
}
|
||||
}
|
||||
+338
@@ -0,0 +1,338 @@
|
||||
package at.mocode.masterdata.application.usecase
|
||||
|
||||
import at.mocode.masterdata.domain.model.BundeslandDefinition
|
||||
import at.mocode.masterdata.domain.repository.BundeslandRepository
|
||||
import at.mocode.core.utils.validation.ValidationResult
|
||||
import at.mocode.core.utils.validation.ValidationError
|
||||
import com.benasher44.uuid.Uuid
|
||||
import kotlinx.datetime.Clock
|
||||
|
||||
/**
|
||||
* Use case for creating and updating federal state information.
|
||||
*
|
||||
* This use case encapsulates the business logic for federal state management
|
||||
* including validation, duplicate checking, and persistence.
|
||||
*/
|
||||
class CreateBundeslandUseCase(
|
||||
private val bundeslandRepository: BundeslandRepository
|
||||
) {
|
||||
|
||||
/**
|
||||
* Request data for creating a new federal state.
|
||||
*/
|
||||
data class CreateBundeslandRequest(
|
||||
val landId: Uuid,
|
||||
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
|
||||
)
|
||||
|
||||
/**
|
||||
* Request data for updating an existing federal state.
|
||||
*/
|
||||
data class UpdateBundeslandRequest(
|
||||
val bundeslandId: Uuid,
|
||||
val landId: Uuid,
|
||||
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
|
||||
)
|
||||
|
||||
/**
|
||||
* Response data for federal state creation.
|
||||
*/
|
||||
data class CreateBundeslandResponse(
|
||||
val bundesland: BundeslandDefinition?,
|
||||
val success: Boolean,
|
||||
val errors: List<String> = emptyList()
|
||||
)
|
||||
|
||||
/**
|
||||
* Response data for federal state update.
|
||||
*/
|
||||
data class UpdateBundeslandResponse(
|
||||
val bundesland: BundeslandDefinition?,
|
||||
val success: Boolean,
|
||||
val errors: List<String> = emptyList()
|
||||
)
|
||||
|
||||
/**
|
||||
* Response data for federal state deletion.
|
||||
*/
|
||||
data class DeleteBundeslandResponse(
|
||||
val success: Boolean,
|
||||
val errors: List<String> = emptyList()
|
||||
)
|
||||
|
||||
/**
|
||||
* Creates a new federal state after validation.
|
||||
*
|
||||
* @param request The federal state creation request
|
||||
* @return CreateBundeslandResponse with the created federal state or validation errors
|
||||
*/
|
||||
suspend fun createBundesland(request: CreateBundeslandRequest): CreateBundeslandResponse {
|
||||
// Validate the request
|
||||
val validationResult = validateCreateRequest(request)
|
||||
if (!validationResult.isValid()) {
|
||||
val errors = (validationResult as ValidationResult.Invalid).errors.map { it.message }
|
||||
return CreateBundeslandResponse(
|
||||
bundesland = null,
|
||||
success = false,
|
||||
errors = errors
|
||||
)
|
||||
}
|
||||
|
||||
// Check for duplicates
|
||||
val duplicateCheck = checkForDuplicates(request.oepsCode, request.iso3166_2_Code, request.landId)
|
||||
if (!duplicateCheck.isValid()) {
|
||||
val errors = (duplicateCheck as ValidationResult.Invalid).errors.map { it.message }
|
||||
return CreateBundeslandResponse(
|
||||
bundesland = null,
|
||||
success = false,
|
||||
errors = errors
|
||||
)
|
||||
}
|
||||
|
||||
// Create the domain object
|
||||
val now = Clock.System.now()
|
||||
val bundesland = BundeslandDefinition(
|
||||
landId = request.landId,
|
||||
oepsCode = request.oepsCode?.trim(),
|
||||
iso3166_2_Code = request.iso3166_2_Code?.trim()?.uppercase(),
|
||||
name = request.name.trim(),
|
||||
kuerzel = request.kuerzel?.trim(),
|
||||
wappenUrl = request.wappenUrl?.trim(),
|
||||
istAktiv = request.istAktiv,
|
||||
sortierReihenfolge = request.sortierReihenfolge,
|
||||
createdAt = now,
|
||||
updatedAt = now
|
||||
)
|
||||
|
||||
// Save to repository
|
||||
val savedBundesland = bundeslandRepository.save(bundesland)
|
||||
return CreateBundeslandResponse(
|
||||
bundesland = savedBundesland,
|
||||
success = true
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an existing federal state after validation.
|
||||
*
|
||||
* @param request The federal state update request
|
||||
* @return UpdateBundeslandResponse containing the updated federal state or validation errors
|
||||
*/
|
||||
suspend fun updateBundesland(request: UpdateBundeslandRequest): UpdateBundeslandResponse {
|
||||
// Check if federal state exists
|
||||
val existingBundesland = bundeslandRepository.findById(request.bundeslandId)
|
||||
if (existingBundesland == null) {
|
||||
return UpdateBundeslandResponse(
|
||||
bundesland = null,
|
||||
success = false,
|
||||
errors = listOf("Federal state with ID ${request.bundeslandId} not found")
|
||||
)
|
||||
}
|
||||
|
||||
// Validate the request
|
||||
val validationResult = validateUpdateRequest(request)
|
||||
if (!validationResult.isValid()) {
|
||||
val errors = (validationResult as ValidationResult.Invalid).errors.map { it.message }
|
||||
return UpdateBundeslandResponse(
|
||||
bundesland = null,
|
||||
success = false,
|
||||
errors = errors
|
||||
)
|
||||
}
|
||||
|
||||
// Check for duplicates (excluding current federal state)
|
||||
val duplicateCheck = checkForDuplicatesExcluding(
|
||||
request.oepsCode,
|
||||
request.iso3166_2_Code,
|
||||
request.landId,
|
||||
request.bundeslandId
|
||||
)
|
||||
if (!duplicateCheck.isValid()) {
|
||||
val errors = (duplicateCheck as ValidationResult.Invalid).errors.map { it.message }
|
||||
return UpdateBundeslandResponse(
|
||||
bundesland = null,
|
||||
success = false,
|
||||
errors = errors
|
||||
)
|
||||
}
|
||||
|
||||
// Update the domain object
|
||||
val updatedBundesland = existingBundesland.copy(
|
||||
landId = request.landId,
|
||||
oepsCode = request.oepsCode?.trim(),
|
||||
iso3166_2_Code = request.iso3166_2_Code?.trim()?.uppercase(),
|
||||
name = request.name.trim(),
|
||||
kuerzel = request.kuerzel?.trim(),
|
||||
wappenUrl = request.wappenUrl?.trim(),
|
||||
istAktiv = request.istAktiv,
|
||||
sortierReihenfolge = request.sortierReihenfolge,
|
||||
updatedAt = Clock.System.now()
|
||||
)
|
||||
|
||||
// Save to repository
|
||||
val savedBundesland = bundeslandRepository.save(updatedBundesland)
|
||||
return UpdateBundeslandResponse(
|
||||
bundesland = savedBundesland,
|
||||
success = true
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a federal state by ID.
|
||||
*
|
||||
* @param bundeslandId The unique identifier of the federal state to delete
|
||||
* @return DeleteBundeslandResponse indicating success or failure
|
||||
*/
|
||||
suspend fun deleteBundesland(bundeslandId: Uuid): DeleteBundeslandResponse {
|
||||
val deleted = bundeslandRepository.delete(bundeslandId)
|
||||
return if (deleted) {
|
||||
DeleteBundeslandResponse(success = true)
|
||||
} else {
|
||||
DeleteBundeslandResponse(
|
||||
success = false,
|
||||
errors = listOf("Federal state with ID $bundeslandId not found or could not be deleted")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a create federal state request.
|
||||
*/
|
||||
private fun validateCreateRequest(request: CreateBundeslandRequest): ValidationResult {
|
||||
val errors = mutableListOf<ValidationError>()
|
||||
|
||||
// Name validation
|
||||
if (request.name.isBlank()) {
|
||||
errors.add(ValidationError("name", "Name is required", "REQUIRED"))
|
||||
} else if (request.name.length > 100) {
|
||||
errors.add(ValidationError("name", "Name must not exceed 100 characters", "MAX_LENGTH"))
|
||||
}
|
||||
|
||||
// OEPS code validation
|
||||
request.oepsCode?.let { code ->
|
||||
if (code.isBlank()) {
|
||||
errors.add(ValidationError("oepsCode", "OEPS code cannot be empty if provided", "INVALID_FORMAT"))
|
||||
} else if (code.length > 10) {
|
||||
errors.add(ValidationError("oepsCode", "OEPS code must not exceed 10 characters", "MAX_LENGTH"))
|
||||
}
|
||||
}
|
||||
|
||||
// ISO 3166-2 code validation
|
||||
request.iso3166_2_Code?.let { code ->
|
||||
if (code.isBlank()) {
|
||||
errors.add(ValidationError("iso3166_2_Code", "ISO 3166-2 code cannot be empty if provided", "INVALID_FORMAT"))
|
||||
} else if (code.length > 10) {
|
||||
errors.add(ValidationError("iso3166_2_Code", "ISO 3166-2 code must not exceed 10 characters", "MAX_LENGTH"))
|
||||
}
|
||||
}
|
||||
|
||||
// Kuerzel validation
|
||||
request.kuerzel?.let { kuerzel ->
|
||||
if (kuerzel.length > 10) {
|
||||
errors.add(ValidationError("kuerzel", "Kuerzel must not exceed 10 characters", "MAX_LENGTH"))
|
||||
}
|
||||
}
|
||||
|
||||
// Sorting order validation
|
||||
request.sortierReihenfolge?.let { order ->
|
||||
if (order < 0) {
|
||||
errors.add(ValidationError("sortierReihenfolge", "Sorting order must be non-negative", "INVALID_VALUE"))
|
||||
}
|
||||
}
|
||||
|
||||
return if (errors.isEmpty()) {
|
||||
ValidationResult.Valid
|
||||
} else {
|
||||
ValidationResult.Invalid(errors)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates an update federal state request.
|
||||
*/
|
||||
private fun validateUpdateRequest(request: UpdateBundeslandRequest): ValidationResult {
|
||||
// Use the same validation logic as create request
|
||||
val createRequest = CreateBundeslandRequest(
|
||||
landId = request.landId,
|
||||
oepsCode = request.oepsCode,
|
||||
iso3166_2_Code = request.iso3166_2_Code,
|
||||
name = request.name,
|
||||
kuerzel = request.kuerzel,
|
||||
wappenUrl = request.wappenUrl,
|
||||
istAktiv = request.istAktiv,
|
||||
sortierReihenfolge = request.sortierReihenfolge
|
||||
)
|
||||
return validateCreateRequest(createRequest)
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for duplicate codes.
|
||||
*/
|
||||
private suspend fun checkForDuplicates(oepsCode: String?, iso3166_2_Code: String?, landId: Uuid): ValidationResult {
|
||||
val errors = mutableListOf<ValidationError>()
|
||||
|
||||
oepsCode?.let { code ->
|
||||
if (bundeslandRepository.existsByOepsCode(code.trim(), landId)) {
|
||||
errors.add(ValidationError("oepsCode", "Federal state with OEPS code '$code' already exists for this country", "DUPLICATE"))
|
||||
}
|
||||
}
|
||||
|
||||
iso3166_2_Code?.let { code ->
|
||||
if (bundeslandRepository.existsByIso3166_2_Code(code.trim().uppercase())) {
|
||||
errors.add(ValidationError("iso3166_2_Code", "Federal state with ISO 3166-2 code '${code.uppercase()}' already exists", "DUPLICATE"))
|
||||
}
|
||||
}
|
||||
|
||||
return if (errors.isEmpty()) {
|
||||
ValidationResult.Valid
|
||||
} else {
|
||||
ValidationResult.Invalid(errors)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for duplicate codes excluding a specific federal state ID.
|
||||
*/
|
||||
private suspend fun checkForDuplicatesExcluding(
|
||||
oepsCode: String?,
|
||||
iso3166_2_Code: String?,
|
||||
landId: Uuid,
|
||||
excludeId: Uuid
|
||||
): ValidationResult {
|
||||
val errors = mutableListOf<ValidationError>()
|
||||
|
||||
// Check OEPS code
|
||||
oepsCode?.let { code ->
|
||||
val existing = bundeslandRepository.findByOepsCode(code.trim(), landId)
|
||||
if (existing != null && existing.bundeslandId != excludeId) {
|
||||
errors.add(ValidationError("oepsCode", "Federal state with OEPS code '$code' already exists for this country", "DUPLICATE"))
|
||||
}
|
||||
}
|
||||
|
||||
// Check ISO 3166-2 code
|
||||
iso3166_2_Code?.let { code ->
|
||||
val existing = bundeslandRepository.findByIso3166_2_Code(code.trim().uppercase())
|
||||
if (existing != null && existing.bundeslandId != excludeId) {
|
||||
errors.add(ValidationError("iso3166_2_Code", "Federal state with ISO 3166-2 code '${code.uppercase()}' already exists", "DUPLICATE"))
|
||||
}
|
||||
}
|
||||
|
||||
return if (errors.isEmpty()) {
|
||||
ValidationResult.Valid
|
||||
} else {
|
||||
ValidationResult.Invalid(errors)
|
||||
}
|
||||
}
|
||||
}
|
||||
+455
@@ -0,0 +1,455 @@
|
||||
package at.mocode.masterdata.application.usecase
|
||||
|
||||
import at.mocode.core.domain.model.PlatzTypE
|
||||
import at.mocode.masterdata.domain.model.Platz
|
||||
import at.mocode.masterdata.domain.repository.PlatzRepository
|
||||
import at.mocode.core.utils.validation.ValidationResult
|
||||
import at.mocode.core.utils.validation.ValidationError
|
||||
import com.benasher44.uuid.Uuid
|
||||
import kotlinx.datetime.Clock
|
||||
|
||||
/**
|
||||
* Use case for creating and updating venue/arena information.
|
||||
*
|
||||
* This use case encapsulates the business logic for venue management
|
||||
* including validation, duplicate checking, and persistence.
|
||||
*/
|
||||
class CreatePlatzUseCase(
|
||||
private val platzRepository: PlatzRepository
|
||||
) {
|
||||
|
||||
/**
|
||||
* Request data for creating a new venue.
|
||||
*/
|
||||
data class CreatePlatzRequest(
|
||||
val turnierId: Uuid,
|
||||
val name: String,
|
||||
val dimension: String? = null,
|
||||
val boden: String? = null,
|
||||
val typ: PlatzTypE,
|
||||
val istAktiv: Boolean = true,
|
||||
val sortierReihenfolge: Int? = null
|
||||
)
|
||||
|
||||
/**
|
||||
* Request data for updating an existing venue.
|
||||
*/
|
||||
data class UpdatePlatzRequest(
|
||||
val platzId: Uuid,
|
||||
val turnierId: Uuid,
|
||||
val name: String,
|
||||
val dimension: String? = null,
|
||||
val boden: String? = null,
|
||||
val typ: PlatzTypE,
|
||||
val istAktiv: Boolean = true,
|
||||
val sortierReihenfolge: Int? = null
|
||||
)
|
||||
|
||||
/**
|
||||
* Response data for venue creation.
|
||||
*/
|
||||
data class CreatePlatzResponse(
|
||||
val platz: Platz?,
|
||||
val success: Boolean,
|
||||
val errors: List<String> = emptyList()
|
||||
)
|
||||
|
||||
/**
|
||||
* Response data for venue update.
|
||||
*/
|
||||
data class UpdatePlatzResponse(
|
||||
val platz: Platz?,
|
||||
val success: Boolean,
|
||||
val errors: List<String> = emptyList()
|
||||
)
|
||||
|
||||
/**
|
||||
* Response data for venue deletion.
|
||||
*/
|
||||
data class DeletePlatzResponse(
|
||||
val success: Boolean,
|
||||
val errors: List<String> = emptyList()
|
||||
)
|
||||
|
||||
/**
|
||||
* Creates a new venue after validation.
|
||||
*
|
||||
* @param request The venue creation request
|
||||
* @return CreatePlatzResponse with the created venue or validation errors
|
||||
*/
|
||||
suspend fun createPlatz(request: CreatePlatzRequest): CreatePlatzResponse {
|
||||
// Validate the request
|
||||
val validationResult = validateCreateRequest(request)
|
||||
if (!validationResult.isValid()) {
|
||||
val errors = (validationResult as ValidationResult.Invalid).errors.map { it.message }
|
||||
return CreatePlatzResponse(
|
||||
platz = null,
|
||||
success = false,
|
||||
errors = errors
|
||||
)
|
||||
}
|
||||
|
||||
// Check for duplicates
|
||||
val duplicateCheck = checkForDuplicates(request.name, request.turnierId)
|
||||
if (!duplicateCheck.isValid()) {
|
||||
val errors = (duplicateCheck as ValidationResult.Invalid).errors.map { it.message }
|
||||
return CreatePlatzResponse(
|
||||
platz = null,
|
||||
success = false,
|
||||
errors = errors
|
||||
)
|
||||
}
|
||||
|
||||
// Create the domain object
|
||||
val now = Clock.System.now()
|
||||
val platz = Platz(
|
||||
turnierId = request.turnierId,
|
||||
name = request.name.trim(),
|
||||
dimension = request.dimension?.trim(),
|
||||
boden = request.boden?.trim(),
|
||||
typ = request.typ,
|
||||
istAktiv = request.istAktiv,
|
||||
sortierReihenfolge = request.sortierReihenfolge,
|
||||
createdAt = now,
|
||||
updatedAt = now
|
||||
)
|
||||
|
||||
// Save to repository
|
||||
val savedPlatz = platzRepository.save(platz)
|
||||
return CreatePlatzResponse(
|
||||
platz = savedPlatz,
|
||||
success = true
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an existing venue after validation.
|
||||
*
|
||||
* @param request The venue update request
|
||||
* @return UpdatePlatzResponse containing the updated venue or validation errors
|
||||
*/
|
||||
suspend fun updatePlatz(request: UpdatePlatzRequest): UpdatePlatzResponse {
|
||||
// Check if venue exists
|
||||
val existingPlatz = platzRepository.findById(request.platzId)
|
||||
if (existingPlatz == null) {
|
||||
return UpdatePlatzResponse(
|
||||
platz = null,
|
||||
success = false,
|
||||
errors = listOf("Venue with ID ${request.platzId} not found")
|
||||
)
|
||||
}
|
||||
|
||||
// Validate the request
|
||||
val validationResult = validateUpdateRequest(request)
|
||||
if (!validationResult.isValid()) {
|
||||
val errors = (validationResult as ValidationResult.Invalid).errors.map { it.message }
|
||||
return UpdatePlatzResponse(
|
||||
platz = null,
|
||||
success = false,
|
||||
errors = errors
|
||||
)
|
||||
}
|
||||
|
||||
// Check for duplicates (excluding current venue)
|
||||
val duplicateCheck = checkForDuplicatesExcluding(request.name, request.turnierId, request.platzId)
|
||||
if (!duplicateCheck.isValid()) {
|
||||
val errors = (duplicateCheck as ValidationResult.Invalid).errors.map { it.message }
|
||||
return UpdatePlatzResponse(
|
||||
platz = null,
|
||||
success = false,
|
||||
errors = errors
|
||||
)
|
||||
}
|
||||
|
||||
// Update the domain object
|
||||
val updatedPlatz = existingPlatz.copy(
|
||||
turnierId = request.turnierId,
|
||||
name = request.name.trim(),
|
||||
dimension = request.dimension?.trim(),
|
||||
boden = request.boden?.trim(),
|
||||
typ = request.typ,
|
||||
istAktiv = request.istAktiv,
|
||||
sortierReihenfolge = request.sortierReihenfolge,
|
||||
updatedAt = Clock.System.now()
|
||||
)
|
||||
|
||||
// Save to repository
|
||||
val savedPlatz = platzRepository.save(updatedPlatz)
|
||||
return UpdatePlatzResponse(
|
||||
platz = savedPlatz,
|
||||
success = true
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a venue by ID.
|
||||
*
|
||||
* @param platzId The unique identifier of the venue to delete
|
||||
* @return DeletePlatzResponse indicating success or failure
|
||||
*/
|
||||
suspend fun deletePlatz(platzId: Uuid): DeletePlatzResponse {
|
||||
val deleted = platzRepository.delete(platzId)
|
||||
return if (deleted) {
|
||||
DeletePlatzResponse(success = true)
|
||||
} else {
|
||||
DeletePlatzResponse(
|
||||
success = false,
|
||||
errors = listOf("Venue with ID $platzId not found or could not be deleted")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a create venue request.
|
||||
*/
|
||||
private fun validateCreateRequest(request: CreatePlatzRequest): ValidationResult {
|
||||
val errors = mutableListOf<ValidationError>()
|
||||
|
||||
// Name validation
|
||||
if (request.name.isBlank()) {
|
||||
errors.add(ValidationError("name", "Name is required", "REQUIRED"))
|
||||
} else if (request.name.length > 200) {
|
||||
errors.add(ValidationError("name", "Name must not exceed 200 characters", "MAX_LENGTH"))
|
||||
}
|
||||
|
||||
// Dimension validation
|
||||
request.dimension?.let { dimension ->
|
||||
if (dimension.isBlank()) {
|
||||
errors.add(ValidationError("dimension", "Dimension cannot be empty if provided", "INVALID_FORMAT"))
|
||||
} else if (dimension.length > 50) {
|
||||
errors.add(ValidationError("dimension", "Dimension must not exceed 50 characters", "MAX_LENGTH"))
|
||||
} else if (!dimension.matches(Regex("^\\d+x\\d+m?$"))) {
|
||||
errors.add(ValidationError("dimension", "Dimension must be in format like '20x60m' or '20x40'", "INVALID_FORMAT"))
|
||||
}
|
||||
}
|
||||
|
||||
// Ground type validation
|
||||
request.boden?.let { boden ->
|
||||
if (boden.isBlank()) {
|
||||
errors.add(ValidationError("boden", "Ground type cannot be empty if provided", "INVALID_FORMAT"))
|
||||
} else if (boden.length > 100) {
|
||||
errors.add(ValidationError("boden", "Ground type must not exceed 100 characters", "MAX_LENGTH"))
|
||||
}
|
||||
}
|
||||
|
||||
// Sorting order validation
|
||||
request.sortierReihenfolge?.let { order ->
|
||||
if (order < 0) {
|
||||
errors.add(ValidationError("sortierReihenfolge", "Sorting order must be non-negative", "INVALID_VALUE"))
|
||||
}
|
||||
}
|
||||
|
||||
return if (errors.isEmpty()) {
|
||||
ValidationResult.Valid
|
||||
} else {
|
||||
ValidationResult.Invalid(errors)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates an update venue request.
|
||||
*/
|
||||
private fun validateUpdateRequest(request: UpdatePlatzRequest): ValidationResult {
|
||||
// Use the same validation logic as create request
|
||||
val createRequest = CreatePlatzRequest(
|
||||
turnierId = request.turnierId,
|
||||
name = request.name,
|
||||
dimension = request.dimension,
|
||||
boden = request.boden,
|
||||
typ = request.typ,
|
||||
istAktiv = request.istAktiv,
|
||||
sortierReihenfolge = request.sortierReihenfolge
|
||||
)
|
||||
return validateCreateRequest(createRequest)
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for duplicate venue names within a tournament.
|
||||
*/
|
||||
private suspend fun checkForDuplicates(name: String, turnierId: Uuid): ValidationResult {
|
||||
val errors = mutableListOf<ValidationError>()
|
||||
|
||||
if (platzRepository.existsByNameAndTournament(name.trim(), turnierId)) {
|
||||
errors.add(ValidationError("name", "Venue with name '$name' already exists for this tournament", "DUPLICATE"))
|
||||
}
|
||||
|
||||
return if (errors.isEmpty()) {
|
||||
ValidationResult.Valid
|
||||
} else {
|
||||
ValidationResult.Invalid(errors)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for duplicate venue names excluding a specific venue ID.
|
||||
*/
|
||||
private suspend fun checkForDuplicatesExcluding(name: String, turnierId: Uuid, excludeId: Uuid): ValidationResult {
|
||||
val errors = mutableListOf<ValidationError>()
|
||||
|
||||
// Get all venues with the same name and tournament
|
||||
val existingVenues = platzRepository.findByName(name.trim(), turnierId, 10)
|
||||
val duplicateExists = existingVenues.any { it.id != excludeId }
|
||||
|
||||
if (duplicateExists) {
|
||||
errors.add(ValidationError("name", "Venue with name '$name' already exists for this tournament", "DUPLICATE"))
|
||||
}
|
||||
|
||||
return if (errors.isEmpty()) {
|
||||
ValidationResult.Valid
|
||||
} else {
|
||||
ValidationResult.Invalid(errors)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates venue configuration for specific discipline requirements.
|
||||
* This is a business logic method that can be used by other parts of the application.
|
||||
*
|
||||
* @param platzId The venue ID
|
||||
* @param requiredType The required venue type for the discipline
|
||||
* @param requiredDimensions Optional required dimensions
|
||||
* @param requiredGroundType Optional required ground type
|
||||
* @return ValidationResult indicating suitability or reasons for unsuitability
|
||||
*/
|
||||
suspend fun validateVenueForDiscipline(
|
||||
platzId: Uuid,
|
||||
requiredType: PlatzTypE,
|
||||
requiredDimensions: String? = null,
|
||||
requiredGroundType: String? = null
|
||||
): ValidationResult {
|
||||
val errors = mutableListOf<ValidationError>()
|
||||
|
||||
// Get the venue
|
||||
val platz = platzRepository.findById(platzId)
|
||||
if (platz == null) {
|
||||
errors.add(ValidationError("platzId", "Venue not found", "NOT_FOUND"))
|
||||
return ValidationResult.Invalid(errors)
|
||||
}
|
||||
|
||||
// Check if venue is active
|
||||
if (!platz.istAktiv) {
|
||||
errors.add(ValidationError("platz", "Venue is not active", "INACTIVE"))
|
||||
}
|
||||
|
||||
// Check venue type
|
||||
if (platz.typ != requiredType) {
|
||||
errors.add(ValidationError("typ", "Venue type ${platz.typ} does not match required type $requiredType", "TYPE_MISMATCH"))
|
||||
}
|
||||
|
||||
// Check dimensions if required
|
||||
requiredDimensions?.let { required ->
|
||||
if (platz.dimension != required.trim()) {
|
||||
errors.add(ValidationError("dimension", "Venue dimensions '${platz.dimension}' do not match required dimensions '$required'", "DIMENSION_MISMATCH"))
|
||||
}
|
||||
}
|
||||
|
||||
// Check ground type if required
|
||||
requiredGroundType?.let { required ->
|
||||
if (platz.boden != required.trim()) {
|
||||
errors.add(ValidationError("boden", "Venue ground type '${platz.boden}' does not match required ground type '$required'", "GROUND_TYPE_MISMATCH"))
|
||||
}
|
||||
}
|
||||
|
||||
return if (errors.isEmpty()) {
|
||||
ValidationResult.Valid
|
||||
} else {
|
||||
ValidationResult.Invalid(errors)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates multiple venues for a tournament in batch.
|
||||
* This is a convenience method for setting up tournament venues efficiently.
|
||||
*
|
||||
* @param turnierId The tournament ID
|
||||
* @param venueRequests List of venue creation requests
|
||||
* @return List of creation responses for each venue
|
||||
*/
|
||||
suspend fun createMultipleVenues(turnierId: Uuid, venueRequests: List<CreatePlatzRequest>): List<CreatePlatzResponse> {
|
||||
val responses = mutableListOf<CreatePlatzResponse>()
|
||||
|
||||
for (request in venueRequests) {
|
||||
// Ensure all requests are for the same tournament
|
||||
val adjustedRequest = request.copy(turnierId = turnierId)
|
||||
val response = createPlatz(adjustedRequest)
|
||||
responses.add(response)
|
||||
}
|
||||
|
||||
return responses
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates venue capacity and setup for tournament requirements.
|
||||
* This method performs comprehensive checks for tournament venue setup.
|
||||
*
|
||||
* @param turnierId The tournament ID
|
||||
* @param requiredVenueTypes Map of venue type to minimum count required
|
||||
* @return ValidationResult indicating if the tournament has adequate venue setup
|
||||
*/
|
||||
suspend fun validateTournamentVenueSetup(
|
||||
turnierId: Uuid,
|
||||
requiredVenueTypes: Map<PlatzTypE, Int>
|
||||
): ValidationResult {
|
||||
val errors = mutableListOf<ValidationError>()
|
||||
|
||||
// Get all active venues for the tournament
|
||||
val venues = platzRepository.findByTournament(turnierId, activeOnly = true, orderBySortierung = false)
|
||||
val venuesByType = venues.groupBy { it.typ }
|
||||
|
||||
// Check if each required venue type has sufficient count
|
||||
for ((requiredType, requiredCount) in requiredVenueTypes) {
|
||||
val availableCount = venuesByType[requiredType]?.size ?: 0
|
||||
|
||||
if (availableCount < requiredCount) {
|
||||
errors.add(ValidationError(
|
||||
"venues",
|
||||
"Tournament requires $requiredCount venues of type $requiredType but only has $availableCount",
|
||||
"INSUFFICIENT_VENUES"
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
// Check if tournament has any venues at all
|
||||
if (venues.isEmpty()) {
|
||||
errors.add(ValidationError("venues", "Tournament has no active venues configured", "NO_VENUES"))
|
||||
}
|
||||
|
||||
return if (errors.isEmpty()) {
|
||||
ValidationResult.Valid
|
||||
} else {
|
||||
ValidationResult.Invalid(errors)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Optimizes venue sorting order for a tournament.
|
||||
* This method automatically assigns sorting orders based on venue type and name.
|
||||
*
|
||||
* @param turnierId The tournament ID
|
||||
* @return Number of venues updated
|
||||
*/
|
||||
suspend fun optimizeVenueSorting(turnierId: Uuid): Int {
|
||||
val venues = platzRepository.findByTournament(turnierId, activeOnly = false, orderBySortierung = false)
|
||||
|
||||
// Sort venues by type first, then by name
|
||||
val sortedVenues = venues.sortedWith(compareBy<Platz> { it.typ.ordinal }.thenBy { it.name })
|
||||
|
||||
var updatedCount = 0
|
||||
|
||||
// Assign new sorting orders
|
||||
sortedVenues.forEachIndexed { index, venue ->
|
||||
val newSortOrder = (index + 1) * 10 // Leave gaps for future insertions
|
||||
|
||||
if (venue.sortierReihenfolge != newSortOrder) {
|
||||
val updatedVenue = venue.copy(
|
||||
sortierReihenfolge = newSortOrder,
|
||||
updatedAt = Clock.System.now()
|
||||
)
|
||||
platzRepository.save(updatedVenue)
|
||||
updatedCount++
|
||||
}
|
||||
}
|
||||
|
||||
return updatedCount
|
||||
}
|
||||
}
|
||||
+185
@@ -0,0 +1,185 @@
|
||||
package at.mocode.masterdata.application.usecase
|
||||
|
||||
import at.mocode.core.domain.model.SparteE
|
||||
import at.mocode.masterdata.domain.model.AltersklasseDefinition
|
||||
import at.mocode.masterdata.domain.repository.AltersklasseRepository
|
||||
import com.benasher44.uuid.Uuid
|
||||
|
||||
/**
|
||||
* Use case for retrieving age class information.
|
||||
*
|
||||
* This use case encapsulates the business logic for fetching age class data
|
||||
* and provides a clean interface for the application layer.
|
||||
*/
|
||||
class GetAltersklasseUseCase(
|
||||
private val altersklasseRepository: AltersklasseRepository
|
||||
) {
|
||||
|
||||
/**
|
||||
* Retrieves an age class by its unique ID.
|
||||
*
|
||||
* @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)
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves 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 getByCode(altersklasseCode: String): AltersklasseDefinition? {
|
||||
require(altersklasseCode.isNotBlank()) { "Age class code cannot be blank" }
|
||||
return altersklasseRepository.findByCode(altersklasseCode.trim().uppercase())
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for 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 (default: 50)
|
||||
* @return List of matching age classes
|
||||
*/
|
||||
suspend fun searchByName(searchTerm: String, limit: Int = 50): List<AltersklasseDefinition> {
|
||||
require(searchTerm.isNotBlank()) { "Search term cannot be blank" }
|
||||
require(limit > 0) { "Limit must be positive" }
|
||||
return altersklasseRepository.findByName(searchTerm.trim(), limit)
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves 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 getAllActive(sparteFilter: SparteE? = null, geschlechtFilter: Char? = null): List<AltersklasseDefinition> {
|
||||
geschlechtFilter?.let { gender ->
|
||||
require(gender == 'M' || gender == 'W') { "Gender filter must be 'M' or 'W'" }
|
||||
}
|
||||
return altersklasseRepository.findAllActive(sparteFilter, geschlechtFilter)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 getApplicableForAge(age: Int, sparteFilter: SparteE? = null, geschlechtFilter: Char? = null): List<AltersklasseDefinition> {
|
||||
require(age >= 0) { "Age must be non-negative" }
|
||||
geschlechtFilter?.let { gender ->
|
||||
require(gender == 'M' || gender == 'W') { "Gender filter must be 'M' or 'W'" }
|
||||
}
|
||||
return altersklasseRepository.findApplicableForAge(age, sparteFilter, geschlechtFilter)
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves age classes by sport type.
|
||||
*
|
||||
* @param sparte The sport type
|
||||
* @param activeOnly Whether to return only active age classes (default: true)
|
||||
* @return List of age classes for the sport type
|
||||
*/
|
||||
suspend fun getBySparte(sparte: SparteE, activeOnly: Boolean = true): List<AltersklasseDefinition> {
|
||||
return altersklasseRepository.findBySparte(sparte, activeOnly)
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves age classes by gender filter.
|
||||
*
|
||||
* @param geschlecht The gender ('M', 'W')
|
||||
* @param activeOnly Whether to return only active age classes (default: true)
|
||||
* @return List of age classes for the gender
|
||||
*/
|
||||
suspend fun getByGeschlecht(geschlecht: Char, activeOnly: Boolean = true): List<AltersklasseDefinition> {
|
||||
require(geschlecht == 'M' || geschlecht == 'W') { "Gender must be 'M' or 'W'" }
|
||||
return altersklasseRepository.findByGeschlecht(geschlecht, activeOnly)
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves age classes by age range.
|
||||
*
|
||||
* @param minAge Minimum age (inclusive)
|
||||
* @param maxAge Maximum age (inclusive)
|
||||
* @param activeOnly Whether to return only active age classes (default: true)
|
||||
* @return List of age classes within the age range
|
||||
*/
|
||||
suspend fun getByAgeRange(minAge: Int?, maxAge: Int?, activeOnly: Boolean = true): List<AltersklasseDefinition> {
|
||||
minAge?.let { min ->
|
||||
require(min >= 0) { "Minimum age must be non-negative" }
|
||||
}
|
||||
maxAge?.let { max ->
|
||||
require(max >= 0) { "Maximum age must be non-negative" }
|
||||
minAge?.let { min ->
|
||||
require(max >= min) { "Maximum age must be greater than or equal to minimum age" }
|
||||
}
|
||||
}
|
||||
return altersklasseRepository.findByAgeRange(minAge, maxAge, activeOnly)
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves age classes by OETO rule reference.
|
||||
*
|
||||
* @param oetoRegelReferenzId The OETO rule reference ID
|
||||
* @return List of age classes linked to the rule
|
||||
*/
|
||||
suspend fun getByOetoRegelReferenz(oetoRegelReferenzId: Uuid): List<AltersklasseDefinition> {
|
||||
return altersklasseRepository.findByOetoRegelReferenz(oetoRegelReferenzId)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
require(altersklasseCode.isNotBlank()) { "Age class code cannot be blank" }
|
||||
return altersklasseRepository.existsByCode(altersklasseCode.trim().uppercase())
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
return altersklasseRepository.countActive(sparteFilter)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
require(age >= 0) { "Age must be non-negative" }
|
||||
require(geschlecht == 'M' || geschlecht == 'W') { "Gender must be 'M' or 'W'" }
|
||||
return altersklasseRepository.isEligible(altersklasseId, age, geschlecht)
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves age classes suitable for a participant based on age, gender, and sport.
|
||||
* This is a convenience method that combines multiple filters.
|
||||
*
|
||||
* @param age The participant's age
|
||||
* @param geschlecht The participant's gender ('M', 'W')
|
||||
* @param sparte The sport type
|
||||
* @return List of suitable age classes
|
||||
*/
|
||||
suspend fun getSuitableForParticipant(age: Int, geschlecht: Char, sparte: SparteE): List<AltersklasseDefinition> {
|
||||
require(age >= 0) { "Age must be non-negative" }
|
||||
require(geschlecht == 'M' || geschlecht == 'W') { "Gender must be 'M' or 'W'" }
|
||||
return altersklasseRepository.findApplicableForAge(age, sparte, geschlecht)
|
||||
}
|
||||
}
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
package at.mocode.masterdata.application.usecase
|
||||
|
||||
import at.mocode.masterdata.domain.model.BundeslandDefinition
|
||||
import at.mocode.masterdata.domain.repository.BundeslandRepository
|
||||
import com.benasher44.uuid.Uuid
|
||||
|
||||
/**
|
||||
* Use case for retrieving federal state information.
|
||||
*
|
||||
* This use case encapsulates the business logic for fetching federal state data
|
||||
* and provides a clean interface for the application layer.
|
||||
*/
|
||||
class GetBundeslandUseCase(
|
||||
private val bundeslandRepository: BundeslandRepository
|
||||
) {
|
||||
|
||||
/**
|
||||
* Retrieves a federal state by its unique ID.
|
||||
*
|
||||
* @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)
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a federal state by its OEPS code for a specific country.
|
||||
*
|
||||
* @param oepsCode The OEPS code (e.g., "01", "02")
|
||||
* @param landId The country ID
|
||||
* @return The federal state if found, null otherwise
|
||||
*/
|
||||
suspend fun getByOepsCode(oepsCode: String, landId: Uuid): BundeslandDefinition? {
|
||||
require(oepsCode.isNotBlank()) { "OEPS code cannot be blank" }
|
||||
return bundeslandRepository.findByOepsCode(oepsCode.trim(), landId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a federal state by its ISO 3166-2 code.
|
||||
*
|
||||
* @param iso3166_2_Code The ISO 3166-2 code (e.g., "AT-1", "DE-BY")
|
||||
* @return The federal state if found, null otherwise
|
||||
*/
|
||||
suspend fun getByIso3166_2_Code(iso3166_2_Code: String): BundeslandDefinition? {
|
||||
require(iso3166_2_Code.isNotBlank()) { "ISO 3166-2 code cannot be blank" }
|
||||
return bundeslandRepository.findByIso3166_2_Code(iso3166_2_Code.trim().uppercase())
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all federal states for a specific country.
|
||||
*
|
||||
* @param landId The country ID
|
||||
* @param activeOnly Whether to return only active federal states (default: true)
|
||||
* @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)
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all active federal states.
|
||||
*
|
||||
* @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)
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a federal state with the given OEPS code exists for a country.
|
||||
*
|
||||
* @param oepsCode The OEPS code to check
|
||||
* @param landId The country ID
|
||||
* @return true if a federal state with this code exists, false otherwise
|
||||
*/
|
||||
suspend fun existsByOepsCode(oepsCode: String, landId: Uuid): Boolean {
|
||||
require(oepsCode.isNotBlank()) { "OEPS code cannot be blank" }
|
||||
return bundeslandRepository.existsByOepsCode(oepsCode.trim(), landId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a federal state with the given ISO 3166-2 code exists.
|
||||
*
|
||||
* @param iso3166_2_Code The ISO 3166-2 code to check
|
||||
* @return true if a federal state with this code exists, false otherwise
|
||||
*/
|
||||
suspend fun existsByIso3166_2_Code(iso3166_2_Code: String): Boolean {
|
||||
require(iso3166_2_Code.isNotBlank()) { "ISO 3166-2 code cannot be blank" }
|
||||
return bundeslandRepository.existsByIso3166_2_Code(iso3166_2_Code.trim().uppercase())
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts the total number of active federal states for a country.
|
||||
*
|
||||
* @param landId The country ID
|
||||
* @return The total count of active federal states
|
||||
*/
|
||||
suspend fun countActiveByCountry(landId: Uuid): Long {
|
||||
return bundeslandRepository.countActiveByCountry(landId)
|
||||
}
|
||||
}
|
||||
+275
@@ -0,0 +1,275 @@
|
||||
package at.mocode.masterdata.application.usecase
|
||||
|
||||
import at.mocode.core.domain.model.PlatzTypE
|
||||
import at.mocode.masterdata.domain.model.Platz
|
||||
import at.mocode.masterdata.domain.repository.PlatzRepository
|
||||
import com.benasher44.uuid.Uuid
|
||||
|
||||
/**
|
||||
* Use case for retrieving venue/arena information.
|
||||
*
|
||||
* This use case encapsulates the business logic for fetching venue data
|
||||
* and provides a clean interface for the application layer.
|
||||
*/
|
||||
class GetPlatzUseCase(
|
||||
private val platzRepository: PlatzRepository
|
||||
) {
|
||||
|
||||
/**
|
||||
* Retrieves a venue by its unique ID.
|
||||
*
|
||||
* @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)
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all venues for a specific tournament.
|
||||
*
|
||||
* @param turnierId The tournament ID
|
||||
* @param activeOnly Whether to return only active venues (default: true)
|
||||
* @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)
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for venues by name (partial match).
|
||||
*
|
||||
* @param searchTerm The search term to match against venue names
|
||||
* @param turnierId Optional tournament ID to limit search
|
||||
* @param limit Maximum number of results to return (default: 50)
|
||||
* @return List of matching venues
|
||||
*/
|
||||
suspend fun searchByName(searchTerm: String, turnierId: Uuid? = null, limit: Int = 50): List<Platz> {
|
||||
require(searchTerm.isNotBlank()) { "Search term cannot be blank" }
|
||||
require(limit > 0) { "Limit must be positive" }
|
||||
return platzRepository.findByName(searchTerm.trim(), turnierId, limit)
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves venues by type.
|
||||
*
|
||||
* @param typ The venue type
|
||||
* @param turnierId Optional tournament ID to limit search
|
||||
* @param activeOnly Whether to return only active venues (default: true)
|
||||
* @return List of venues of the specified type
|
||||
*/
|
||||
suspend fun getByType(typ: PlatzTypE, turnierId: Uuid? = null, activeOnly: Boolean = true): List<Platz> {
|
||||
return platzRepository.findByType(typ, turnierId, activeOnly)
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves venues by ground type.
|
||||
*
|
||||
* @param boden The ground type (e.g., "Sand", "Gras", "Kunststoff")
|
||||
* @param turnierId Optional tournament ID to limit search
|
||||
* @param activeOnly Whether to return only active venues (default: true)
|
||||
* @return List of venues with the specified ground type
|
||||
*/
|
||||
suspend fun getByGroundType(boden: String, turnierId: Uuid? = null, activeOnly: Boolean = true): List<Platz> {
|
||||
require(boden.isNotBlank()) { "Ground type cannot be blank" }
|
||||
return platzRepository.findByGroundType(boden.trim(), turnierId, activeOnly)
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves venues by dimensions.
|
||||
*
|
||||
* @param dimension The venue dimensions (e.g., "20x60m", "20x40m")
|
||||
* @param turnierId Optional tournament ID to limit search
|
||||
* @param activeOnly Whether to return only active venues (default: true)
|
||||
* @return List of venues with the specified dimensions
|
||||
*/
|
||||
suspend fun getByDimensions(dimension: String, turnierId: Uuid? = null, activeOnly: Boolean = true): List<Platz> {
|
||||
require(dimension.isNotBlank()) { "Dimension cannot be blank" }
|
||||
return platzRepository.findByDimensions(dimension.trim(), turnierId, activeOnly)
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all active venues.
|
||||
*
|
||||
* @param orderBySortierung Whether to order by sortierReihenfolge field (default: true)
|
||||
* @return List of active venues
|
||||
*/
|
||||
suspend fun getAllActive(orderBySortierung: Boolean = true): List<Platz> {
|
||||
return platzRepository.findAllActive(orderBySortierung)
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds venues suitable for a specific discipline based on type and dimensions.
|
||||
*
|
||||
* @param requiredType The required venue type
|
||||
* @param requiredDimensions Optional required dimensions
|
||||
* @param turnierId Optional tournament ID to limit search
|
||||
* @return List of suitable venues
|
||||
*/
|
||||
suspend fun getSuitableForDiscipline(
|
||||
requiredType: PlatzTypE,
|
||||
requiredDimensions: String? = null,
|
||||
turnierId: Uuid? = null
|
||||
): List<Platz> {
|
||||
requiredDimensions?.let { dimensions ->
|
||||
require(dimensions.isNotBlank()) { "Required dimensions cannot be blank if provided" }
|
||||
}
|
||||
return platzRepository.findSuitableForDiscipline(requiredType, requiredDimensions?.trim(), turnierId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a venue with the given name exists for a tournament.
|
||||
*
|
||||
* @param name The venue name to check
|
||||
* @param turnierId The tournament ID
|
||||
* @return true if a venue with this name exists, false otherwise
|
||||
*/
|
||||
suspend fun existsByNameAndTournament(name: String, turnierId: Uuid): Boolean {
|
||||
require(name.isNotBlank()) { "Venue name cannot be blank" }
|
||||
return platzRepository.existsByNameAndTournament(name.trim(), turnierId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts the total number of active venues for a tournament.
|
||||
*
|
||||
* @param turnierId The tournament ID
|
||||
* @return The total count of active venues
|
||||
*/
|
||||
suspend fun countActiveByTournament(turnierId: Uuid): Long {
|
||||
return platzRepository.countActiveByTournament(turnierId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts venues by type for a tournament.
|
||||
*
|
||||
* @param typ The venue type
|
||||
* @param turnierId The tournament ID
|
||||
* @param activeOnly Whether to count only active venues (default: true)
|
||||
* @return The count of venues of the specified type
|
||||
*/
|
||||
suspend fun countByTypeAndTournament(typ: PlatzTypE, turnierId: Uuid, activeOnly: Boolean = true): Long {
|
||||
return platzRepository.countByTypeAndTournament(typ, turnierId, activeOnly)
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds available venues for a specific time slot.
|
||||
* This method can be extended when venue scheduling functionality is added.
|
||||
*
|
||||
* @param turnierId The tournament ID
|
||||
* @param startTime The start time (placeholder for future scheduling feature)
|
||||
* @param endTime The end time (placeholder for future scheduling feature)
|
||||
* @return List of available venues (currently returns all active venues)
|
||||
*/
|
||||
suspend fun getAvailableForTimeSlot(turnierId: Uuid, startTime: String? = null, endTime: String? = null): List<Platz> {
|
||||
return platzRepository.findAvailableForTimeSlot(turnierId, startTime, endTime)
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves venues grouped by type for a tournament.
|
||||
* This is a convenience method that provides venues organized by their type.
|
||||
*
|
||||
* @param turnierId The tournament ID
|
||||
* @param activeOnly Whether to include only active venues (default: true)
|
||||
* @return Map of venue type to list of venues
|
||||
*/
|
||||
suspend fun getGroupedByTypeForTournament(turnierId: Uuid, activeOnly: Boolean = true): Map<PlatzTypE, List<Platz>> {
|
||||
val venues = platzRepository.findByTournament(turnierId, activeOnly, true)
|
||||
return venues.groupBy { it.typ }
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves venues with specific characteristics for discipline matching.
|
||||
* This method combines multiple filters to find venues suitable for specific disciplines.
|
||||
*
|
||||
* @param turnierId The tournament ID
|
||||
* @param requiredType The required venue type
|
||||
* @param preferredDimensions Preferred dimensions (optional)
|
||||
* @param preferredGroundType Preferred ground type (optional)
|
||||
* @param activeOnly Whether to include only active venues (default: true)
|
||||
* @return List of venues matching the criteria, sorted by preference
|
||||
*/
|
||||
suspend fun getForDisciplineRequirements(
|
||||
turnierId: Uuid,
|
||||
requiredType: PlatzTypE,
|
||||
preferredDimensions: String? = null,
|
||||
preferredGroundType: String? = null,
|
||||
activeOnly: Boolean = true
|
||||
): List<Platz> {
|
||||
// Start with venues of the required type
|
||||
val typeMatches = platzRepository.findByType(requiredType, turnierId, activeOnly)
|
||||
|
||||
// If no specific preferences, return all type matches
|
||||
if (preferredDimensions == null && preferredGroundType == null) {
|
||||
return typeMatches
|
||||
}
|
||||
|
||||
// Filter and sort by preferences
|
||||
val exactMatches = mutableListOf<Platz>()
|
||||
val partialMatches = mutableListOf<Platz>()
|
||||
val otherMatches = mutableListOf<Platz>()
|
||||
|
||||
for (venue in typeMatches) {
|
||||
val dimensionMatch = preferredDimensions == null || venue.dimension == preferredDimensions.trim()
|
||||
val groundMatch = preferredGroundType == null || venue.boden == preferredGroundType.trim()
|
||||
|
||||
when {
|
||||
dimensionMatch && groundMatch -> exactMatches.add(venue)
|
||||
dimensionMatch || groundMatch -> partialMatches.add(venue)
|
||||
else -> otherMatches.add(venue)
|
||||
}
|
||||
}
|
||||
|
||||
// Return sorted by preference: exact matches first, then partial, then others
|
||||
return exactMatches + partialMatches + otherMatches
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates venue availability and suitability for a specific use case.
|
||||
* This method performs comprehensive checks for venue usage.
|
||||
*
|
||||
* @param platzId The venue ID
|
||||
* @param requiredType Optional required venue type
|
||||
* @param requiredDimensions Optional required dimensions
|
||||
* @param requiredGroundType Optional required ground type
|
||||
* @return Pair of (isValid, reasons) where reasons contains any validation issues
|
||||
*/
|
||||
suspend fun validateVenueSuitability(
|
||||
platzId: Uuid,
|
||||
requiredType: PlatzTypE? = null,
|
||||
requiredDimensions: String? = null,
|
||||
requiredGroundType: String? = null
|
||||
): Pair<Boolean, List<String>> {
|
||||
val venue = platzRepository.findById(platzId)
|
||||
val issues = mutableListOf<String>()
|
||||
|
||||
if (venue == null) {
|
||||
issues.add("Venue not found")
|
||||
return Pair(false, issues)
|
||||
}
|
||||
|
||||
if (!venue.istAktiv) {
|
||||
issues.add("Venue is not active")
|
||||
}
|
||||
|
||||
requiredType?.let { type ->
|
||||
if (venue.typ != type) {
|
||||
issues.add("Venue type ${venue.typ} does not match required type $type")
|
||||
}
|
||||
}
|
||||
|
||||
requiredDimensions?.let { dimensions ->
|
||||
if (venue.dimension != dimensions.trim()) {
|
||||
issues.add("Venue dimensions '${venue.dimension}' do not match required dimensions '$dimensions'")
|
||||
}
|
||||
}
|
||||
|
||||
requiredGroundType?.let { groundType ->
|
||||
if (venue.boden != groundType.trim()) {
|
||||
issues.add("Venue ground type '${venue.boden}' does not match required ground type '$groundType'")
|
||||
}
|
||||
}
|
||||
|
||||
return Pair(issues.isEmpty(), issues)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user