diff --git a/application/controllers/api/frontend/v1/tempus/Config.php b/application/controllers/api/frontend/v1/tempus/Config.php index 69711d455..2819b0ff3 100644 --- a/application/controllers/api/frontend/v1/tempus/Config.php +++ b/application/controllers/api/frontend/v1/tempus/Config.php @@ -39,8 +39,7 @@ class Config extends FHCAPI_Controller 'ignore_zeitsperre', 'ignore_resources_collisions', 'roomless_planning', - 'priority_room_planning', - 'dialog_room_planning' + 'room_planning', ); $result = $this->_ci->VariableModel->getVariables(getAuthUID(), $var_array); @@ -49,7 +48,6 @@ class Config extends FHCAPI_Controller "type" => "checkbox", "label" => $this->p->t('ui', 'ignore_kollision'), "value" => ($data['ignore_kollision'] ?? 'false') === 'true' - ]; $config['kollision_student'] = [ @@ -83,19 +81,16 @@ class Config extends FHCAPI_Controller "value" => ($data['roomless_planning'] ?? 'false') === 'true' ]; - $config['priority_room_planning'] = [ - "type" => "checkbox", - "label" => $this->p->t('ui', 'priority_room_planning'), - "value" => ($data['priority_room_planning'] ?? 'false') === 'true' + $config['room_planning'] = [ + "type" => "select", + "label" => $this->p->t('ui', 'room_planning'), + "value" => $data['room_planning'] ?? "dialog_room_planning", + "options" => [ + 'dialog_room_planning' => $this->p->t('ui', 'dialog_room_planning'), + 'priority_room_planning' => $this->p->t('ui', 'priority_room_planning'), + ], ]; - $config['dialog_room_planning'] = [ - "type" => "checkbox", - "label" => $this->p->t('ui', 'dialog_room_planning'), - "value" => ($data['dialog_room_planning'] ?? 'false') === 'true' - ]; - - $this->terminateWithSuccess($config); } public function getHeader() @@ -121,6 +116,8 @@ class Config extends FHCAPI_Controller public function set() { + $this->_ci->load->library('form_validation'); + $configs = array( 'ignore_kollision', 'kollision_student', @@ -128,10 +125,18 @@ class Config extends FHCAPI_Controller 'ignore_zeitsperre', 'ignore_resources_collisions', 'roomless_planning', - 'priority_room_planning', - 'dialog_room_planning' ); + $this->form_validation->set_rules( + 'room_planning', + $this->p->t('ui', 'room_planning'), + 'required|in_list[dialog_room_planning,priority_room_planning]' + ); + + if (!$this->form_validation->run()) + $this->terminateWithValidationErrors($this->form_validation->error_array()); + + foreach ($configs as $config) { $this->_ci->VariableModel->setVariable( @@ -141,6 +146,12 @@ class Config extends FHCAPI_Controller ); } + $this->VariableModel->setVariable( + getAuthUID(), + 'room_planning', + $this->input->post('room_planning') + ); + $this->terminateWithSuccess(); } diff --git a/application/controllers/api/frontend/v1/tempus/Kalender.php b/application/controllers/api/frontend/v1/tempus/Kalender.php index 2a0ba1832..f338b37fe 100644 --- a/application/controllers/api/frontend/v1/tempus/Kalender.php +++ b/application/controllers/api/frontend/v1/tempus/Kalender.php @@ -457,7 +457,8 @@ class Kalender extends FHCAPI_Controller $ort_kurzbz = $this->_ci->input->post('ort_kurzbz', TRUE); $start_date = $this->_ci->input->post('start_date', TRUE); $end_date = $this->_ci->input->post('end_date', TRUE); - $result = $this->_ci->kalenderlib->addKalenderEvent($start_date, $end_date, $lehreinheit_id, $ort_kurzbz); + $first_run = $this->_ci->input->post('first_run', TRUE) ?? true; + $result = $this->_ci->kalenderlib->addKalenderEvent($start_date, $end_date, $lehreinheit_id, $ort_kurzbz, $first_run); if (isError($result)) $this->terminateWithError(getError($result)); diff --git a/application/controllers/api/frontend/v1/tempus/Reservierung.php b/application/controllers/api/frontend/v1/tempus/Reservierung.php index 5a7067bcf..74ce97c33 100644 --- a/application/controllers/api/frontend/v1/tempus/Reservierung.php +++ b/application/controllers/api/frontend/v1/tempus/Reservierung.php @@ -138,6 +138,12 @@ class Reservierung extends FHCAPI_Controller return in_array($gruppe->studiengang_kz, $stg_berechtigungen); }); + + usort($gefilterte_gruppen, function ($a, $b) + { + return [(int) $a->semester, $a->gruppe_kurzbz] <=> [(int) $b->semester, $b->gruppe_kurzbz]; + }); + $this->terminateWithSuccess($gefilterte_gruppen); } diff --git a/application/libraries/KalenderLib.php b/application/libraries/KalenderLib.php index 7752d1c0f..8375589fe 100644 --- a/application/libraries/KalenderLib.php +++ b/application/libraries/KalenderLib.php @@ -50,7 +50,6 @@ class KalenderLib public function getForRaumvorschlag($start_date, $end_date, $lektor_uids = [], $gruppen_kurzbz = [], $lehrverband_gruppen = []) { $start_date = date('Y-m-d', strtotime($start_date)); - $end_date = date('Y-m-d', strtotime($end_date . ' +1 day')); $this->_buildBasePlanQuery(); @@ -169,23 +168,38 @@ class KalenderLib if (!is_null($studiengaenge)) { $db = $this->_ci->KalenderModel->db; - $or_conditions = array(); + $lv_or_conditions = array(); + $teilnehmer_or_conditions = array(); foreach ($studiengaenge as $studiengang) { - $conditions = array(); - $conditions[] = 'filter_lv.studiengang_kz = ' . $db->escape($studiengang['stg_kz']); + $stg_kz = $db->escape($studiengang['stg_kz']); + + $lv_conditions = array('filter_lv.studiengang_kz = '. $stg_kz); + $teilnehmer_conditions = array('COALESCE(filter_teilnehmer.studiengang_kz, filter_grp.studiengang_kz) = ' .$stg_kz); + if (isset($studiengang['semester'])) - $conditions[] = 'filter_lv.semester = ' . $db->escape($studiengang['semester']); + { + $semester = $db->escape($studiengang['semester']); + + $lv_conditions[] = 'filter_lv.semester = ' . $semester; + $teilnehmer_conditions[] = 'COALESCE(filter_teilnehmer.semester, filter_grp.semester) = ' . $semester; + } if (isset($studiengang['orgform_kurzbz'])) - $conditions[] = 'filter_lv.orgform_kurzbz = ' . $db->escape($studiengang['orgform_kurzbz']); + { + $orgform_kurzbz = $db->escape($studiengang['orgform_kurzbz']); + $lv_conditions[] = 'filter_lv.orgform_kurzbz = ' . $orgform_kurzbz; + $teilnehmer_conditions[] = 'COALESCE(filter_lehrverband.orgform_kurzbz, filter_grp.orgform_kurzbz) = ' . $orgform_kurzbz; + } - $or_conditions[] = '(' . implode(' AND ', $conditions) . ')'; + $lv_or_conditions[] = '(' . implode(' AND ', $lv_conditions) . ')'; + $teilnehmer_or_conditions[] = '(' . implode(' AND ', $teilnehmer_conditions) . ')'; } - $or_block = implode(' OR ', $or_conditions); + $lv_or_block = implode(' OR ', $lv_or_conditions); + $teilnehmer_or_block = implode(' OR ', $teilnehmer_or_conditions); $this->_ci->KalenderModel->db->where( "(EXISTS ( @@ -194,8 +208,20 @@ class KalenderLib JOIN lehre.tbl_lehreinheit filter_le ON filter_le.lehreinheit_id = filter_kl.lehreinheit_id JOIN lehre.tbl_lehrveranstaltung filter_lv ON filter_lv.lehrveranstaltung_id = filter_le.lehrveranstaltung_id WHERE filter_kl.kalender_id = tbl_kalender.kalender_id - AND ($or_block) - ))" + AND ($lv_or_block) + ) + OR EXISTS ( + SELECT 1 + FROM lehre.tbl_kalender_event_teilnehmer filter_teilnehmer + LEFT JOIN public.tbl_gruppe filter_grp ON filter_grp.gruppe_kurzbz = filter_teilnehmer.gruppe_kurzbz + LEFT JOIN public.tbl_lehrverband filter_lehrverband ON filter_lehrverband.studiengang_kz = filter_teilnehmer.studiengang_kz + AND filter_lehrverband.semester = filter_teilnehmer.semester + AND TRIM(COALESCE(filter_teilnehmer.verband::text, '')) = TRIM(filter_lehrverband.verband::text) + AND TRIM(COALESCE(filter_teilnehmer.gruppe::text, '')) = TRIM(filter_lehrverband.gruppe::text) + WHERE filter_teilnehmer.kalender_id = tbl_kalender.kalender_id + AND (filter_teilnehmer.gruppe_kurzbz IS NOT NULL OR filter_teilnehmer.studiengang_kz IS NOT NULL) + AND ($teilnehmer_or_block) + ))", null, false ); } @@ -537,6 +563,11 @@ class KalenderLib 'false' ); + $this->_ci->StundeModel->addLimit(1); + $this->_ci->StundeModel->addOrder('stunde', 'DESC'); + $letzte_stunde_result = $this->_ci->StundeModel->load(); + + $letzte_stunde = getData($letzte_stunde_result)[0]; while ($rest > 0 && new DateTime($current_date) < $ende) { $max_skip = 52; @@ -564,12 +595,7 @@ class KalenderLib if (!hasData($end_stunde_result)) { - $errors[] = [ - 'datum' => $current_date, - 'message' => 'Endzeit entspricht keinem gültigen Raster', - ]; - $current_date = $this->_addWeeks($current_date, $wochenrythmus); - continue; + $current_block = $letzte_stunde->stunde - $start_stunde_nr + 1; } $end_time = getData($end_stunde_result)[0]->ende; @@ -655,7 +681,7 @@ class KalenderLib } - public function addKalenderEvent($start_date, $end_date, $lehreinheit_id, $ort_kurzbz) + public function addKalenderEvent($start_date, $end_date, $lehreinheit_id, $ort_kurzbz, $first_run = false) { if (!$this->_checkPermission((array)$lehreinheit_id)) { @@ -674,24 +700,45 @@ class KalenderLib $raum_vorschlaege = $this->_ci->raumvorschlaglib->getVorschlaegeByLehreinheit($lehreinheit_id, $start_date, $end_date); - if (($this->_ci->variablelib->getVar('dialog_room_planning') ?? 'false') === 'true') + $room_planning = $this->_ci->variablelib->getVar('room_planning') ?? 'priority_room_planning'; + if ($first_run || (($this->_ci->variablelib->getVar('roomless_planning') ?? 'false') !== 'true')) { - return success([ - 'needs_room_selection' => true, - 'raum_vorschlaege' => is_array($raum_vorschlaege) ? $raum_vorschlaege : [], - ]); - } + if ($room_planning === 'dialog_room_planning') + { + if (!$first_run) + { + return error([ + 'message' => $this->_ci->phraseslib->t('ui', 'roomless_planning_error'), + 'errorCode' => 'roomless_planning_error' + ]); + } + else + { + return success([ + 'needs_room_selection' => true, + 'raum_vorschlaege' => is_array($raum_vorschlaege) ? $raum_vorschlaege : [], + ]); + } + } + else if ($room_planning === 'priority_room_planning') + { + $ort_kurzbz = !empty($raum_vorschlaege[0]['ort_kurzbz']) ? $raum_vorschlaege[0]['ort_kurzbz'] : null; - if (($this->_ci->variablelib->getVar('priority_room_planning') ?? 'false') === 'true') - { - $ort_kurzbz = (is_array($raum_vorschlaege)) ? $raum_vorschlaege[0]['ort_kurzbz'] : null; - } - else if (!(($this->_ci->variablelib->getVar('roomless_planning') ?? 'false') === 'true')) - { - return error([ - 'message' => $this->_ci->phraseslib->t('ui', 'roomless_planning_error'), - 'errorCode' => 'roomless_planning_error' - ]); + if (is_null($ort_kurzbz) && !(($this->_ci->variablelib->getVar('roomless_planning') ?? 'false') === 'true')) + { + return error([ + 'message' => $this->_ci->phraseslib->t('ui', 'roomless_prio_planning_error'), + 'errorCode' => 'roomless_planning_error' + ]); + } + } + else if (!(($this->_ci->variablelib->getVar('roomless_planning') ?? 'false') === 'true')) + { + return error([ + 'message' => $this->_ci->phraseslib->t('ui', 'roomless_planning_error'), + 'errorCode' => 'roomless_planning_error' + ]); + } } } @@ -901,7 +948,7 @@ class KalenderLib if (!empty($errors)) { return error($errors); } - + $this->_ci->db->trans_start(); $allowedResourceIDs = array_filter(array_map( @@ -984,7 +1031,7 @@ class KalenderLib if (!is_null($orte)) { - $kalender_entry->ort_kurbz = !isEmptyArray($orte['ort_kurzbz'] ?? null) ? (array) $orte['ort_kurzbz'] : $kalender_entry->ort_kurzbz; + $kalender_entry->ort_kurzbz = !isEmptyArray($orte['ort_kurzbz'] ?? null) ? (array) $orte['ort_kurzbz'] : $kalender_entry->ort_kurzbz; $kalender_entry->location = array_key_exists('location', $orte) && !is_null($orte['location']) ? $orte['location'] : $kalender_entry->location; } $kalender_entry->von = $start_time ?? $kalender_entry->von; @@ -1657,7 +1704,7 @@ class KalenderLib if ($row->verband_grp) { - if (!in_array($row->teilnehmerg_grp, array_column($events[$id]->teilnehmer_gruppe, 'gruppe_kurzbz'))) + if (!in_array($row->verband_grp, array_column($events[$id]->teilnehmer_gruppe, 'gruppe_kurzbz'))) { $events[$id]->teilnehmer_gruppe[] = [ 'gruppe_kurzbz' => $row->verband_grp, @@ -1718,6 +1765,11 @@ class KalenderLib } } + foreach ($events as $event) + { + usort($event->ort_kurzbz, 'strnatcasecmp'); + } + return array_values($events); } @@ -1753,6 +1805,8 @@ class KalenderLib if (hasData($ferien_result)) return true; + + return false; } private function _insertKalenderEventRaw($start_date, $end_date, $lehreinheit_id, $ort_kurzbz) @@ -2140,7 +2194,7 @@ class KalenderLib $lehreinheit = $this->_ci->LehreinheitModel->load(); if (!hasData($lehreinheit)) - return error('Lehreinheit nicht gefunden'); + return false; $studiengaenge = array_unique(array_column(getData($lehreinheit), 'studiengang_kz')); diff --git a/public/js/api/factory/tempus/kalender.js b/public/js/api/factory/tempus/kalender.js index 08dd2d678..115f3c6b0 100644 --- a/public/js/api/factory/tempus/kalender.js +++ b/public/js/api/factory/tempus/kalender.js @@ -96,11 +96,11 @@ export default { params: { kalender_id} }; }, - addKalenderEvent(lehreinheit_id, ort_kurzbz, start_date, end_date) { + addKalenderEvent(lehreinheit_id, ort_kurzbz, start_date, end_date, first_run) { return { method: 'post', url: '/api/frontend/v1/tempus/Kalender/addKalenderEvent', - params: { lehreinheit_id, ort_kurzbz, start_date, end_date} + params: { lehreinheit_id, ort_kurzbz, start_date, end_date, first_run} }; }, calculateMultiWeekPlan(lehreinheit_id, ort_kurzbz, start_date, end_date) { diff --git a/public/js/components/Calendar/Base/Grid.js b/public/js/components/Calendar/Base/Grid.js index df322c151..5a2187d9f 100644 --- a/public/js/components/Calendar/Base/Grid.js +++ b/public/js/components/Calendar/Base/Grid.js @@ -383,12 +383,12 @@ export default { if (accumulated >= duration) { const overflow = accumulated.minus(duration); - return blockEnd.minus(overflow); + return {end: blockEnd.minus(overflow), shortened: false} } } const lastBlock = blocks[blocks.length - 1]; - return dropStart.startOf('day').plus(lastBlock.end); + return {end: dropStart.startOf('day').plus(lastBlock.end), shortened: true} }, onDropEvent(evt, items, date) @@ -398,7 +398,7 @@ export default { else this.onDropFree(evt, items, date) }, - onDropSnap(evt, items, date, part) { + async onDropSnap(evt, items, date, part) { let obj = items; if (!obj?.orig) return; @@ -419,7 +419,13 @@ export default { const dropStart = snappedPart ? dropDay.plus(snappedPart.start) : grabTime; let nettoDuration = this._getNettoDurationForDrop(obj); - let dropEnd = this.calculateDropEnd(dropStart, nettoDuration); + + let {end: dropEnd, shortened} = this.calculateDropEnd(dropStart, nettoDuration); + + if (shortened && await this.$fhcAlert.confirm({message: 'Wird auf ' + dropEnd.toFormat('HH:mm') + ' gekürzt. Fortfahren?'}) === false) + { + return; + } this.onDrop?.({ item: [obj], @@ -449,7 +455,7 @@ export default { return luxon.Duration.fromObject({ minutes: 45 }); }, - onDropFree(evt, items, date) + async onDropFree(evt, items, date) { let obj = items; if (!obj?.orig) @@ -459,8 +465,12 @@ export default { const dropStart = luxon.DateTime.fromMillis(timestamp); let nettoDuration = this._getNettoDurationForDrop(obj); - let dropEnd = this.calculateDropEnd(dropStart, nettoDuration); + let {end: dropEnd, shortened} = this.calculateDropEnd(dropStart, nettoDuration); + if (shortened && await this.$fhcAlert.confirm({message: 'Wird auf ' + dropEnd.toFormat('HH:mm') + ' gekürzt. Fortfahren?'}) === false) + { + return; + } this.onDrop?.({ item: [obj], start: dropStart.toISO(), diff --git a/public/js/components/Calendar/Mode/Table.js b/public/js/components/Calendar/Mode/Table.js index 529927d5f..09cd9f131 100644 --- a/public/js/components/Calendar/Mode/Table.js +++ b/public/js/components/Calendar/Mode/Table.js @@ -48,7 +48,8 @@ export default { this.$emit('update:range', this.range); }, template: ` -