(vision) SCS/DDD

This commit is contained in:
2025-07-18 23:07:05 +02:00
parent 029b0c86bc
commit 611e31e196
68 changed files with 6949 additions and 137 deletions
@@ -3,6 +3,7 @@ package at.mocode.masterdata.application.usecase
import at.mocode.masterdata.domain.model.LandDefinition
import at.mocode.masterdata.domain.repository.LandRepository
import at.mocode.validation.ValidationResult
import at.mocode.validation.ValidationError
import com.benasher44.uuid.Uuid
import kotlinx.datetime.Clock
@@ -49,23 +50,59 @@ class CreateCountryUseCase(
val sortierReihenfolge: Int? = null
)
/**
* Response data for country creation.
*/
data class CreateCountryResponse(
val country: LandDefinition?,
val success: Boolean,
val errors: List<String> = emptyList()
)
/**
* Response data for country update.
*/
data class UpdateCountryResponse(
val country: LandDefinition?,
val success: Boolean,
val errors: List<String> = emptyList()
)
/**
* Response data for country deletion.
*/
data class DeleteCountryResponse(
val success: Boolean,
val errors: List<String> = emptyList()
)
/**
* Creates a new country after validation.
*
* @param request The country creation request
* @return ValidationResult containing the created country or validation errors
* @return CreateCountryResponse with the created country or validation errors
*/
suspend fun createCountry(request: CreateCountryRequest): ValidationResult<LandDefinition> {
suspend fun createCountry(request: CreateCountryRequest): CreateCountryResponse {
// Validate the request
val validationResult = validateCreateRequest(request)
if (!validationResult.isValid) {
return ValidationResult.failure(validationResult.errors)
if (!validationResult.isValid()) {
val errors = (validationResult as ValidationResult.Invalid).errors.map { it.message }
return CreateCountryResponse(
country = null,
success = false,
errors = errors
)
}
// Check for duplicates
val duplicateCheck = checkForDuplicates(request.isoAlpha2Code, request.isoAlpha3Code)
if (!duplicateCheck.isValid) {
return ValidationResult.failure(duplicateCheck.errors)
if (!duplicateCheck.isValid()) {
val errors = (duplicateCheck as ValidationResult.Invalid).errors.map { it.message }
return CreateCountryResponse(
country = null,
success = false,
errors = errors
)
}
// Create the domain object
@@ -87,7 +124,10 @@ class CreateCountryUseCase(
// Save to repository
val savedCountry = landRepository.save(country)
return ValidationResult.success(savedCountry)
return CreateCountryResponse(
country = savedCountry,
success = true
)
}
/**
@@ -96,15 +136,26 @@ class CreateCountryUseCase(
* @param request The country update request
* @return ValidationResult containing the updated country or validation errors
*/
suspend fun updateCountry(request: UpdateCountryRequest): ValidationResult<LandDefinition> {
suspend fun updateCountry(request: UpdateCountryRequest): UpdateCountryResponse {
// Check if country exists
val existingCountry = landRepository.findById(request.landId)
?: return ValidationResult.failure(listOf("Country with ID ${request.landId} not found"))
if (existingCountry == null) {
return UpdateCountryResponse(
country = null,
success = false,
errors = listOf("Country with ID ${request.landId} not found")
)
}
// Validate the request
val validationResult = validateUpdateRequest(request)
if (!validationResult.isValid) {
return ValidationResult.failure(validationResult.errors)
if (!validationResult.isValid()) {
val errors = (validationResult as ValidationResult.Invalid).errors.map { it.message }
return UpdateCountryResponse(
country = null,
success = false,
errors = errors
)
}
// Check for duplicates (excluding current country)
@@ -113,8 +164,13 @@ class CreateCountryUseCase(
request.isoAlpha3Code,
request.landId
)
if (!duplicateCheck.isValid) {
return ValidationResult.failure(duplicateCheck.errors)
if (!duplicateCheck.isValid()) {
val errors = (duplicateCheck as ValidationResult.Invalid).errors.map { it.message }
return UpdateCountryResponse(
country = null,
success = false,
errors = errors
)
}
// Update the domain object
@@ -134,80 +190,86 @@ class CreateCountryUseCase(
// Save to repository
val savedCountry = landRepository.save(updatedCountry)
return ValidationResult.success(savedCountry)
return UpdateCountryResponse(
country = savedCountry,
success = true
)
}
/**
* Deletes a country by ID.
*
* @param countryId The unique identifier of the country to delete
* @return ValidationResult indicating success or failure
* @return DeleteCountryResponse indicating success or failure
*/
suspend fun deleteCountry(countryId: Uuid): ValidationResult<Unit> {
suspend fun deleteCountry(countryId: Uuid): DeleteCountryResponse {
val deleted = landRepository.delete(countryId)
return if (deleted) {
ValidationResult.success(Unit)
DeleteCountryResponse(success = true)
} else {
ValidationResult.failure(listOf("Country with ID $countryId not found or could not be deleted"))
DeleteCountryResponse(
success = false,
errors = listOf("Country with ID $countryId not found or could not be deleted")
)
}
}
/**
* Validates a create country request.
*/
private fun validateCreateRequest(request: CreateCountryRequest): ValidationResult<Unit> {
val errors = mutableListOf<String>()
private fun validateCreateRequest(request: CreateCountryRequest): ValidationResult {
val errors = mutableListOf<ValidationError>()
// ISO Alpha-2 Code validation
if (request.isoAlpha2Code.isBlank()) {
errors.add("ISO Alpha-2 code is required")
errors.add(ValidationError("isoAlpha2Code", "ISO Alpha-2 code is required", "REQUIRED"))
} else if (request.isoAlpha2Code.length != 2) {
errors.add("ISO Alpha-2 code must be exactly 2 characters")
errors.add(ValidationError("isoAlpha2Code", "ISO Alpha-2 code must be exactly 2 characters", "INVALID_LENGTH"))
} else if (!request.isoAlpha2Code.all { it.isLetter() }) {
errors.add("ISO Alpha-2 code must contain only letters")
errors.add(ValidationError("isoAlpha2Code", "ISO Alpha-2 code must contain only letters", "INVALID_FORMAT"))
}
// ISO Alpha-3 Code validation
if (request.isoAlpha3Code.isBlank()) {
errors.add("ISO Alpha-3 code is required")
errors.add(ValidationError("isoAlpha3Code", "ISO Alpha-3 code is required", "REQUIRED"))
} else if (request.isoAlpha3Code.length != 3) {
errors.add("ISO Alpha-3 code must be exactly 3 characters")
errors.add(ValidationError("isoAlpha3Code", "ISO Alpha-3 code must be exactly 3 characters", "INVALID_LENGTH"))
} else if (!request.isoAlpha3Code.all { it.isLetter() }) {
errors.add("ISO Alpha-3 code must contain only letters")
errors.add(ValidationError("isoAlpha3Code", "ISO Alpha-3 code must contain only letters", "INVALID_FORMAT"))
}
// German name validation
if (request.nameDeutsch.isBlank()) {
errors.add("German name is required")
errors.add(ValidationError("nameDeutsch", "German name is required", "REQUIRED"))
} else if (request.nameDeutsch.length > 100) {
errors.add("German name must not exceed 100 characters")
errors.add(ValidationError("nameDeutsch", "German name must not exceed 100 characters", "MAX_LENGTH"))
}
// English name validation
request.nameEnglisch?.let { name ->
if (name.length > 100) {
errors.add("English name must not exceed 100 characters")
errors.add(ValidationError("nameEnglisch", "English name must not exceed 100 characters", "MAX_LENGTH"))
}
}
// Sorting order validation
request.sortierReihenfolge?.let { order ->
if (order < 0) {
errors.add("Sorting order must be non-negative")
errors.add(ValidationError("sortierReihenfolge", "Sorting order must be non-negative", "INVALID_VALUE"))
}
}
return if (errors.isEmpty()) {
ValidationResult.success(Unit)
ValidationResult.Valid
} else {
ValidationResult.failure(errors)
ValidationResult.Invalid(errors)
}
}
/**
* Validates an update country request.
*/
private fun validateUpdateRequest(request: UpdateCountryRequest): ValidationResult<Unit> {
private fun validateUpdateRequest(request: UpdateCountryRequest): ValidationResult {
// Use the same validation logic as create request
val createRequest = CreateCountryRequest(
isoAlpha2Code = request.isoAlpha2Code,
@@ -227,21 +289,21 @@ class CreateCountryUseCase(
/**
* Checks for duplicate ISO codes.
*/
private suspend fun checkForDuplicates(isoAlpha2Code: String, isoAlpha3Code: String): ValidationResult<Unit> {
val errors = mutableListOf<String>()
private suspend fun checkForDuplicates(isoAlpha2Code: String, isoAlpha3Code: String): ValidationResult {
val errors = mutableListOf<ValidationError>()
if (landRepository.existsByIsoAlpha2Code(isoAlpha2Code.uppercase())) {
errors.add("Country with ISO Alpha-2 code '${isoAlpha2Code.uppercase()}' already exists")
errors.add(ValidationError("isoAlpha2Code", "Country with ISO Alpha-2 code '${isoAlpha2Code.uppercase()}' already exists", "DUPLICATE"))
}
if (landRepository.existsByIsoAlpha3Code(isoAlpha3Code.uppercase())) {
errors.add("Country with ISO Alpha-3 code '${isoAlpha3Code.uppercase()}' already exists")
errors.add(ValidationError("isoAlpha3Code", "Country with ISO Alpha-3 code '${isoAlpha3Code.uppercase()}' already exists", "DUPLICATE"))
}
return if (errors.isEmpty()) {
ValidationResult.success(Unit)
ValidationResult.Valid
} else {
ValidationResult.failure(errors)
ValidationResult.Invalid(errors)
}
}
@@ -252,25 +314,25 @@ class CreateCountryUseCase(
isoAlpha2Code: String,
isoAlpha3Code: String,
excludeId: Uuid
): ValidationResult<Unit> {
val errors = mutableListOf<String>()
): ValidationResult {
val errors = mutableListOf<ValidationError>()
// Check Alpha-2 code
val existingAlpha2 = landRepository.findByIsoAlpha2Code(isoAlpha2Code.uppercase())
if (existingAlpha2 != null && existingAlpha2.landId != excludeId) {
errors.add("Country with ISO Alpha-2 code '${isoAlpha2Code.uppercase()}' already exists")
errors.add(ValidationError("isoAlpha2Code", "Country with ISO Alpha-2 code '${isoAlpha2Code.uppercase()}' already exists", "DUPLICATE"))
}
// Check Alpha-3 code
val existingAlpha3 = landRepository.findByIsoAlpha3Code(isoAlpha3Code.uppercase())
if (existingAlpha3 != null && existingAlpha3.landId != excludeId) {
errors.add("Country with ISO Alpha-3 code '${isoAlpha3Code.uppercase()}' already exists")
errors.add(ValidationError("isoAlpha3Code", "Country with ISO Alpha-3 code '${isoAlpha3Code.uppercase()}' already exists", "DUPLICATE"))
}
return if (errors.isEmpty()) {
ValidationResult.success(Unit)
ValidationResult.Valid
} else {
ValidationResult.failure(errors)
ValidationResult.Invalid(errors)
}
}
}