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
wall_start = 1
[reso]
day = 3
time = 900
[general]
wiki_url = "https://wiki.kif.rocks/w/index.php?title=KIF470:Arbeitskreise&action=raw"

View file

@ -1,6 +1,7 @@
package de.kif.common
import de.kif.common.model.ConstraintType
import de.kif.common.model.Room
import de.kif.common.model.Schedule
import kotlinx.serialization.Serializable
@ -16,10 +17,15 @@ data class ConstraintMap(
fun checkConstraints(
check: List<Schedule>,
against: List<Schedule>
against: List<Schedule>,
rooms: List<Room>,
resoDay: Int,
resoTime: Int
): ConstraintMap {
val map = mutableMapOf<Long, List<ConstraintError>>()
val roomMap = rooms.associateBy { it.id }
for (schedule in check) {
if (schedule.id == null) continue
val errors = mutableListOf<ConstraintError>()
@ -46,7 +52,7 @@ fun checkConstraints(
schedule.time,
schedule.time + schedule.workGroup.length
)
}.any {it}
}.any { it }
if (blocked) {
errors += ConstraintError("The room ${schedule.room.name} is blocked!")
}
@ -54,10 +60,20 @@ fun checkConstraints(
val start = schedule.getAbsoluteStartTime()
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 (s in against) {
if (
schedule != s &&
schedule.day == s.day &&
leader in s.workGroup.leader &&
start < s.getAbsoluteEndTime() &&
s.getAbsoluteStartTime() < end
@ -70,6 +86,7 @@ fun checkConstraints(
for (s in against) {
if (
schedule != s &&
schedule.day == s.day &&
schedule.room.id == s.room.id &&
start < s.getAbsoluteEndTime() &&
s.getAbsoluteStartTime() < end
@ -146,6 +163,7 @@ fun checkConstraints(
for (s in against) {
if (
s.workGroup.id == constraint.workGroup &&
schedule.day == s.day &&
start <= s.getAbsoluteEndTime() &&
s.getAbsoluteStartTime() <= end
) {
@ -159,14 +177,25 @@ fun checkConstraints(
for (constraint in constraints) {
for (s in against) {
if (
s.workGroup.id == constraint.workGroup &&
s.getAbsoluteEndTime() > start
s.workGroup.id == constraint.workGroup && (
s.day > schedule.day ||
s.day == schedule.day && s.getAbsoluteEndTime() > start
)
) {
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 day: Int? = null,
val time: Int? = null,
val workGroup: Long? = null
val workGroup: Long? = null,
val room: Long? = null
)
enum class ConstraintType {
/**
* Requires day, permits time and workGroup.
* Requires day, permits time, workGroup and room.
*/
OnlyOnDay,
/**
* Requires day, permits time and workGroup.
* Requires day, permits time, workGroup and room.
*/
NotOnDay,
/**
* Requires time, optionally allows day, permits workGroup.
* Requires time, optionally allows day, permits workGroup and room.
*/
OnlyAfterTime,
/**
* Requires time, optionally allows day, permits workGroup.
* Requires time, optionally allows day, permits workGroup and room.
*/
OnlyBeforeTime,
/**
* Requires time, optionally allows day, permits workGroup
* Requires time, optionally allows day, permits workGroup and room.
*/
ExactTime,
/**
* Requires workGroup, permits day and time.
* Requires workGroup, permits day, time and room.
*/
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
import de.kif.frontend.launch
import de.kif.frontend.repository.RoomRepository
import de.kif.frontend.repository.WorkGroupRepository
import de.westermann.kobserve.event.EventListener
import de.westermann.kwebview.View
@ -10,6 +11,7 @@ import de.westermann.kwebview.createHtmlView
import de.westermann.kwebview.iterator
import org.w3c.dom.*
import kotlin.browser.document
import kotlin.dom.clear
fun initWorkGroupConstraints() {
var index = 10000
@ -149,10 +151,15 @@ fun initWorkGroupConstraints() {
launch {
val all = WorkGroupRepository.all()
val id = (select.options[select.selectedIndex] as? HTMLOptionElement)?.value
for (wg in all) {
val option = createHtmlView<HTMLOptionElement>()
option.value = wg.id.toString()
option.textContent = wg.name
if (option.value == id) {
option.selected = true
}
select.appendChild(option)
}
}
@ -177,10 +184,49 @@ fun initWorkGroupConstraints() {
launch {
val all = WorkGroupRepository.all()
val id = (select.options[select.selectedIndex] as? HTMLOptionElement)?.value
for (wg in all) {
val option = createHtmlView<HTMLOptionElement>()
option.value = wg.id.toString()
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)
}
}
@ -195,7 +241,6 @@ fun initWorkGroupConstraints() {
val span = child.firstElementChild as HTMLElement
span.addEventListener("click", org.w3c.dom.events.EventListener {
println("click")
child.remove()
})
}

View file

@ -1,5 +1,7 @@
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.iterator
import org.w3c.dom.HTMLFormElement
@ -14,11 +16,14 @@ fun initTableLayout() {
val table = document.getElementsByClassName("table-layout-table")[0] as HTMLTableElement
launch {
val tracks = TrackRepository.all()
val list = table.getElementsByTagName("tr").iterator().asSequence().filter {
it.dataset["search"] != null
}.map {
when (it.dataset["edit"]) {
"workgroup" -> WorkGroupTableLine(it)
"workgroup" -> WorkGroupTableLine(it, tracks)
"room" -> RoomTableLine(it)
else -> TableLine(it)
}
@ -31,4 +36,5 @@ fun initTableLayout() {
row.search(search.value)
}
}
}
}

View file

@ -13,7 +13,7 @@ import org.w3c.dom.HTMLElement
import org.w3c.dom.HTMLSpanElement
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
@ -24,9 +24,7 @@ class WorkGroupTableLine(view: HTMLElement) : TableLine(view) {
private val spanWorkGroupLength: TextView
private val spanWorkGroupInterested: TextView
private val spanWorkGroupTrack: TextView
private val spanWorkGroupProjector: TextView
private val spanWorkGroupResolution: TextView
private val spanWorkGroupLanguage: TextView
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)
spanWorkGroupTrack =
TextView.wrap(spans.first { it.dataset["editType"] == "workgroup-track" } as HTMLSpanElement)
spanWorkGroupProjector =
TextView.wrap(spans.first { it.dataset["editType"] == "workgroup-projector" } as HTMLSpanElement)
spanWorkGroupResolution =
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) {
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) {
launch {
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 {
val wg = workGroup.get()
if (wg.language == it) return@launch
WorkGroupRepository.update(wg.copy(language = it))
}
}
val list = listOf<Track?>(null) + tracks
launch {
val tracks = listOf<Track?>(null) + TrackRepository.all()
setupList(spanWorkGroupTrack, tracks, { it.name }) {
setupList(spanWorkGroupTrack, list, { it.name }) {
launch x@{
val wg = workGroup.get()
if (wg.track == it) return@x
@ -124,9 +102,7 @@ class WorkGroupTableLine(view: HTMLElement) : TableLine(view) {
spanWorkGroupLength.text = wg.length.toString()
spanWorkGroupInterested.text = wg.interested.toString()
spanWorkGroupTrack.text = wg.track?.name ?: ""
spanWorkGroupProjector.text = wg.projector.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)
}
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 {
var config = Config {
addSpec(ServerSpec)
@ -119,6 +129,7 @@ object Configuration {
addSpec(SecuritySpec)
addSpec(GeneralSpec)
addSpec(TwitterSpec)
addSpec(ResoSpec)
}.from.toml.resource("portal.toml")
for (file in Files.list(Paths.get("."))) {

View file

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

View file

@ -70,7 +70,7 @@ fun DIV.renderCalendar(
from: Int,
to: Int,
rooms: List<Room>,
schedules: Map<Room, Map<Int, Schedule>>
schedules: Map<Room, Map<Int, List<Schedule>>>
) {
val gridLabelWidth = 60
val minutesOfDay = to - from
@ -144,9 +144,9 @@ fun DIV.renderCalendar(
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)
}
}
@ -231,7 +231,7 @@ fun Route.calendar() {
val list = ScheduleRepository.getByDay(day)
val schedules = list.groupBy { it.room }.mapValues { (_, it) ->
it.associateBy {
it.groupBy {
it.time
}
}

View file

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

View file

@ -2,6 +2,7 @@ package de.kif.backend.route
import de.kif.backend.authenticateOrRedirect
import de.kif.backend.prefix
import de.kif.backend.repository.RoomRepository
import de.kif.backend.repository.TrackRepository
import de.kif.backend.repository.WorkGroupRepository
import de.kif.backend.view.TableTemplate
@ -59,15 +60,9 @@ fun Route.workGroup() {
th {
+"Track"
}
th {
+"Beamer"
}
th {
+"Resolution"
}
th {
+"Sprache"
}
th(classes = "action") {
+"Aktion"
}
@ -111,13 +106,6 @@ fun Route.workGroup() {
+(u.track?.name ?: "")
}
}
td {
span {
attributes["data-edit-type"] = "workgroup-projector"
+u.projector.toString()
}
}
td {
span {
attributes["data-edit-type"] = "workgroup-resolution"
@ -125,13 +113,6 @@ fun Route.workGroup() {
+u.resolution.toString()
}
}
td {
span {
attributes["data-edit-type"] = "workgroup-language"
+u.language.localeName
}
}
td(classes = "action") {
a("$prefix/workgroup/${u.id}") {
i("material-icons") { +"edit" }
@ -157,6 +138,12 @@ fun Route.workGroup() {
WorkGroupRepository.get(it)!!
}
val rooms = editWorkGroup.constraints.mapNotNull {
it.room
}.distinct().associateWith {
RoomRepository.get(it)!!
}
respondMain {
content {
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") -> {
value?.toLongOrNull()?.let { WorkGroupConstraint(ConstraintType.OnlyAfterWorkGroup, workGroup = it) }
}
key.startsWith("constraint-room") -> {
value?.toLongOrNull()?.let { WorkGroupConstraint(ConstraintType.Room, room = it) }
}
else -> null
}
}.groupBy({ it.first }) {

View file

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

View file

@ -16,6 +16,10 @@ reference = "1970-01-01"
offset = 0
wall_start = 0
[reso]
day = 0
time = 0
[security]
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"