Add ak constraints

This commit is contained in:
Lars Westermann 2019-06-08 22:35:20 +02:00
parent 743e57395a
commit 70e1c2ec26
Signed by: lars.westermann
GPG key ID: 9D417FA5BB9D5E1D
12 changed files with 336 additions and 86 deletions

View file

@ -40,39 +40,80 @@ fun checkConstraints(
errors += ConstraintError("Work group requires accessible, but room does not have one!")
}
for (constraint in schedule.workGroup.constraints) {
when (constraint.type) {
for ((type, constraints) in schedule.workGroup.constraints.groupBy { it.type }) {
when (type) {
ConstraintType.OnlyOnDay -> {
if (constraint.number.toInt() != schedule.day) {
errors += ConstraintError("Work group requires day ${constraint.number}, but is on ${schedule.day}!")
val onlyOnDay = constraints.map { it.day == schedule.day }
if (!onlyOnDay.any()) {
val dayList = constraints.mapNotNull { it.day }.distinct().sorted()
errors += ConstraintError("Work group requires days $dayList, but is on ${schedule.day}!")
}
}
ConstraintType.OnlyAfterTime -> {
if (constraint.number.toInt() > schedule.time) {
errors += ConstraintError("Work group requires time after ${constraint.number}, but is on ${schedule.time}!")
ConstraintType.NotOnDay -> {
val notOnDay = constraints.map { it.day == schedule.day }
if (notOnDay.any()) {
val dayList = constraints.mapNotNull { it.day }.distinct().sorted()
errors += ConstraintError("Work group requires not days $dayList, but is on ${schedule.day}!")
}
}
ConstraintType.NotAtSameTime -> {
val start = schedule.getAbsoluteStartTime()
val end = schedule.getAbsoluteEndTime()
for (s in against) {
if (
s.workGroup.id == constraint.number &&
start <= s.getAbsoluteEndTime() &&
s.getAbsoluteStartTime() <= end
) {
errors += ConstraintError("Work group requires not same time with ${s.workGroup.name}!")
ConstraintType.OnlyBeforeTime -> {
for (it in constraints) {
if (it.time == null) continue
if (it.day == null) {
if (it.time > schedule.time) {
errors += ConstraintError("Work group requires before time ${it.time}, but is on ${schedule.time}!")
}
} else {
if (it.day == schedule.day && it.time > schedule.time) {
errors += ConstraintError("Work group requires before time ${it.time} on day ${it.day}, but is on ${schedule.time}!")
}
}
}
}
ConstraintType.OnlyAfterTime -> {
for (it in constraints) {
if (it.time == null) continue
if (it.day == null) {
if (it.time < schedule.time) {
errors += ConstraintError("Work group requires after time ${it.time}, but is on ${schedule.time}!")
}
} else {
if (it.day == schedule.day && it.time < schedule.time) {
errors += ConstraintError("Work group requires after time ${it.time} on day ${it.day}, but is on ${schedule.time}!")
}
}
}
}
ConstraintType.NotAtSameTime -> {
val start = schedule.getAbsoluteStartTime()
val end = schedule.getAbsoluteEndTime()
for (constraint in constraints) {
for (s in against) {
if (
s.workGroup.id == constraint.workGroup &&
start <= s.getAbsoluteEndTime() &&
s.getAbsoluteStartTime() <= end
) {
errors += ConstraintError("Work group requires not same time with ${s.workGroup.name}!")
}
}
}
}
ConstraintType.OnlyAfterWorkGroup -> {
val start = schedule.getAbsoluteStartTime()
for (s in against) {
if (
s.workGroup.id == constraint.number &&
s.getAbsoluteEndTime() > start
) {
errors += ConstraintError("Work group requires after ${s.workGroup.name}!")
for (constraint in constraints) {
for (s in against) {
if (
s.workGroup.id == constraint.workGroup &&
s.getAbsoluteEndTime() > start
) {
errors += ConstraintError("Work group requires after ${s.workGroup.name}!")
}
}
}
}

View file

@ -5,9 +5,40 @@ import kotlinx.serialization.Serializable
@Serializable
data class Constraint(
val type: ConstraintType,
val number: Long
val day: Int? = null,
val time: Int? = null,
val workGroup: Long? = null
)
enum class ConstraintType {
OnlyOnDay, OnlyAfterTime, NotAtSameTime, OnlyAfterWorkGroup
/**
* Requires day, permits time and workGroup.
*/
OnlyOnDay,
/**
* Requires day, permits time and workGroup.
*/
NotOnDay,
/**
* Requires time, optionally allows day, permits workGroup.
*/
OnlyAfterTime,
/**
* Requires time, optionally allows day, permits workGroup.
*/
OnlyBeforeTime,
/**
* Requires workGroup, permits day and time.
*/
NotAtSameTime,
/**
* Requires workGroup, permits day and time.
*/
OnlyAfterWorkGroup
}

View file

@ -18,6 +18,7 @@ data class WorkGroup(
val accessible: Boolean,
val length: Int,
val language: Language,
val leader: List<String>,
val constraints: List<Constraint>,
override val createdAt: Long = 0,
override val updateAt: Long = 0

View file

@ -51,6 +51,44 @@ fun initWorkGroupConstraints() {
}.html)
}
}
addList.textView("Add not on day") {
onClick {
constraints.appendChild(View.wrap(createHtmlView<HTMLDivElement>()) {
classList += "input-group"
html.appendChild(TextView("Not day").apply {
classList += "form-btn"
onClick { this@wrap.html.remove() }
}.html)
html.appendChild(InputView(InputType.NUMBER).apply {
classList += "form-control"
html.name = "constraint-not-on-day-${index++}"
min = -1337.0
max = 1337.0
}.html)
}.html)
}
}
addList.textView("Add only before time") {
onClick {
constraints.appendChild(View.wrap(createHtmlView<HTMLDivElement>()) {
classList += "input-group"
html.appendChild(TextView("Before time").apply {
classList += "form-btn"
onClick { this@wrap.html.remove() }
}.html)
html.appendChild(InputView(InputType.TEXT).apply {
classList += "form-control"
html.name = "constraint-only-before-time-day-${index++}"
}.html)
html.appendChild(InputView(InputType.NUMBER).apply {
classList += "form-control"
html.name = "constraint-only-before-time-${index++}"
min = -1337.0
max = 133700.0
}.html)
}.html)
}
}
addList.textView("Add only after time") {
onClick {
constraints.appendChild(View.wrap(createHtmlView<HTMLDivElement>()) {
@ -59,6 +97,10 @@ fun initWorkGroupConstraints() {
classList += "form-btn"
onClick { this@wrap.html.remove() }
}.html)
html.appendChild(InputView(InputType.TEXT).apply {
classList += "form-control"
html.name = "constraint-only-after-time-day-${index++}"
}.html)
html.appendChild(InputView(InputType.NUMBER).apply {
classList += "form-control"
html.name = "constraint-only-after-time-${index++}"
@ -125,7 +167,6 @@ fun initWorkGroupConstraints() {
}
}
console.log(constraints)
for (child in constraints.children.iterator()) {
console.log(child)
if (child.classList.contains("input-group")) {

View file

@ -15,7 +15,7 @@ import org.w3c.dom.get
class WorkGroupTableLine(view: HTMLElement) : TableLine(view) {
var lineId = dataset["id"]?.toLongOrNull() ?: -1
private var lineId = dataset["id"]?.toLongOrNull() ?: -1
private val workGroup =
RepositoryDelegate(WorkGroupRepository, lineId)

View file

@ -12,6 +12,7 @@
margin: 1px;
transition: border-color $transitionTime;
color: var(--text-primary-color);
min-width: 0;
&:focus {
border-color: var(--primary-color);
@ -41,6 +42,16 @@ select:-moz-focusring {
padding-bottom: 0.3rem;
padding-left: 0.2rem;
}
&:after {
content: attr(data-hint);
position: absolute;
left: 100%;
color: var(--text-secondary-color);
line-height: 2.5rem;
margin-left: 1rem;
width: 100%;
}
}
.form-switch {
@ -177,7 +188,7 @@ form {
& > * {
margin-right: 0;
flex-grow: 1;
flex-shrink: 1;
flex-basis: 0;
&:not(:first-child) {
border-top-left-radius: 0;

View file

@ -90,7 +90,9 @@
margin-bottom: 0.5rem;
span {
width: 4rem;
width: 8rem;
flex-basis: 8rem;
flex-grow: 0;
position: relative;
text-align: center;
overflow: hidden;

View file

@ -29,6 +29,7 @@ object DbWorkGroup : Table() {
val accessible = bool("accessible")
val language = enumeration("language", Language::class)
val leader = text("leader").default("[]")
val length = integer("length")
val constraints = text("constraints")

View file

@ -12,6 +12,7 @@ import de.kif.common.model.WorkGroup
import de.westermann.kobserve.event.EventHandler
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.list
import kotlinx.serialization.serializer
import org.jetbrains.exposed.sql.*
import java.util.Date
@ -35,6 +36,7 @@ object WorkGroupRepository : Repository<WorkGroup> {
val accessible = row[DbWorkGroup.accessible]
val length = row[DbWorkGroup.length]
val language = row[DbWorkGroup.language]
val leader = Message.json.parse(String.serializer().list, row[DbWorkGroup.leader])
val constraints = Message.json.parse(Constraint.serializer().list, row[DbWorkGroup.constraints])
val createdAt = row[DbWorkGroup.createdAt]
@ -56,6 +58,7 @@ object WorkGroupRepository : Repository<WorkGroup> {
accessible,
length,
language,
leader,
constraints,
createdAt,
updatedAt
@ -89,6 +92,7 @@ object WorkGroupRepository : Repository<WorkGroup> {
it[accessible] = model.accessible
it[length] = model.length
it[language] = model.language
it[leader] = Message.json.stringify(String.serializer().list, model.leader)
it[constraints] = Message.json.stringify(Constraint.serializer().list, model.constraints)
it[createdAt] = now
@ -120,6 +124,7 @@ object WorkGroupRepository : Repository<WorkGroup> {
it[accessible] = model.accessible
it[length] = model.length
it[language] = model.language
it[leader] = Message.json.stringify(String.serializer().list, model.leader)
it[constraints] = Message.json.stringify(Constraint.serializer().list, model.constraints)
it[updatedAt] = now

View file

@ -3,15 +3,12 @@ package de.kif.backend.route
import de.kif.backend.authenticateOrRedirect
import de.kif.backend.repository.TrackRepository
import de.kif.backend.repository.WorkGroupRepository
import de.kif.backend.view.MainTemplate
import de.kif.backend.view.MenuTemplate
import de.kif.backend.view.TableTemplate
import de.kif.backend.view.respondMain
import de.kif.common.Search
import de.kif.common.model.*
import io.ktor.application.call
import io.ktor.html.insert
import io.ktor.html.respondHtmlTemplate
import io.ktor.request.receiveParameters
import io.ktor.response.respondRedirect
import io.ktor.routing.Route
@ -23,6 +20,8 @@ import kotlinx.css.Display
import kotlinx.html.*
import kotlin.collections.set
private const val separator = "###"
fun Route.workGroup() {
get("workgroups") {
authenticateOrRedirect(Permission.WORK_GROUP) { user ->
@ -152,11 +151,7 @@ fun Route.workGroup() {
val tracks = TrackRepository.all()
val workGroups = editWorkGroup.constraints.mapNotNull {
when (it.type) {
ConstraintType.NotAtSameTime -> it.number
ConstraintType.OnlyAfterWorkGroup -> it.number
else -> null
}
it.workGroup
}.distinct().associateWith {
WorkGroupRepository.get(it)!!
}
@ -270,6 +265,24 @@ fun Route.workGroup() {
}
}
div("form-group") {
attributes["data-hint"] = "Use '$separator' to separate multiple leaders"
label {
htmlFor = "leader"
+"Leader"
}
input(
name = "leader",
classes = "form-control"
) {
id = "leader"
placeholder = "Leader"
value = editWorkGroup.leader.joinToString(" $separator ")
}
}
div("form-switch-group") {
div("form-group form-switch") {
input(
@ -383,22 +396,66 @@ fun Route.workGroup() {
classes = "form-control",
type = InputType.number
) {
value = constraint.number.toString()
value = constraint.day.toString()
min = "-1337"
max = "1337"
}
}
ConstraintType.NotOnDay -> {
span("form-btn") {
+"Not day"
}
input(
name = "constraint-not-on-day-$index",
classes = "form-control",
type = InputType.number
) {
value = constraint.day.toString()
min = "-1337"
max = "1337"
}
}
ConstraintType.OnlyBeforeTime -> {
span("form-btn") {
+"Before time"
}
input(
name = "constraint-only-before-time-day-$index",
classes = "form-control"
) {
value = constraint.day?.toString() ?: ""
placeholder = "day"
}
input(
name = "constraint-only-before-time-$index",
classes = "form-control",
type = InputType.number
) {
value = constraint.time.toString()
min = "-1337"
max = "133700"
}
}
ConstraintType.OnlyAfterTime -> {
span("form-btn") {
+"After time"
}
input(
name = "constraint-only-after-time-day-$index",
classes = "form-control"
) {
value = constraint.day?.toString() ?: ""
placeholder = "day"
}
input(
name = "constraint-only-after-time-$index",
classes = "form-control",
type = InputType.number
) {
value = constraint.number.toString()
value = constraint.time.toString()
min = "-1337"
max = "133700"
@ -415,8 +472,8 @@ fun Route.workGroup() {
option {
selected = true
value = constraint.number.toString()
+(workGroups[constraint.number]?.name ?: "")
value = constraint.workGroup.toString()
+(workGroups[constraint.workGroup!!]?.name ?: "")
}
}
}
@ -431,8 +488,8 @@ fun Route.workGroup() {
option {
selected = true
value = constraint.number.toString()
+(workGroups[constraint.number]?.name ?: "")
value = constraint.workGroup.toString()
+(workGroups[constraint.workGroup!!]?.name ?: "")
}
}
}
@ -490,25 +547,13 @@ fun Route.workGroup() {
editWorkGroup =
editWorkGroup.copy(language = Language.values().find { l -> l.code == it } ?: Language.GERMAN)
}
val constraints = params.mapNotNull { (key, value) ->
when {
key.startsWith("constraint-only-on-day") -> {
value?.toLongOrNull()?.let { Constraint(ConstraintType.OnlyOnDay, it) }
}
key.startsWith("constraint-only-after-time") -> {
value?.toLongOrNull()?.let { Constraint(ConstraintType.OnlyAfterTime, it) }
}
key.startsWith("constraint-not-at-same-time") -> {
value?.toLongOrNull()?.let { Constraint(ConstraintType.NotAtSameTime, it) }
}
key.startsWith("constraint-only-after-work-group") -> {
value?.toLongOrNull()?.let { Constraint(ConstraintType.OnlyAfterWorkGroup, it) }
}
else -> null
}
params["leader"]?.let {
val leader = it.split("\\s*$separator+\\s*".toRegex())
editWorkGroup = editWorkGroup.copy(leader = leader)
}
val constraints = parseConstraintParam(params)
editWorkGroup = editWorkGroup.copy(constraints = constraints)
WorkGroupRepository.update(editWorkGroup)
@ -628,6 +673,25 @@ fun Route.workGroup() {
}
}
div("form-group") {
attributes["data-hint"] = "Use '$separator' to separate multiple leaders"
label {
htmlFor = "leader"
+"Leader"
}
input(
name = "leader",
classes = "form-control"
) {
id = "leader"
placeholder = "Leader"
value = ""
}
}
div("form-switch-group") {
div("form-group form-switch") {
input(
@ -763,29 +827,14 @@ fun Route.workGroup() {
val language = (params["language"] ?: return@post).let {
Language.values().find { l -> l.code == it } ?: Language.GERMAN
}
val leader = params["leader"]?.split("\\s*$separator+\\s*".toRegex()) ?: return@post
val internet = params["internet"] == "on"
val whiteboard = params["whiteboard"] == "on"
val blackboard = params["blackboard"] == "on"
val accessible = params["accessible"] == "on"
val constraints = params.mapNotNull { (key, value) ->
when {
key.startsWith("constraint-only-on-day") -> {
value?.toLongOrNull()?.let { Constraint(ConstraintType.OnlyOnDay, it) }
}
key.startsWith("constraint-only-after-time") -> {
value?.toLongOrNull()?.let { Constraint(ConstraintType.OnlyAfterTime, it) }
}
key.startsWith("constraint-not-at-same-time") -> {
value?.toLongOrNull()?.let { Constraint(ConstraintType.NotAtSameTime, it) }
}
key.startsWith("constraint-only-after-work-group") -> {
value?.toLongOrNull()?.let { Constraint(ConstraintType.OnlyAfterWorkGroup, it) }
}
else -> null
}
}
val constraints = parseConstraintParam(params)
val workGroup = WorkGroup(
null,
@ -801,6 +850,7 @@ fun Route.workGroup() {
accessible = accessible,
length = length,
language = language,
leader = leader,
constraints = constraints
)
@ -820,3 +870,63 @@ fun Route.workGroup() {
}
}
}
private fun parseConstraintParam(params: Map<String, String?>) = params.map { (key, value) ->
val id = key.substringAfterLast("-").toIntOrNull() ?: -1
id to when {
key.startsWith("constraint-only-on-day") -> {
value?.toIntOrNull()?.let { Constraint(ConstraintType.OnlyOnDay, day = it) }
}
key.startsWith("constraint-not-on-day") -> {
value?.toIntOrNull()?.let { Constraint(ConstraintType.NotOnDay, day = it) }
}
key.startsWith("constraint-only-after-time") -> {
if ("day" in key) {
value?.toIntOrNull()?.let { Constraint(ConstraintType.OnlyAfterTime, day = it) }
} else {
value?.toIntOrNull()?.let { Constraint(ConstraintType.OnlyAfterTime, time = it) }
}
}
key.startsWith("constraint-only-before-time") -> {
if ("day" in key) {
value?.toIntOrNull()?.let { Constraint(ConstraintType.OnlyBeforeTime, day = it) }
} else {
value?.toIntOrNull()?.let { Constraint(ConstraintType.OnlyBeforeTime, time = it) }
}
}
key.startsWith("constraint-not-at-same-time") -> {
value?.toLongOrNull()?.let { Constraint(ConstraintType.NotAtSameTime, workGroup = it) }
}
key.startsWith("constraint-only-after-work-group") -> {
value?.toLongOrNull()?.let { Constraint(ConstraintType.OnlyAfterWorkGroup, workGroup = it) }
}
else -> null
}
}.groupBy({ it.first }) {
it.second
}.mapNotNull { (_, c) ->
when {
c.isEmpty() -> null
c.size == 1 -> c.first()
c.size == 2 -> {
val c1 = c[0] ?: return@mapNotNull null
val c2 = c[1] ?: return@mapNotNull null
when {
c1.type != c2.type -> null
c1.type == ConstraintType.OnlyBeforeTime -> Constraint(
ConstraintType.OnlyBeforeTime,
day = c1.day ?: c2.day,
time = c1.time ?: c2.time
)
c1.type == ConstraintType.OnlyAfterTime -> Constraint(
ConstraintType.OnlyAfterTime,
day = c1.day ?: c2.day,
time = c1.time ?: c2.time
)
else -> null
}
}
else -> null
}
}

View file

@ -49,25 +49,31 @@ data class Backup(
suspend fun import(data: String) {
val backup = Message.json.parse(serializer(), data)
val userMap = backup.users.associateWith { UserRepository.create(it) }
val postMap = backup.posts.associateWith { PostRepository.create(it) }
backup.users.forEach { UserRepository.create(it) }
backup.posts.forEach { PostRepository.create(it) }
val roomMap = backup.rooms.associateWith { RoomRepository.create(it) }
val trackMap = backup.tracks.associateWith { TrackRepository.create(it) }
val workGroupMap = backup.workGroups.associateWith {
backup.rooms.forEach { RoomRepository.create(it) }
val roomMap = RoomRepository.all().associateWith { it.id!! }
backup.tracks.forEach { TrackRepository.create(it) }
val trackMap =TrackRepository.all().associateWith { it.id!! }
backup.workGroups.forEach {
var workGroup = it
val track = workGroup.track
if (track != null) {
workGroup = workGroup.copy(track = track.copy(id = trackMap[track] ?: return@associateWith -1L))
workGroup = workGroup.copy(track = track.copy(id = trackMap[track] ?: return@forEach))
}
WorkGroupRepository.create(workGroup)
}
val scheduleMap = backup.schedules.associateWith {
val workGroupMap = WorkGroupRepository.all().associateWith { it.id!! }
backup.schedules.forEach {
ScheduleRepository.create(
it.copy(
room = it.room.copy(id = roomMap[it.room] ?: return@associateWith -1L),
workGroup = it.workGroup.copy(id = workGroupMap[it.workGroup] ?: return@associateWith -1L)
room = it.room.copy(id = roomMap[it.room] ?: return@forEach),
workGroup = it.workGroup.copy(id = workGroupMap[it.workGroup] ?: return@forEach)
)
)
}

View file

@ -147,6 +147,7 @@ object WikiImporter {
accessible = false,
length = akLength,
language = Language.GERMAN,
leader = listOf(who),
constraints = emptyList()
)
}