feat: integrate new desktop shell and extend backend & ADRs
- Added `meldestelle-desktop` module using JVM/Compose Desktop, registered in `settings.gradle.kts`. - Integrated new screens and desktop navigation into core: `Veranstaltungen`, `TurnierDetail`, etc. - Expanded backend with `ExposedFunktionaerRepository` in `officials-infrastructure`. - Completed ADRs for bounded context mapping (`ADR-0014`) and context map (`ADR-0015`). - Updated and extended project documentation with session logs and architecture decisions. Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
This commit is contained in:
+136
@@ -0,0 +1,136 @@
|
||||
@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class)
|
||||
|
||||
package at.mocode.persons.domain.model
|
||||
|
||||
import at.mocode.core.domain.model.DatenQuelleE
|
||||
import at.mocode.core.domain.model.LizenzKlasseE
|
||||
import at.mocode.core.domain.model.SparteE
|
||||
import at.mocode.core.domain.serialization.KotlinInstantSerializer
|
||||
import at.mocode.core.domain.serialization.KotlinLocalDateSerializer
|
||||
import at.mocode.core.domain.serialization.UuidSerializer
|
||||
import kotlinx.datetime.LocalDate
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlin.time.Clock
|
||||
import kotlin.time.Instant
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
/**
|
||||
* Domain model representing a rider (Reiter) in the actor-context.
|
||||
*
|
||||
* A rider is a specialization of a person with additional equestrian-specific
|
||||
* attributes such as license, start card, and competition eligibility.
|
||||
* Data is primarily sourced from the OEPS ZNS (LIZENZ01.DAT).
|
||||
*
|
||||
* Key rules (ÖTO):
|
||||
* - A rider requires an active Startkarte (annual fee paid) to compete nationally.
|
||||
* - LizenzKlasse determines which competition classes the rider may enter.
|
||||
* - Satznummer (6-digit) is the primary key for ZNS data exchange.
|
||||
* - Kopfnummer is NOT a unique identifier – it can change.
|
||||
*
|
||||
* @property reiterId Unique internal identifier (UUID).
|
||||
* @property personId Reference to the base DomPerson record (UUID).
|
||||
* @property satznummer 6-digit ZNS primary key for data exchange. Primary key for ZNS.
|
||||
* @property lizenzNummer OEPS license number (from ZNS LIZENZ01.DAT).
|
||||
* @property lizenzKlasse License class determining competition eligibility (e.g. R1, RD2).
|
||||
* @property lizenzSparten Disciplines for which the license is valid.
|
||||
* @property startkartAktiv Whether the annual start card fee has been paid.
|
||||
* @property startkartSaison Season year for which the start card is valid (e.g. 2026).
|
||||
* @property feiId FEI international rider ID (optional).
|
||||
* @property nation Nation code (e.g. AUT).
|
||||
* @property geburtsdatum Date of birth (for age class validation).
|
||||
* @property vereinsNummer Club number (OEPS).
|
||||
* @property vereinsName Club name.
|
||||
* @property istGastreiter Whether the rider is a guest rider (foreign nationality, not in Austrian club).
|
||||
* @property istAktiv Whether the rider is currently active in the system.
|
||||
* @property datenQuelle Source of the data.
|
||||
* @property createdAt Timestamp when this record was created.
|
||||
* @property updatedAt Timestamp when this record was last updated.
|
||||
*/
|
||||
@Serializable
|
||||
data class DomReiter(
|
||||
@Serializable(with = UuidSerializer::class)
|
||||
val reiterId: Uuid = Uuid.random(),
|
||||
|
||||
// Reference to base person
|
||||
@Serializable(with = UuidSerializer::class)
|
||||
val personId: Uuid,
|
||||
|
||||
// ZNS Identification
|
||||
val satznummer: String,
|
||||
val lizenzNummer: String? = null,
|
||||
|
||||
// License & Eligibility
|
||||
val lizenzKlasse: LizenzKlasseE = LizenzKlasseE.LIZENZFREI,
|
||||
val lizenzSparten: List<SparteE> = emptyList(),
|
||||
|
||||
// Start Card (Startkarte) – annual fee proof
|
||||
val startkartAktiv: Boolean = false,
|
||||
val startkartSaison: Int? = null,
|
||||
|
||||
// International
|
||||
val feiId: String? = null,
|
||||
val nation: String? = null,
|
||||
|
||||
// Personal Data (denormalized from DomPerson for performance)
|
||||
val nachname: String,
|
||||
val vorname: String,
|
||||
@Serializable(with = KotlinLocalDateSerializer::class)
|
||||
val geburtsdatum: LocalDate? = null,
|
||||
|
||||
// Club Affiliation
|
||||
val vereinsNummer: String? = null,
|
||||
val vereinsName: String? = null,
|
||||
|
||||
// Status
|
||||
val istGastreiter: Boolean = false,
|
||||
val istAktiv: Boolean = true,
|
||||
val datenQuelle: DatenQuelleE = DatenQuelleE.IMPORT_ZNS,
|
||||
|
||||
// Audit Fields
|
||||
@Serializable(with = KotlinInstantSerializer::class)
|
||||
val createdAt: Instant = Clock.System.now(),
|
||||
@Serializable(with = KotlinInstantSerializer::class)
|
||||
var updatedAt: Instant = Clock.System.now()
|
||||
) {
|
||||
/**
|
||||
* Returns the display name of the rider.
|
||||
*/
|
||||
fun getDisplayName(): String = "$vorname $nachname"
|
||||
|
||||
/**
|
||||
* Checks if the rider is eligible to compete nationally.
|
||||
* Requires an active start card (Startkarte).
|
||||
*/
|
||||
fun isStartberechtigt(): Boolean = istAktiv && startkartAktiv
|
||||
|
||||
/**
|
||||
* Checks if the rider holds a license for the given discipline.
|
||||
*/
|
||||
fun hasLizenzForSparte(sparte: SparteE): Boolean =
|
||||
lizenzKlasse == LizenzKlasseE.LIZENZFREI || lizenzSparten.contains(sparte)
|
||||
|
||||
/**
|
||||
* Validates the rider for competition entry.
|
||||
* Returns a list of warning messages (never hard errors – TBA has final say).
|
||||
*/
|
||||
fun validateForNennung(sparte: SparteE): List<String> {
|
||||
val warnings = mutableListOf<String>()
|
||||
|
||||
if (!istAktiv) {
|
||||
warnings.add("Reiter ${getDisplayName()} ist nicht aktiv")
|
||||
}
|
||||
if (!startkartAktiv) {
|
||||
warnings.add("Reiter ${getDisplayName()} hat keine aktive Startkarte für Saison $startkartSaison")
|
||||
}
|
||||
if (!hasLizenzForSparte(sparte)) {
|
||||
warnings.add("Reiter ${getDisplayName()} hat keine Lizenz für Sparte $sparte (Lizenzklasse: $lizenzKlasse)")
|
||||
}
|
||||
|
||||
return warnings
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a copy of this rider with an updated timestamp.
|
||||
*/
|
||||
fun withUpdatedTimestamp(): DomReiter = this.copy(updatedAt = Clock.System.now())
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class)
|
||||
|
||||
package at.mocode.persons.domain.repository
|
||||
|
||||
import at.mocode.core.domain.model.LizenzKlasseE
|
||||
import at.mocode.core.domain.model.SparteE
|
||||
import at.mocode.persons.domain.model.DomReiter
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
/**
|
||||
* Repository-Interface für DomReiter (Reiter) Domain-Operationen.
|
||||
*
|
||||
* Definiert den Vertrag für Datenzugriffs-Operationen ohne Abhängigkeit
|
||||
* von konkreten Implementierungsdetails (Datenbank, etc.).
|
||||
*/
|
||||
interface ReiterRepository {
|
||||
|
||||
/**
|
||||
* Sucht einen Reiter anhand seiner eindeutigen ID.
|
||||
*/
|
||||
suspend fun findById(id: Uuid): DomReiter?
|
||||
|
||||
/**
|
||||
* Sucht einen Reiter anhand seiner Satznummer (OEPS-Mitgliedsnummer).
|
||||
*/
|
||||
suspend fun findBySatznummer(satznummer: String): DomReiter?
|
||||
|
||||
/**
|
||||
* Sucht einen Reiter anhand seiner FEI-ID.
|
||||
*/
|
||||
suspend fun findByFeiId(feiId: String): DomReiter?
|
||||
|
||||
/**
|
||||
* Sucht Reiter anhand von Vor- und/oder Nachname (Teilübereinstimmung).
|
||||
*/
|
||||
suspend fun findByName(searchTerm: String, limit: Int = 50): List<DomReiter>
|
||||
|
||||
/**
|
||||
* Sucht alle Reiter eines bestimmten Vereins.
|
||||
*/
|
||||
suspend fun findByVereinsNummer(vereinsNummer: String, activeOnly: Boolean = true): List<DomReiter>
|
||||
|
||||
/**
|
||||
* Sucht alle Reiter mit einer bestimmten Lizenzklasse.
|
||||
*/
|
||||
suspend fun findByLizenzKlasse(lizenzKlasse: LizenzKlasseE, activeOnly: Boolean = true): List<DomReiter>
|
||||
|
||||
/**
|
||||
* Sucht alle Reiter, die für eine bestimmte Sparte lizenziert sind.
|
||||
*/
|
||||
suspend fun findBySparte(sparte: SparteE, activeOnly: Boolean = true): List<DomReiter>
|
||||
|
||||
/**
|
||||
* Sucht alle Gastreiter.
|
||||
*/
|
||||
suspend fun findGastreiter(activeOnly: Boolean = true): List<DomReiter>
|
||||
|
||||
/**
|
||||
* Gibt alle aktiven Reiter zurück (paginiert).
|
||||
*/
|
||||
suspend fun findAllActive(limit: Int = 100, offset: Int = 0): List<DomReiter>
|
||||
|
||||
/**
|
||||
* Gibt alle Reiter zurück (paginiert).
|
||||
*/
|
||||
suspend fun findAll(limit: Int = 100, offset: Int = 0): List<DomReiter>
|
||||
|
||||
/**
|
||||
* Speichert einen Reiter (Insert oder Update).
|
||||
*/
|
||||
suspend fun save(reiter: DomReiter): DomReiter
|
||||
|
||||
/**
|
||||
* Löscht einen Reiter anhand seiner ID.
|
||||
*
|
||||
* @return true wenn gelöscht, false wenn nicht gefunden
|
||||
*/
|
||||
suspend fun delete(id: Uuid): Boolean
|
||||
|
||||
/**
|
||||
* Zählt alle aktiven Reiter.
|
||||
*/
|
||||
suspend fun countActive(): Long
|
||||
|
||||
/**
|
||||
* Prüft ob ein Reiter mit der gegebenen Satznummer bereits existiert.
|
||||
*/
|
||||
suspend fun existsBySatznummer(satznummer: String): Boolean
|
||||
}
|
||||
Reference in New Issue
Block a user