Add reso check and room list

This commit is contained in:
Lars Westermann 2019-06-12 19:04:44 +02:00
parent fface0e5ba
commit 94c35b9067
Signed by: lars.westermann
GPG key ID: 9D417FA5BB9D5E1D
13 changed files with 193 additions and 90 deletions

View file

@ -9,6 +9,10 @@ reference = "2019-06-12"
offset = 7200000 offset = 7200000
wall_start = 1 wall_start = 1
[reso]
day = 3
time = 900
[general] [general]
wiki_url = "https://wiki.kif.rocks/w/index.php?title=KIF470:Arbeitskreise&action=raw" wiki_url = "https://wiki.kif.rocks/w/index.php?title=KIF470:Arbeitskreise&action=raw"

View file

@ -1,6 +1,7 @@
package de.kif.common package de.kif.common
import de.kif.common.model.ConstraintType import de.kif.common.model.ConstraintType
import de.kif.common.model.Room
import de.kif.common.model.Schedule import de.kif.common.model.Schedule
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@ -16,10 +17,15 @@ data class ConstraintMap(
fun checkConstraints( fun checkConstraints(
check: List<Schedule>, check: List<Schedule>,
against: List<Schedule> against: List<Schedule>,
rooms: List<Room>,
resoDay: Int,
resoTime: Int
): ConstraintMap { ): ConstraintMap {
val map = mutableMapOf<Long, List<ConstraintError>>() val map = mutableMapOf<Long, List<ConstraintError>>()
val roomMap = rooms.associateBy { it.id }
for (schedule in check) { for (schedule in check) {
if (schedule.id == null) continue if (schedule.id == null) continue
val errors = mutableListOf<ConstraintError>() val errors = mutableListOf<ConstraintError>()
@ -54,10 +60,20 @@ fun checkConstraints(
val start = schedule.getAbsoluteStartTime() val start = schedule.getAbsoluteStartTime()
val end = schedule.getAbsoluteEndTime() val end = schedule.getAbsoluteEndTime()
if (schedule.workGroup.resolution) {
val resoDeadline = resoDay * 24 * 60 + resoTime
if (end > resoDeadline) {
errors += ConstraintError("The work group is ${end - resoDeadline} minutes after resolution deadline")
}
}
for (leader in schedule.workGroup.leader) { for (leader in schedule.workGroup.leader) {
for (s in against) { for (s in against) {
if ( if (
schedule != s && schedule != s &&
schedule.day == s.day &&
leader in s.workGroup.leader && leader in s.workGroup.leader &&
start < s.getAbsoluteEndTime() && start < s.getAbsoluteEndTime() &&
s.getAbsoluteStartTime() < end s.getAbsoluteStartTime() < end
@ -70,6 +86,7 @@ fun checkConstraints(
for (s in against) { for (s in against) {
if ( if (
schedule != s && schedule != s &&
schedule.day == s.day &&
schedule.room.id == s.room.id && schedule.room.id == s.room.id &&
start < s.getAbsoluteEndTime() && start < s.getAbsoluteEndTime() &&
s.getAbsoluteStartTime() < end s.getAbsoluteStartTime() < end
@ -146,6 +163,7 @@ fun checkConstraints(
for (s in against) { for (s in against) {
if ( if (
s.workGroup.id == constraint.workGroup && s.workGroup.id == constraint.workGroup &&
schedule.day == s.day &&
start <= s.getAbsoluteEndTime() && start <= s.getAbsoluteEndTime() &&
s.getAbsoluteStartTime() <= end s.getAbsoluteStartTime() <= end
) { ) {
@ -159,14 +177,25 @@ fun checkConstraints(
for (constraint in constraints) { for (constraint in constraints) {
for (s in against) { for (s in against) {
if ( if (
s.workGroup.id == constraint.workGroup && s.workGroup.id == constraint.workGroup && (
s.getAbsoluteEndTime() > start s.day > schedule.day ||
s.day == schedule.day && s.getAbsoluteEndTime() > start
)
) { ) {
errors += ConstraintError("Work group requires after ${s.workGroup.name}!") errors += ConstraintError("Work group requires after ${s.workGroup.name}!")
} }
} }
} }
} }
ConstraintType.Room -> {
val roomBools = constraints.map { it.room == schedule.room.id }
if (roomBools.none { it }) {
val roomList = constraints.mapNotNull { it.room }.distinct().sorted().map {
"${(roomMap[it]?.name ?: "")}($it)"
}
errors += ConstraintError("Work group requires rooms $roomList, but is in room ${schedule.room.name}(${schedule.room.id})")
}
}
} }
} }

View file

@ -7,43 +7,49 @@ data class WorkGroupConstraint(
val type: ConstraintType, val type: ConstraintType,
val day: Int? = null, val day: Int? = null,
val time: Int? = null, val time: Int? = null,
val workGroup: Long? = null val workGroup: Long? = null,
val room: Long? = null
) )
enum class ConstraintType { enum class ConstraintType {
/** /**
* Requires day, permits time and workGroup. * Requires day, permits time, workGroup and room.
*/ */
OnlyOnDay, OnlyOnDay,
/** /**
* Requires day, permits time and workGroup. * Requires day, permits time, workGroup and room.
*/ */
NotOnDay, NotOnDay,
/** /**
* Requires time, optionally allows day, permits workGroup. * Requires time, optionally allows day, permits workGroup and room.
*/ */
OnlyAfterTime, OnlyAfterTime,
/** /**
* Requires time, optionally allows day, permits workGroup. * Requires time, optionally allows day, permits workGroup and room.
*/ */
OnlyBeforeTime, OnlyBeforeTime,
/** /**
* Requires time, optionally allows day, permits workGroup * Requires time, optionally allows day, permits workGroup and room.
*/ */
ExactTime, ExactTime,
/** /**
* Requires workGroup, permits day and time. * Requires workGroup, permits day, time and room.
*/ */
NotAtSameTime, NotAtSameTime,
/** /**
* Requires workGroup, permits day and time. * Requires workGroup, permits day, time and room.
*/ */
OnlyAfterWorkGroup OnlyAfterWorkGroup,
/**
* Requires room, permits day, time and workGroup
*/
Room
} }

View file

@ -1,6 +1,7 @@
package de.kif.frontend.views package de.kif.frontend.views
import de.kif.frontend.launch import de.kif.frontend.launch
import de.kif.frontend.repository.RoomRepository
import de.kif.frontend.repository.WorkGroupRepository import de.kif.frontend.repository.WorkGroupRepository
import de.westermann.kobserve.event.EventListener import de.westermann.kobserve.event.EventListener
import de.westermann.kwebview.View import de.westermann.kwebview.View
@ -10,6 +11,7 @@ import de.westermann.kwebview.createHtmlView
import de.westermann.kwebview.iterator import de.westermann.kwebview.iterator
import org.w3c.dom.* import org.w3c.dom.*
import kotlin.browser.document import kotlin.browser.document
import kotlin.dom.clear
fun initWorkGroupConstraints() { fun initWorkGroupConstraints() {
var index = 10000 var index = 10000
@ -149,10 +151,15 @@ fun initWorkGroupConstraints() {
launch { launch {
val all = WorkGroupRepository.all() val all = WorkGroupRepository.all()
val id = (select.options[select.selectedIndex] as? HTMLOptionElement)?.value
for (wg in all) { for (wg in all) {
val option = createHtmlView<HTMLOptionElement>() val option = createHtmlView<HTMLOptionElement>()
option.value = wg.id.toString() option.value = wg.id.toString()
option.textContent = wg.name option.textContent = wg.name
if (option.value == id) {
option.selected = true
}
select.appendChild(option) select.appendChild(option)
} }
} }
@ -177,10 +184,49 @@ fun initWorkGroupConstraints() {
launch { launch {
val all = WorkGroupRepository.all() val all = WorkGroupRepository.all()
val id = (select.options[select.selectedIndex] as? HTMLOptionElement)?.value
for (wg in all) { for (wg in all) {
val option = createHtmlView<HTMLOptionElement>() val option = createHtmlView<HTMLOptionElement>()
option.value = wg.id.toString() option.value = wg.id.toString()
option.textContent = wg.name option.textContent = wg.name
if (option.value == id) {
option.selected = true
}
select.appendChild(option)
}
}
html.appendChild(select)
}.html)
}
}
addList.textView("In Raum x") {
onClick {
constraints.appendChild(View.wrap(createHtmlView<HTMLDivElement>()) {
classList += "input-group"
html.appendChild(TextView("Raum").apply {
classList += "form-btn"
onClick { this@wrap.html.remove() }
}.html)
val select = createHtmlView<HTMLSelectElement>()
select.classList.add("form-control")
select.name = "constraint-room-${index++}"
val id = (select.options[select.selectedIndex] as? HTMLOptionElement)?.value
launch {
val all = RoomRepository.all()
select.clear()
for (room in all) {
val option = createHtmlView<HTMLOptionElement>()
option.value = room.id.toString()
option.textContent = room.name
if (option.value == id) {
option.selected = true
}
select.appendChild(option) select.appendChild(option)
} }
} }
@ -195,7 +241,6 @@ fun initWorkGroupConstraints() {
val span = child.firstElementChild as HTMLElement val span = child.firstElementChild as HTMLElement
span.addEventListener("click", org.w3c.dom.events.EventListener { span.addEventListener("click", org.w3c.dom.events.EventListener {
println("click")
child.remove() child.remove()
}) })
} }

View file

@ -1,5 +1,7 @@
package de.kif.frontend.views.table package de.kif.frontend.views.table
import de.kif.frontend.launch
import de.kif.frontend.repository.TrackRepository
import de.westermann.kwebview.components.InputView import de.westermann.kwebview.components.InputView
import de.westermann.kwebview.iterator import de.westermann.kwebview.iterator
import org.w3c.dom.HTMLFormElement import org.w3c.dom.HTMLFormElement
@ -14,11 +16,14 @@ fun initTableLayout() {
val table = document.getElementsByClassName("table-layout-table")[0] as HTMLTableElement val table = document.getElementsByClassName("table-layout-table")[0] as HTMLTableElement
launch {
val tracks = TrackRepository.all()
val list = table.getElementsByTagName("tr").iterator().asSequence().filter { val list = table.getElementsByTagName("tr").iterator().asSequence().filter {
it.dataset["search"] != null it.dataset["search"] != null
}.map { }.map {
when (it.dataset["edit"]) { when (it.dataset["edit"]) {
"workgroup" -> WorkGroupTableLine(it) "workgroup" -> WorkGroupTableLine(it, tracks)
"room" -> RoomTableLine(it) "room" -> RoomTableLine(it)
else -> TableLine(it) else -> TableLine(it)
} }
@ -32,3 +37,4 @@ fun initTableLayout() {
} }
} }
} }
}

View file

@ -13,7 +13,7 @@ import org.w3c.dom.HTMLElement
import org.w3c.dom.HTMLSpanElement import org.w3c.dom.HTMLSpanElement
import org.w3c.dom.get import org.w3c.dom.get
class WorkGroupTableLine(view: HTMLElement) : TableLine(view) { class WorkGroupTableLine(view: HTMLElement, tracks: List<Track>) : TableLine(view) {
private var lineId = dataset["id"]?.toLongOrNull() ?: -1 private var lineId = dataset["id"]?.toLongOrNull() ?: -1
@ -24,9 +24,7 @@ class WorkGroupTableLine(view: HTMLElement) : TableLine(view) {
private val spanWorkGroupLength: TextView private val spanWorkGroupLength: TextView
private val spanWorkGroupInterested: TextView private val spanWorkGroupInterested: TextView
private val spanWorkGroupTrack: TextView private val spanWorkGroupTrack: TextView
private val spanWorkGroupProjector: TextView
private val spanWorkGroupResolution: TextView private val spanWorkGroupResolution: TextView
private val spanWorkGroupLanguage: TextView
override var searchElement: SearchElement = super.searchElement override var searchElement: SearchElement = super.searchElement
@ -41,12 +39,8 @@ class WorkGroupTableLine(view: HTMLElement) : TableLine(view) {
TextView.wrap(spans.first { it.dataset["editType"] == "workgroup-interested" } as HTMLSpanElement) TextView.wrap(spans.first { it.dataset["editType"] == "workgroup-interested" } as HTMLSpanElement)
spanWorkGroupTrack = spanWorkGroupTrack =
TextView.wrap(spans.first { it.dataset["editType"] == "workgroup-track" } as HTMLSpanElement) TextView.wrap(spans.first { it.dataset["editType"] == "workgroup-track" } as HTMLSpanElement)
spanWorkGroupProjector =
TextView.wrap(spans.first { it.dataset["editType"] == "workgroup-projector" } as HTMLSpanElement)
spanWorkGroupResolution = spanWorkGroupResolution =
TextView.wrap(spans.first { it.dataset["editType"] == "workgroup-resolution" } as HTMLSpanElement) TextView.wrap(spans.first { it.dataset["editType"] == "workgroup-resolution" } as HTMLSpanElement)
spanWorkGroupLanguage =
TextView.wrap(spans.first { it.dataset["editType"] == "workgroup-language" } as HTMLSpanElement)
setupEditable(spanWorkGroupName) { setupEditable(spanWorkGroupName) {
launch { launch {
@ -77,13 +71,6 @@ class WorkGroupTableLine(view: HTMLElement) : TableLine(view) {
} }
} }
setupBoolean(spanWorkGroupProjector) {
launch {
val wg = workGroup.get()
WorkGroupRepository.update(wg.copy(projector = !wg.projector))
}
}
setupBoolean(spanWorkGroupResolution) { setupBoolean(spanWorkGroupResolution) {
launch { launch {
val wg = workGroup.get() val wg = workGroup.get()
@ -91,19 +78,10 @@ class WorkGroupTableLine(view: HTMLElement) : TableLine(view) {
} }
} }
setupList(spanWorkGroupLanguage, Language.values().sortedBy { it.localeName }, { it.localeName }) {
if (it == null) return@setupList
launch { launch {
val wg = workGroup.get() val list = listOf<Track?>(null) + tracks
if (wg.language == it) return@launch
WorkGroupRepository.update(wg.copy(language = it))
}
}
launch { setupList(spanWorkGroupTrack, list, { it.name }) {
val tracks = listOf<Track?>(null) + TrackRepository.all()
setupList(spanWorkGroupTrack, tracks, { it.name }) {
launch x@{ launch x@{
val wg = workGroup.get() val wg = workGroup.get()
if (wg.track == it) return@x if (wg.track == it) return@x
@ -124,9 +102,7 @@ class WorkGroupTableLine(view: HTMLElement) : TableLine(view) {
spanWorkGroupLength.text = wg.length.toString() spanWorkGroupLength.text = wg.length.toString()
spanWorkGroupInterested.text = wg.interested.toString() spanWorkGroupInterested.text = wg.interested.toString()
spanWorkGroupTrack.text = wg.track?.name ?: "" spanWorkGroupTrack.text = wg.track?.name ?: ""
spanWorkGroupProjector.text = wg.projector.toString()
spanWorkGroupResolution.text = wg.resolution.toString() spanWorkGroupResolution.text = wg.resolution.toString()
spanWorkGroupLanguage.text = wg.language.localeName
} }
} }
} }

View file

@ -111,6 +111,16 @@ object Configuration {
val timeline by c(TwitterSpec.timeline) val timeline by c(TwitterSpec.timeline)
} }
private object ResoSpec : ConfigSpec("reso") {
val day by required<Int>()
val time by required<Int>()
}
object Reso {
val day by c(ResoSpec.day)
val time by c(ResoSpec.time)
}
init { init {
var config = Config { var config = Config {
addSpec(ServerSpec) addSpec(ServerSpec)
@ -119,6 +129,7 @@ object Configuration {
addSpec(SecuritySpec) addSpec(SecuritySpec)
addSpec(GeneralSpec) addSpec(GeneralSpec)
addSpec(TwitterSpec) addSpec(TwitterSpec)
addSpec(ResoSpec)
}.from.toml.resource("portal.toml") }.from.toml.resource("portal.toml")
for (file in Files.list(Paths.get("."))) { for (file in Files.list(Paths.get("."))) {

View file

@ -60,7 +60,7 @@ fun Route.board() {
val list = ScheduleRepository.getByDay(day) val list = ScheduleRepository.getByDay(day)
val rooms = RoomRepository.all() val rooms = RoomRepository.all()
val schedules = list.groupBy { it.room }.mapValues { (_, it) -> val schedules = list.groupBy { it.room }.mapValues { (_, it) ->
it.associateBy { it.groupBy {
it.time it.time
} }
} }

View file

@ -70,7 +70,7 @@ fun DIV.renderCalendar(
from: Int, from: Int,
to: Int, to: Int,
rooms: List<Room>, rooms: List<Room>,
schedules: Map<Room, Map<Int, Schedule>> schedules: Map<Room, Map<Int, List<Schedule>>>
) { ) {
val gridLabelWidth = 60 val gridLabelWidth = 60
val minutesOfDay = to - from val minutesOfDay = to - from
@ -144,9 +144,9 @@ fun DIV.renderCalendar(
title = room.name + " - " + timeString title = room.name + " - " + timeString
val schedule = (start..end).mapNotNull { schedules[room]?.get(it) }.firstOrNull() val cellSchedules = (start..end).flatMap { schedules[room]?.get(it) ?: emptyList() }
if (schedule != null) { for(schedule in cellSchedules) {
calendarEntry(schedule, diff, currentTime) calendarEntry(schedule, diff, currentTime)
} }
} }
@ -231,7 +231,7 @@ fun Route.calendar() {
val list = ScheduleRepository.getByDay(day) val list = ScheduleRepository.getByDay(day)
val schedules = list.groupBy { it.room }.mapValues { (_, it) -> val schedules = list.groupBy { it.room }.mapValues { (_, it) ->
it.associateBy { it.groupBy {
it.time it.time
} }
} }

View file

@ -17,7 +17,7 @@ import kotlin.math.min
data class WallData( data class WallData(
val number: Int, val number: Int,
val schedules: Map<Room, Map<Int, Schedule>>, val schedules: Map<Room, Map<Int, List<Schedule>>>,
val max: Int?, val max: Int?,
val min: Int? val min: Int?
) )
@ -26,11 +26,10 @@ suspend fun genWallData(day: Int): WallData {
val list = ScheduleRepository.getByDay(day) val list = ScheduleRepository.getByDay(day)
val rooms = RoomRepository.all() val rooms = RoomRepository.all()
if (list.isEmpty()) return WallData(day, rooms.associateWith { emptyMap<Int, Schedule>() }, null, null) if (list.isEmpty()) return WallData(day, rooms.associateWith { emptyMap<Int, List<Schedule>>() }, null, null)
val schedules = val schedules = list.groupBy { it.room }.mapValues { (_, it) ->
rooms.associateWith { emptyMap<Int, Schedule>() } + list.groupBy { it.room }.mapValues { (_, it) -> it.groupBy {
it.associateBy {
it.time it.time
} }
} }

View file

@ -2,6 +2,7 @@ package de.kif.backend.route
import de.kif.backend.authenticateOrRedirect import de.kif.backend.authenticateOrRedirect
import de.kif.backend.prefix import de.kif.backend.prefix
import de.kif.backend.repository.RoomRepository
import de.kif.backend.repository.TrackRepository import de.kif.backend.repository.TrackRepository
import de.kif.backend.repository.WorkGroupRepository import de.kif.backend.repository.WorkGroupRepository
import de.kif.backend.view.TableTemplate import de.kif.backend.view.TableTemplate
@ -59,15 +60,9 @@ fun Route.workGroup() {
th { th {
+"Track" +"Track"
} }
th {
+"Beamer"
}
th { th {
+"Resolution" +"Resolution"
} }
th {
+"Sprache"
}
th(classes = "action") { th(classes = "action") {
+"Aktion" +"Aktion"
} }
@ -111,13 +106,6 @@ fun Route.workGroup() {
+(u.track?.name ?: "") +(u.track?.name ?: "")
} }
} }
td {
span {
attributes["data-edit-type"] = "workgroup-projector"
+u.projector.toString()
}
}
td { td {
span { span {
attributes["data-edit-type"] = "workgroup-resolution" attributes["data-edit-type"] = "workgroup-resolution"
@ -125,13 +113,6 @@ fun Route.workGroup() {
+u.resolution.toString() +u.resolution.toString()
} }
} }
td {
span {
attributes["data-edit-type"] = "workgroup-language"
+u.language.localeName
}
}
td(classes = "action") { td(classes = "action") {
a("$prefix/workgroup/${u.id}") { a("$prefix/workgroup/${u.id}") {
i("material-icons") { +"edit" } i("material-icons") { +"edit" }
@ -157,6 +138,12 @@ fun Route.workGroup() {
WorkGroupRepository.get(it)!! WorkGroupRepository.get(it)!!
} }
val rooms = editWorkGroup.constraints.mapNotNull {
it.room
}.distinct().associateWith {
RoomRepository.get(it)!!
}
respondMain { respondMain {
content { content {
h1 { +"Arbeitskreis bearbeiten" } h1 { +"Arbeitskreis bearbeiten" }
@ -541,6 +528,22 @@ fun Route.workGroup() {
} }
} }
} }
ConstraintType.Room -> {
span("form-btn") {
+"Raum"
}
select(
classes = "form-control"
) {
name = "constraint-room-$index"
option {
selected = true
value = constraint.room.toString()
+(rooms[constraint.room!!]?.name ?: "")
}
}
}
} }
} }
@ -985,6 +988,9 @@ private fun parseConstraintParam(params: Map<String, String?>) = params.map { (k
key.startsWith("constraint-only-after-work-group") -> { key.startsWith("constraint-only-after-work-group") -> {
value?.toLongOrNull()?.let { WorkGroupConstraint(ConstraintType.OnlyAfterWorkGroup, workGroup = it) } value?.toLongOrNull()?.let { WorkGroupConstraint(ConstraintType.OnlyAfterWorkGroup, workGroup = it) }
} }
key.startsWith("constraint-room") -> {
value?.toLongOrNull()?.let { WorkGroupConstraint(ConstraintType.Room, room = it) }
}
else -> null else -> null
} }
}.groupBy({ it.first }) { }.groupBy({ it.first }) {

View file

@ -1,6 +1,8 @@
package de.kif.backend.route.api package de.kif.backend.route.api
import de.kif.backend.Configuration
import de.kif.backend.authenticate import de.kif.backend.authenticate
import de.kif.backend.repository.RoomRepository
import de.kif.backend.repository.ScheduleRepository import de.kif.backend.repository.ScheduleRepository
import de.kif.common.checkConstraints import de.kif.common.checkConstraints
import de.kif.common.model.Permission import de.kif.common.model.Permission
@ -14,14 +16,22 @@ fun Route.constraintsApi() {
try { try {
authenticate(Permission.SCHEDULE) { authenticate(Permission.SCHEDULE) {
val schedules = ScheduleRepository.all() val schedules = ScheduleRepository.all()
val rooms = RoomRepository.all()
val errors = checkConstraints(schedules, schedules) val errors = checkConstraints(
schedules,
schedules,
rooms,
Configuration.Reso.day,
Configuration.Reso.time
)
call.success(errors) call.success(errors)
} onFailure { } onFailure {
call.error(HttpStatusCode.Unauthorized) call.error(HttpStatusCode.Unauthorized)
} }
} catch (_: Exception) { } catch (e: Exception) {
e.printStackTrace()
call.error(HttpStatusCode.InternalServerError) call.error(HttpStatusCode.InternalServerError)
} }
} }
@ -31,10 +41,17 @@ fun Route.constraintsApi() {
authenticate(Permission.SCHEDULE) { authenticate(Permission.SCHEDULE) {
val id = call.parameters["id"]?.toLongOrNull() val id = call.parameters["id"]?.toLongOrNull()
val schedules = ScheduleRepository.all() val schedules = ScheduleRepository.all()
val rooms = RoomRepository.all()
val check = schedules.filter { it.workGroup.id == id } val check = schedules.filter { it.workGroup.id == id }
val errors = checkConstraints(check, schedules) val errors = checkConstraints(
check,
schedules,
rooms,
Configuration.Reso.day,
Configuration.Reso.time
)
call.success(errors) call.success(errors)
} onFailure { } onFailure {

View file

@ -16,6 +16,10 @@ reference = "1970-01-01"
offset = 0 offset = 0
wall_start = 0 wall_start = 0
[reso]
day = 0
time = 0
[security] [security]
session_name = "SESSION" session_name = "SESSION"
sign_key = "d1 20 23 8c 01 f8 f0 0d 9d 7c ff 68 21 97 75 31 38 3f fb 91 20 3a 8d 86 d4 e9 d8 50 f8 71 f1 dc" sign_key = "d1 20 23 8c 01 f8 f0 0d 9d 7c ff 68 21 97 75 31 38 3f fb 91 20 3a 8d 86 d4 e9 d8 50 f8 71 f1 dc"