Extend Bewerb DTOs and APIs: add comprehensive properties, mapping functions, and RichterEinsatz support, and align backend and frontend implementations.

This commit is contained in:
Stefan Mogeritsch 2026-04-08 22:59:22 +02:00
parent 2d42578378
commit 8b6ea11d46
2 changed files with 226 additions and 5 deletions

View File

@ -2,20 +2,149 @@
package at.mocode.entries.service.bewerbe
import at.mocode.core.domain.model.AbteilungsTeilungsTypE
import at.mocode.core.domain.model.BeginnZeitTypE
import at.mocode.entries.domain.model.RichterEinsatz
import kotlinx.datetime.LocalDate
import kotlinx.datetime.LocalTime
import org.springframework.http.HttpStatus
import org.springframework.web.bind.annotation.*
import kotlin.uuid.Uuid
// --- DTOs ---
data class RichterEinsatzDto(
val funktionaerId: String,
val position: String,
)
data class CreateBewerbRequest(
// Basis
val klasse: String,
val hoeheCm: Int? = null,
val bezeichnung: String,
// Abteilungs-Konfiguration
val teilungsTyp: AbteilungsTeilungsTypE? = null,
// Text & Details
val beschreibung: String? = null,
val aufgabe: String? = null,
val aufgabenNummer: String? = null,
val paraGrade: String? = null,
// Ort & Funktionäre
val austragungsplatzId: String? = null,
val richterEinsaetze: List<RichterEinsatzDto> = emptyList(),
// Zeitplan
val geplantesDatum: LocalDate? = null,
val beginnZeitTyp: BeginnZeitTypE? = null,
val beginnZeit: LocalTime? = null,
val reitdauerMinuten: Int? = null,
val umbauMinuten: Int? = null,
val besichtigungMinuten: Int? = null,
val stechenGeplant: Boolean = false,
// Finanzen
val startgeldCent: Long? = null,
val geldpreisAusbezahlt: Boolean = false,
)
data class UpdateBewerbRequest(
// Basis
val klasse: String,
val hoeheCm: Int? = null,
val bezeichnung: String,
// Abteilungs-Konfiguration
val teilungsTyp: AbteilungsTeilungsTypE? = null,
// Text & Details
val beschreibung: String? = null,
val aufgabe: String? = null,
val aufgabenNummer: String? = null,
val paraGrade: String? = null,
// Ort & Funktionäre
val austragungsplatzId: String? = null,
val richterEinsaetze: List<RichterEinsatzDto> = emptyList(),
// Zeitplan
val geplantesDatum: LocalDate? = null,
val beginnZeitTyp: BeginnZeitTypE? = null,
val beginnZeit: LocalTime? = null,
val reitdauerMinuten: Int? = null,
val umbauMinuten: Int? = null,
val besichtigungMinuten: Int? = null,
val stechenGeplant: Boolean = false,
// Finanzen
val startgeldCent: Long? = null,
val geldpreisAusbezahlt: Boolean = false,
)
data class BewerbResponse(
val id: String,
val turnierId: String,
val klasse: String,
val hoeheCm: Int?,
val bezeichnung: String,
// Abteilungs-Konfiguration
val teilungsTyp: AbteilungsTeilungsTypE?,
// Text & Details
val beschreibung: String?,
val aufgabe: String?,
val aufgabenNummer: String?,
val paraGrade: String?,
// Ort & Funktionäre
val austragungsplatzId: String?,
val richterEinsaetze: List<RichterEinsatzDto>,
// Zeitplan
val geplantesDatum: LocalDate?,
val beginnZeitTyp: BeginnZeitTypE?,
val beginnZeit: LocalTime?,
val reitdauerMinuten: Int?,
val umbauMinuten: Int?,
val besichtigungMinuten: Int?,
val stechenGeplant: Boolean,
// Finanzen
val startgeldCent: Long?,
val geldpreisAusbezahlt: Boolean,
)
private fun RichterEinsatzDto.toDomain(): RichterEinsatz =
RichterEinsatz(
funktionaerId = Uuid.parse(this.funktionaerId),
position = this.position
)
private fun domainToDto(b: Bewerb): BewerbResponse = BewerbResponse(
id = b.id.toString(),
turnierId = b.turnierId.toString(),
klasse = b.klasse,
hoeheCm = b.hoeheCm,
bezeichnung = b.bezeichnung,
teilungsTyp = b.teilungsTyp,
beschreibung = b.beschreibung,
aufgabe = b.aufgabe,
aufgabenNummer = b.aufgabenNummer,
paraGrade = b.paraGrade,
austragungsplatzId = b.austragungsplatzId?.toString(),
richterEinsaetze = b.richterEinsaetze.map { RichterEinsatzDto(it.funktionaerId.toString(), it.position) },
geplantesDatum = b.geplantesDatum,
beginnZeitTyp = b.beginnZeitTyp,
beginnZeit = b.beginnZeit,
reitdauerMinuten = b.reitdauerMinuten,
umbauMinuten = b.umbauMinuten,
besichtigungMinuten = b.besichtigungMinuten,
stechenGeplant = b.stechenGeplant,
startgeldCent = b.startgeldCent,
geldpreisAusbezahlt = b.geldpreisAusbezahlt,
)
@RestController
@ -28,21 +157,26 @@ class BewerbeController(
suspend fun create(
@PathVariable turnierId: String,
@RequestBody body: CreateBewerbRequest
): Bewerb = service.create(Uuid.parse(turnierId), body.klasse, body.hoeheCm, body.bezeichnung)
): BewerbResponse = domainToDto(
service.create(
Uuid.parse(turnierId),
body
)
)
@GetMapping("/turniere/{turnierId}/bewerbe")
suspend fun list(
@PathVariable turnierId: String,
@RequestParam(required = false) klasse: String?,
@RequestParam(required = false) q: String?,
): List<Bewerb> = service.list(Uuid.parse(turnierId), klasse, q)
): List<BewerbResponse> = service.list(Uuid.parse(turnierId), klasse, q).map(::domainToDto)
@GetMapping("/bewerbe/{id}")
suspend fun get(@PathVariable id: String): Bewerb = service.get(Uuid.parse(id))
suspend fun get(@PathVariable id: String): BewerbResponse = domainToDto(service.get(Uuid.parse(id)))
@PutMapping("/bewerbe/{id}")
suspend fun update(@PathVariable id: String, @RequestBody body: UpdateBewerbRequest): Bewerb =
service.update(Uuid.parse(id), body.klasse, body.hoeheCm, body.bezeichnung)
suspend fun update(@PathVariable id: String, @RequestBody body: UpdateBewerbRequest): BewerbResponse =
domainToDto(service.update(Uuid.parse(id), body))
@DeleteMapping("/bewerbe/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)

View File

@ -0,0 +1,87 @@
package at.mocode.turnier.feature.data.remote
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.request.*
import io.ktor.http.*
import kotlinx.datetime.LocalDate
import kotlinx.datetime.LocalTime
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class RichterEinsatzDto(
val funktionaerId: String,
val position: String,
)
@Serializable
data class CreateBewerbPayload(
// Basis
val klasse: String,
val hoeheCm: Int? = null,
val bezeichnung: String,
// Text & Details
val beschreibung: String? = null,
val aufgabe: String? = null,
val aufgabenNummer: String? = null,
val paraGrade: String? = null,
// Ort & Funktionäre
val austragungsplatzId: String? = null,
val richterEinsaetze: List<RichterEinsatzDto> = emptyList(),
// Zeitplan
val geplantesDatum: LocalDate? = null,
@SerialName("beginnZeitTyp") val beginnZeitTyp: String? = null, // enum name
val beginnZeit: LocalTime? = null,
val reitdauerMinuten: Int? = null,
val umbauMinuten: Int? = null,
val besichtigungMinuten: Int? = null,
val stechenGeplant: Boolean = false,
// Finanzen
val startgeldCent: Long? = null,
val geldpreisAusbezahlt: Boolean = false,
)
@Serializable
data class BewerbResponse(
val id: String,
val turnierId: String,
val klasse: String,
val hoeheCm: Int? = null,
val bezeichnung: String,
// Text & Details
val beschreibung: String? = null,
val aufgabe: String? = null,
val aufgabenNummer: String? = null,
val paraGrade: String? = null,
// Ort & Funktionäre
val austragungsplatzId: String? = null,
val richterEinsaetze: List<RichterEinsatzDto> = emptyList(),
// Zeitplan
val geplantesDatum: LocalDate? = null,
@SerialName("beginnZeitTyp") val beginnZeitTyp: String? = null,
val beginnZeit: LocalTime? = null,
val reitdauerMinuten: Int? = null,
val umbauMinuten: Int? = null,
val besichtigungMinuten: Int? = null,
val stechenGeplant: Boolean = false,
// Finanzen
val startgeldCent: Long? = null,
val geldpreisAusbezahlt: Boolean = false,
)
class BewerbApi(private val apiClient: HttpClient) {
suspend fun createBewerb(turnierId: String, payload: CreateBewerbPayload): BewerbResponse =
apiClient.post("/turniere/$turnierId/bewerbe") {
contentType(ContentType.Application.Json)
setBody(payload)
}.body()
}