Upload via GUI (56 Dateien)
This commit is contained in:
@@ -427,6 +427,7 @@ class TicketRepository(
|
||||
DepartmentSaveRequest(
|
||||
id = id, name = input.name, email = input.email, isDefault = input.isDefault, signature = input.signature,
|
||||
isActive = input.isActive, managerId = input.managerId, slaPlanId = input.slaPlanId, notes = input.notes,
|
||||
autoAssignMode = input.autoAssignMode, autoAssignStaffId = input.autoAssignStaffId,
|
||||
),
|
||||
).id
|
||||
|
||||
@@ -794,7 +795,7 @@ private fun CustomerDetailDto.toDomain() = CustomerDetail(id, name, email, organ
|
||||
private fun BanEntryDto.toDomain() = BanEntry(id, matchType, value, reason, addedByName, createdAt)
|
||||
private fun DepartmentDto.toDomain() = Department(
|
||||
id, name, email, isDefault, signature, isActive, managerId, managerName, slaPlanId, notes,
|
||||
members.map { DepartmentMember(it.id, it.name, it.isPrimary) }, updatedAt,
|
||||
members.map { DepartmentMember(it.id, it.name, it.isPrimary) }, updatedAt, autoAssignMode, autoAssignStaffId,
|
||||
)
|
||||
private fun PriorityFullDto.toDomain() = PriorityFull(id, name, color, responseHours, resolutionHours, sortOrder)
|
||||
private fun HelpTopicDto.toDomain() = HelpTopic(id, name, departmentId, departmentName, priorityId, priorityName, priorityColor, isActive, sortOrder, isPublic, parentId, slaPlanId, path.ifBlank { name }, notes, updatedAt)
|
||||
|
||||
@@ -463,6 +463,9 @@ data class DepartmentDto(
|
||||
val managerName: String? = null,
|
||||
val slaPlanId: Int? = null,
|
||||
val notes: String? = null,
|
||||
// Neue Tickets automatisch zuweisen: "none", "staff" (bestimmter Mitarbeiter) oder "round_robin" (reihum)
|
||||
val autoAssignMode: String = "none",
|
||||
val autoAssignStaffId: Int? = null,
|
||||
val members: List<DepartmentMemberDto> = emptyList(),
|
||||
val createdAt: String? = null,
|
||||
val updatedAt: String? = null,
|
||||
@@ -488,6 +491,8 @@ data class DepartmentSaveRequest(
|
||||
val managerId: Int? = null,
|
||||
val slaPlanId: Int? = null,
|
||||
val notes: String = "",
|
||||
val autoAssignMode: String? = null,
|
||||
val autoAssignStaffId: Int? = null,
|
||||
)
|
||||
|
||||
data class DepartmentDeleteRequest(val action: String = "delete", val id: Int)
|
||||
|
||||
@@ -313,6 +313,8 @@ data class Department(
|
||||
val notes: String? = null,
|
||||
val members: List<DepartmentMember> = emptyList(),
|
||||
val updatedAt: String? = null,
|
||||
val autoAssignMode: String = "none",
|
||||
val autoAssignStaffId: Int? = null,
|
||||
)
|
||||
|
||||
data class DepartmentMember(val id: Int, val name: String, val isPrimary: Boolean)
|
||||
@@ -329,6 +331,8 @@ data class DepartmentInput(
|
||||
val slaPlanId: Int?,
|
||||
val notes: String,
|
||||
val signature: String?,
|
||||
val autoAssignMode: String = "none",
|
||||
val autoAssignStaffId: Int? = null,
|
||||
)
|
||||
|
||||
data class DepartmentData(
|
||||
|
||||
@@ -1421,6 +1421,13 @@ private fun TicketFilterBar(
|
||||
}
|
||||
}
|
||||
|
||||
/** "2026-10-23 14:32:10" (Serverzeit) -> "23.10.2026 14:32"; unbekannte Formate bleiben unverändert. */
|
||||
internal fun formatTicketDate(raw: String): String {
|
||||
val m = Regex("""^(\d{4})-(\d{2})-(\d{2})[ T](\d{2}):(\d{2})""").find(raw.trim()) ?: return raw
|
||||
val (y, mo, d, h, mi) = m.destructured
|
||||
return "$d.$mo.$y $h:$mi"
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TicketCard(
|
||||
ticket: TicketSummary,
|
||||
@@ -1472,6 +1479,12 @@ private fun TicketCard(
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
Text(
|
||||
"Eingang: " + formatTicketDate(ticket.createdAt),
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
maxLines = 1,
|
||||
)
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(10.dp),
|
||||
@@ -3619,6 +3632,11 @@ private fun AdminDepartmentsScreen(
|
||||
var managerExpanded by remember { mutableStateOf(false) }
|
||||
var slaPlanId by rememberSaveable(dept?.id) { mutableStateOf(dept?.slaPlanId) }
|
||||
var slaExpanded by remember { mutableStateOf(false) }
|
||||
var autoMode by rememberSaveable(dept?.id) { mutableStateOf(dept?.autoAssignMode ?: "none") }
|
||||
var autoStaffId by rememberSaveable(dept?.id) { mutableStateOf(dept?.autoAssignStaffId) }
|
||||
var autoModeExpanded by remember { mutableStateOf(false) }
|
||||
var autoStaffExpanded by remember { mutableStateOf(false) }
|
||||
val autoModes = listOf("none" to "Nicht automatisch zuweisen", "staff" to "Immer an einen bestimmten Mitarbeiter", "round_robin" to "Reihum an die Mitarbeiter der Abteilung")
|
||||
Column(Modifier.padding(padding).fillMaxSize()) {
|
||||
ScrollableTabRow(selectedTabIndex = tab, edgePadding = 12.dp) {
|
||||
Tab(selected = tab == 0, onClick = { tab = 0 }, text = { Text("Abteilung") })
|
||||
@@ -3643,6 +3661,15 @@ private fun AdminDepartmentsScreen(
|
||||
DropdownMenuItem(text = { Text("— Keine(r) —") }, onClick = { managerId = null; managerExpanded = false })
|
||||
state.departmentStaffOptions.forEach { m -> DropdownMenuItem(text = { Text(m.name) }, onClick = { managerId = m.id; managerExpanded = false }) }
|
||||
}
|
||||
SelectionField(label = "Neue Tickets automatisch zuweisen", value = autoModes.firstOrNull { it.first == autoMode }?.second ?: autoModes[0].second, expanded = autoModeExpanded, onExpand = { autoModeExpanded = true }, onDismiss = { autoModeExpanded = false }) {
|
||||
autoModes.forEach { (key, label) -> DropdownMenuItem(text = { Text(label) }, onClick = { autoMode = key; autoModeExpanded = false }) }
|
||||
}
|
||||
if (autoMode == "staff") {
|
||||
SelectionField(label = "Mitarbeiter", value = state.departmentStaffOptions.firstOrNull { it.id == autoStaffId }?.name ?: "— Bitte wählen —", expanded = autoStaffExpanded, onExpand = { autoStaffExpanded = true }, onDismiss = { autoStaffExpanded = false }) {
|
||||
state.departmentStaffOptions.forEach { m -> DropdownMenuItem(text = { Text(m.name) }, onClick = { autoStaffId = m.id; autoStaffExpanded = false }) }
|
||||
}
|
||||
}
|
||||
Text("Gilt für Tickets aus Formular, E-Mail, API und Live-Chat. Eine Ticket-Regel mit „Mitarbeiter zuweisen“ hat Vorrang. Im Urlaub bekommt die Vertretung das Ticket, „Reihum“ überspringt Mitarbeiter im Urlaub oder offline.", style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||
SelectionField(label = "SLA-Plan", value = state.departmentSlaPlans.firstOrNull { it.id == slaPlanId }?.name ?: "Zeiten der Priorität verwenden", expanded = slaExpanded, onExpand = { slaExpanded = true }, onDismiss = { slaExpanded = false }) {
|
||||
DropdownMenuItem(text = { Text("Zeiten der Priorität verwenden") }, onClick = { slaPlanId = null; slaExpanded = false })
|
||||
state.departmentSlaPlans.forEach { p -> DropdownMenuItem(text = { Text(p.name) }, onClick = { slaPlanId = p.id; slaExpanded = false }) }
|
||||
@@ -3692,7 +3719,7 @@ private fun AdminDepartmentsScreen(
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) {
|
||||
Button(
|
||||
onClick = {
|
||||
onSave(dept?.id, DepartmentInput(name.trim(), email.trim(), isDefault, isActive, managerId, slaPlanId, notes.trim(), signature.trim().ifBlank { null })) { editingId = null; isCreating = false; tab = 0 }
|
||||
onSave(dept?.id, DepartmentInput(name.trim(), email.trim(), isDefault, isActive, managerId, slaPlanId, notes.trim(), signature.trim().ifBlank { null }, autoMode, if (autoMode == "staff") autoStaffId else null)) { editingId = null; isCreating = false; tab = 0 }
|
||||
},
|
||||
shape = MaterialTheme.shapes.medium,
|
||||
enabled = !state.departments2MutationLoading && name.isNotBlank(),
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package de.deskhub.staff.ui
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
/** Eingangsdatum in der Ticketliste: "2026-10-23 14:32:10" wird zu "23.10.2026 14:32". */
|
||||
class TicketDateTest {
|
||||
@Test
|
||||
fun formatsServerTimestamp() {
|
||||
assertEquals("23.10.2026 14:32", formatTicketDate("2026-10-23 14:32:10"))
|
||||
assertEquals("01.01.2027 00:05", formatTicketDate("2027-01-01 00:05:00"))
|
||||
assertEquals("23.10.2026 14:32", formatTicketDate("2026-10-23T14:32:10"))
|
||||
assertEquals("23.10.2026 14:32", formatTicketDate(" 2026-10-23 14:32 "))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun keepsUnknownFormats() {
|
||||
assertEquals("gestern", formatTicketDate("gestern"))
|
||||
assertEquals("", formatTicketDate(""))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user