feat(db+domain): add turniernummer and einschraenkungen fields for tournament scope and constraints
- **Database Changes:** Introduced `turnier_nummer` (mandatory, 5 digits) and `einschraenkungen` (mandatory, ÖTO-specific constraints) columns in `turniere` table. Seeded `turnier_nummer` with `oeps_turniernummer` where applicable. - **Domain Models:** Extended `Turnier` and `DomTurnier` with `turnierNummer` and `einschraenkungen` fields. Added `TeilnehmerKreisE` enum for mapping restriction types. - **Services and Controllers:** Updated repository and service operations to handle the new fields. Controllers reflect the new request models for creation and updates. - **Validation:** Enforced input validation for `turnierNummer` format and `einschraenkungen` values. Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
This commit is contained in:
parent
7bf89c58d3
commit
6b9177e818
|
|
@ -11,6 +11,8 @@ object TurnierTable : Table("turniere") {
|
|||
val id = javaUUID("id").autoGenerate()
|
||||
val veranstaltungId = javaUUID("veranstaltung_id")
|
||||
val oepsTurniernummer = varchar("oeps_turniernummer", 50)
|
||||
val turnierNummer = varchar("turnier_nummer", 5)
|
||||
val einschraenkungen = text("einschraenkungen")
|
||||
|
||||
// V3 Felder
|
||||
val status = varchar("status", 16).default("DRAFT")
|
||||
|
|
|
|||
|
|
@ -2,12 +2,15 @@
|
|||
|
||||
package at.mocode.entries.service.turniere
|
||||
|
||||
import at.mocode.core.domain.model.TeilnehmerKreisE
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
data class Turnier(
|
||||
val id: Uuid,
|
||||
val veranstaltungId: Uuid,
|
||||
val oepsTurniernummer: String,
|
||||
val turnierNummer: String,
|
||||
val einschraenkungen: List<TeilnehmerKreisE>,
|
||||
val status: String,
|
||||
val publishedAt: String?,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
package at.mocode.entries.service.turniere
|
||||
|
||||
import at.mocode.core.domain.model.TeilnehmerKreisE
|
||||
import at.mocode.entries.service.persistence.TurnierTable
|
||||
import at.mocode.entries.service.tenant.tenantTransaction
|
||||
import org.jetbrains.exposed.v1.core.ResultRow
|
||||
|
|
@ -21,6 +22,11 @@ class TurnierRepositoryImpl : TurnierRepository {
|
|||
id = row[TurnierTable.id].toKotlinUuid(),
|
||||
veranstaltungId = row[TurnierTable.veranstaltungId].toKotlinUuid(),
|
||||
oepsTurniernummer = row[TurnierTable.oepsTurniernummer],
|
||||
turnierNummer = row[TurnierTable.turnierNummer],
|
||||
einschraenkungen = row[TurnierTable.einschraenkungen]
|
||||
.split(',')
|
||||
.mapNotNull { key -> key.trim().takeIf { it.isNotBlank() } }
|
||||
.map(TeilnehmerKreisE::valueOf),
|
||||
status = row[TurnierTable.status],
|
||||
publishedAt = row[TurnierTable.publishedAt]?.toString(),
|
||||
)
|
||||
|
|
@ -31,6 +37,8 @@ class TurnierRepositoryImpl : TurnierRepository {
|
|||
stmt[TurnierTable.id] = t.id.toJavaUuid()
|
||||
stmt[TurnierTable.veranstaltungId] = t.veranstaltungId.toJavaUuid()
|
||||
stmt[TurnierTable.oepsTurniernummer] = t.oepsTurniernummer
|
||||
stmt[TurnierTable.turnierNummer] = t.turnierNummer
|
||||
stmt[TurnierTable.einschraenkungen] = t.einschraenkungen.joinToString(",") { it.name }
|
||||
stmt[TurnierTable.status] = t.status
|
||||
stmt[TurnierTable.publishedAt] = null
|
||||
stmt[TurnierTable.createdAt] = now
|
||||
|
|
@ -63,6 +71,8 @@ class TurnierRepositoryImpl : TurnierRepository {
|
|||
val now = Clock.System.now()
|
||||
TurnierTable.update({ TurnierTable.id eq t.id.toJavaUuid() }) { stmt ->
|
||||
stmt[TurnierTable.oepsTurniernummer] = t.oepsTurniernummer
|
||||
stmt[TurnierTable.turnierNummer] = t.turnierNummer
|
||||
stmt[TurnierTable.einschraenkungen] = t.einschraenkungen.joinToString(",") { it.name }
|
||||
stmt[TurnierTable.status] = t.status
|
||||
stmt[TurnierTable.updatedAt] = now
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
package at.mocode.entries.service.turniere
|
||||
|
||||
import at.mocode.core.domain.model.TeilnehmerKreisE
|
||||
import at.mocode.entries.domain.repository.NennungRepository
|
||||
import at.mocode.entries.service.errors.LockedException
|
||||
import at.mocode.entries.service.errors.ValidationException
|
||||
|
|
@ -12,11 +13,19 @@ class TurnierService(
|
|||
private val nennungen: NennungRepository,
|
||||
) {
|
||||
|
||||
suspend fun create(veranstaltungId: Uuid, oepsNr: String, status: String? = null): Turnier {
|
||||
suspend fun create(
|
||||
veranstaltungId: Uuid,
|
||||
oepsNr: String,
|
||||
turnierNummer: String,
|
||||
einschraenkungen: List<TeilnehmerKreisE>,
|
||||
status: String? = null,
|
||||
): Turnier {
|
||||
val t = Turnier(
|
||||
id = Uuid.random(),
|
||||
veranstaltungId = veranstaltungId,
|
||||
oepsTurniernummer = oepsNr,
|
||||
turnierNummer = turnierNummer,
|
||||
einschraenkungen = einschraenkungen,
|
||||
status = status ?: "DRAFT",
|
||||
publishedAt = null,
|
||||
)
|
||||
|
|
@ -27,12 +36,24 @@ class TurnierService(
|
|||
|
||||
suspend fun list(status: String?, oepsNr: String?): List<Turnier> = repo.findAll(status, oepsNr)
|
||||
|
||||
suspend fun update(id: Uuid, oepsNr: String): Turnier {
|
||||
suspend fun update(id: Uuid, oepsNr: String, turnierNummer: String, einschraenkungen: List<TeilnehmerKreisE>): Turnier {
|
||||
val current = get(id)
|
||||
if (current.status == "PUBLISHED" && current.oepsTurniernummer != oepsNr) {
|
||||
if (
|
||||
current.status == "PUBLISHED" && (
|
||||
current.oepsTurniernummer != oepsNr ||
|
||||
current.turnierNummer != turnierNummer ||
|
||||
current.einschraenkungen != einschraenkungen
|
||||
)
|
||||
) {
|
||||
throw LockedException("Turnier ist PUBLISHED – strukturelle Felder nicht änderbar")
|
||||
}
|
||||
return repo.update(current.copy(oepsTurniernummer = oepsNr))
|
||||
return repo.update(
|
||||
current.copy(
|
||||
oepsTurniernummer = oepsNr,
|
||||
turnierNummer = turnierNummer,
|
||||
einschraenkungen = einschraenkungen,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun delete(id: Uuid): Boolean {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
package at.mocode.entries.service.turniere
|
||||
|
||||
import at.mocode.core.domain.model.TeilnehmerKreisE
|
||||
import at.mocode.entries.service.persistence.VeranstaltungTable
|
||||
import at.mocode.entries.service.tenant.tenantTransaction
|
||||
import org.jetbrains.exposed.v1.jdbc.selectAll
|
||||
|
|
@ -12,11 +13,15 @@ import kotlin.uuid.toKotlinUuid
|
|||
|
||||
data class CreateTurnierRequest(
|
||||
val oepsTurniernummer: String,
|
||||
val turnierNummer: String,
|
||||
val einschraenkungen: List<TeilnehmerKreisE> = emptyList(),
|
||||
val status: String? = null,
|
||||
)
|
||||
|
||||
data class UpdateTurnierRequest(
|
||||
val oepsTurniernummer: String,
|
||||
val turnierNummer: String,
|
||||
val einschraenkungen: List<TeilnehmerKreisE> = emptyList(),
|
||||
)
|
||||
|
||||
data class UpdateTurnierStatusRequest(
|
||||
|
|
@ -34,7 +39,13 @@ class TurniereController(
|
|||
suspend fun create(@RequestBody body: CreateTurnierRequest): Turnier {
|
||||
// Veranstaltung pro Tenant auflösen: wir nehmen die einzige vorhandene ID aus dem Schema
|
||||
val veranstaltungId = resolveVeranstaltungId()
|
||||
return service.create(veranstaltungId, body.oepsTurniernummer, body.status)
|
||||
return service.create(
|
||||
veranstaltungId = veranstaltungId,
|
||||
oepsNr = body.oepsTurniernummer,
|
||||
turnierNummer = body.turnierNummer,
|
||||
einschraenkungen = body.einschraenkungen,
|
||||
status = body.status,
|
||||
)
|
||||
}
|
||||
|
||||
@GetMapping("/turniere")
|
||||
|
|
@ -48,7 +59,12 @@ class TurniereController(
|
|||
|
||||
@PutMapping("/turniere/{id}")
|
||||
suspend fun update(@PathVariable id: String, @RequestBody body: UpdateTurnierRequest): Turnier =
|
||||
service.update(Uuid.parse(id), body.oepsTurniernummer)
|
||||
service.update(
|
||||
id = Uuid.parse(id),
|
||||
oepsNr = body.oepsTurniernummer,
|
||||
turnierNummer = body.turnierNummer,
|
||||
einschraenkungen = body.einschraenkungen,
|
||||
)
|
||||
|
||||
@DeleteMapping("/turniere/{id}")
|
||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
-- V4: Add turnier_nummer and einschraenkungen to turniere
|
||||
-- Context: ÖTO § 3 Turniernummer + Teilnehmerkreis-Einschränkungen
|
||||
|
||||
ALTER TABLE IF EXISTS turniere
|
||||
ADD COLUMN IF NOT EXISTS turnier_nummer VARCHAR(5);
|
||||
|
||||
ALTER TABLE IF EXISTS turniere
|
||||
ADD COLUMN IF NOT EXISTS einschraenkungen TEXT;
|
||||
|
||||
UPDATE turniere
|
||||
SET turnier_nummer = oeps_turniernummer
|
||||
WHERE (turnier_nummer IS NULL OR turnier_nummer = '')
|
||||
AND oeps_turniernummer ~ '^\d{5}$';
|
||||
|
||||
UPDATE turniere
|
||||
SET einschraenkungen = ''
|
||||
WHERE einschraenkungen IS NULL;
|
||||
|
||||
ALTER TABLE turniere
|
||||
ALTER COLUMN turnier_nummer SET NOT NULL;
|
||||
|
||||
ALTER TABLE turniere
|
||||
ALTER COLUMN einschraenkungen SET NOT NULL;
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
package at.mocode.events.domain.model
|
||||
|
||||
import at.mocode.core.domain.model.SparteE
|
||||
import at.mocode.core.domain.model.TeilnehmerKreisE
|
||||
import at.mocode.core.domain.model.TurnierkategorieE
|
||||
import at.mocode.core.domain.model.TurnierStatusE
|
||||
import at.mocode.events.domain.validation.TurnierBewerbDescriptor
|
||||
|
|
@ -48,9 +49,11 @@ data class DomTurnier(
|
|||
|
||||
// Basis-Informationen
|
||||
var name: String,
|
||||
val turnierNummer: String,
|
||||
var sparte: SparteE,
|
||||
var kategorie: TurnierkategorieE,
|
||||
var datum: LocalDate,
|
||||
var einschraenkungen: List<TeilnehmerKreisE> = emptyList(),
|
||||
|
||||
// Funktionäre
|
||||
@Serializable(with = UuidSerializer::class)
|
||||
|
|
@ -98,6 +101,9 @@ data class DomTurnier(
|
|||
if (name.isBlank()) {
|
||||
warnings.add("Turniername ist erforderlich.")
|
||||
}
|
||||
if (!Regex("^\\d{5}$").matches(turnierNummer)) {
|
||||
warnings.add("Turniernummer muss exakt 5 Ziffern enthalten (z.B. 26128).")
|
||||
}
|
||||
maxBewerbe?.let { max ->
|
||||
if (max <= 0) {
|
||||
warnings.add("Maximale Bewerb-Anzahl muss positiv sein.")
|
||||
|
|
|
|||
|
|
@ -441,6 +441,36 @@ enum class TurnierStatusE {
|
|||
ABGESAGT
|
||||
}
|
||||
|
||||
/**
|
||||
* Einschränkungen des Teilnehmerkreises gemäß ÖTO § 3 (Zusatz-Buchstaben bei Turnieren).
|
||||
*/
|
||||
@Serializable
|
||||
enum class TeilnehmerKreisE {
|
||||
/** Jugend/Junioren/Junge Reiter (J) */
|
||||
JUGEND_JUNIOREN_YR,
|
||||
|
||||
/** Ponys (P) */
|
||||
PONYS,
|
||||
|
||||
/** Noriker (N) */
|
||||
NORIKER,
|
||||
|
||||
/** Haflinger (H) */
|
||||
HAFLINGER,
|
||||
|
||||
/** Ländliche Reiter (L) */
|
||||
LAENDLICHE_REITER,
|
||||
|
||||
/** Vollblutaraber (A) */
|
||||
VOLLBLUTARABER,
|
||||
|
||||
/** Kaltblut (K) */
|
||||
KALTBLUT,
|
||||
|
||||
/** Damensattel (D) */
|
||||
DAMENSATTEL
|
||||
}
|
||||
|
||||
/**
|
||||
* Status einer Ausschreibung gemäß ÖTO-Genehmigungsworkflow.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -155,7 +155,8 @@ Die ÖTO definiert sparten- und klassenabhängige Schwellenwerte, ab wievielen S
|
|||
| **TBA** | Turnierbeauftragter. Hat bei Regelkonflikten immer das letzte Wort. Jede Überschreibung wird als → *Override-Event* gespeichert. | ÖTO § 24/§ 25 |
|
||||
| **Tierwohl-Euro** | Gebühr, die **pro Start** anfällt (nicht pro Nennung!). | ÖTO Gebührenordnung |
|
||||
| **Turnier** | In unserer Software: Eine pferdesportliche Veranstaltung mit einer offiziellen **Ausschreibung** und einer vom OEPS/LFV vergebenen, eindeutigen **Turniernummer**. Entspricht ÖTO § 2 Abs. 2. Ist eine Spezialisierung von → *Veranstaltung*. | ÖTO § 2 Abs. 2, § 5, § 24 |
|
||||
| **Turniernummer** | Vom OEPS vergebene, eindeutige Kennung eines Turniers (z.B. `25123`). Ohne diese Nummer darf keine offizielle Ausschreibung veröffentlicht werden. | ZNS A-Satz |
|
||||
| **Turniernummer** | Offizielle, vom OEPS vergebene **5-stellige** Kennung eines Turniers (z.B. `26128`). Sie ist eindeutig und Voraussetzung für die offizielle Ausschreibung. | ÖTO § 3, ZNS A-Satz |
|
||||
| **Teilnehmerkreis-Einschränkung** | Optionale Einschränkung des zulässigen Teilnehmerkreises eines Turniers gemäß ÖTO-Gliederung (§ 3), dargestellt über Zusatz-Buchstaben (z.B. `-H`, `-P`, `-J`). | ÖTO § 3 |
|
||||
| **Turnierkategorie** | Siehe → *Kategorie*. | ÖTO § 3 Abs. 4 |
|
||||
| **Turnierkassa** | Kassa auf Ebene des einzelnen → *Turniers*. Hält Belege, Tagesabschlüsse und Barbestände pro Turnier. Wird in der → *Veranstaltungs‑Kassa* konsolidiert. | Billing Context |
|
||||
| **TeilnehmerKonto** | Konto eines Zahlers auf Ebene der → *Veranstaltung* (nicht nur eines Turniers). Aggregiert Saldo, Einzahlungen und Verrechnungen über alle → *Turniere* derselben Veranstaltung hinweg (Multi‑Turnier‑Aggregation). Ermöglicht, dass eine einzelne Zahlung mehrere Rechnungen in verschiedenen Turnieren derselben Veranstaltung ausgleicht. | Billing Context |
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user