feat(domain+frontend): implement structured division warnings and enhance validation rules
- **Domain Updates:** - Introduced `AbteilungsWarnung` entity for structured warning handling compliant with ÖTO § 39. - Added validation rules for mandatory and optional division thresholds and structural completeness. - Implemented `CompetitionWarningService` and `AbteilungsRegelService` for domain-centric validations. - Updated domain models (`Bewerb`, `Abteilung`) to reflect structured warning logic. - **Services:** - Expanded `BewerbService` to include warning validation through `CompetitionWarningService`. - **Frontend Enhancements:** - Updated `TurnierBewerbeTab` to display warnings using tooltips with clear descriptions and structured formatting. - Modified `BewerbUiModel` to handle warnings and integrate them into the UI. - **Persistence:** - Implemented `CompetitionRepositoryImpl` to map database rows to the new domain models and validation logic. - **Testing:** - Added comprehensive unit tests for `validateStrukturellesTeilung` and division-specific warnings. - Enhanced existing tests to validate the new warning structure and code-based assertions. - **Docs:** - Updated roadmap to reflect the completion of structural warnings implementation.
This commit is contained in:
+7
@@ -9,6 +9,13 @@ data class Bewerb(
|
||||
val sparte: String,
|
||||
val klasse: String,
|
||||
val nennungen: Int,
|
||||
val warnungen: List<AbteilungsWarnung> = emptyList(),
|
||||
)
|
||||
|
||||
data class AbteilungsWarnung(
|
||||
val code: String,
|
||||
val nachricht: String,
|
||||
val oetoParagraph: String?
|
||||
)
|
||||
|
||||
interface BewerbRepository {
|
||||
|
||||
+1
-1
@@ -114,7 +114,7 @@ class BewerbViewModel(
|
||||
scope.launch {
|
||||
manager.getConnectedPeers().collect { peers ->
|
||||
reduce { it.copy(discoveredNodes = peers.map { p ->
|
||||
at.mocode.frontend.core.network.discovery.DiscoveredService("P2P", p, 0)
|
||||
DiscoveredService("P2P", p, 0)
|
||||
}) }
|
||||
}
|
||||
}
|
||||
|
||||
+34
-3
@@ -11,7 +11,9 @@ import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Refresh
|
||||
import androidx.compose.material.icons.filled.Warning
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.material3.TooltipAnchorPosition
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -293,6 +295,7 @@ private fun RowScope.TableHeaderCell(text: String, width: androidx.compose.ui.un
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun BewerbeTableRow(bewerb: BewerbUiModel, isSelected: Boolean, onClick: () -> Unit) {
|
||||
Row(
|
||||
@@ -315,6 +318,30 @@ private fun BewerbeTableRow(bewerb: BewerbUiModel, isSelected: Boolean, onClick:
|
||||
Text(bewerb.beginn, fontSize = 12.sp, modifier = Modifier.width(55.dp))
|
||||
Text(bewerb.ende, fontSize = 12.sp, modifier = Modifier.width(55.dp))
|
||||
Text(bewerb.name, fontSize = 12.sp, modifier = Modifier.weight(1f), maxLines = 2)
|
||||
if (bewerb.warnungen.isNotEmpty()) {
|
||||
TooltipBox(
|
||||
positionProvider = TooltipDefaults.rememberTooltipPositionProvider(),
|
||||
tooltip = {
|
||||
PlainTooltip {
|
||||
Column {
|
||||
bewerb.warnungen.forEach { warnung ->
|
||||
Text("${warnung.oetoParagraph ?: ""}: ${warnung.nachricht}", fontSize = 11.sp)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
state = rememberTooltipState(),
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.Warning,
|
||||
contentDescription = "Warnungen vorhanden",
|
||||
modifier = Modifier.padding(horizontal = 4.dp).size(16.dp),
|
||||
tint = Color(0xFFFACC15) // Yellow-400
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Spacer(Modifier.width(24.dp))
|
||||
}
|
||||
Text("${bewerb.zns}", fontSize = 12.sp, modifier = Modifier.width(45.dp))
|
||||
Text("${bewerb.nennungen}", fontSize = 12.sp, modifier = Modifier.width(75.dp))
|
||||
}
|
||||
@@ -484,7 +511,8 @@ private fun BewerbListItem.toUiModel() = BewerbUiModel(
|
||||
typ = "",
|
||||
zeile1 = "",
|
||||
zns = 1,
|
||||
nennungen = nennungen
|
||||
nennungen = nennungen,
|
||||
warnungen = warnungen
|
||||
)
|
||||
|
||||
@Composable
|
||||
@@ -839,6 +867,7 @@ data class BewerbUiModel(
|
||||
val zeile1: String,
|
||||
val zns: Int,
|
||||
val nennungen: Int,
|
||||
val warnungen: List<at.mocode.turnier.feature.domain.AbteilungsWarnung> = emptyList(),
|
||||
)
|
||||
|
||||
private fun sampleBewerbe() = listOf(
|
||||
@@ -853,7 +882,8 @@ private fun sampleBewerbe() = listOf(
|
||||
"Dressur",
|
||||
"Pony Einsteiger Cup OO",
|
||||
0,
|
||||
0
|
||||
0,
|
||||
emptyList()
|
||||
),
|
||||
BewerbUiModel(
|
||||
"28.05.2023",
|
||||
@@ -866,7 +896,8 @@ private fun sampleBewerbe() = listOf(
|
||||
"Dressur",
|
||||
"Pony Einsteiger Cup OO",
|
||||
0,
|
||||
0
|
||||
0,
|
||||
emptyList()
|
||||
),
|
||||
BewerbUiModel(
|
||||
"28.05.2023",
|
||||
|
||||
Reference in New Issue
Block a user