diff --git a/application/config/routes.php b/application/config/routes.php
index bb0b0f65e..7a42e6c69 100644
--- a/application/config/routes.php
+++ b/application/config/routes.php
@@ -140,6 +140,8 @@ $route['api/frontend/v1/treemenudata/lvverwaltung/stdsem/(:any)/emp/(:any)/stg/(
// load routes from extensions, also look for environment-specific configs
$subdirs = ['application/config/extensions', 'application/config/' . ENVIRONMENT . '/extensions'];
+$route['api/frontend/v1/tempus/Tags/getTagsByCalendar/(:num)'] = 'api/frontend/v1/tempus/Tags/getTagsByCalendar/$1';
+
foreach($subdirs as $subdir)
{
if(is_dir($subdir))
diff --git a/application/config/tempus.php b/application/config/tempus.php
index e3e18b954..833f5655f 100644
--- a/application/config/tempus.php
+++ b/application/config/tempus.php
@@ -2,4 +2,11 @@
$config['send_update_mails'] = false;
$config['calendar_start'] = 7;
-$config['calendar_end'] = 23;
\ No newline at end of file
+$config['calendar_end'] = 23;
+
+
+$config['tempus_tags'] = [
+ 'prioone' => ['readonly' => false],
+ 'priotwo' => ['readonly' => true],
+ 'hinweis' => ['readonly' => false],
+];
\ No newline at end of file
diff --git a/application/controllers/Tempus.php b/application/controllers/Tempus.php
index 139f0b690..f0e2623c3 100644
--- a/application/controllers/Tempus.php
+++ b/application/controllers/Tempus.php
@@ -16,25 +16,54 @@ class Tempus extends Auth_Controller
// Load Libraries
$this->load->library('VariableLib', ['uid' => getAuthUID()]);
-
+
// Load Config
$this->load->config('calendar');
}
- /**
- * @return void
- */
- public function _remap()
+ public function index()
{
+ $this->_loadView();
+ }
+ public function sync()
+ {
+ $this->load->model('organisation/Studiensemester_model', 'StudiensemesterModel');
+ $this->load->model('organisation/Studienplan_model', 'StudienplanModel');
+ $this->load->model('organisation/Studiengang_model', 'StudiengangModel');
+ $this->load->model('organisation/Organisationseinheit_model', 'OrganisationseinheitModel');
+ $this->load->model('ressource/Kalenderstatus_model', 'KalenderStatusModel');
+
+ $this->OrganisationseinheitModel->addJoin('tbl_studiengang', 'oe_kurzbz', 'LEFT');
+
+ $this->OrganisationseinheitModel->addSelect('public.tbl_organisationseinheit.organisationseinheittyp_kurzbz,
+ public.tbl_organisationseinheit.bezeichnung,
+ public.tbl_studiengang.studiengang_kz,
+ public.tbl_organisationseinheit.oe_kurzbz,
+ tbl_studiengang.typ,
+ COALESCE(public.tbl_studiengang.bezeichnung, \'\') as stgbezeichnung');
+ $this->OrganisationseinheitModel->addOrder('tbl_organisationseinheit.aktiv', 'DESC');
+ $this->OrganisationseinheitModel->addOrder('tbl_organisationseinheit.organisationseinheittyp_kurzbz');
+ $organisationen = getData($this->OrganisationseinheitModel->loadWhere(array("tbl_organisationseinheit.aktiv" => true)));
+
+
+ $this->StudiensemesterModel->addOrder('start', 'DESC');
+ $studiensemestern = getData($this->StudiensemesterModel->load());
+
+ $this->_loadView(array('organisationen' => $organisationen, 'studiensemestern' => $studiensemestern));
+ }
+
+ private function _loadView($extraVariables = [])
+ {
$this->load->view('Tempus', [
'permissions' => [
- 'admin' => $this->permissionlib->isBerechtigt('admin')
+ 'admin' => $this->permissionlib->isBerechtigt('admin'),
+ 'stundenraster' => $this->permissionlib->isBerechtigt('basis/tempus_stundenraster'),
],
- 'variables' => [
+ 'variables' => array_merge([
'semester_aktuell' => $this->variablelib->getVar('semester_aktuell'),
'timezone' => $this->config->item('timezone')
- ]
+ ], $extraVariables)
]);
}
-}
+}
\ No newline at end of file
diff --git a/application/controllers/api/frontend/v1/tempus/Config.php b/application/controllers/api/frontend/v1/tempus/Config.php
index 30be2cc6f..69711d455 100644
--- a/application/controllers/api/frontend/v1/tempus/Config.php
+++ b/application/controllers/api/frontend/v1/tempus/Config.php
@@ -11,10 +11,10 @@ class Config extends FHCAPI_Controller
public function __construct()
{
parent::__construct([
- 'get' => ['admin:r', 'assistenz:r'],
- 'getHeader' => ['admin:r', 'assistenz:r'],
- 'set' => ['admin:r', 'assistenz:r'],
- 'updateCollision' => ['admin:r', 'assistenz:r'],
+ 'get' => ['admin:r', 'assistenz:rw', 'lehre/lvplan:rw'],
+ 'getHeader' => ['admin:r', 'assistenz:rw', 'lehre/lvplan:rw'],
+ 'set' => ['admin:r', 'assistenz:rw', 'lehre/lvplan:rw'],
+ 'updateCollision' => ['admin:r', 'assistenz:rw', 'lehre/lvplan:rw'],
]);
// Load Phrases
@@ -32,7 +32,17 @@ class Config extends FHCAPI_Controller
{
$config = [];
- $result = $this->_ci->VariableModel->getVariables(getAuthUID(), ['ignore_kollision', 'kollision_student', 'ignore_reservierung', 'ignore_zeitsperre', 'ignore_resources_collisions']);
+ $var_array = array(
+ 'ignore_kollision',
+ 'kollision_student',
+ 'ignore_reservierung',
+ 'ignore_zeitsperre',
+ 'ignore_resources_collisions',
+ 'roomless_planning',
+ 'priority_room_planning',
+ 'dialog_room_planning'
+ );
+ $result = $this->_ci->VariableModel->getVariables(getAuthUID(), $var_array);
$data = $this->getDataOrTerminateWithError($result);
$config['ignore_kollision'] = [
@@ -67,6 +77,25 @@ class Config extends FHCAPI_Controller
"value" => ($data['ignore_resources_collisions'] ?? 'false') === 'true'
];
+ $config['roomless_planning'] = [
+ "type" => "checkbox",
+ "label" => $this->p->t('ui', 'roomless_planning'),
+ "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['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()
@@ -92,31 +121,26 @@ class Config extends FHCAPI_Controller
public function set()
{
- $this->_ci->VariableModel->setVariable(
- getAuthUID(),
+ $configs = array(
'ignore_kollision',
- $this->input->post('ignore_kollision') === true ? 'true' : 'false'
- );
- $this->_ci->VariableModel->setVariable(
- getAuthUID(),
'kollision_student',
- $this->input->post('kollision_student') === true ? 'true' : 'false'
- );
- $this->_ci->VariableModel->setVariable(
- getAuthUID(),
'ignore_reservierung',
- $this->input->post('ignore_reservierung') === true ? 'true' : 'false'
- );
- $this->_ci->VariableModel->setVariable(
- getAuthUID(),
'ignore_zeitsperre',
- $this->input->post('ignore_zeitsperre') === true ? 'true' : 'false'
- );
- $this->_ci->VariableModel->setVariable(
- getAuthUID(),
'ignore_resources_collisions',
- $this->input->post('ignore_resources_collisions') === true ? 'true' : 'false'
+ 'roomless_planning',
+ 'priority_room_planning',
+ 'dialog_room_planning'
);
+
+ foreach ($configs as $config)
+ {
+ $this->_ci->VariableModel->setVariable(
+ getAuthUID(),
+ $config,
+ $this->input->post($config) === true ? 'true' : 'false'
+ );
+ }
+
$this->terminateWithSuccess();
}
diff --git a/application/controllers/api/frontend/v1/tempus/Coursepicker.php b/application/controllers/api/frontend/v1/tempus/Coursepicker.php
index fd4ca5c75..28b10920f 100644
--- a/application/controllers/api/frontend/v1/tempus/Coursepicker.php
+++ b/application/controllers/api/frontend/v1/tempus/Coursepicker.php
@@ -5,11 +5,13 @@ if (! defined('BASEPATH')) exit('No direct script access allowed');
class Coursepicker extends FHCAPI_Controller
{
private $_ci;
+
+ const ALLOWED_COURSE_FILTER = ['uid', 'stg'];
+
public function __construct()
{
parent::__construct([
- 'search' => self::PERM_LOGGED,
- 'getByStg' => self::PERM_LOGGED
+ 'getCourses' => 'lehre/lvplan:rw',
]);
$this->_ci = &get_instance();
@@ -22,88 +24,40 @@ class Coursepicker extends FHCAPI_Controller
$this->loadPhrases(['ui']);
}
- public function search()
- {
- $query = $this->input->get('query');
- if (is_null($query))
- $this->terminateWithError($this->p->t('ui', 'ungueltigeParameter'), self::ERROR_TYPE_GENERAL);
-
- $query_words = explode(' ', $query);
-
- //TODO Where weiter anpassen z.B. Fachbereich
- $this->_ci->LehreinheitModel->addSelect('tbl_lehreinheit.lehreinheit_id,
- tbl_lehreinheit.lvnr,
- tbl_lehreinheit.lehrfach_id,
- lehrfach.kurzbz AS lehrfach,
- lehrfach.bezeichnung AS lehrfach_bez,
- lehrfach.farbe AS lehrfach_farbe,
- tbl_lehreinheit.lehrform_kurzbz AS lehrform,
- lema.mitarbeiter_uid AS lektor_uid,
- tbl_mitarbeiter.kurzbz AS lektor,
- tbl_studiengang.studiengang_kz,
- upper(tbl_studiengang.typ::character varying::text || tbl_studiengang.kurzbz::text) AS studiengang,
- lvb.semester,
- lvb.verband,
- lvb.gruppe,
- lvb.gruppe_kurzbz,
- tbl_lehreinheit.raumtyp,
- tbl_lehreinheit.raumtypalternativ,
- tbl_lehreinheit.stundenblockung,
- tbl_lehreinheit.wochenrythmus,
- lema.semesterstunden,
- lema.planstunden,
- tbl_lehreinheit.start_kw,
- tbl_lehreinheit.anmerkung,
- tbl_lehreinheit.studiensemester_kurzbz');
-
- $this->_ci->LehreinheitModel->addJoin('lehre.tbl_lehreinheitmitarbeiter lema', 'tbl_lehreinheit.lehreinheit_id = lema.lehreinheit_id');
- $this->_ci->LehreinheitModel->addJoin('lehre.tbl_lehreinheitgruppe lvb', 'tbl_lehreinheit.lehreinheit_id = lvb.lehreinheit_id');
- $this->_ci->LehreinheitModel->addJoin('public.tbl_studiengang', 'lvb.studiengang_kz = tbl_studiengang.studiengang_kz');
- $this->_ci->LehreinheitModel->addJoin('lehre.tbl_lehrveranstaltung lehrfach', 'tbl_lehreinheit.lehrfach_id = lehrfach.lehrveranstaltung_id');
- $this->_ci->LehreinheitModel->addJoin('public.tbl_mitarbeiter', 'lema.mitarbeiter_uid = tbl_mitarbeiter.mitarbeiter_uid');
- $this->_ci->LehreinheitModel->addJoin('lehre.tbl_lehrform', 'tbl_lehrform.lehrform_kurzbz = tbl_lehreinheit.lehrform_kurzbz');
-
- $this->_ci->MitarbeiterModel->db->group_start();
-
- foreach ($query_words as $word)
- {
- $this->_ci->LehreinheitModel->db->group_start();
- $this->_ci->LehreinheitModel->db->where('lema.mitarbeiter_uid ILIKE', "%" . $word . "%");
- $this->_ci->LehreinheitModel->db->or_where('lvb.gruppe_kurzbz ILIKE', "%" . $word . "%");
- $this->_ci->LehreinheitModel->db->or_where('tbl_studiengang.kurzbzlang ILIKE', "%" . $word . "%");
- $this->_ci->LehreinheitModel->db->or_where('lvb.verband ILIKE', "%" . $word . "%");
- $this->_ci->LehreinheitModel->db->or_where('lvb.gruppe ILIKE', "%" . $word . "%");
- $this->_ci->LehreinheitModel->db->or_where('lehrfach.bezeichnung ILIKE', "%" . $word . "%");
-
- if (is_numeric($word))
- {
- $this->_ci->LehreinheitModel->db->or_where('tbl_studiengang.studiengang_kz', $word);
- $this->_ci->LehreinheitModel->db->or_where('lvb.semester', $word);
- }
- $this->_ci->LehreinheitModel->db->group_end();
-
- }
- $this->_ci->LehreinheitModel->db->group_end();
- $this->_ci->LehreinheitModel->db->where('tbl_lehreinheit.studiensemester_kurzbz = \'SS2025\'');
- $this->_ci->LehreinheitModel->db->where(array('tbl_lehrform.verplanen' => true));
-
- $result = $this->_ci->LehreinheitModel->load();
-
- $this->terminateWithSuccess(hasData($result) ? getData($result) : array());
- }
-
- public function getByStg()
+ public function getCourses()
{
$this->_ci->form_validation->set_data($_POST);
- $this->_ci->form_validation->set_rules('studiengaenge[]',"studiengaenge","required");
$this->_ci->form_validation->set_rules('studiensemester_kurzbz',"studiensemester_kurzbz","required");
if($this->_ci->form_validation->run() === FALSE)
$this->terminateWithValidationErrors($this->_ci->form_validation->error_array());
- $studiengaenge = $this->input->post('studiengaenge');
- $studiensemester_kurzbz = $this->input->post('studiensemester_kurzbz');
+ $studiensemester_kurzbz = $this->_ci->input->post('studiensemester_kurzbz', TRUE);
+ $filter = $this->_checkFilter(self::ALLOWED_COURSE_FILTER);
+
+ $this->_getBasePlan($studiensemester_kurzbz);
+
+ $this->_ci->LehreinheitModel->db->group_start();
+ if (isset($filter->stg))
+ $this->_addStgWhere($filter->stg);
+
+ if (isset($filter->uid))
+ {
+ $uid_array = (array) $filter->uid;
+ $this->_ci->LehreinheitModel->db->or_where_in('tbl_benutzer.uid', $uid_array);
+ }
+
+ $this->_ci->LehreinheitModel->db->group_end();
+
+ $this->_ci->LehreinheitModel->addOrder('lehreinheit_id');
+ $result = $this->_ci->LehreinheitModel->load();
+ $result = hasData($result) ? getData($result) : array();
+ $this->terminateWithSuccess($this->_mapEvents($result));
+ }
+
+ private function _getBasePlan($studiensemester_kurzbz)
+ {
$this->_ci->LehreinheitModel->addSelect('
tbl_lehreinheit.lehreinheit_id,
tbl_lehreinheit.lvnr,
@@ -142,6 +96,15 @@ class Coursepicker extends FHCAPI_Controller
$this->_ci->LehreinheitModel->addJoin('public.tbl_benutzer', 'ma.mitarbeiter_uid = tbl_benutzer.uid');
$this->_ci->LehreinheitModel->addJoin('public.tbl_person', 'tbl_benutzer.person_id = tbl_person.person_id');
+ $this->_ci->LehreinheitModel->db->group_start();
+ $this->_ci->LehreinheitModel->db->where('tbl_lehrform.verplanen', true);
+ $this->_ci->LehreinheitModel->db->where('tbl_lehreinheit.studiensemester_kurzbz', $studiensemester_kurzbz);
+ $this->_ci->LehreinheitModel->db->group_end();
+
+ }
+
+ private function _addStgWhere($studiengaenge)
+ {
$this->_ci->LehreinheitModel->db->group_start();
$first = true;
foreach ($studiengaenge as $studiengang)
@@ -165,14 +128,10 @@ class Coursepicker extends FHCAPI_Controller
$this->_ci->LehreinheitModel->db->group_end();
}
$this->_ci->LehreinheitModel->db->group_end();
+ }
- $result = $this->_ci->LehreinheitModel->loadWhere(array(
- 'tbl_lehrform.verplanen' => true,
- 'tbl_lehreinheit.studiensemester_kurzbz' => $studiensemester_kurzbz
- ));
-
- $result = hasData($result) ? getData($result) : array();
-
+ private function _mapEvents($result)
+ {
$lehreinheit_ids = array_values(array_unique(array_column($result, 'lehreinheit_id')));
$offen_map = array();
@@ -281,10 +240,8 @@ class Coursepicker extends FHCAPI_Controller
$group->verplant = $this->_formatArr($group->verplant);
$group->offenestunden = $this->_formatArr($group->offenestunden);
}
-
- $this->terminateWithSuccess(array_values($grouped));
+ return array_values($grouped);
}
-
private function _formatArr($arr)
{
$values = array_values(array_unique($arr));
@@ -296,5 +253,24 @@ class Coursepicker extends FHCAPI_Controller
return $formatted;
}
+ private function _checkFilter($filters)
+ {
+ $filter_valid = false;
+ $filter_object = new stdClass();
+ foreach ($filters as $filter)
+ {
+ if ($this->_ci->input->post($filter))
+ {
+ $filter_valid = true;
+ $filter_object->$filter = $this->_ci->input->post($filter);
+ }
+ }
+
+ if (!$filter_valid)
+ $this->terminateWithError($this->p->t('ui', 'ungueltigeParameter'), self::ERROR_TYPE_GENERAL);
+
+ return $filter_object;
+ }
+
}
diff --git a/application/controllers/api/frontend/v1/tempus/Favorites.php b/application/controllers/api/frontend/v1/tempus/Favorites.php
index 94ee39707..3bfb5b9c1 100644
--- a/application/controllers/api/frontend/v1/tempus/Favorites.php
+++ b/application/controllers/api/frontend/v1/tempus/Favorites.php
@@ -7,8 +7,8 @@ class Favorites extends FHCAPI_Controller
public function __construct()
{
parent::__construct([
- 'index' => self::PERM_LOGGED,
- 'set' => self::PERM_LOGGED
+ 'index' => 'lehre/lvplan:rw',
+ 'set' => 'lehre/lvplan:rw'
]);
// Load models
diff --git a/application/controllers/api/frontend/v1/tempus/Info.php b/application/controllers/api/frontend/v1/tempus/Info.php
index b8ab2b9b0..9ef71da3e 100644
--- a/application/controllers/api/frontend/v1/tempus/Info.php
+++ b/application/controllers/api/frontend/v1/tempus/Info.php
@@ -12,7 +12,7 @@ class Info extends FHCAPI_Controller
public function __construct()
{
parent::__construct([
- 'getStudiengaenge' => self::PERM_LOGGED,
+ 'getStudiengaenge' => 'lehre/lvplan:rw',
]);
$this->_ci =& get_instance();
diff --git a/application/controllers/api/frontend/v1/tempus/Kalender.php b/application/controllers/api/frontend/v1/tempus/Kalender.php
index 63b1ac9d5..ccf6fdf75 100644
--- a/application/controllers/api/frontend/v1/tempus/Kalender.php
+++ b/application/controllers/api/frontend/v1/tempus/Kalender.php
@@ -8,18 +8,19 @@ class Kalender extends FHCAPI_Controller
const ALLOWED_PLAN_FILTER = ['ort', 'uid', 'stg'];
const ALLOWED_ROOM_FILTER = ['lehreinheit_id', 'kalender_id'];
- const ALLOWED_TO_UPDATE = ['start_time', 'end_time', 'ort_kurzbz'];
+ const ALLOWED_TO_UPDATE = ['start_time', 'end_time', 'orte'];
/**
* Object initialization
*/
public function __construct()
{
parent::__construct([
- 'getStunden' => self::PERM_LOGGED,
- 'getCalendarHours' => self::PERM_LOGGED,
- 'getPlan' => self::PERM_LOGGED,
- 'getPlanByOrt' => self::PERM_LOGGED,
- 'getRaumvorschlag' => self::PERM_LOGGED,
+ 'getStunden' => 'lehre/lvplan:rw',
+ 'getCalendarHours' => 'lehre/lvplan:rw',
+ 'getPlan' => 'lehre/lvplan:rw',
+ 'getPlanByOrt' => 'lehre/lvplan:rw',
+ 'getRaumvorschlag' => 'lehre/lvplan:rw',
+ 'getRaumvorschlagSlots' => 'lehre/lvplan:rw',
'getLehreinheiten' => 'lehre/lvplan:rw',
'getHistory' => 'lehre/lvplan:rw',
'deleteEntry' => 'lehre/lvplan:rw',
@@ -27,8 +28,8 @@ class Kalender extends FHCAPI_Controller
'syncToStudent' => 'lehre/lvplan:rw',
'getPlanLecturer' =>'lehre/lvplan:rw',
'getPlanStudent' => 'lehre/lvplan:rw',
- 'getZeitwuensche' => self::PERM_LOGGED,
- 'getZeitsperren' => self::PERM_LOGGED,
+ 'getZeitwuensche' => 'lehre/lvplan:rw',
+ 'getZeitsperren' => 'lehre/lvplan:rw',
'updateKalenderEvent' => 'lehre/lvplan:rw',
'deleteOrtEntry' => 'lehre/lvplan:rw',
'deleteFromKalenderEvent' => 'lehre/lvplan:rw',
@@ -44,6 +45,7 @@ class Kalender extends FHCAPI_Controller
$this->_ci->load->library('LogLib');
$this->_ci->load->library('form_validation');
$this->_ci->load->library('KalenderLib');
+ $this->_ci->load->library('KalenderSyncLib');
$this->_ci->load->library('RaumvorschlagLib');
$this->loadPhrases([
'ui'
@@ -227,7 +229,7 @@ class Kalender extends FHCAPI_Controller
$updateFields = $this->_checkUpdate($this->_ci->input->post('updatedInfos', TRUE));
$kalender_id = $this->_ci->input->post('kalender_id', TRUE);
- $result = $this->_ci->kalenderlib->updateKalenderEvent($kalender_id, $updateFields->ort_kurzbz ?? null, $updateFields->start_time ?? null, $updateFields->end_time ?? null);
+ $result = $this->_ci->kalenderlib->updateKalenderEvent($kalender_id, $updateFields->orte ?? null, $updateFields->start_time ?? null, $updateFields->end_time ?? null, $updateFields->location ?? null);
if (isError($result))
$this->terminateWithError(getError($result), $result->code);
@@ -260,6 +262,22 @@ class Kalender extends FHCAPI_Controller
$this->terminateWithSuccess($this->_ci->raumvorschlaglib->getVorschlaege($filter->kalender_id));
}
+ public function getRaumvorschlagSlots()
+ {
+ $this->_ci->form_validation->set_data($_GET);
+ $this->_ci->form_validation->set_rules('lehreinheit_id',"lehreinheit_id","required");
+ $this->_ci->form_validation->set_rules('start_date',"start_date","required");
+ $this->_ci->form_validation->set_rules('end_date',"end_date","required");
+
+ if($this->_ci->form_validation->run() === FALSE)
+ $this->terminateWithValidationErrors($this->_ci->form_validation->error_array());
+
+ $lehreinheit_id = $this->_ci->input->get('lehreinheit_id', TRUE);
+ $start_date = $this->_ci->input->get('start_date', TRUE);
+ $end_date = $this->_ci->input->get('end_date', TRUE);
+ $this->terminateWithSuccess(getData($this->_ci->raumvorschlaglib->getVorschlaegeSlots($lehreinheit_id, $start_date, $end_date)));
+ }
+
public function getLehreinheiten()
{
$this->_ci->form_validation->set_data($_GET);
@@ -311,7 +329,7 @@ class Kalender extends FHCAPI_Controller
public function sync()
{
- $result = $this->_ci->kalenderlib->sync();
+ $result = $this->_ci->kalendersynclib->sync();
$this->terminateWithSuccess(getData($result));
}
public function syncToLecturer()
diff --git a/application/controllers/api/frontend/v1/tempus/KalenderSync.php b/application/controllers/api/frontend/v1/tempus/KalenderSync.php
new file mode 100644
index 000000000..13e4cc012
--- /dev/null
+++ b/application/controllers/api/frontend/v1/tempus/KalenderSync.php
@@ -0,0 +1,304 @@
+ ['admin:r', 'assistenz:r'],
+ 'loadSync' => ['admin:r', 'assistenz:r'],
+ 'add' => ['admin:r', 'assistenz:r'],
+ 'start' => ['admin:r', 'assistenz:r'],
+ 'delete' => ['admin:r', 'assistenz:r'],
+ 'updateSync' => ['admin:r', 'assistenz:r'],
+ 'getStudienplan' => ['admin:r', 'assistenz:r'],
+ 'getSyncStatus' => ['admin:r', 'assistenz:r'],
+ ]);
+
+ $this->loadPhrases([
+ 'lehre',
+ ]);
+
+ $this->_ci = &get_instance();
+ $this->_ci->load->library('form_validation');
+ $this->_ci->load->model('organisation/Studienplan_model', 'StudienplanModel');
+ $this->_ci->load->model('organisation/Studiengang_model', 'StudiengangModel');
+ $this->_ci->load->model('ressource/Kalendersyncstatus_model', 'KalendersyncstatusModel');
+ $this->_ci->load->model('ressource/Kalenderstatus_model', 'KalenderStatusModel');
+ $this->_ci->load->library('KalenderSyncLib');
+ }
+
+ public function getSyncs()
+ {
+ $this->_ci->form_validation->set_data($_GET);
+ $this->_ci->form_validation->set_rules('studiensemester_kurzbz',"studiensemester_kurzbz","required");
+
+ if($this->_ci->form_validation->run() === FALSE)
+ $this->terminateWithValidationErrors($this->_ci->form_validation->error_array());
+ $studiensemester_kurzbz = $this->_ci->input->get('studiensemester_kurzbz', TRUE);
+
+ $language = getUserLanguage() == 'German' ? 0 : 1;
+
+ $this->_ci->KalenderStatusModel->addSelect('tbl_kalender_syncstatus.*, array_to_json(bezeichnung_mehrsprachig::varchar[])->>' . $language .' AS sync_status, tbl_organisationseinheit.bezeichnung as oebezeichnung, tbl_studienplan.bezeichnung as studienplanbezeichnung');
+ $this->_ci->KalenderStatusModel->addJoin('lehre.tbl_kalender_status', 'tbl_kalender_syncstatus.sync_status_kurzbz = tbl_kalender_status.status_kurzbz');
+ $this->_ci->KalenderStatusModel->addJoin('public.tbl_organisationseinheit', 'tbl_kalender_syncstatus.oe_kurzbz = tbl_organisationseinheit.oe_kurzbz');
+ $this->_ci->KalenderStatusModel->addJoin('lehre.tbl_studienplan', 'tbl_kalender_syncstatus.studienplan_id = tbl_studienplan.studienplan_id', 'LEFT');
+ $this->_ci->KalenderStatusModel->addJoin('public.tbl_studiensemester', 'tbl_kalender_syncstatus.studiensemester_kurzbz = tbl_studiensemester.studiensemester_kurzbz');
+ $this->_ci->KalenderStatusModel->addOrder('public.tbl_studiensemester.start', 'DESC');
+
+
+ if ($studiensemester_kurzbz === 'all')
+ $data = $this->_ci->KalendersyncstatusModel->load();
+ else
+ $data = $this->_ci->KalendersyncstatusModel->loadWhere(array('tbl_kalender_syncstatus.studiensemester_kurzbz' => $studiensemester_kurzbz));
+
+ if (isError($data))
+ $this->terminateWithError(getError($data));
+
+ $this->terminateWithSuccess(getData($data));
+ }
+
+ public function delete()
+ {
+ $this->load->library('form_validation');
+
+ $this->form_validation->set_rules('kalender_syncstatus_id', 'kalender_syncstatus_id', 'required');
+
+ if (!$this->form_validation->run())
+ $this->terminateWithValidationErrors($this->form_validation->error_array());
+
+ $kalender_syncstatus_id = $this->input->post('kalender_syncstatus_id');
+
+ $result = $this->_ci->KalendersyncstatusModel->load($kalender_syncstatus_id);
+
+ if (isError($result))
+ $this->terminateWithError(getError($result));
+
+ if (!hasData($result))
+ $this->terminateWithError($this->p->t('lehre', 'errorDeleting'));
+
+ $del_result = $this->_ci->KalendersyncstatusModel->delete(getData($result)[0]->kalender_syncstatus_id);
+
+ if (isError($del_result))
+ $this->terminateWithError(getError($del_result));
+ $this->terminateWithSuccess($del_result);
+ }
+
+ public function loadSync()
+ {
+ $this->_ci->form_validation->set_data($_GET);
+ $this->_ci->form_validation->set_rules('kalender_syncstatus_id',"kalender_syncstatus_id","required");
+
+ if($this->_ci->form_validation->run() === FALSE)
+ $this->terminateWithValidationErrors($this->_ci->form_validation->error_array());
+ $kalender_syncstatus_id = $this->_ci->input->get('kalender_syncstatus_id', TRUE);
+ $data = $this->_ci->KalendersyncstatusModel->loadWhere(array('kalender_syncstatus_id' => $kalender_syncstatus_id));
+ if (isError($data))
+ $this->terminateWithError(getError($data));
+
+ $this->terminateWithSuccess(getData($data)[0]);
+ }
+
+ public function getSyncStatus()
+ {
+ $language = getUserLanguage() == 'German' ? 0 : 1;
+
+ $this->_ci->KalenderStatusModel->addSelect('*, array_to_json(bezeichnung_mehrsprachig::varchar[])->>' . $language .' AS bezeichnung, status_kurzbz');
+ $this->_ci->KalenderStatusModel->db->where_in('status_kurzbz', self::ALLOWED_STATUS);
+ $data = $this->_ci->KalenderStatusModel->load();
+
+ if (isError($data))
+ $this->terminateWithError(getError($data));
+
+ $this->terminateWithSuccess(getData($data));
+ }
+
+
+ public function add()
+ {
+ $formData = $this->input->post('formData');
+
+ $this->_ci->form_validation->set_data($formData);
+
+ $this->_ci->form_validation->set_rules('oe_kurzbz',"oe_kurzbz","required");
+ $this->_ci->form_validation->set_rules('studiensemester_kurzbz',"studiensemester_kurzbz","required");
+ $this->_ci->form_validation->set_rules('sync_status_kurzbz',"sync_status_kurzbz","required");
+
+ if($this->_ci->form_validation->run() === FALSE)
+ $this->terminateWithValidationErrors($this->_ci->form_validation->error_array());
+
+ $ausbildungssemester = isEmptyString($formData['ausbildungssemester']) ? null : $formData['ausbildungssemester'];
+ $studienplan_id = isEmptyString($formData['studienplan_id']) ? null : $formData['studienplan_id'];
+
+ $existing = $this->_checkIfExist(
+ $formData['oe_kurzbz'],
+ $formData['studiensemester_kurzbz'],
+ $ausbildungssemester,
+ $studienplan_id
+ );
+
+ if (isError($existing))
+ $this->terminateWithError(getError($existing));
+
+ if (hasData($existing))
+ {
+ $this->terminateWithError('Es existiert bereits ein Eintrag.');
+ }
+
+ $result = $this->_ci->KalendersyncstatusModel->insert(
+ array(
+ 'oe_kurzbz' => $formData['oe_kurzbz'],
+ 'studiensemester_kurzbz' => $formData['studiensemester_kurzbz'],
+ 'datum_bis' => $formData['datum_bis'],
+ 'studienplan_id' => $studienplan_id,
+ 'ausbildungssemester' => $ausbildungssemester,
+ 'sync_status_kurzbz' => $formData['sync_status_kurzbz'],
+ 'mail' => $formData['mail'],
+ 'insertvon' => getAuthUID()
+ )
+ );
+
+ $this->terminateWithSuccess($result);
+ }
+
+
+ public function start()
+ {
+ $formData = $this->input->post('formData');
+
+ $this->_ci->form_validation->set_data($formData);
+
+ $this->_ci->form_validation->set_rules('oe_kurzbz',"oe_kurzbz","required");
+ $this->_ci->form_validation->set_rules('studiensemester_kurzbz',"studiensemester_kurzbz","required");
+ $this->_ci->form_validation->set_rules('sync_status_kurzbz',"sync_status_kurzbz","required");
+
+ if($this->_ci->form_validation->run() === FALSE)
+ $this->terminateWithValidationErrors($this->_ci->form_validation->error_array());
+
+ $result = $this->_ci->kalendersynclib->runManual($formData['oe_kurzbz'], $formData['studiensemester_kurzbz'], $formData['ausbildungssemester'], $formData['studienplan_id'], $formData['mail'], $formData['datum_bis'], $formData['sync_status_kurzbz']);
+
+ if (isError($result))
+ $this->terminateWithError(getError($result));
+
+ $this->terminateWithSuccess($result);
+ }
+ public function updateSync()
+ {
+ $formData = $this->input->post('formData');
+
+ $this->_ci->form_validation->set_data($formData);
+
+ $this->_ci->form_validation->set_rules('kalender_syncstatus_id',"kalender_syncstatus_id","required");
+ $this->_ci->form_validation->set_rules('oe_kurzbz',"oe_kurzbz","required");
+ $this->_ci->form_validation->set_rules('studiensemester_kurzbz',"studiensemester_kurzbz","required");
+ $this->_ci->form_validation->set_rules('sync_status_kurzbz',"sync_status_kurzbz","required");
+
+ if($this->_ci->form_validation->run() === FALSE)
+ $this->terminateWithValidationErrors($this->_ci->form_validation->error_array());
+
+ $ausbildungssemester = isEmptyString($formData['ausbildungssemester']) ? null : $formData['ausbildungssemester'];
+ $studienplan_id = isEmptyString($formData['studienplan_id']) ? null : $formData['studienplan_id'];
+
+ $existing = $this->_checkIfExist(
+ $formData['oe_kurzbz'],
+ $formData['studiensemester_kurzbz'],
+ $ausbildungssemester,
+ $studienplan_id,
+ $formData['kalender_syncstatus_id']
+ );
+
+ if (isError($existing))
+ $this->terminateWithError(getError($existing));
+
+ if (hasData($existing))
+ {
+ $this->terminateWithError('Es existiert bereits ein Eintrag.');
+ }
+
+ $this->_ci->KalendersyncstatusModel->update(
+ array('kalender_syncstatus_id' => $formData['kalender_syncstatus_id']),
+ array(
+ 'oe_kurzbz' => $formData['oe_kurzbz'],
+ 'studiensemester_kurzbz' => $formData['studiensemester_kurzbz'],
+ 'datum_bis' => $formData['datum_bis'],
+ 'studienplan_id' => $studienplan_id,
+ 'ausbildungssemester' => $ausbildungssemester,
+ 'sync_status_kurzbz' => $formData['sync_status_kurzbz'],
+ 'mail' => $formData['mail'],
+ 'updatevon' => getAuthUID(),
+ 'updateamum' => date('c'),
+ )
+ );
+ }
+ public function getStudienplan()
+ {
+ $this->_ci->form_validation->set_data($_GET);
+ $this->_ci->form_validation->set_rules('oe_kurzbz',"oe_kurzbz","required");
+
+ if($this->_ci->form_validation->run() === FALSE)
+ $this->terminateWithValidationErrors($this->_ci->form_validation->error_array());
+
+ $oe_kurzbz = $this->_ci->input->get('oe_kurzbz', TRUE);
+ $studiensemester_kurzbz = $this->_ci->input->get('studiensemester_kurzbz', TRUE);
+ $ausbildungssemester = $this->_ci->input->get('ausbildungssemester', TRUE);
+
+ $this->_ci->StudiengangModel->addJoin('tbl_organisationseinheit', 'oe_kurzbz');
+ $is_studiengang = $this->_ci->StudiengangModel->loadWhere(array('oe_kurzbz' => $oe_kurzbz));
+
+ if (hasData($is_studiengang) && !isEmptyString($studiensemester_kurzbz))
+ {
+ $studiengang = getData($is_studiengang)[0];
+
+ $studienplaene = $this->_ci->StudienplanModel->getStudienplaeneBySemester(
+ $studiengang->studiengang_kz,
+ $studiensemester_kurzbz,
+ isEmptyString($ausbildungssemester) ? null : $ausbildungssemester
+ );
+
+
+ $data = $this->getDataOrTerminateWithError($studienplaene);
+
+ $unique = array();
+ foreach ($data as $row)
+ {
+ $unique[$row->studienplan_id] = (object) array(
+ 'studienplan_id' => $row->studienplan_id,
+ 'studienordnung_id' => $row->studienordnung_id,
+ 'bezeichnung' => $row->bezeichnung,
+ 'orgform_kurzbz' => $row->orgform_kurzbz
+ );
+ }
+
+ $this->terminateWithSuccess(array_values($unique));
+ }
+ $this->terminateWithSuccess();
+ }
+
+ private function _checkIfExist($oe_kurzbz, $studiensemester_kurzbz, $ausbildungssemester, $studienplan_id, $kalender_syncstatus_id = null)
+ {
+ $this->_ci->KalendersyncstatusModel->db->where('oe_kurzbz', $oe_kurzbz);
+ $this->_ci->KalendersyncstatusModel->db->where('studiensemester_kurzbz', $studiensemester_kurzbz);
+
+ if (is_null($ausbildungssemester))
+ $this->_ci->KalendersyncstatusModel->db->where('ausbildungssemester IS NULL', null, false);
+ else
+ $this->_ci->KalendersyncstatusModel->db->where('ausbildungssemester', $ausbildungssemester);
+
+ if (is_null($studienplan_id))
+ $this->_ci->KalendersyncstatusModel->db->where('studienplan_id IS NULL', null, false);
+ else
+ $this->_ci->KalendersyncstatusModel->db->where('studienplan_id', $studienplan_id);
+
+ if (!is_null($kalender_syncstatus_id))
+ $this->_ci->KalendersyncstatusModel->db->where('kalender_syncstatus_id !=', $kalender_syncstatus_id);
+ return $this->_ci->KalendersyncstatusModel->load();
+ }
+
+}
diff --git a/application/controllers/api/frontend/v1/tempus/OperationalResourceToCalenderAPI.php b/application/controllers/api/frontend/v1/tempus/OperationalResourceToCalenderAPI.php
index f5a09deff..76f6fd369 100644
--- a/application/controllers/api/frontend/v1/tempus/OperationalResourceToCalenderAPI.php
+++ b/application/controllers/api/frontend/v1/tempus/OperationalResourceToCalenderAPI.php
@@ -9,9 +9,9 @@ class OperationalResourceToCalenderAPI extends FHCAPI_Controller
public function __construct()
{
parent::__construct([
- 'getAssignedResourcesByCalender' => ['admin:r', 'assistenz:r'],
- 'storeResourcesToCalendarRelationship' => ['admin:w', 'assistenz:w'],
- 'getSchedulableResourcesByCalendar' => ['admin:r', 'assistenz:r'],
+ 'getAssignedResourcesByCalender' => ['admin:r', 'assistenz:r', 'lehre/lvplan:rw'],
+ 'storeResourcesToCalendarRelationship' => ['admin:w', 'assistenz:w', 'lehre/lvplan:rw'],
+ 'getSchedulableResourcesByCalendar' => ['admin:r', 'assistenz:r', 'lehre/lvplan:rw'],
]);
$this->load->model('ressource/BetriebsmittelKalender_model', 'BetriebsmittelKalenderModel');
diff --git a/application/controllers/api/frontend/v1/tempus/Reservierung.php b/application/controllers/api/frontend/v1/tempus/Reservierung.php
index 1b1baa963..5a7067bcf 100644
--- a/application/controllers/api/frontend/v1/tempus/Reservierung.php
+++ b/application/controllers/api/frontend/v1/tempus/Reservierung.php
@@ -162,9 +162,9 @@ class Reservierung extends FHCAPI_Controller
if ($this->_ci->form_validation->run() === FALSE)
$this->terminateWithValidationErrors($this->_ci->form_validation->error_array());
- $ort_kurzbz = $this->_ci->input->post('ort_kurzbz', TRUE);
+ $orte = $this->_ci->input->post('orte', TRUE);
- if (empty($ort_kurzbz) || !is_array($ort_kurzbz))
+ if (empty($orte) || (empty($orte['ort_kurzbz']) && empty($orte['location'])))
{
$this->terminateWithValidationErrors(['ort_kurzbz' => $this->p->t('ui', 'error_fieldRequired', ['field' => 'Raum'])]);
}
@@ -189,7 +189,7 @@ class Reservierung extends FHCAPI_Controller
$teilnehmer[] = array('uid' => getAuthUID(), 'rolle' => 'organisator');
- $result = $this->_ci->kalenderlib->addReservierung($titel, $beschreibung, $ort_kurzbz, $start_date, $end_date, $teilnehmer, $specialFinalGroups, $specialGroups, $groups);
+ $result = $this->_ci->kalenderlib->addReservierung($titel, $beschreibung, $orte, $start_date, $end_date, $teilnehmer, $specialFinalGroups, $specialGroups, $groups);
if (isError($result))
diff --git a/application/controllers/api/frontend/v1/tempus/Tags.php b/application/controllers/api/frontend/v1/tempus/Tags.php
new file mode 100644
index 000000000..c5223cd9a
--- /dev/null
+++ b/application/controllers/api/frontend/v1/tempus/Tags.php
@@ -0,0 +1,53 @@
+ self::BERECHTIGUNG_KURZBZ,
+ 'getTags' => self::BERECHTIGUNG_KURZBZ,
+ 'getTagsByCalendar' => self::BERECHTIGUNG_KURZBZ,
+ 'addTag' => self::BERECHTIGUNG_KURZBZ,
+ 'updateTag' => self::BERECHTIGUNG_KURZBZ,
+ 'doneTag' => self::BERECHTIGUNG_KURZBZ,
+ 'deleteTag' => self::BERECHTIGUNG_KURZBZ
+ ]);
+
+ $this->config->load('tempus');
+ }
+
+ public function getTag($readonly_tags = null)
+ {
+ parent::getTag($this->config->item('tempus_tags'));
+ }
+ public function getTags($tags = null)
+ {
+ parent::getTags($this->config->item('tempus_tags'));
+ }
+ public function getTagsByCalendar($eindeutige_kalender_gruppen_id = null)
+ {
+ parent::getTagsByAssignmentTypeValue('eindeutige_kalender_gruppen_id', $eindeutige_kalender_gruppen_id, $this->config->item('tempus_tags'));
+ }
+ public function addTag($withZuordnung = true, $updatable_tags = null)
+ {
+ parent::addTag(true, $this->config->item('tempus_tags'));
+ }
+ public function updateTag($updatable_tags = null)
+ {
+ parent::updateTag($this->config->item('tempus_tags'));
+ }
+ public function deleteTag($withZuordnung = true, $updatable_tags = null)
+ {
+ parent::deleteTag(true, $this->config->item('tempus_tags'));
+ }
+ public function doneTag($updatable_tags = null)
+ {
+ parent::doneTag($this->config->item('tempus_tags'));
+ }
+}
diff --git a/application/controllers/system/MigrateKalender.php b/application/controllers/system/MigrateKalender.php
index 15b6d28ce..67de262dd 100644
--- a/application/controllers/system/MigrateKalender.php
+++ b/application/controllers/system/MigrateKalender.php
@@ -121,7 +121,14 @@ class MigrateKalender extends CLI_Controller
$ids = is_array($block->stundenplandev_ids) ? $block->stundenplandev_ids : explode(',', $block->stundenplandev_ids);
/*$ids = array_map('intval', $ids);*/
- $this->SyncModel->db->where_in('stundenplandev_id', $ids);
+ $this->SyncModel->db->group_start();
+ $where_ids_chunks = array_chunk($ids,25);
+ foreach($where_ids_chunks as $where_ids)
+ {
+ $this->SyncModel->db->or_where_in('stundenplandev_id', $where_ids);
+ }
+ $this->SyncModel->db->group_end();
+
$sync_result = $this->SyncModel->load();
if (!hasData($sync_result))
@@ -230,7 +237,14 @@ class MigrateKalender extends CLI_Controller
$ids = is_array($block->reservierung_ids) ? $block->reservierung_ids : explode(',', $block->reservierung_ids);
- $this->SyncReservierungModel->db->where_in('reservierung_id', $ids);
+ $this->SyncReservierungModel->db->group_start();
+ $where_ids_chunks = array_chunk($ids,25);
+ foreach($where_ids_chunks as $where_ids)
+ {
+ $this->SyncReservierungModel->db->or_where_in('reservierung_id', $where_ids);
+ }
+ $this->SyncReservierungModel->db->group_end();
+
$sync_result = $this->SyncReservierungModel->load();
if (!hasData($sync_result))
diff --git a/application/core/Tag_Controller.php b/application/core/Tag_Controller.php
index c4b6edb81..3659dfbff 100644
--- a/application/core/Tag_Controller.php
+++ b/application/core/Tag_Controller.php
@@ -44,7 +44,6 @@ class Tag_Controller extends FHCAPI_Controller
{
$language = $this->_getLanguageIndex();
$index_bezeichnung_mehrsprachig = $language - 1;
-
$id = $this->input->get('id');
if (is_array($readonly_tags) && !isEmptyArray($readonly_tags))
@@ -123,7 +122,36 @@ class Tag_Controller extends FHCAPI_Controller
$notiztypen = $this->NotiztypModel->loadWhere(array('aktiv' => true));
$this->terminateWithSuccess(hasData($notiztypen) ? getData($notiztypen) : array());
}
+ public function getTagsByAssignmentTypeValue($zuordnung_typ, $zuordnung_id, $tags = null)
+ {
+ $language = $this->_getLanguageIndex();
+ $index_bezeichnung_mehrsprachig = $language - 1;
+ $this->NotizzuordnungModel->addSelect(
+ 'tbl_notizzuordnung.notiz_id as notiz_id,
+ typ_kurzbz as tag_typ_kurzbz,
+ array_to_json(bezeichnung_mehrsprachig::varchar[])->>'. $index_bezeichnung_mehrsprachig. ' as bezeichnung,
+ style,
+ beschreibung,
+ tag,
+ tbl_notiz.erledigt as done
+ '
+ );
+
+ if (is_array($tags) && !isEmptyArray($tags))
+ {
+ $tags = $this->_filterTag($tags, false);
+ $this->NotizzuordnungModel->db->where_in('tbl_notiz_typ.typ_kurzbz', $tags);
+ }
+
+ $this->NotizzuordnungModel->addJoin('public.tbl_notiz', 'public.tbl_notizzuordnung.notiz_id = public.tbl_notiz.notiz_id');
+ $this->NotizzuordnungModel->addJoin('public.tbl_notiz_typ', 'public.tbl_notiz.typ = public.tbl_notiz_typ.typ_kurzbz');
+
+ $this->NotizzuordnungModel->addOrder('prioritaet');
+
+ $notiztypen = $this->NotizzuordnungModel->loadWhere(array('aktiv' => true, $zuordnung_typ => $zuordnung_id));
+ $this->terminateWithSuccess(hasData($notiztypen) ? getData($notiztypen) : array());
+ }
public function addTag($withZuordnung = true, $updatable_tags = null)
{
$postData = $this->getPostJson();
@@ -143,7 +171,7 @@ class Tag_Controller extends FHCAPI_Controller
if (!in_array($postData->tag_typ_kurzbz, $tags))
$this->terminateWithError($this->p->t('ui', 'keineBerechtigung'));
}
-
+
if ($withZuordnung)
{
$return = array();
@@ -152,7 +180,7 @@ class Tag_Controller extends FHCAPI_Controller
$this->terminateWithError('Error occurred', self::ERROR_TYPE_GENERAL);
$values = array_unique($postData->values);
-
+
foreach ($values as $value)
{
$insertResult = $this->addNotiz($postData);
diff --git a/application/libraries/KalenderLib.php b/application/libraries/KalenderLib.php
index 44c2fe71d..f39e257bd 100644
--- a/application/libraries/KalenderLib.php
+++ b/application/libraries/KalenderLib.php
@@ -17,13 +17,13 @@ class KalenderLib
$this->_ci->load->model('ressource/BetriebsmittelKalender_model', 'BetriebsmittelKalenderModel');
$this->_ci->load->model('education/Lehreinheit_model', 'LehreinheitModel');
$this->_ci->load->model('education/Lehrveranstaltung_model', 'LehrveranstaltungModel');
- $this->_ci->load->model('education/LehreinheitMitarbeiter_model', 'LehreinheitMitarbeiterModel');
$this->_ci->load->model('ressource/Ort_model', 'OrtModel');
$this->_ci->load->model('organisation/gruppe_model', 'GruppeModel');
$this->_ci->load->model('organisation/Lehrverband_model', 'LehrverbandModel');
$this->_ci->load->model('ressource/Stunde_model', 'StundeModel');
$this->_ci->load->model('organisation/Studiensemester_model', 'StudiensemesterModel');
$this->_ci->load->model('system/Variable_model', 'VariableModel');
+ $this->_ci->load->model('system/Sprache_model', 'SpracheModel');
$this->_ci->load->model('organisation/Ferien_model', 'FerienModel');
@@ -36,59 +36,75 @@ class KalenderLib
private function _getBasePlan($start_date, $end_date)
{
+ $language = $this->getLanguageIndex();
+ $index_bezeichnung_mehrsprachig = $language - 1;
+
$end_date = date('Y-m-d', strtotime($end_date . ' +1 day'));
$this->_ci->KalenderModel->addSelect('tbl_kalender.kalender_id,
- tbl_kalender.eindeutige_kalender_gruppen_id,
- tbl_kalender.status_kurzbz,
- tbl_kalender.typ,
- tbl_kalender.von,
- tbl_kalender.bis,
- tbl_kalender_ort.ort_kurzbz,
- tbl_lehreinheit.lehreinheit_id,
- tbl_lehreinheit.lehrveranstaltung_id,
- tbl_lehreinheit.lehrfach_id,
- tbl_lehreinheit.lehrform_kurzbz,
- tbl_lehrveranstaltung.oe_kurzbz,
- lehrfach.kurzbz AS lehrfach_kurzbz,
- lehrfach.bezeichnung AS lehrfach_bezeichnung,
- lehrfach.farbe,
- COALESCE(orginasator.uid, tbl_lehreinheitmitarbeiter.mitarbeiter_uid) as mitarbeiter_uid,
- COALESCE(reservierung_person.vorname, tbl_person.vorname) as vorname,
- COALESCE(reservierung_person.nachname, tbl_person.nachname) as nachname,
- COALESCE(reservierung_ma.kurzbz, tbl_mitarbeiter.kurzbz) AS ma_kurzbz,
- teilnehmergruppe.gruppe_kurzbz as teilnehmerg_grp,
- COALESCE (
- UPPER(tbl_studiengang.typ || tbl_studiengang.kurzbz) ||
- COALESCE(verbandgruppe.semester::varchar, \'\') ||
- COALESCE(verbandgruppe.verband::varchar, \'\') ||
- COALESCE(verbandgruppe.gruppe, \'\'),
- \'\') as verband_grp,
- tbl_kalender_event.beschreibung,
- tbl_kalender_event.titel,
- teilmitarbeiter.kurzbz as teilnehmer_kurzbz,
- teilperson.vorname as teilnehmer_vorname,
- teilperson.nachname as teilnehmer_nachname,
- teilbenutzer.uid as teilnehmer_uid,
- CASE
- WHEN tbl_lehreinheitgruppe.gruppe_kurzbz IS NULL THEN
- COALESCE(
- UPPER(le_studiengang.typ || le_studiengang.kurzbz) ||
- COALESCE(tbl_lehreinheitgruppe.semester::varchar, \'\') ||
- COALESCE(tbl_lehreinheitgruppe.verband::varchar, \'\') ||
- COALESCE(tbl_lehreinheitgruppe.gruppe, \'\'),
- \'\')
- ELSE tbl_lehreinheitgruppe.gruppe_kurzbz
- END AS lehreinheit_gruppe_bezeichnung,
- tbl_lehreinheitgruppe.gruppe_kurzbz as le_gruppe_kurzbz,
- tbl_lehreinheitgruppe.studiengang_kz as le_studiengang_kz,
- tbl_lehreinheitgruppe.semester as le_semester,
- tbl_lehreinheitgruppe.verband as le_verband,
- tbl_lehreinheitgruppe.gruppe as le_gruppe,
- le_gruppe.direktinskription as le_direktinskription,
-
-
- ');
+ tbl_kalender.eindeutige_kalender_gruppen_id,
+ tbl_kalender.status_kurzbz,
+ tbl_kalender.typ,
+ tbl_kalender.von,
+ tbl_kalender.bis,
+ tbl_kalender_ort.location,
+ tbl_kalender_ort.ort_kurzbz,
+ tbl_lehreinheit.lehreinheit_id,
+ tbl_lehreinheit.lehrveranstaltung_id,
+ tbl_lehreinheit.lehrfach_id,
+ tbl_lehreinheit.lehrform_kurzbz,
+ tbl_lehrveranstaltung.oe_kurzbz,
+ lehrfach.kurzbz AS lehrfach_kurzbz,
+ lehrfach.bezeichnung AS lehrfach_bezeichnung,
+ lehrfach.farbe,
+ COALESCE(orginasator.uid, tbl_lehreinheitmitarbeiter.mitarbeiter_uid) as mitarbeiter_uid,
+ COALESCE(reservierung_person.vorname, tbl_person.vorname) as vorname,
+ COALESCE(reservierung_person.nachname, tbl_person.nachname) as nachname,
+ COALESCE(reservierung_ma.kurzbz, tbl_mitarbeiter.kurzbz) AS ma_kurzbz,
+ teilnehmergruppe.gruppe_kurzbz as teilnehmerg_grp,
+ COALESCE (
+ UPPER(tbl_studiengang.typ || tbl_studiengang.kurzbz) ||
+ COALESCE(verbandgruppe.semester::varchar, \'\') ||
+ COALESCE(verbandgruppe.verband::varchar, \'\') ||
+ COALESCE(verbandgruppe.gruppe, \'\'),
+ \'\') as verband_grp,
+ tbl_kalender_event.beschreibung,
+ tbl_kalender_event.titel,
+ teilmitarbeiter.kurzbz as teilnehmer_kurzbz,
+ teilperson.vorname as teilnehmer_vorname,
+ teilperson.nachname as teilnehmer_nachname,
+ teilbenutzer.uid as teilnehmer_uid,
+ CASE
+ WHEN tbl_lehreinheitgruppe.gruppe_kurzbz IS NULL THEN
+ COALESCE(
+ UPPER(le_studiengang.typ || le_studiengang.kurzbz) ||
+ COALESCE(tbl_lehreinheitgruppe.semester::varchar, \'\') ||
+ COALESCE(tbl_lehreinheitgruppe.verband::varchar, \'\') ||
+ COALESCE(tbl_lehreinheitgruppe.gruppe, \'\'),
+ \'\')
+ ELSE tbl_lehreinheitgruppe.gruppe_kurzbz
+ END AS lehreinheit_gruppe_bezeichnung,
+ tbl_lehreinheitgruppe.gruppe_kurzbz as le_gruppe_kurzbz,
+ tbl_lehreinheitgruppe.studiengang_kz as le_studiengang_kz,
+ tbl_lehreinheitgruppe.semester as le_semester,
+ tbl_lehreinheitgruppe.verband as le_verband,
+ tbl_lehreinheitgruppe.gruppe as le_gruppe,
+ le_gruppe.direktinskription as le_direktinskription,
+ tag_data_agg.tags as tags,
+ resource_data_agg.resources as resources,
+ CASE
+ WHEN vonstunde.stunde IS NOT NULL AND bisstunde.stunde IS NOT NULL
+ THEN bisstunde.stunde - vonstunde.stunde + 1
+ ELSE ROUND(
+ COALESCE(
+ (
+ SELECT SUM(EXTRACT(EPOCH FROM (LEAST(stunde.ende, tbl_kalender.bis::time) - GREATEST(stunde.beginn, tbl_kalender.von::time))) / 60) / 45
+ FROM lehre.tbl_stunde stunde
+ WHERE stunde.beginn < tbl_kalender.bis::time AND stunde.ende > tbl_kalender.von::time)
+ ,0)
+ ,2
+ ) END AS verplante_stunden
+ ');
$this->_ci->KalenderModel->addJoin('lehre.tbl_kalender_lehreinheit', 'tbl_kalender.kalender_id = tbl_kalender_lehreinheit.kalender_id', 'LEFT');
$this->_ci->KalenderModel->addJoin('lehre.tbl_kalender_event', 'tbl_kalender.kalender_id = tbl_kalender_event.kalender_id', 'LEFT');
@@ -134,6 +150,72 @@ class KalenderLib
);
$this->_ci->KalenderModel->addJoin('public.tbl_studiengang le_studiengang', 'le_lehrverband.studiengang_kz = le_studiengang.studiengang_kz', 'LEFT');
+ // Include tags in the query
+ $this->_ci->load->config('tempus');
+ $tags = $this->_ci->config->item('tempus_tags');
+
+ $whereTags = '';
+ if (is_array($tags) && !isEmptyArray($tags)) {
+ $tags = array_keys($tags);
+
+ foreach ($tags as $key => $tag) {
+ $tags[$key] = $this->_ci->KalenderModel->escape($tag);
+ }
+ $whereTags = " AND nt.typ_kurzbz IN (" . implode(",", $tags) . ")";
+ }
+ $subQueryTag = "(
+ SELECT
+ tag.eindeutige_kalender_gruppen_id,
+ COALESCE(json_agg(tag ORDER BY tag.done, tag.prioritaet), '[]'::json) AS tags
+ FROM (
+ SELECT DISTINCT ON (n.notiz_id)
+ n.notiz_id AS id,
+ nt.typ_kurzbz,
+ array_to_json(nt.bezeichnung_mehrsprachig::varchar[])->>". $index_bezeichnung_mehrsprachig. " as beschreibung,
+ n.text AS notiz,
+ nt.style,
+ n.erledigt AS done,
+ nt.prioritaet AS prioritaet,
+ nz.eindeutige_kalender_gruppen_id
+ FROM public.tbl_notizzuordnung AS nz
+ JOIN public.tbl_notiz AS n ON nz.notiz_id = n.notiz_id
+ JOIN public.tbl_notiz_typ AS nt ON n.typ = nt.typ_kurzbz " . $whereTags . "
+ ) AS tag
+ GROUP BY tag.eindeutige_kalender_gruppen_id
+ ) AS tag_data_agg";
+
+ $this->_ci->KalenderModel->addJoin($subQueryTag, 'tag_data_agg.eindeutige_kalender_gruppen_id = tbl_kalender.eindeutige_kalender_gruppen_id', 'LEFT');
+ // End of tags inclusion
+
+ // Include assigned operational resources in the query
+ $subQueryResource = "(
+ SELECT
+ resource.eindeutige_kalender_gruppen_id,
+ COALESCE(json_agg(resource ORDER BY resource.beschreibung), '[]'::json) AS resources
+ FROM (
+ SELECT
+ betriebsmittel_kalender.*,
+ betriebsmittel.beschreibung,
+ betriebsmittel.verplanen
+ FROM lehre.tbl_betriebsmittel_kalender AS betriebsmittel_kalender
+ JOIN wawi.tbl_betriebsmittel AS betriebsmittel
+ ON betriebsmittel.betriebsmittel_id = betriebsmittel_kalender.betriebsmittel_id
+ WHERE betriebsmittel.verplanen = TRUE
+ ) AS resource
+ GROUP BY resource.eindeutige_kalender_gruppen_id
+ ) AS resource_data_agg";
+
+ $this->_ci->KalenderModel->addJoin(
+ $subQueryResource,
+ 'resource_data_agg.eindeutige_kalender_gruppen_id = tbl_kalender.eindeutige_kalender_gruppen_id',
+ 'LEFT'
+ );
+ // End of operational resources inclusion
+
+
+ $this->_ci->KalenderModel->db->join('lehre.tbl_stunde vonstunde', 'tbl_kalender.von::time = vonstunde.beginn', 'LEFT', FALSE);
+ $this->_ci->KalenderModel->db->join('lehre.tbl_stunde bisstunde', 'tbl_kalender.bis::time = bisstunde.ende', 'LEFT', FALSE);
+
$this->_ci->KalenderModel->db->where('tbl_kalender.von >=', $start_date);
$this->_ci->KalenderModel->db->where('tbl_kalender.bis <', $end_date);
@@ -157,6 +239,8 @@ class KalenderLib
{
$von = new DateTime($row->von);
$bis = new DateTime($row->bis);
+ $resources = isset($row->resources) ? $row->resources : [];
+ $parsedResources = is_string($resources) ? json_decode($resources, true) : $resources;
$events[$id] = (object) [
'eindeutige_kalender_gruppen_id' => $row->eindeutige_kalender_gruppen_id,
@@ -169,11 +253,13 @@ class KalenderLib
'tooltip' => 'tip',
'status_kurzbz' => $row->status_kurzbz,
'ort_kurzbz' => [],
+ 'location' => null,
'lehrform' => isset($row->lehrform_kurzbz) ? $row->lehrform_kurzbz : '',
'lehrfach' => isset($row->lehrfach_kurzbz) ? $row->lehrfach_kurzbz : '',
'lehrfach_bez' => isset($row->lehrfach_bezeichnung) ? $row->lehrfach_bezeichnung : '',
'farbe' => isset($row->farbe) ? $row->farbe : '',
'lehrveranstaltung_id' => $row->lehrveranstaltung_id,
+ 'verplante_stunden' => $row->verplante_stunden,
'organisationseinheit' => isset($row->oe_kurzbz) ? $row->oe_kurzbz : '',
'kalender_id' => $id,
'lehreinheit_id' => [],
@@ -185,7 +271,9 @@ class KalenderLib
'beschreibung' => isset($row->beschreibung) ? $row->beschreibung : '',
'topic' => [],
'collisions' => false,
- 'has_assigned_resources' => isset($row->has_assigned_resources) ? $row->has_assigned_resources : false,
+ 'has_assigned_resources' => !empty($parsedResources),
+ 'resources' => is_array($parsedResources) ? $parsedResources : [],
+ 'tags' => isset($row->tags) ? $row->tags : [],
];
}
@@ -195,6 +283,9 @@ class KalenderLib
if (isset($row->ort_kurzbz) && $row->ort_kurzbz !== '' && !in_array($row->ort_kurzbz, $events[$id]->ort_kurzbz))
$events[$id]->ort_kurzbz[] = $row->ort_kurzbz;
+ if (isset($row->location) && $row->location !== '' && is_null($events[$id]->location))
+ $events[$id]->location = $row->location;
+
$topic = trim((isset($row->lehrfach_kurzbz) ? $row->lehrfach_kurzbz : '').' '.(isset($row->lehrform_kurzbz) ? $row->lehrform_kurzbz : ''));
if ($topic !== '' && !in_array($topic, $events[$id]->topic))
$events[$id]->topic[] = $topic;
@@ -301,8 +392,6 @@ class KalenderLib
return $this->_mapEvents($data);
}
-
-
public function getForRaumvorschlag($start_date, $end_date, $lektor_uids = [], $gruppen_kurzbz = [], $lehrverband_gruppen = [])
{
$start_date = date('Y-m-d', strtotime($start_date));
@@ -328,6 +417,7 @@ class KalenderLib
foreach ($lehrverband_gruppen as $lvg)
{
+ $lvg = (array) $lvg;
$this->_ci->KalenderModel->db->or_group_start();
$this->_ci->KalenderModel->db->where('tbl_lehreinheitgruppe.studiengang_kz', $lvg['studiengang_kz']);
$this->_ci->KalenderModel->db->where('tbl_lehreinheitgruppe.semester', $lvg['semester']);
@@ -474,7 +564,7 @@ class KalenderLib
{
$this->_getBasePlan($start_date, $end_date);
- $this->_ci->KalenderModel->db->where('status_kurzbz', 'live');
+ $this->_ci->KalenderModel->db->where_in('status_kurzbz', array('live', 'to_delete_live'));
$data = $this->_ci->KalenderModel->load();
@@ -606,6 +696,17 @@ class KalenderLib
}
+ private function _checkCollisionForLehreinheit($start_date, $end_date, $lehreinheit_id, $ort_kurzbz)
+ {
+ $checkerData = new stdClass();
+ $checkerData->von = $start_date;
+ $checkerData->bis = $end_date;
+ $checkerData->lehreinheit_id = $lehreinheit_id;
+ $checkerData->ort_kurzbz = $ort_kurzbz;
+
+ return $this->_ci->collisionchecker->run($checkerData);
+ }
+
public function addToKalenderEvent($target_kalender_id, $lehreinheit_id)
{
$entryResult = $this->_loadKalenderEntry($target_kalender_id);
@@ -613,7 +714,15 @@ class KalenderLib
$kalender_entry = getData($entryResult);
- if (in_array($kalender_entry->status_kurzbz, array('todelete', 'deleted')))
+ if (!$this->_checkPermission($kalender_entry->lehreinheit_id))
+ {
+ return error([
+ 'message' => $this->_ci->phraseslib->t('ui', 'keineBerechtigung'),
+ 'errorCode' => 'not_allowed'
+ ]);
+ }
+
+ if (in_array($kalender_entry->status_kurzbz, array('to_delete', 'to_delete_live', 'to_delete_preview', 'deleted')))
{
return error([
'message' => $this->_ci->phraseslib->t('ui', 'entry_not_editable'),
@@ -656,7 +765,15 @@ class KalenderLib
$kalender_entry = getData($entryResult);
- if (in_array($kalender_entry->status_kurzbz, array('todelete', 'deleted')))
+ if (!$this->_checkPermission($kalender_entry->lehreinheit_id))
+ {
+ return error([
+ 'message' => $this->_ci->phraseslib->t('ui', 'keineBerechtigung'),
+ 'errorCode' => 'not_allowed'
+ ]);
+ }
+
+ if (in_array($kalender_entry->status_kurzbz, array('to_delete', 'to_delete_live', 'to_delete_preview', 'deleted')))
{
return error([
'message' => $this->_ci->phraseslib->t('ui', 'entry_not_editable'),
@@ -748,16 +865,6 @@ class KalenderLib
return ['kalender_id' => $kalender_id, 'result' => $ortresult];
}
- $entryResult = $this->_loadKalenderEntry($kalender_id);
- if (isError($entryResult))
- return ['kalender_id' => $kalender_id, 'result' => $entryResult];
-
- $kalender_entry = getData($entryResult);
- $errors = $this->_ci->collisionchecker->run($kalender_entry);
-
- if (!empty($errors))
- return ['kalender_id' => $kalender_id, 'result' => error($errors)];
-
return ['kalender_id' => $kalender_id, 'result' => $kalenderlehreinheitresult];
}
@@ -822,8 +929,6 @@ class KalenderLib
'false'
);
- $this->_ci->KalenderModel->db->trans_start();
-
while ($rest > 0 && new DateTime($current_date) < $ende)
{
$max_skip = 52;
@@ -864,18 +969,9 @@ class KalenderLib
$start_str = $current_date . ' ' . $start_time;
$end_str = $current_date . ' ' . $end_time;
- $insert = $this->_insertKalenderEventRaw($start_str, $end_str, $lehreinheit_id, $ort_kurzbz);
- $kalender_id = $insert['kalender_id'];
- $insert_result = $insert['result'];
+ $collisions = $this->_checkCollisionForLehreinheit($start_str, $end_str, $lehreinheit_id, $ort_kurzbz);
- $collisions = isError($insert_result) ? getError($insert_result) : [];
-
- $raum_vorschlaege = [];
- if ($kalender_id)
- {
- $vorschlaege = $this->_ci->raumvorschlaglib->getVorschlaege($kalender_id);
- $raum_vorschlaege = is_array($vorschlaege) ? $vorschlaege : [];
- }
+ $raum_vorschlaege = $this->_ci->raumvorschlaglib->getVorschlaegeByLehreinheit($lehreinheit_id, $start_str, $end_str);
$plan[] = [
'datum' => $current_date,
@@ -884,14 +980,13 @@ class KalenderLib
'block' => $current_block,
'ort_kurzbz' => $ort_kurzbz,
'collisions' => $collisions,
- 'raum_vorschlaege' => $raum_vorschlaege,
+ 'raum_vorschlaege' => is_array($raum_vorschlaege) ? $raum_vorschlaege : [],
];
$rest -= $current_block;
$current_date = $this->_addWeeks($current_date, $wochenrythmus);
}
- $this->_ci->KalenderModel->db->trans_rollback();
$this->_ci->VariableModel->setVariable(
getAuthUID(),
@@ -954,6 +1049,49 @@ class KalenderLib
public function addKalenderEvent($start_date, $end_date, $lehreinheit_id, $ort_kurzbz)
{
+ if (!$this->_checkPermission((array)$lehreinheit_id))
+ {
+ return error([
+ 'message' => $this->_ci->phraseslib->t('ui', 'keineBerechtigung'),
+ 'errorCode' => 'not_allowed'
+ ]);
+ }
+
+ if (isEmptyString($ort_kurzbz))
+ {
+ $errors = $this->_checkCollisionForLehreinheit($start_date, $end_date, $lehreinheit_id, $ort_kurzbz);
+
+ if (!empty($errors))
+ return error($errors);
+
+ $raum_vorschlaege = $this->_ci->raumvorschlaglib->getVorschlaegeByLehreinheit($lehreinheit_id, $start_date, $end_date);
+
+ if (($this->_ci->variablelib->getVar('dialog_room_planning') ?? 'false') === 'true')
+ {
+ return success([
+ 'needs_room_selection' => true,
+ 'raum_vorschlaege' => is_array($raum_vorschlaege) ? $raum_vorschlaege : [],
+ ]);
+ }
+
+ 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'
+ ]);
+ }
+ }
+
+ $errors = $this->_checkCollisionForLehreinheit($start_date, $end_date, $lehreinheit_id, $ort_kurzbz);
+
+ if (!empty($errors))
+ return error($errors);
+
$this->_ci->KalenderModel->db->trans_start();
$insert = $this->_insertKalenderEventRaw($start_date, $end_date, $lehreinheit_id, $ort_kurzbz);
@@ -969,14 +1107,14 @@ class KalenderLib
return $result;
}
- public function addReservierung($titel, $beschreibung, $ort_kurzbz, $start_date, $end_date, $teilnehmer, $specialFinalGroups, $specialGroups, $groups)
+ public function addReservierung($titel, $beschreibung, $orte, $start_date, $end_date, $teilnehmer, $specialFinalGroups, $specialGroups, $groups)
{
- if (!is_null($ort_kurzbz))
+ if (!is_null($orte) && !isEmptyArray($orte['ort_kurzbz']))
{
$this->_ci->KalenderModel->addSelect('1');
$this->_ci->KalenderModel->addJoin('lehre.tbl_kalender_ort', 'kalender_id');
- $this->_ci->KalenderModel->db->where_in('ort_kurzbz', $ort_kurzbz);
+ $this->_ci->KalenderModel->db->where_in('ort_kurzbz', $orte['ort_kurzbz']);
$reservierung_vorhanden = $this->_ci->KalenderModel->load(array (
'von <=' => $end_date,
'bis >=' => $start_date,
@@ -1037,19 +1175,25 @@ class KalenderLib
}
}
- /* foreach ($specialGroups as $group)
+ if (isSuccess($kalendereventresult) && !isEmptyArray($orte['ort_kurzbz']))
{
- $this->_addSpecialGroupToEvent($kalender_id, $group['gruppe_kurzbz'], $group['rolle']);
+ $ortresult = $this->_addKalenderOrt($kalender_id, $orte['ort_kurzbz']);
+ if (isError($ortresult))
+ {
+ $this->_ci->KalenderModel->db->trans_rollback();
+ return $ortresult;
+ }
}
- foreach ($groups as $group)
+ if (isSuccess($kalendereventresult) && !is_null($orte['location']))
{
- $this->_addGroupToEvent($kalender_id, $group['stg_kz'], $group['semester'], $group['verband'], $group['gruppe'], $group['rolle']);
- }*/
+ $ortresult = $this->_ci->KalenderOrtModel->insert(
+ array (
+ 'kalender_id' => $kalender_id,
+ 'location' => $orte['location']
+ )
+ );
- if (isSuccess($kalendereventresult) && !is_null($ort_kurzbz))
- {
- $ortresult = $this->_addKalenderOrt($kalender_id, $ort_kurzbz);
if (isError($ortresult))
{
$this->_ci->KalenderModel->db->trans_rollback();
@@ -1249,26 +1393,39 @@ class KalenderLib
return success();
}
- public function updateKalenderEvent($kalender_id, $ort_kurzbz = null, $start_time = null, $end_time = null)
+ public function updateKalenderEvent($kalender_id, $orte = null, $start_time = null, $end_time = null)
{
$entryResult = $this->_loadKalenderEntry($kalender_id);
if (isError($entryResult)) return $entryResult;
$kalender_entry = getData($entryResult);
+ if (!$this->_checkPermission($kalender_entry->lehreinheit_id))
+ {
+ return error([
+ 'message' => $this->_ci->phraseslib->t('ui', 'keineBerechtigung'),
+ 'errorCode' => 'not_allowed'
+ ]);
+ }
+
$calendarResources = $this->_ci->BetriebsmittelKalenderModel->loadWhere(['eindeutige_kalender_gruppen_id' => $kalender_entry->eindeutige_kalender_gruppen_id]);
if (isError($calendarResources)) return $calendarResources;
-
+
$calendarResourcesItems = getData($calendarResources);
$calendarResourcesIDs = [];
- if (is_array($calendarResourcesItems)) {
+ if (is_array($calendarResourcesItems)) {
$calendarResourcesIDs = array_map(function($resource) {
return $resource->betriebsmittel_id;
}, getData($calendarResources));
- }
+ }
$kalender_entry->betriebsmittel_ids = $calendarResourcesIDs;
- $kalender_entry->ort_kurzbz = !is_null($ort_kurzbz) ? (array) $ort_kurzbz : $kalender_entry->ort_kurzbz;
+
+ if (!is_null($orte))
+ {
+ $kalender_entry->ort_kurbz = !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;
$kalender_entry->bis = $end_time ?? $kalender_entry->bis;
@@ -1278,9 +1435,9 @@ class KalenderLib
$result = $kalender_entry;
- if (!is_null($ort_kurzbz))
+ if (!is_null($orte))
{
- $result = $this->updateOrt($kalender_id, $ort_kurzbz);
+ $result = $this->updateOrt($kalender_id, $orte);
if (isError($result)) return $result;
if (hasData($result))
@@ -1307,7 +1464,7 @@ class KalenderLib
if ($kalender_entry->typ === 'lehreinheit')
{
- if (in_array($kalender_entry->status_kurzbz, array('todelete', 'deleted')))
+ if (in_array($kalender_entry->status_kurzbz, array('to_delete', 'to_delete_live', 'to_delete_preview', 'deleted')))
{
return error([
'message' => $this->_ci->phraseslib->t('ui', 'entry_not_editable'),
@@ -1363,9 +1520,11 @@ class KalenderLib
return success($result);
}
- public function updateOrt($kalender_id, $ort_kurzbz_array)
+ public function updateOrt($kalender_id, $orte_array)
{
- $ort_kurzbz_array = (array)$ort_kurzbz_array;
+ $ort_kurzbz_neu = (array)($orte_array['ort_kurzbz'] ?? []);
+ $location_neu = $orte_array['location'] ?? null;
+
$entryResult = $this->_loadKalenderEntry($kalender_id);
if (isError($entryResult)) return $entryResult;
@@ -1373,7 +1532,15 @@ class KalenderLib
if ($kalender_entry->typ === 'lehreinheit')
{
- if (in_array($kalender_entry->status_kurzbz, array('todelete', 'deleted')))
+ if (!$this->_checkPermission($kalender_entry->lehreinheit_id))
+ {
+ return error([
+ 'message' => $this->_ci->phraseslib->t('ui', 'keineBerechtigung'),
+ 'errorCode' => 'not_allowed'
+ ]);
+ }
+
+ if (in_array($kalender_entry->status_kurzbz, array('to_delete', 'to_delete_live', 'to_delete_preview', 'deleted')))
{
return error([
'message' => $this->_ci->phraseslib->t('ui', 'entry_not_editable'),
@@ -1382,14 +1549,30 @@ class KalenderLib
}
if (in_array($kalender_entry->status_kurzbz, array('live', 'preview')))
- return $this->_createHistoryEntry($kalender_entry, null, null, $ort_kurzbz_array);
+ return $this->_createHistoryEntry($kalender_entry, null, null, $orte_array);
}
$bestehende = $this->_ci->KalenderOrtModel->load(array('kalender_id' => $kalender_id));
- $alte_orte = hasData($bestehende) ? array_column(getData($bestehende), 'ort_kurzbz') : [];
+ $alte_orte = hasData($bestehende) ? getData($bestehende) : [];
- $zu_loeschen = array_diff($alte_orte, $ort_kurzbz_array);
- $neu_anzulegen = array_diff($ort_kurzbz_array, $alte_orte);
+
+ $alte_orte_kurzbz = array_values(array_filter(array_map(function ($ort) {
+ return $ort->ort_kurzbz;
+ }, $alte_orte)));
+
+ $alte_location = null;
+
+ foreach ($alte_orte as $ort)
+ {
+ if (!is_null($ort->location))
+ {
+ $alte_location = $ort->location;
+ break;
+ }
+ }
+
+ $zu_loeschen = array_diff($alte_orte_kurzbz, $ort_kurzbz_neu);
+ $neu_anzulegen = array_diff($ort_kurzbz_neu, $alte_orte_kurzbz);
if (!empty($zu_loeschen))
{
@@ -1398,12 +1581,34 @@ class KalenderLib
$this->_ci->KalenderOrtModel->db->delete('lehre.tbl_kalender_ort');
}
- foreach ($neu_anzulegen as $ort_kurzbz)
+ foreach ($neu_anzulegen as $ort)
{
- $result = $this->_addKalenderOrt($kalender_id, $ort_kurzbz);
+ $result = $this->_addKalenderOrt($kalender_id, $ort);
if (isError($result)) return $result;
}
+ if ($alte_location !== $location_neu)
+ {
+ if (!is_null($alte_location))
+ {
+ $this->_ci->KalenderOrtModel->db->where('kalender_id', $kalender_id);
+ $this->_ci->KalenderOrtModel->db->where('ort_kurzbz IS NULL', null, false);
+ $this->_ci->KalenderOrtModel->db->delete('lehre.tbl_kalender_ort');
+ }
+
+ if (!isEmptyString($location_neu))
+ {
+ $ortresult = $this->_ci->KalenderOrtModel->insert(
+ array (
+ 'kalender_id' => $kalender_id,
+ 'location' => $location_neu
+ )
+ );
+
+ if (isError($ortresult)) return $ortresult;
+ }
+ }
+
return success();
}
@@ -1414,6 +1619,14 @@ class KalenderLib
$kalender_entry = getData($entryResult);
+ if (!$this->_checkPermission($kalender_entry->lehreinheit_id))
+ {
+ return error([
+ 'message' => $this->_ci->phraseslib->t('ui', 'keineBerechtigung'),
+ 'errorCode' => 'not_allowed'
+ ]);
+ }
+
$allowed = array(
'planning' => array('sync_preview', 'sync_live'),
'sync_preview' => array('sync_live'),
@@ -1478,22 +1691,21 @@ class KalenderLib
if (!is_null($ort_kurzbz_array))
{
- $new_orte = (array) $ort_kurzbz_array;
- $new_locations = array_fill(0, count($new_orte), null);
+ $new_orte = (array) ($ort_kurzbz_array['ort_kurzbz'] ?? []);
+ $new_locations = $ort_kurzbz_array['location'] ?? null;
}
else
{
$new_orte = (array) $kalender_entry->ort_kurzbz;
- $new_locations = (array) $kalender_entry->location;
+ $new_locations = $kalender_entry->location;
}
- foreach ($new_orte as $index => $ort)
+ foreach ($new_orte as $ort)
{
$ortresult = $this->_ci->KalenderOrtModel->insert(
array(
'kalender_id' => $new_kalender_id,
- 'ort_kurzbz' => $ort,
- 'location' => $new_locations[$index] ?? null
+ 'ort_kurzbz' => $ort
)
);
@@ -1501,6 +1713,19 @@ class KalenderLib
return $ortresult;
}
+ if (!is_null($new_locations))
+ {
+ $locresult = $this->_ci->KalenderOrtModel->insert(
+ array(
+ 'kalender_id' => $new_kalender_id,
+ 'location' => $new_locations
+ )
+ );
+
+ if (!isSuccess($locresult))
+ return $locresult;
+ }
+
return success($new_kalender_id);
}
private function _loadKalenderEntry($kalender_id)
@@ -1523,8 +1748,8 @@ class KalenderLib
$this->_ci->KalenderModel->addJoin(
'(SELECT kalender_id,
- array_agg(ort_kurzbz ORDER BY ort_kurzbz) AS ort_kurzbz,
- array_agg(location ORDER BY ort_kurzbz) AS location
+ array_agg(ort_kurzbz ORDER BY ort_kurzbz) FILTER(WHERE ort_kurzbz is NOT NULL) AS ort_kurzbz,
+ MAX(location) AS location
FROM lehre.tbl_kalender_ort
GROUP BY kalender_id) orte',
'tbl_kalender.kalender_id = orte.kalender_id',
@@ -1540,7 +1765,7 @@ class KalenderLib
$entry = getData($result)[0];
$entry->lehreinheit_id = $entry->lehreinheit_id ?? [];
$entry->ort_kurzbz = $entry->ort_kurzbz ?? [];
- $entry->location = $entry->location ?? [];
+ $entry->location = $entry->location ?? null;
return success($entry);
}
@@ -1595,44 +1820,76 @@ class KalenderLib
private function _deleteTypLehreinheit($entry)
{
- $result_lehreinheit = $this->_ci->KalenderLehreinheitModel->loadWhere(array('kalender_id' => $entry->kalender_id));
- if (isError($result_lehreinheit)) return $result_lehreinheit;
- $has_lehreinheit = hasData($result_lehreinheit) ? getData($result_lehreinheit)[0] : false;
+ $entryResult = $this->_loadKalenderEntry($entry->kalender_id);
- $result_ort = $this->_ci->KalenderOrtModel->loadWhere(array('kalender_id' => $entry->kalender_id));
- if (isError($result_ort)) return $result_ort;
- $has_ort = hasData($result_ort) ? getData($result_ort)[0] : false;
- if ($entry->status_kurzbz === 'planning')
+ if (isError($entryResult)) return $entryResult;
+
+ $kalender_entry = getData($entryResult);
+
+ if (!$this->_checkPermission($kalender_entry->lehreinheit_id))
{
- if ($has_ort)
- $this->_deleteOrtEntry($has_ort);
-
- if ($has_lehreinheit)
- $this->_deleteLehreinheitEntry($has_lehreinheit);
-
- $this->_ci->KalenderModel->delete(array('tbl_kalender.kalender_id' => $entry->kalender_id));
+ return error([
+ 'message' => $this->_ci->phraseslib->t('ui', 'keineBerechtigung'),
+ 'errorCode' => 'not_allowed'
+ ]);
}
- else if ($entry->status_kurzbz === 'sync_preview' || $entry->status_kurzbz === 'sync_live')
+
+
+ if ($kalender_entry->status_kurzbz === 'sync_preview' || $kalender_entry->status_kurzbz === 'sync_live' || $kalender_entry->status_kurzbz === 'planning')
{
- $history = $this->getHistory($entry->kalender_id);
- $history = hasData($history) ? getData($history) : false;
+ $history = $this->getHistory($kalender_entry->kalender_id, false, false);
- if ($has_ort)
- $result = $this->_deleteOrtEntry($has_ort);
+ $history = hasData($history) && getData($history);
- if ($has_lehreinheit)
- $result = $this->_deleteLehreinheitEntry($has_lehreinheit);
+ if ($history)
+ {
+ $result = $this->_ci->KalenderModel->update(
+ array('kalender_id' => $kalender_entry->kalender_id),
+ array('status_kurzbz' => 'to_delete','status_kurzbz_max' => 'planning')
+ );
- $this->_ci->KalenderModel->delete(array('tbl_kalender.kalender_id' => $entry->kalender_id));
+ if (isError($result))
+ return error($result);
+ }
+ else
+ {
+
+ if (!isEmptyArray($kalender_entry->ort_kurzbz))
+ {
+ $result = $this->_deleteOrtEntry($kalender_entry);
+ if (isError($result))
+ return error($result);
+ }
+
+
+ if (!isEmptyArray($kalender_entry->lehreinheit_id))
+ {
+ $result = $this->_deleteLehreinheitEntry($kalender_entry);
+ if (isError($result))
+ return error($result);
+ }
+
+ $result = $this->_ci->KalenderModel->delete(array('tbl_kalender.kalender_id' => $kalender_entry->kalender_id));
+
+ if (isError($result))
+ return error($result);
+ }
}
- else if ($entry->status_kurzbz === 'preview' || $entry->status_kurzbz === 'live')
+ else if ($kalender_entry->status_kurzbz === 'preview' || $kalender_entry->status_kurzbz === 'live')
{
- //TODO (david) überprüfen ob sinnvoll, verschwindet direkt in den ansichten (student, lecturer)
+ if ($kalender_entry->status_kurzbz === 'preview')
+ $new_status = 'to_delete_preview';
+ else
+ $new_status = 'to_delete_live';
+
$result = $this->_ci->KalenderModel->update(
- array('kalender_id' => $entry->kalender_id),
- array('status_kurzbz' => 'todelete')
+ array('kalender_id' => $kalender_entry->kalender_id),
+ array('status_kurzbz' => $new_status)
);
+
+ if (isError($result))
+ return error($result);
}
return success();
@@ -1657,11 +1914,11 @@ class KalenderLib
return $this->_ci->KalenderEventModel->delete(array('kalender_id' => $entry->kalender_id));
}
- public function getHistory($kalender_id, $only_previous = false)
+ public function getHistory($kalender_id, $only_previous = false, $include_self = true)
{
$dbModel = new DB_Model();
- $history_entries = $dbModel->execReadOnlyQuery("
- WITH RECURSIVE
+ $history_sql ="
+ WITH RECURSIVE
vorgaenger(kalender_id, vorgaenger_kalender_id) AS (
SELECT kalender_id, vorgaenger_kalender_id
FROM lehre.tbl_kalender
@@ -1700,97 +1957,55 @@ class KalenderLib
SELECT kalender_id FROM nachfolger
) combined
JOIN lehre.tbl_kalender USING(kalender_id)
- LEFT JOIN orte USING(kalender_id)
- ORDER BY kalender_id DESC
-
- " . ($only_previous ? ' LIMIT 1' : ''), array($kalender_id, $kalender_id));
+ LEFT JOIN orte USING(kalender_id)";
- return $history_entries;
+ $params = array($kalender_id, $kalender_id);
+
+ if (!$include_self)
+ {
+ $history_sql .= " WHERE kalender_id != ?";
+ $params[] = $kalender_id;
+ }
+
+ $history_sql .= " ORDER BY kalender_id DESC" . ($only_previous ? ' LIMIT 1' : '');
+
+ return $dbModel->execReadOnlyQuery($history_sql, $params);
}
- //TODO in eine eigen Lib?
- public function sync()
+ private function _checkPermission($lehreinheit_ids)
{
- $this->_ci->load->model('ressource/Kalender_model', 'KalenderModel');
+ if (isEmptyArray($lehreinheit_ids))
+ return true;
- $this->_ci->KalenderModel->addSelect('tbl_kalender.*, tbl_kalender_ort.ort_kurzbz, tbl_kalender_ort.location');
- $this->_ci->KalenderModel->addJoin('lehre.tbl_kalender_ort', 'tbl_kalender.kalender_id = tbl_kalender_ort.kalender_id', 'LEFT');
- $this->_ci->KalenderModel->db->where_in('status_kurzbz', array('sync_live', 'sync_preview', 'todelete', 'planning', 'preview'));
- $this->_ci->KalenderModel->addOrder('tbl_kalender.kalender_id', 'DESC');
- $to_update = $this->_ci->KalenderModel->load();
+ $allowed = false;
- if (!hasData($to_update))
- return success();
+ $this->_ci->LehreinheitModel->addSelect('studiengang_kz');
+ $this->_ci->LehreinheitModel->addJoin('lehre.tbl_lehrveranstaltung', 'tbl_lehreinheit.lehrveranstaltung_id = tbl_lehrveranstaltung.lehrveranstaltung_id');
+ $this->_ci->LehreinheitModel->db->where_in('lehreinheit_id', $lehreinheit_ids);
- $mail_infos = [];
+ $lehreinheit = $this->_ci->LehreinheitModel->load();
+ if (!hasData($lehreinheit))
+ return error('Lehreinheit nicht gefunden');
- foreach (getData($to_update) as $entry)
+ $studiengaenge = array_unique(array_column(getData($lehreinheit), 'studiengang_kz'));
+
+ foreach ($studiengaenge as $studiengang_kz)
{
- if ($entry->status_kurzbz === 'todelete')
+ if ($this->_ci->permissionlib->isBerechtigt('lehre/lvplan', 'suid', $studiengang_kz))
{
- $this->_ci->KalenderModel->update(array('kalender_id' => $entry->kalender_id), array('status_kurzbz' => 'deleted'));
- $this->_archiveVorgaenger($entry->vorgaenger_kalender_id, false);
- $mail_infos[] = array('entry' => $entry, 'new_status' => 'deleted', 'notify' => array('lektor', 'student'));
- }
-
- if ($entry->status_kurzbz === 'sync_preview')
- {
- $this->_ci->KalenderModel->update(array('kalender_id' => $entry->kalender_id), array('status_kurzbz' => 'preview'));
- $this->_archiveVorgaenger($entry->vorgaenger_kalender_id, true);
- $mail_infos[] = array('entry' => $entry, 'new_status' => 'preview', 'notify' => array('lektor'));
- }
-
- if ($entry->status_kurzbz === 'sync_live' || $entry->status_kurzbz === 'planning')
- {
- $this->_ci->KalenderModel->update(array('kalender_id' => $entry->kalender_id), array('status_kurzbz' => 'live'));
- $this->_archiveVorgaenger($entry->vorgaenger_kalender_id, false);
- $mail_infos[] = array('entry' => $entry, 'new_status' => 'live', 'notify' => array('lektor', 'student'));
- }
-
- if ($entry->status_kurzbz === 'preview')
- {
- $current = getData($this->_ci->KalenderModel->load(array('kalender_id' => $entry->kalender_id)))[0];
- if ($current->status_kurzbz === 'archived')
- continue;
-
- $this->_ci->KalenderModel->update(array('kalender_id' => $entry->kalender_id), array('status_kurzbz' => 'live'));
- $this->_archiveVorgaenger($entry->vorgaenger_kalender_id, false);
- $mail_infos[] = array('entry' => $entry, 'new_status' => 'live', 'notify' => array('lektor', 'student'));
+ $allowed = true;
+ break;
}
}
- $this->_ci->load->library('KalenderNotificationLib');
- $this->_ci->kalendernotificationlib->sendMails($mail_infos);
- return success();
-
+ return $allowed;
}
- private function _archiveVorgaenger($vorgaenger_kalender_id, $stop_at_live = false)
+ private function getLanguageIndex()
{
- if (!$vorgaenger_kalender_id) return;
+ $this->_ci->SpracheModel->addSelect('index');
+ $result = $this->_ci->SpracheModel->loadWhere(array('sprache' => getUserLanguage()));
- $current_id = $vorgaenger_kalender_id;
-
- while ($current_id)
- {
- $vorgaenger = $this->_ci->KalenderModel->load(array('kalender_id' => $current_id));
- if (!hasData($vorgaenger)) return;
-
- $vorgaenger = getData($vorgaenger)[0];
-
- if ($stop_at_live && $vorgaenger->status_kurzbz === 'live')
- return;
-
- if (in_array($vorgaenger->status_kurzbz, array('preview', 'live')))
- {
- $this->_ci->KalenderModel->update(
- array('kalender_id' => $current_id),
- array('status_kurzbz' => 'archived')
- );
- }
-
- $current_id = $vorgaenger->vorgaenger_kalender_id;
- }
+ return hasData($result) ? getData($result)[0]->index : 1;
}
-
}
diff --git a/application/libraries/KalenderNotificationLib.php b/application/libraries/KalenderNotificationLib.php
index d611eb247..acf14bae7 100644
--- a/application/libraries/KalenderNotificationLib.php
+++ b/application/libraries/KalenderNotificationLib.php
@@ -10,7 +10,7 @@ class KalenderNotificationLib
$this->_ci =& get_instance();
$this->_ci->load->library('MailLib');
$this->_ci->load->config('tempus');
-
+ $this->_ci->load->model('ressource/Kalender_model', 'KalenderModel');
}
public function sendMails($mail_infos)
@@ -90,6 +90,7 @@ class KalenderNotificationLib
if (!empty($student_deleted))
$student_entries .= $this->_addToList($student_deleted, 'gelöscht') . '
';
+ //TODO (david) richtigen Empfänger
if (!empty($lektor_entries))
$this->_ci->maillib->send('', 'ma0048@technikum-wien.at', 'Lektor Tempus Update', $lektor_entries);
if (!empty($student_entries))
diff --git a/application/libraries/KalenderSyncLib.php b/application/libraries/KalenderSyncLib.php
new file mode 100644
index 000000000..fce0ecd5d
--- /dev/null
+++ b/application/libraries/KalenderSyncLib.php
@@ -0,0 +1,429 @@
+ array('planning', 'sync_preview'),
+ 'live' => array('planning', 'sync_preview', 'preview', 'sync_live')
+ );
+
+ public function __construct()
+ {
+ $this->_ci =& get_instance();
+
+ $this->_ci->load->model('ressource/Kalender_model', 'KalenderModel');
+ $this->_ci->load->model('ressource/Kalendersyncstatus_model', 'KalendersyncstatusModel');
+ $this->_ci->load->model('organisation/Studiengang_model', 'StudiengangModel');
+ $this->_ci->load->model('organisation/Organisationseinheit_model', 'OrganisationseinheitModel');
+ $this->_ci->load->library('KalenderLib');
+ }
+
+ public function run($studiensemester_kurzbz)
+ {
+ $this->_ci->KalendersyncstatusModel->db->where('studiensemester_kurzbz', $studiensemester_kurzbz);
+
+ $configs = $this->_ci->KalendersyncstatusModel->load();
+
+ if (isError($configs))
+ return $configs;
+
+ if (!hasData($configs))
+ return success([]);
+
+ $results = [];
+ $mail_infos_gesamt = [];
+
+ foreach (getData($configs) as $config)
+ {
+
+ $ziel_status = isEmptyString($config->sync_status_kurzbz) ? 'live' : $config->sync_status_kurzbz;
+
+ $relevante_stati = array_merge($this->status_map[$ziel_status], array('to_delete', 'to_delete_live', 'to_delete_preview'));
+
+ $kalender_ids_result = $this->getKalenderIds(
+ $config->oe_kurzbz,
+ $config->studiensemester_kurzbz,
+ $relevante_stati,
+ $config->ausbildungssemester,
+ $config->studienplan_id,
+ $config->datum_bis
+ );
+
+ if (isError($kalender_ids_result))
+ return $kalender_ids_result;
+
+ $kalender_ids = getData($kalender_ids_result);
+
+ $sync_result = $this->_syncKalenderIds($kalender_ids, $ziel_status);
+
+ if (isError($sync_result))
+ return $sync_result;
+
+ $sync_data = getData($sync_result);
+
+ if ((bool) $config->mail)
+ $mail_infos_gesamt = array_merge($mail_infos_gesamt, $sync_data['mail_infos']);
+
+ $results[] = [
+ 'kalender_syncstatus_id' => $config->kalender_syncstatus_id,
+ 'oe_kurzbz' => $config->oe_kurzbz,
+ 'ausbildungssemester' => $config->ausbildungssemester,
+ 'studienplan_id' => $config->studienplan_id,
+ 'ziel_status' => $ziel_status,
+ 'mail' => (bool) $config->mail,
+ 'synced' => $sync_data['synced']
+ ];
+ }
+
+ if (!empty($mail_infos_gesamt))
+ {
+ $this->_ci->load->library('KalenderNotificationLib');
+ $this->_ci->kalendernotificationlib->sendMails($mail_infos_gesamt);
+ }
+
+ return success($results);
+ }
+
+ public function runManual($oe_kurzbz, $studiensemester_kurzbz, $ausbildungssemester = null, $studienplan_id = null, $mail = true, $datum_bis = null, $ziel_status = 'live')
+ {
+ $relevante_stati = array_merge($this->status_map[$ziel_status], array('to_delete', 'to_delete_live', 'to_delete_preview'));
+
+ $kalender_ids_result = $this->getKalenderIds(
+ $oe_kurzbz,
+ $studiensemester_kurzbz,
+ $relevante_stati,
+ $ausbildungssemester,
+ $studienplan_id,
+ $datum_bis
+ );
+
+ if (isError($kalender_ids_result))
+ return $kalender_ids_result;
+
+ $kalender_ids = getData($kalender_ids_result);
+
+ $sync_result = $this->_syncKalenderIds($kalender_ids, $ziel_status);
+
+ if (isError($sync_result))
+ return $sync_result;
+
+ $sync_data = getData($sync_result);
+
+ if ($mail && !empty($sync_data['mail_infos']))
+ {
+ $this->_ci->load->library('KalenderNotificationLib');
+ $this->_ci->kalendernotificationlib->sendMails($sync_data['mail_infos']);
+ }
+
+ return success(['synced' => $sync_data['synced']]);
+ }
+
+ public function sync()
+ {
+ $mail_infos_gesamt = [];
+ $synced_gesamt = 0;
+
+ $todelete_ids_result = $this->_getManuallySetStatus(array('to_delete', 'to_delete_live', 'to_delete_preview'));
+
+ if (isError($todelete_ids_result))
+ return $todelete_ids_result;
+
+ $todelete_result = $this->_syncKalenderIds(getData($todelete_ids_result), null);
+
+ if (isError($todelete_result))
+ return $todelete_result;
+
+ $todelete_data = getData($todelete_result);
+ $mail_infos_gesamt = array_merge($mail_infos_gesamt, $todelete_data['mail_infos']);
+ $synced_gesamt += $todelete_data['synced'];
+
+ $preview_ids_result = $this->_getManuallySetStatus(array('sync_preview'));
+
+ if (isError($preview_ids_result))
+ return $preview_ids_result;
+
+ $preview_result = $this->_syncKalenderIds(getData($preview_ids_result), 'preview');
+
+ if (isError($preview_result))
+ return $preview_result;
+
+ $preview_data = getData($preview_result);
+ $mail_infos_gesamt = array_merge($mail_infos_gesamt, $preview_data['mail_infos']);
+ $synced_gesamt += $preview_data['synced'];
+
+ $live_ids_result = $this->_getManuallySetStatus(array('sync_live'));
+
+ if (isError($live_ids_result))
+ return $live_ids_result;
+
+ $live_result = $this->_syncKalenderIds(getData($live_ids_result), 'live');
+
+ if (isError($live_result))
+ return $live_result;
+
+ $live_data = getData($live_result);
+ $mail_infos_gesamt = array_merge($mail_infos_gesamt, $live_data['mail_infos']);
+ $synced_gesamt += $live_data['synced'];
+
+ if (!empty($mail_infos_gesamt))
+ {
+ $this->_ci->load->library('KalenderNotificationLib');
+ $this->_ci->kalendernotificationlib->sendMails($mail_infos_gesamt);
+ }
+
+ return success(['synced' => $synced_gesamt]);
+ }
+
+ private function _getManuallySetStatus($status_kurzbz_list)
+ {
+ if (empty($status_kurzbz_list))
+ return success([]);
+
+ $this->_ci->KalenderModel->addSelect('tbl_kalender.kalender_id');
+ $this->_ci->KalenderModel->db->where_in('status_kurzbz', $status_kurzbz_list);
+ $result = $this->_ci->KalenderModel->load();
+
+ if (isError($result))
+ return $result;
+
+ if (!hasData($result))
+ return success([]);
+
+ return success(array_column(getData($result), 'kalender_id'));
+ }
+
+ public function getKalenderIds($oe_kurzbz, $studiensemester_kurzbz, $status_kurzbz_list, $ausbildungssemester = null, $studienplan_id = null, $datum_bis = null)
+ {
+ if (empty($status_kurzbz_list))
+ return success([]);
+
+ $studiengang_result = $this->_getStudiengang($oe_kurzbz);
+
+ if (isError($studiengang_result))
+ return $studiengang_result;
+
+ $studiengang_kz = getData($studiengang_result);
+
+ if (!hasData($studiengang_result))
+ {
+ $organisationseinheiten = $this->_ci->OrganisationseinheitModel->getChilds($oe_kurzbz);
+ $oe_kurzbz = hasData($organisationseinheiten) ? array_column(getData($organisationseinheiten), 'oe_kurzbz') : [];
+ }
+
+ $this->_ci->KalenderModel->addDistinct('tbl_kalender.kalender_id');
+ $this->_ci->KalenderModel->addSelect('tbl_kalender.kalender_id');
+ $this->_ci->KalenderModel->addJoin('lehre.tbl_kalender_lehreinheit', 'tbl_kalender.kalender_id = tbl_kalender_lehreinheit.kalender_id');
+ $this->_ci->KalenderModel->addJoin('lehre.tbl_lehreinheit', 'tbl_kalender_lehreinheit.lehreinheit_id = tbl_lehreinheit.lehreinheit_id');
+ $this->_ci->KalenderModel->addJoin('lehre.tbl_lehrveranstaltung', 'tbl_lehreinheit.lehrveranstaltung_id = tbl_lehrveranstaltung.lehrveranstaltung_id');
+
+ if (!is_null($studiengang_kz))
+ {
+ $this->_ci->KalenderModel->db->where('tbl_lehrveranstaltung.studiengang_kz', $studiengang_kz);
+ }
+ else
+ {
+ $this->_ci->KalenderModel->db->where_in('tbl_lehrveranstaltung.oe_kurzbz', $oe_kurzbz);
+ }
+
+ $this->_ci->KalenderModel->db->where('tbl_lehreinheit.studiensemester_kurzbz', $studiensemester_kurzbz);
+ $this->_ci->KalenderModel->db->where_in('tbl_kalender.status_kurzbz', $status_kurzbz_list);
+
+ $this->_ci->KalenderModel->db->where('NOT EXISTS (
+ SELECT 1 FROM lehre.tbl_kalender nachfolger
+ WHERE nachfolger.vorgaenger_kalender_id = tbl_kalender.kalender_id)', null, false);
+
+ if (!is_null($studienplan_id))
+ {
+ $this->_ci->KalenderModel->addJoin('lehre.tbl_studienplan_lehrveranstaltung', 'tbl_lehrveranstaltung.lehrveranstaltung_id = tbl_studienplan_lehrveranstaltung.lehrveranstaltung_id');
+ $this->_ci->KalenderModel->db->where('tbl_studienplan_lehrveranstaltung.studienplan_id', $studienplan_id);
+ }
+
+ if (!is_null($ausbildungssemester))
+ $this->_ci->KalenderModel->db->where('tbl_lehrveranstaltung.semester', $ausbildungssemester);
+
+ if (!is_null($datum_bis))
+ {
+ $end_date = date('Y-m-d', strtotime($datum_bis . ' +1 day'));
+ $this->_ci->KalenderModel->db->where('tbl_kalender.von <', $end_date);
+ }
+
+ $result = $this->_ci->KalenderModel->load();
+
+ if (isError($result))
+ return $result;
+
+ if (!hasData($result))
+ return success([]);
+
+ return success(array_column(getData($result), 'kalender_id'));
+ }
+
+
+ private function _archiveVorgaenger($vorgaenger_kalender_id, $stop_at_live = false)
+ {
+ $return_array = array();
+ if (!$vorgaenger_kalender_id) return success($return_array);
+
+ $current_id = $vorgaenger_kalender_id;
+
+ while ($current_id)
+ {
+ $vorgaenger = $this->_ci->KalenderModel->load(array('kalender_id' => $current_id));
+ if (!hasData($vorgaenger)) return success($return_array);
+
+ $vorgaenger = getData($vorgaenger)[0];
+
+ if ($stop_at_live && $vorgaenger->status_kurzbz === 'live')
+ return success($return_array);
+
+ if (in_array($vorgaenger->status_kurzbz, array('preview', 'live')))
+ {
+ $this->_ci->KalenderModel->update(
+ array('kalender_id' => $current_id),
+ array('status_kurzbz' => 'archived', 'status_kurzbz_max' => $vorgaenger->status_kurzbz)
+ );
+
+ $return_array[] = $vorgaenger;
+ }
+
+ $current_id = $vorgaenger->vorgaenger_kalender_id;
+ }
+ return success($return_array);
+ }
+
+ private function _syncKalenderIds($kalender_ids, $ziel_status)
+ {
+ if (empty($kalender_ids))
+ return success(['synced' => 0, 'mail_infos' => []]);
+
+ $this->_ci->KalenderModel->addSelect('tbl_kalender.*, tbl_kalender_ort.ort_kurzbz, tbl_kalender_ort.location');
+ $this->_ci->KalenderModel->addJoin('lehre.tbl_kalender_ort', 'tbl_kalender.kalender_id = tbl_kalender_ort.kalender_id', 'LEFT');
+ $this->_ci->KalenderModel->db->where_in('tbl_kalender.kalender_id', $kalender_ids);
+ $this->_ci->KalenderModel->addOrder('tbl_kalender.kalender_id', 'DESC');
+ $to_update = $this->_ci->KalenderModel->load();
+
+ if (isError($to_update))
+ return $to_update;
+
+ if (!hasData($to_update))
+ return success(['synced' => 0, 'mail_infos' => []]);
+
+ $mail_infos = [];
+ $synced = 0;
+
+ foreach (getData($to_update) as $entry)
+ {
+ if (in_array($entry->status_kurzbz, array('to_delete', 'to_delete_live', 'to_delete_preview')))
+ {
+ $notify = array();
+ if ($entry->status_kurzbz === 'to_delete_live')
+ {
+ $status_max = 'live';
+ $notify = array('lektor', 'student');
+ }
+ else if ($entry->status_kurzbz === 'to_delete_preview')
+ {
+ $status_max = 'preview';
+ $notify = array('lektor');
+ }
+ else if ($entry->status_kurzbz === 'to_delete')
+ {
+ $status_max = 'planning';
+ }
+
+ $this->_ci->KalenderModel->update(array('kalender_id' => $entry->kalender_id), array('status_kurzbz' => 'deleted', 'status_kurzbz_max' => $status_max));
+ $mail_infos[] = array('entry' => $entry, 'new_status' => 'deleted', 'notify' => $notify);
+
+ $vorgaenger_array = $this->_archiveVorgaenger($entry->vorgaenger_kalender_id);
+
+
+
+ if (hasData($vorgaenger_array))
+ {
+ foreach(getData($vorgaenger_array) as $vorgaenger)
+ {
+ $archive_notify = array();
+
+ if ($vorgaenger->status_kurzbz === 'preview')
+ $archive_notify = array('lektor');
+ else if ($vorgaenger->status_kurzbz === 'live')
+ {
+ $archive_notify = array('student', 'lektor');
+
+ $history = $this->_ci->kalenderlib->getHistory($vorgaenger->kalender_id, false, false);
+ if (hasData($history))
+ {
+ $history = getData($history);
+
+ foreach ($history as $entry)
+ {
+ if ($entry->status_kurzbz === 'deleted' && $entry->status_kurzbz_max === 'preview')
+ {
+ $archive_notify = array('student');
+ break;
+ }
+ }
+ }
+ }
+
+ $mail_infos[] = array('entry' => $vorgaenger, 'new_status' => 'deleted', 'notify' => $archive_notify);
+ }
+ }
+
+ $synced++;
+ continue;
+ }
+
+ if ($ziel_status === 'preview' && in_array($entry->status_kurzbz, array('planning', 'sync_preview')))
+ {
+ $this->_ci->KalenderModel->update(array('kalender_id' => $entry->kalender_id), array('status_kurzbz' => 'preview'));
+ $this->_archiveVorgaenger($entry->vorgaenger_kalender_id, true);
+ $mail_infos[] = array('entry' => $entry, 'new_status' => 'preview', 'notify' => array('lektor'));
+ $synced++;
+ continue;
+ }
+
+ if ($ziel_status === 'live' && in_array($entry->status_kurzbz, array('planning', 'sync_preview', 'sync_live')))
+ {
+ $this->_ci->KalenderModel->update(array('kalender_id' => $entry->kalender_id), array('status_kurzbz' => 'live'));
+ $this->_archiveVorgaenger($entry->vorgaenger_kalender_id, false);
+ $mail_infos[] = array('entry' => $entry, 'new_status' => 'live', 'notify' => array('lektor', 'student'));
+ $synced++;
+ continue;
+ }
+
+ if ($ziel_status === 'live' && $entry->status_kurzbz === 'preview')
+ {
+ $current = getData($this->_ci->KalenderModel->load(array('kalender_id' => $entry->kalender_id)))[0];
+ if ($current->status_kurzbz === 'archived')
+ continue;
+
+ $this->_ci->KalenderModel->update(array('kalender_id' => $entry->kalender_id), array('status_kurzbz' => 'live'));
+ $this->_archiveVorgaenger($entry->vorgaenger_kalender_id, false);
+ $mail_infos[] = array('entry' => $entry, 'new_status' => 'live', 'notify' => array('lektor', 'student'));
+ $synced++;
+ }
+ }
+
+ return success(['synced' => $synced, 'mail_infos' => $mail_infos]);
+ }
+
+
+ private function _getStudiengang($oe_kurzbz)
+ {
+ $this->_ci->StudiengangModel->addJoin('tbl_organisationseinheit', 'oe_kurzbz');
+ $result = $this->_ci->StudiengangModel->loadWhere(array('oe_kurzbz' => $oe_kurzbz));
+
+ if (isError($result))
+ return $result;
+
+ if (!hasData($result))
+ return success(null);
+
+ return success(getData($result)[0]->studiengang_kz);
+ }
+}
\ No newline at end of file
diff --git a/application/libraries/RaumvorschlagLib.php b/application/libraries/RaumvorschlagLib.php
index 8cb4aa8a4..2953de428 100644
--- a/application/libraries/RaumvorschlagLib.php
+++ b/application/libraries/RaumvorschlagLib.php
@@ -22,13 +22,14 @@ class RaumvorschlagLib
$this->_ci->load->model('ressource/Ort_model', 'OrtModel');
$this->_ci->load->model('organisation/gruppe_model', 'GruppeModel');
$this->_ci->load->model('organisation/Lehrverband_model', 'LehrverbandModel');
-
- $this->_ci->load->model('education/Lehreinheitgruppe_model', 'LehreinheitgruppeModel');
+ $this->_ci->load->model('ressource/Zeitsperre_model', 'ZeitsperreModel');
+ $this->_ci->load->model('ressource/Stunde_model', 'StundeModel');
$this->_ci->load->model('education/Lehreinheitgruppe_model', 'LehreinheitgruppeModel');
$this->_ci->load->library('CollisionChecker');
$this->_ci->load->library('KalenderLib');
$this->_ci->load->library('PhrasesLib', array('ui'));
+ $this->_ci->load->library('VariableLib', array('uid' => getAuthUID()));
}
@@ -36,47 +37,295 @@ class RaumvorschlagLib
{
$event = $this->_ci->kalenderlib->getByKalenderId($kalender_id);
$event = $event[0];
+ return $this->_getVorschlaegeForEvent($event);
+ }
- $lektor_uids = array_column($event->lektor, 'mitarbeiter_uid');
- $gruppen_kurzbz = array_values(array_filter(array_column($event->gruppe, 'gruppe_kurzbz')));
+ public function getVorschlaegeByLehreinheit($lehreinheit_id, $von, $bis)
+ {
+ $teilnehmer = $this->_getLehreinheitTeilnehmer($lehreinheit_id);
- $lehrverband_gruppen = array_values(array_filter($event->gruppe, function($gruppe)
+ $event = (object) [
+ 'kalender_id' => null,
+ 'lehreinheit_id' => [$lehreinheit_id],
+ 'isostart' => (new DateTime($von))->format('c'),
+ 'isoend' => (new DateTime($bis))->format('c'),
+ 'datum' => (new DateTime($von))->format('Y-m-d'),
+ 'lektor' => $teilnehmer->lektor_data,
+ 'gruppe' => $teilnehmer->gruppen_data,
+ ];
+
+ return $this->_getVorschlaegeForEvent($event);
+ }
+
+ public function getVorschlaegeSlots($lehreinheit_id, $start_date, $end_date)
+ {
+ $lehreinheit_result = $this->_ci->LehreinheitModel->load($lehreinheit_id);
+ if (!hasData($lehreinheit_result))
+ return error('Lehreinheit nicht gefunden');
+ $lehreinheit = getData($lehreinheit_result)[0];
+
+ $block = $lehreinheit->stundenblockung;
+ if ($block <= 0)
+ return error('Stundenblockung ist ungültig');
+
+ $this->_ci->StundeModel->addOrder('stunde', 'ASC');
+ $stunden_result = $this->_ci->StundeModel->load();
+ $stunden = hasData($stunden_result) ? array_values(getData($stunden_result)) : [];
+
+ if (empty($stunden))
+ return error('Kein Stunden-Raster gefunden');
+
+ $teilnehmer = $this->_getLehreinheitTeilnehmer($lehreinheit_id);
+ $grp = $this->_splitGruppen($teilnehmer->gruppen_data);
+ $lektor_uids = array_column($teilnehmer->lektor_data, 'mitarbeiter_uid');
+
+ $verplante_events = $this->_ci->kalenderlib->getForRaumvorschlag(
+ $start_date,
+ $end_date,
+ $lektor_uids,
+ $grp->gruppen_kurzbz,
+ $grp->lehrverband_gruppen
+ );
+
+ $start_day = strtotime($start_date);
+ $end_day = strtotime($end_date);
+
+ $moegliche_slots = array();
+
+ $verplante_zeitsperren = $this->_getZeitsperen($start_date, $end_date, $lektor_uids);
+ while ($start_day <= $end_day)
+ {
+ $tag = date('Y-m-d', $start_day);
+
+ $verplante_tages_events = array_values(array_filter($verplante_events, function($event) use ($tag)
+ {
+ return $event->datum === $tag;
+ }));
+
+ foreach ($stunden as $index => $start_stunde)
+ {
+ if (!isset($stunden[$index + $block - 1]))
+ continue;
+
+ $end_stunde = $stunden[$index + $block - 1];
+
+ $slot_start = $tag . ' ' . $start_stunde->beginn;
+ $slot_end = $tag . ' ' . $end_stunde->ende;
+
+ if (!$this->_isFrei($slot_start, $slot_end, $verplante_zeitsperren, $lektor_uids, 'lektor'))
+ continue;
+
+ if (!$this->_isFrei($slot_start, $slot_end, $verplante_tages_events, $lektor_uids, 'lektor'))
+ continue;
+
+ if (!$this->_isFrei($slot_start, $slot_end, $verplante_tages_events, $grp->gruppen_kurzbz, 'gruppe'))
+ continue;
+
+ if (!$this->_isFrei($slot_start, $slot_end, $verplante_tages_events, $grp->lehrverband_gruppen_bezeichnung, 'lehrverband'))
+ continue;
+
+ $moegliche_slots[] = [
+ 'isostart' => (new DateTime($slot_start))->format('c'),
+ 'isoend' => (new DateTime($slot_end))->format('c'),
+ 'marker_isoend' => (new DateTime($tag . ' ' . $start_stunde->ende))->format('c'),
+ ];
+ }
+
+ $start_day = strtotime('+1 day', $start_day);
+ }
+
+
+ $slots = [];
+ foreach ($moegliche_slots as $slot)
+ {
+ $event_obj = (object) [
+ 'kalender_id' => null,
+ 'lehreinheit_id' => [$lehreinheit_id],
+ 'isostart' => $slot['isostart'],
+ 'isoend' => $slot['isoend'],
+ ];
+
+ $raumkandidaten = $this->_getRaumkandidaten($event_obj);
+
+ if (empty($raumkandidaten))
+ continue;
+
+ $ratings = $this->_getRatings($verplante_events, $slot, $raumkandidaten, $lektor_uids, $grp->gruppen_kurzbz, $grp->lehrverband_gruppen_bezeichnung);
+ $best = $ratings[0];
+ $anzahl_weitere = count($ratings) - 1;
+
+ $slots[] = [
+ 'isostart' => $slot['isostart'],
+ 'isoend' => $slot['isoend'],
+ 'marker_isoend' => $slot['marker_isoend'],
+ 'rating' => $this->_scoreToRating($best['score']),
+ 'label' => $anzahl_weitere > 0 ? $best['ort_kurzbz'] . ' (' . $best['score'] . ') + ' . $anzahl_weitere . ' weitere' : $best['ort_kurzbz'] . ' (' . $best['score'] . ')',
+ 'raeume' => $ratings,
+ ];
+ }
+ return success($slots);
+ }
+
+ private function _getZeitsperen($start_date, $end_date, $uids)
+ {
+
+ if (empty($uids)) return [];
+
+ if (($this->_ci->variablelib->getVar('ignore_kollision') === 'true') || ($this->_ci->variablelib->getVar('ignore_zeitsperre') === 'true')) return [];
+
+
+ $this->_ci->ZeitsperreModel->addSelect('mitarbeiter_uid, vondatum, vonstunde_z.beginn as von_beginn, bisdatum, bisstunde_z.ende as bis_ende');
+ $this->_ci->ZeitsperreModel->addJoin('lehre.tbl_stunde vonstunde_z', 'vonstunde_z.stunde = tbl_zeitsperre.vonstunde', 'LEFT');
+ $this->_ci->ZeitsperreModel->addJoin('lehre.tbl_stunde bisstunde_z', 'bisstunde_z.stunde = tbl_zeitsperre.bisstunde', 'LEFT');
+ $this->_ci->ZeitsperreModel->db->where('zeitsperretyp_kurzbz !=', 'ZVerfueg');
+ $this->_ci->ZeitsperreModel->db->where('(tbl_zeitsperre.vondatum + COALESCE(vonstunde_z.beginn, \'00:00\'))::timestamp <', $end_date);
+ $this->_ci->ZeitsperreModel->db->where('(tbl_zeitsperre.bisdatum + COALESCE(bisstunde_z.ende, \'23:59\'))::timestamp >', $start_date);
+
+ $this->_ci->ZeitsperreModel->db->where_in('mitarbeiter_uid', $uids);
+ $result = $this->_ci->ZeitsperreModel->load();
+
+ if (!hasData($result)) return [];
+
+ $events = [];
+ foreach (getData($result) as $row)
+ {
+ $von = new DateTime($row->vondatum . ' ' . ($row->von_beginn ?? '00:00'));
+ $bis = new DateTime($row->bisdatum . ' ' . ($row->bis_ende ?? '23:59'));
+
+ $events[] = (object)[
+ 'isostart' => $von->format('c'),
+ 'isoend' => $bis->format('c'),
+ 'lektor' => [['mitarbeiter_uid' => $row->mitarbeiter_uid]]
+ ];
+ }
+ return $events;
+ }
+
+ private function _getLehreinheitTeilnehmer($lehreinheit_id)
+ {
+ $lektoren_result = $this->_ci->LehreinheitMitarbeiterModel->loadWhere(['lehreinheit_id' => $lehreinheit_id]);
+ $lektor_data = [];
+ if (hasData($lektoren_result))
+ {
+ $lektor_data = getData($lektoren_result);
+ }
+
+ $gruppen_result = $this->_ci->LehreinheitgruppeModel->getByLehreinheit($lehreinheit_id);
+ $gruppen_data = [];
+ if (hasData($gruppen_result))
+ {
+ $gruppen_rows = getData($gruppen_result);
+
+ foreach ($gruppen_rows as $gruppe)
+ {
+ $gruppen_data[] = (array) $gruppe;
+ }
+ }
+
+
+ return (object)[
+ 'lektor_data' => $lektor_data,
+ 'gruppen_data' => $gruppen_data,
+ ];
+
+ }
+
+ private function _splitGruppen($gruppen_data)
+ {
+ $gruppen_kurzbz = array_values(array_filter(array_column($gruppen_data, 'gruppe_kurzbz')));
+ $lehrverband_gruppen = array_values(array_filter($gruppen_data, function($gruppe)
{
return empty($gruppe['gruppe_kurzbz']);
}));
+ $lehrverband_gruppen_bezeichnung = array_column($lehrverband_gruppen, 'bezeichnung');
+
+ return (object)[
+ 'gruppen_kurzbz' => $gruppen_kurzbz,
+ 'lehrverband_gruppen' => $lehrverband_gruppen,
+ 'lehrverband_gruppen_bezeichnung' => $lehrverband_gruppen_bezeichnung
+ ];
+
+ }
+ //TODO (david) umbauen auf CollisionChecks
+ private function _isFrei($slot_start, $slot_end, $events, $check_array, $type)
+ {
+ if (empty($check_array)) return true;
+
+ $slot_start_ts = strtotime($slot_start);
+ $slot_end_ts = strtotime($slot_end);
+
+ foreach ($events as $event)
+ {
+ $event_start_ts = strtotime($event->isostart);
+ $event_end_ts = strtotime($event->isoend);
+
+ if ($event_end_ts <= $slot_start_ts || $event_start_ts >= $slot_end_ts)
+ continue;
+
+ $event_uids = $this->_getIds($event, $type);
+
+ if (!empty(array_intersect($event_uids, $check_array)))
+ return false;
+ }
+
+ return true;
+ }
+
+ private function _scoreToRating($score)
+ {
+ if ($score >= 90)
+ return 'good';
+ if ($score >= 60)
+ return 'mid';
+ return 'bad';
+ }
+ private function _getVorschlaegeForEvent($event)
+ {
+ $raumkandidaten = $this->_getRaumkandidaten($event);
+ if (empty($raumkandidaten)) return [];
+
+ $lektor_uids = array_column($event->lektor, 'mitarbeiter_uid');
+ $grp = $this->_splitGruppen($event->gruppe);
$tages_events = $this->_ci->kalenderlib->getForRaumvorschlag(
$event->datum,
$event->datum,
$lektor_uids,
- $gruppen_kurzbz,
- $lehrverband_gruppen
+ $grp->gruppen_kurzbz,
+ $grp->lehrverband_gruppen
);
- $lektor_davor = $this->_getEventDavor($tages_events, $event->isostart, $lektor_uids, 'lektor');
- $gruppen_davor = $this->_getEventDavor($tages_events, $event->isostart, $gruppen_kurzbz, 'gruppe');
+ return $this->_getRatings($tages_events, $event, $raumkandidaten, $lektor_uids, $grp->gruppen_kurzbz, $grp->lehrverband_gruppen_bezeichnung);
+ }
+ private function _getRatings($events, $event, $raumkandidaten, $lektor_uids, $gruppen_kurzbz, $lehrverband_gruppen_bezeichnung)
+ {
+ $event = (object)$event;
+ $lektor_davor = $this->_getEventDavor($events, $event->isostart, $lektor_uids, 'lektor');
+ $gruppen_davor = $this->_getEventDavor($events, $event->isostart, $gruppen_kurzbz, 'gruppe');
+ $lehrverband_davor = $this->_getEventDavor($events, $event->isostart, $lehrverband_gruppen_bezeichnung, 'lehrverband');
$lektor_davor_ort = $lektor_davor ? $this->_getOrtDetails($lektor_davor->ort_kurzbz) : null;
$gruppen_davor_ort = $gruppen_davor ? $this->_getOrtDetails($gruppen_davor->ort_kurzbz) : null;
-
- $kandidaten = $this->_getRaumkandidaten($event);
- if (empty($kandidaten)) return [];
+ $lehrverband_davor_ort = $lehrverband_davor ? $this->_getOrtDetails($lehrverband_davor->ort_kurzbz) : null;
$unique = [];
- foreach ($kandidaten as $raum)
+ foreach ($raumkandidaten as $raum)
{
$unique[$raum->ort_kurzbz] = $raum;
}
$kandidaten = array_values($unique);
+
$ratings = [];
foreach ($kandidaten as $raum)
{
- $rating = ['ort_kurzbz' => $raum->ort_kurzbz, 'score' => 100, 'details' => []];
+ $rating = ['ort_kurzbz' => $raum->ort_kurzbz, 'score' => 100, 'details' => [],
+ 'raumtyp_kurzbz' => $raum->raumtyp_kurzbz, 'max_person' => $raum->max_person];
$this->_rateLektor($rating, $raum, $lektor_davor_ort);
$this->_rateGruppen($rating, $raum, $gruppen_davor_ort);
+ $this->_rateGruppen($rating, $raum, $lehrverband_davor_ort);
Events::trigger('room_rating',
function & () use (&$rating) {
@@ -204,10 +453,7 @@ class RaumvorschlagLib
if (empty($event->ort_kurzbz))
continue;
- if ($type === 'lektor')
- $event_uids = array_column($event->lektor, 'mitarbeiter_uid');
- else
- $event_uids = array_column($event->gruppe, 'gruppe_kurzbz');
+ $event_uids = $this->_getIds($event, $type);
if (empty(array_intersect($event_uids, $uids)))
continue;
@@ -219,6 +465,20 @@ class RaumvorschlagLib
return $kandidat;
}
+ private function _getIds($event, $type)
+ {
+ $event_uids = array();
+
+ if ($type === 'lektor')
+ $event_uids = array_column($event->lektor, 'mitarbeiter_uid');
+ else if ($type === 'gruppe')
+ $event_uids = array_column($event->gruppe, 'gruppe_kurzbz');
+ else if ($type === 'lehrverband')
+ $event_uids = array_column($event->gruppe, 'bezeichnung');
+
+ return $event_uids;
+ }
+
private function _getRaumkandidaten($event)
{
$raumtyp = null;
@@ -240,9 +500,19 @@ class RaumvorschlagLib
$this->_ci->KalenderModel->addJoin('lehre.tbl_kalender_ort', 'tbl_kalender.kalender_id = tbl_kalender_ort.kalender_id');
$this->_ci->KalenderModel->db->where('tbl_kalender.von <', $event->isoend);
$this->_ci->KalenderModel->db->where('tbl_kalender.bis >', $event->isostart);
- $this->_ci->KalenderModel->db->where('tbl_kalender.kalender_id !=', $event->kalender_id);
- $this->_ci->KalenderModel->db->where_not_in('tbl_kalender.status_kurzbz', ['deleted']);
+ if (!is_null($event->kalender_id))
+ {
+ $this->_ci->KalenderModel->db->where('tbl_kalender.kalender_id !=', $event->kalender_id);
+ }
+ $this->_ci->KalenderModel->db->where_not_in('tbl_kalender.status_kurzbz', ['deleted', 'to_delete', 'to_delete_live', 'to_delete_preview', 'archived']);
$this->_ci->KalenderModel->db->where('tbl_kalender_ort.ort_kurzbz IS NOT NULL', null, false);
+ $this->_ci->KalenderModel->db->where(
+ 'NOT EXISTS (
+ SELECT 1 FROM lehre.tbl_kalender nachfolger
+ WHERE nachfolger.vorgaenger_kalender_id = tbl_kalender.kalender_id
+ )', null, false
+ );
+
$belegte = $this->_ci->KalenderModel->load();
$belegte_orte = hasData($belegte) ? array_column(getData($belegte), 'ort_kurzbz') : [];
@@ -273,7 +543,7 @@ class RaumvorschlagLib
private function _getFreieRaeume($raumtyp, $belegte_orte)
{
- $this->_ci->OrtModel->addSelect('ort_kurzbz, stockwerk, standort_id');
+ $this->_ci->OrtModel->addSelect('ort_kurzbz, stockwerk, standort_id, max_person, raumtyp_kurzbz');
$this->_ci->OrtModel->addJoin('public.tbl_ortraumtyp', 'ort_kurzbz');
if (!empty($raumtyp))
@@ -290,11 +560,4 @@ class RaumvorschlagLib
return $this->_ci->OrtModel->load();
}
-
-
-
-
-
-
-
}
diff --git a/application/libraries/collision/checks/LectureCollisionCheck.php b/application/libraries/collision/checks/LectureCollisionCheck.php
index 21635e203..750f7c5ca 100644
--- a/application/libraries/collision/checks/LectureCollisionCheck.php
+++ b/application/libraries/collision/checks/LectureCollisionCheck.php
@@ -10,6 +10,7 @@ class LectureCollisionCheck implements ICollisionCheck
$this->_ci =& get_instance();
$this->_ci->load->model('ressource/Kalender_model', 'KalenderModel');
$this->_ci->load->model('ressource/Zeitsperre_model', 'ZeitsperreModel');
+ $this->_ci->load->model('education/Lehreinheitmitarbeiter_model', 'LehreinheitmitarbeiterModel');
$this->_ci->load->library('VariableLib', array('uid' => getAuthUID()));
$this->_ci->load->library('PhrasesLib', array('ui'));
@@ -22,11 +23,17 @@ class LectureCollisionCheck implements ICollisionCheck
public function check($data)
{
- if (!isset($data->von, $data->bis, $data->kalender_id)) return [];
+ if (!isset($data->von, $data->bis)) return [];
+ if (empty($data->kalender_id) && empty($data->lehreinheit_id) && empty($data->uids)) return [];
if ($this->_ci->variablelib->getVar('ignore_kollision') === 'true') return [];
- $uids = $this->_getUids($data->kalender_id);
+ if (!empty($data->kalender_id))
+ $uids = $this->_getUids($data->kalender_id);
+ else if (!empty($data->lehreinheit_id))
+ $uids = $this->_getUidsFromLehreinheit($data->lehreinheit_id);
+ else
+ $uids = $data->uids;
if (empty($uids)) return [];
@@ -58,9 +65,17 @@ class LectureCollisionCheck implements ICollisionCheck
$this->_ci->KalenderModel->db->where('other_kalender.kalender_id != tbl_kalender.kalender_id', null, false);
$this->_ci->KalenderModel->db->where('other_kalender.von < tbl_kalender.bis', null, false);
$this->_ci->KalenderModel->db->where('other_kalender.bis > tbl_kalender.von', null, false);
- $this->_ci->KalenderModel->db->where_not_in('other_kalender.status_kurzbz', ['archived', 'deleted', 'to_delete']);
+ $this->_ci->KalenderModel->db->where_not_in('other_kalender.status_kurzbz', array('archived', 'deleted', 'to_delete', 'to_delete_live', 'to_delete_preview'));
$this->_ci->KalenderModel->db->where_not_in('current_lehreinheit_ma.mitarbeiter_uid', $kollisionsfreie_user);
- $this->_ci->KalenderModel->db->where_in('tbl_kalender.kalender_id', $kalender_ids);
+
+ $this->_ci->KalenderModel->db->group_start();
+ $where_ids_chunks = array_chunk($kalender_ids,25);
+ foreach($where_ids_chunks as $where_ids)
+ {
+ $this->_ci->KalenderModel->db->or_where_in('tbl_kalender.kalender_id', $where_ids);
+ }
+ $this->_ci->KalenderModel->db->group_end();
+
$this->_ci->KalenderModel->db->where(
'other_kalender.kalender_id NOT IN (SELECT vorgaenger_kalender_id FROM lehre.tbl_kalender WHERE vorgaenger_kalender_id IS NOT NULL)',
null, false
@@ -85,9 +100,17 @@ class LectureCollisionCheck implements ICollisionCheck
$this->_ci->KalenderModel->db->where('other_kalender.kalender_id != tbl_kalender.kalender_id', null, false);
$this->_ci->KalenderModel->db->where('other_kalender.von < tbl_kalender.bis', null, false);
$this->_ci->KalenderModel->db->where('other_kalender.bis > tbl_kalender.von', null, false);
- $this->_ci->KalenderModel->db->where_not_in('other_kalender.status_kurzbz', ['archived', 'deleted', 'to_delete']);
+ $this->_ci->KalenderModel->db->where_not_in('other_kalender.status_kurzbz', ['archived', 'deleted', 'to_delete', 'to_delete_live', 'to_delete_preview']);
$this->_ci->KalenderModel->db->where_not_in('current_lehreinheit_ma.mitarbeiter_uid', $kollisionsfreie_user);
- $this->_ci->KalenderModel->db->where_in('tbl_kalender.kalender_id', $kalender_ids);
+
+ $this->_ci->KalenderModel->db->group_start();
+ $where_ids_chunks = array_chunk($kalender_ids,25);
+ foreach($where_ids_chunks as $where_ids)
+ {
+ $this->_ci->KalenderModel->db->or_where_in('tbl_kalender.kalender_id', $where_ids);
+ }
+ $this->_ci->KalenderModel->db->group_end();
+
$this->_ci->KalenderModel->db->where(
'other_kalender.kalender_id NOT IN (SELECT vorgaenger_kalender_id FROM lehre.tbl_kalender WHERE vorgaenger_kalender_id IS NOT NULL)',
null, false
@@ -114,7 +137,13 @@ class LectureCollisionCheck implements ICollisionCheck
$this->_ci->KalenderModel->db->where('(z.vondatum + COALESCE(vonstunde_z.beginn, \'00:00\'))::timestamp < tbl_kalender.bis', null, false);
$this->_ci->KalenderModel->db->where('(z.bisdatum + COALESCE(bisstunde_z.ende, \'23:59\'))::timestamp > tbl_kalender.von', null, false);
$this->_ci->KalenderModel->db->where_not_in('current_lehreinheit_ma.mitarbeiter_uid', $kollisionsfreie_user);
- $this->_ci->KalenderModel->db->where_in('tbl_kalender.kalender_id', $kalender_ids);
+ $this->_ci->KalenderModel->db->group_start();
+ $where_ids_chunks = array_chunk($kalender_ids,25);
+ foreach($where_ids_chunks as $where_ids)
+ {
+ $this->_ci->KalenderModel->db->or_where_in('tbl_kalender.kalender_id', $where_ids);
+ }
+ $this->_ci->KalenderModel->db->group_end();
$result = $this->_ci->KalenderModel->load();
@@ -159,6 +188,20 @@ class LectureCollisionCheck implements ICollisionCheck
return array_unique(array_merge($mitarbeiter_uids, $event_teilnehmer));
}
+ private function _getUidsFromLehreinheit($lehreinheit_id)
+ {
+ $kollisionsfreie_user = unserialize(KOLLISIONSFREIE_USER);
+
+ $result = $this->_ci->LehreinheitmitarbeiterModel->loadWhere(array('lehreinheit_id' => $lehreinheit_id));
+
+ if (isError($result) || !hasData($result)) return [];
+
+ $uids = array_column(getData($result), 'mitarbeiter_uid');
+ $uids = array_diff(array_filter($uids), $kollisionsfreie_user);
+
+ return array_values(array_unique($uids));
+ }
+
private function _checkLehreinheit($uids, $data)
{
$kollisionsfreie_user = unserialize(KOLLISIONSFREIE_USER);
@@ -170,8 +213,9 @@ class LectureCollisionCheck implements ICollisionCheck
$this->_ci->KalenderModel->addJoin('lehre.tbl_lehreinheitmitarbeiter', 'lehreinheit_id', 'LEFT');
$this->_ci->KalenderModel->db->where_in('mitarbeiter_uid', $uids);
- $this->_ci->KalenderModel->db->where('tbl_kalender.kalender_id !=', $data->kalender_id);
- $this->_ci->KalenderModel->db->where_not_in('tbl_kalender.status_kurzbz', array('archived', 'deleted', 'to_delete'));
+ if (!empty($data->kalender_id))
+ $this->_ci->KalenderModel->db->where('tbl_kalender.kalender_id !=', $data->kalender_id);
+ $this->_ci->KalenderModel->db->where_not_in('tbl_kalender.status_kurzbz', array('archived', 'deleted', 'to_delete', 'to_delete_live', 'to_delete_preview'));
$this->_ci->KalenderModel->db->where_not_in('mitarbeiter_uid', $kollisionsfreie_user);
$this->_ci->KalenderModel->db->where(
'tbl_kalender.kalender_id NOT IN (SELECT vorgaenger_kalender_id FROM lehre.tbl_kalender WHERE vorgaenger_kalender_id IS NOT NULL)',
@@ -207,8 +251,9 @@ class LectureCollisionCheck implements ICollisionCheck
$this->_ci->KalenderModel->addJoin('lehre.tbl_kalender_event_teilnehmer', 'tbl_kalender_event.kalender_id = tbl_kalender_event_teilnehmer.kalender_id', 'LEFT');
$this->_ci->KalenderModel->db->where_in('tbl_kalender_event_teilnehmer.uid', $uids);
- $this->_ci->KalenderModel->db->where('tbl_kalender.kalender_id !=', $data->kalender_id);
- $this->_ci->KalenderModel->db->where_not_in('tbl_kalender.status_kurzbz', array('archived', 'deleted', 'to_delete'));
+ if (!empty($data->kalender_id))
+ $this->_ci->KalenderModel->db->where('tbl_kalender.kalender_id !=', $data->kalender_id);
+ $this->_ci->KalenderModel->db->where_not_in('tbl_kalender.status_kurzbz', array('archived', 'deleted', 'to_delete', 'to_delete_live', 'to_delete_preview'));
$this->_ci->KalenderModel->db->where_not_in('uid', $kollisionsfreie_user);
$this->_ci->KalenderModel->db->where(
'tbl_kalender.kalender_id NOT IN (SELECT vorgaenger_kalender_id FROM lehre.tbl_kalender WHERE vorgaenger_kalender_id IS NOT NULL)',
diff --git a/application/libraries/collision/checks/ResourcesCollisionCheck.php b/application/libraries/collision/checks/ResourcesCollisionCheck.php
index 0c957965d..da163977e 100644
--- a/application/libraries/collision/checks/ResourcesCollisionCheck.php
+++ b/application/libraries/collision/checks/ResourcesCollisionCheck.php
@@ -20,7 +20,7 @@ class ResourcesCollisionCheck implements ICollisionCheck
public function check($data)
{
- if (!isset($data->betriebsmittel_ids, $data->von, $data->bis, $data->kalender_id)) return [];
+ if (!isset($data->betriebsmittel_ids, $data->von, $data->bis)) return [];
if (empty($data->betriebsmittel_ids)) return [];
@@ -31,9 +31,10 @@ class ResourcesCollisionCheck implements ICollisionCheck
$this->_ci->KalenderModel->addJoin('lehre.tbl_betriebsmittel_kalender bk', 'bk.eindeutige_kalender_gruppen_id = tbl_kalender.eindeutige_kalender_gruppen_id');
$this->_ci->KalenderModel->addJoin('wawi.tbl_betriebsmittel bm', 'bm.betriebsmittel_id = bk.betriebsmittel_id');
- $this->_ci->KalenderModel->db->where('tbl_kalender.kalender_id !=', $data->kalender_id);
+ if (!empty($data->kalender_id))
+ $this->_ci->KalenderModel->db->where('tbl_kalender.kalender_id !=', $data->kalender_id);
$this->_ci->KalenderModel->db->where('tbl_kalender.kalender_id NOT IN (SELECT vorgaenger_kalender_id FROM lehre.tbl_kalender WHERE vorgaenger_kalender_id IS NOT NULL)', null, false);
- $this->_ci->KalenderModel->db->where_not_in('tbl_kalender.status_kurzbz', ['archived', 'deleted', 'to_delete']);
+ $this->_ci->KalenderModel->db->where_not_in('tbl_kalender.status_kurzbz', ['archived', 'deleted', 'to_delete', 'to_delete_live', 'to_delete_preview']);
$this->_ci->KalenderModel->db->where_in('bk.betriebsmittel_id', $data->betriebsmittel_ids);
@@ -66,8 +67,16 @@ class ResourcesCollisionCheck implements ICollisionCheck
$this->_ci->KalenderModel->db->where('other_kalender.kalender_id != tbl_kalender.kalender_id', null, false);
$this->_ci->KalenderModel->db->where('other_kalender.von < tbl_kalender.bis', null, false);
$this->_ci->KalenderModel->db->where('other_kalender.bis > tbl_kalender.von', null, false);
- $this->_ci->KalenderModel->db->where_not_in('other_kalender.status_kurzbz', ['archived', 'deleted', 'to_delete']);
- $this->_ci->KalenderModel->db->where_in('tbl_kalender.kalender_id', $kalender_ids);
+ $this->_ci->KalenderModel->db->where_not_in('other_kalender.status_kurzbz', ['archived', 'deleted', 'to_delete', 'to_delete_live', 'to_delete_preview']);
+
+ $this->_ci->KalenderModel->db->group_start();
+ $where_ids_chunks = array_chunk($kalender_ids,25);
+ foreach($where_ids_chunks as $where_ids)
+ {
+ $this->_ci->KalenderModel->db->or_where_in('tbl_kalender.kalender_id', $where_ids);
+ }
+ $this->_ci->KalenderModel->db->group_end();
+
$this->_ci->KalenderModel->db->where('other_kalender.kalender_id NOT IN (SELECT vorgaenger_kalender_id FROM lehre.tbl_kalender WHERE vorgaenger_kalender_id IS NOT NULL)', null, false);
$result = $this->_ci->KalenderModel->load();
diff --git a/application/libraries/collision/checks/RoomCollisionCheck.php b/application/libraries/collision/checks/RoomCollisionCheck.php
index b59d79028..6e25f3ef0 100644
--- a/application/libraries/collision/checks/RoomCollisionCheck.php
+++ b/application/libraries/collision/checks/RoomCollisionCheck.php
@@ -20,16 +20,17 @@ class RoomCollisionCheck implements ICollisionCheck
public function check($data)
{
- if (!isset($data->ort_kurzbz, $data->von, $data->bis, $data->kalender_id) || isEmptyArray($data->ort_kurzbz)) return [];
+ if (!isset($data->ort_kurzbz, $data->von, $data->bis) || isEmptyArray($data->ort_kurzbz)) return [];
if ($this->_ci->variablelib->getVar('ignore_kollision') === 'true') return [];
$this->_ci->KalenderModel->addSelect('kalender_id, ort_kurzbz, von, bis');
$this->_ci->KalenderModel->addJoin('lehre.tbl_kalender_ort', 'kalender_id');
- $this->_ci->KalenderModel->db->where('tbl_kalender.kalender_id !=', $data->kalender_id);
+ if (!empty($data->kalender_id))
+ $this->_ci->KalenderModel->db->where('tbl_kalender.kalender_id !=', $data->kalender_id);
$this->_ci->KalenderModel->db->where('tbl_kalender.kalender_id NOT IN (SELECT vorgaenger_kalender_id FROM lehre.tbl_kalender WHERE vorgaenger_kalender_id IS NOT NULL)', null, false);
- $this->_ci->KalenderModel->db->where_not_in('tbl_kalender.status_kurzbz', ['archived', 'deleted', 'to_delete']);
+ $this->_ci->KalenderModel->db->where_not_in('tbl_kalender.status_kurzbz', array('archived', 'deleted', 'to_delete', 'to_delete_live', 'to_delete_preview'));
$this->_ci->KalenderModel->db->where_in('ort_kurzbz', $data->ort_kurzbz);
$result = $this->_ci->KalenderModel->loadWhere(array(
@@ -61,8 +62,16 @@ class RoomCollisionCheck implements ICollisionCheck
$this->_ci->KalenderModel->db->where('other_kalender.kalender_id != tbl_kalender.kalender_id', null, false);
$this->_ci->KalenderModel->db->where('other_kalender.von < tbl_kalender.bis', null, false);
$this->_ci->KalenderModel->db->where('other_kalender.bis > tbl_kalender.von', null, false);
- $this->_ci->KalenderModel->db->where_not_in('other_kalender.status_kurzbz', ['archived', 'deleted', 'to_delete']);
- $this->_ci->KalenderModel->db->where_in('tbl_kalender.kalender_id', $kalender_ids);
+ $this->_ci->KalenderModel->db->where_not_in('other_kalender.status_kurzbz', array('archived', 'deleted', 'to_delete', 'to_delete_live', 'to_delete_preview'));
+
+ $this->_ci->KalenderModel->db->group_start();
+ $where_ids_chunks = array_chunk($kalender_ids,25);
+ foreach($where_ids_chunks as $where_ids)
+ {
+ $this->_ci->KalenderModel->db->or_where_in('tbl_kalender.kalender_id', $where_ids);
+ }
+ $this->_ci->KalenderModel->db->group_end();
+
$this->_ci->KalenderModel->db->where('other_kalender.kalender_id NOT IN (SELECT vorgaenger_kalender_id FROM lehre.tbl_kalender WHERE vorgaenger_kalender_id IS NOT NULL)', null, false);
$result = $this->_ci->KalenderModel->load();
diff --git a/application/libraries/collision/checks/StudentCollisionCheck.php b/application/libraries/collision/checks/StudentCollisionCheck.php
index 4ffeb071e..bb9ce10db 100644
--- a/application/libraries/collision/checks/StudentCollisionCheck.php
+++ b/application/libraries/collision/checks/StudentCollisionCheck.php
@@ -19,7 +19,8 @@ class StudentCollisionCheck implements ICollisionCheck
public function check($data)
{
- if (!isset($data->von, $data->bis, $data->kalender_id)) return [];
+ if (!isset($data->von, $data->bis)) return [];
+ if (!isset($data->kalender_id) && !isset($data->lehreinheit_id)) return [];
if ($this->_ci->variablelib->getVar('ignore_kollision') === 'true') return [];
if ($this->_ci->variablelib->getVar('kollision_student') !== 'true') return [];
@@ -30,7 +31,9 @@ class StudentCollisionCheck implements ICollisionCheck
$dbModel = new DB_Model();
- $qry1 = "
+ if (isset($data->kalender_id))
+ {
+ $qry1 = "
SELECT DISTINCT tbl_benutzergruppe.uid
FROM lehre.tbl_kalender
JOIN lehre.tbl_kalender_lehreinheit USING(kalender_id)
@@ -60,13 +63,48 @@ class StudentCollisionCheck implements ICollisionCheck
AND (tbl_lehreinheitgruppe.gruppe = tbl_studentlehrverband.gruppe OR tbl_lehreinheitgruppe.gruppe IS NULL OR btrim(tbl_lehreinheitgruppe.gruppe::text) = '' OR tbl_studentlehrverband.gruppe IS NULL)
WHERE tbl_kalender.kalender_id = ?
AND tbl_studentlehrverband.student_uid NOT IN ($placeholders)
+ ";
- ";
+ $qry_1_param = $data->kalender_id;
+ }
+ else
+ {
+ $qry1 = "
+ SELECT DISTINCT tbl_benutzergruppe.uid
+ FROM lehre.tbl_lehreinheit
+ JOIN lehre.tbl_lehreinheitgruppe ON tbl_lehreinheitgruppe.lehreinheit_id = tbl_lehreinheit.lehreinheit_id
+ JOIN public.tbl_gruppe
+ ON tbl_gruppe.studiengang_kz = tbl_lehreinheitgruppe.studiengang_kz
+ AND tbl_gruppe.semester = tbl_lehreinheitgruppe.semester
+ AND tbl_gruppe.gruppe_kurzbz = tbl_lehreinheitgruppe.gruppe_kurzbz
+ JOIN public.tbl_benutzergruppe ON tbl_benutzergruppe.gruppe_kurzbz = tbl_gruppe.gruppe_kurzbz
+ AND tbl_benutzergruppe.studiensemester_kurzbz = tbl_lehreinheit.studiensemester_kurzbz
+
+ WHERE tbl_lehreinheit.lehreinheit_id = ?
+ AND tbl_benutzergruppe.uid NOT IN ($placeholders)
+
+ UNION ALL
+
+ SELECT DISTINCT tbl_studentlehrverband.student_uid AS uid
+ FROM lehre.tbl_lehreinheit
+ JOIN lehre.tbl_lehreinheitgruppe ON tbl_lehreinheitgruppe.lehreinheit_id = tbl_lehreinheit.lehreinheit_id
+ JOIN public.tbl_studentlehrverband
+ ON tbl_studentlehrverband.studiengang_kz = tbl_lehreinheitgruppe.studiengang_kz
+ AND tbl_studentlehrverband.semester = tbl_lehreinheitgruppe.semester
+ AND tbl_studentlehrverband.studiensemester_kurzbz = tbl_lehreinheit.studiensemester_kurzbz
+ AND (tbl_lehreinheitgruppe.verband = tbl_studentlehrverband.verband OR tbl_lehreinheitgruppe.verband IS NULL OR btrim(tbl_lehreinheitgruppe.verband::text) = '' OR tbl_studentlehrverband.verband IS NULL)
+ AND (tbl_lehreinheitgruppe.gruppe = tbl_studentlehrverband.gruppe OR tbl_lehreinheitgruppe.gruppe IS NULL OR btrim(tbl_lehreinheitgruppe.gruppe::text) = '' OR tbl_studentlehrverband.gruppe IS NULL)
+ WHERE tbl_lehreinheit.lehreinheit_id = ?
+ AND tbl_studentlehrverband.student_uid NOT IN ($placeholders)
+ ";
+
+ $qry_1_param = $data->lehreinheit_id;
+ }
$result1 = $dbModel->execReadOnlyQuery($qry1, array_merge(
- [$data->kalender_id],
+ [$qry_1_param],
$kollisionsfreie_user,
- [$data->kalender_id],
+ [$qry_1_param],
$kollisionsfreie_user
));
@@ -74,6 +112,9 @@ class StudentCollisionCheck implements ICollisionCheck
$curUids = array_flip(array_column(getData($result1), 'uid'));
+ $exclude_kalender = isset($data->kalender_id) ? "AND tbl_kalender.kalender_id != ?" : "";
+ $exclude_kalender_param = isset($data->kalender_id) ? [$data->kalender_id] : [];
+
$qry2 = "
SELECT DISTINCT tbl_kalender.kalender_id, tbl_kalender.von, tbl_kalender.bis, tbl_benutzergruppe.uid
FROM lehre.tbl_kalender
@@ -88,8 +129,8 @@ class StudentCollisionCheck implements ICollisionCheck
AND tbl_benutzergruppe.studiensemester_kurzbz = tbl_lehreinheit.studiensemester_kurzbz
WHERE tbl_kalender.von < ?
AND tbl_kalender.bis > ?
- AND tbl_kalender.kalender_id != ?
- AND tbl_kalender.status_kurzbz NOT IN ('archived', 'deleted', 'to_delete')
+ $exclude_kalender
+ AND tbl_kalender.status_kurzbz NOT IN ('archived', 'deleted', 'to_delete', 'to_delete_live', 'to_delete_preview')
AND tbl_benutzergruppe.uid NOT IN ($placeholders)
AND NOT EXISTS (
SELECT 1 FROM lehre.tbl_kalender vorgaenger
@@ -97,7 +138,7 @@ class StudentCollisionCheck implements ICollisionCheck
)
UNION ALL
-
+
SELECT DISTINCT tbl_kalender.kalender_id, tbl_kalender.von, tbl_kalender.bis, tbl_studentlehrverband.student_uid AS uid
FROM lehre.tbl_kalender
JOIN lehre.tbl_kalender_lehreinheit ON tbl_kalender_lehreinheit.kalender_id = tbl_kalender.kalender_id
@@ -111,8 +152,8 @@ class StudentCollisionCheck implements ICollisionCheck
AND (tbl_lehreinheitgruppe.gruppe = tbl_studentlehrverband.gruppe OR tbl_lehreinheitgruppe.gruppe IS NULL OR btrim(tbl_lehreinheitgruppe.gruppe::text) = '' OR tbl_studentlehrverband.gruppe IS NULL)
WHERE tbl_kalender.von < ?
AND tbl_kalender.bis > ?
- AND tbl_kalender.kalender_id != ?
- AND tbl_kalender.status_kurzbz NOT IN ('archived', 'deleted', 'to_delete')
+ $exclude_kalender
+ AND tbl_kalender.status_kurzbz NOT IN ('archived', 'deleted', 'to_delete', 'to_delete_live', 'to_delete_preview')
AND tbl_studentlehrverband.student_uid NOT IN ($placeholders)
AND NOT EXISTS (
SELECT 1 FROM lehre.tbl_kalender vorgaenger
@@ -121,10 +162,8 @@ class StudentCollisionCheck implements ICollisionCheck
";
$result2 = $dbModel->execReadOnlyQuery($qry2, array_merge(
- [$data->bis, $data->von, $data->kalender_id],
- $kollisionsfreie_user,
- [$data->bis, $data->von, $data->kalender_id],
- $kollisionsfreie_user
+ [$data->bis, $data->von], $exclude_kalender_param, $kollisionsfreie_user,
+ [$data->bis, $data->von], $exclude_kalender_param, $kollisionsfreie_user
));
if (isError($result2) || !hasData($result2)) return [];
@@ -175,7 +214,7 @@ class StudentCollisionCheck implements ICollisionCheck
ON other_kalender.kalender_id != current_kalender.kalender_id
AND other_kalender.von < current_kalender.bis
AND other_kalender.bis > current_kalender.von
- AND other_kalender.status_kurzbz NOT IN ('archived', 'deleted', 'to_delete')
+ AND other_kalender.status_kurzbz NOT IN ('archived', 'deleted', 'to_delete', 'to_delete_live', 'to_delete_preview')
AND NOT EXISTS (
SELECT 1 FROM lehre.tbl_kalender vorgaenger
WHERE vorgaenger.vorgaenger_kalender_id = other_kalender.kalender_id
@@ -213,7 +252,7 @@ class StudentCollisionCheck implements ICollisionCheck
ON other_kalender.kalender_id != current_kalender.kalender_id
AND other_kalender.von < current_kalender.bis
AND other_kalender.bis > current_kalender.von
- AND other_kalender.status_kurzbz NOT IN ('archived', 'deleted', 'to_delete')
+ AND other_kalender.status_kurzbz NOT IN ('archived', 'deleted', 'to_delete', 'to_delete_live', 'to_delete_preview')
AND NOT EXISTS (
SELECT 1 FROM lehre.tbl_kalender vorgaenger
WHERE vorgaenger.vorgaenger_kalender_id = other_kalender.kalender_id
diff --git a/application/libraries/collision/checks/VerbandCollisionCheck.php b/application/libraries/collision/checks/VerbandCollisionCheck.php
index 40baf38a2..98ae3efee 100644
--- a/application/libraries/collision/checks/VerbandCollisionCheck.php
+++ b/application/libraries/collision/checks/VerbandCollisionCheck.php
@@ -21,18 +21,58 @@ class VerbandCollisionCheck implements ICollisionCheck
public function check($data)
{
- if (!isset($data->von, $data->bis, $data->kalender_id)) return [];
+ if (!isset($data->von, $data->bis)) return [];
+ if (!isset($data->kalender_id) && !isset($data->lehreinheit_id)) return [];
if ($this->_ci->variablelib->getVar('ignore_kollision') === 'true') return [];
$kollision_student = $this->_ci->variablelib->getVar('kollision_student') === 'false';
$kollision_reservierung = $this->_ci->variablelib->getVar('ignore_reservierung') === 'false';
+ if (!isset($data->kalender_id)) $kollision_reservierung = false;
+
if (!$kollision_student && !$kollision_reservierung) return [];
$dbModel = new DB_Model();
$collisions = [];
+ $group_matching_qry = "
+ (
+ (
+ current_lehreinheitguppe.gruppe_kurzbz IS NULL
+ AND other_lehreinheitguppe.gruppe_kurzbz IS NULL
+ AND (
+ current_lehreinheitguppe.verband IS NULL
+ OR (
+ current_lehreinheitguppe.verband = other_lehreinheitguppe.verband
+ AND (current_lehreinheitguppe.gruppe IS NULL OR other_lehreinheitguppe.gruppe IS NULL OR current_lehreinheitguppe.gruppe = other_lehreinheitguppe.gruppe)
+ )
+ )
+ )
+ OR
+ (
+ current_lehreinheitguppe.gruppe_kurzbz IS NOT NULL
+ AND other_lehreinheitguppe.gruppe_kurzbz IS NOT NULL
+ AND current_gruppe.direktinskription IS NOT TRUE
+ AND other_gruppe.direktinskription IS NOT TRUE
+ )
+ OR
+ (
+ (
+ current_lehreinheitguppe.gruppe_kurzbz IS NULL
+ AND other_lehreinheitguppe.gruppe_kurzbz IS NOT NULL
+ AND other_gruppe.direktinskription IS NOT TRUE
+ )
+ OR
+ (
+ current_lehreinheitguppe.gruppe_kurzbz IS NOT NULL
+ AND other_lehreinheitguppe.gruppe_kurzbz IS NULL
+ AND current_gruppe.direktinskription IS NOT TRUE
+ )
+ )
+ )
+ ";
+
if ($kollision_student)
{
$union_event = "";
@@ -47,100 +87,111 @@ class VerbandCollisionCheck implements ICollisionCheck
";
}
- $sql_gruppen = "
- SELECT
- other_kalender.von,
- other_kalender.bis,
- COALESCE(
- other_lehreinheitguppe.gruppe_kurzbz,
- UPPER(stg.typ::text || stg.kurzbz::text) || '-' || other_lehreinheitguppe.semester ||
- COALESCE(other_lehreinheitguppe.verband::text, '') ||
- COALESCE(other_lehreinheitguppe.gruppe::text, '')
- ) AS gruppenname
- FROM lehre.tbl_kalender current_kalender
+ $other_lehreinheitgruppe_subquery = "
+ SELECT tbl_lehreinheitgruppe.studiengang_kz, tbl_lehreinheitgruppe.semester, tbl_lehreinheitgruppe.verband, tbl_lehreinheitgruppe.gruppe,
+ tbl_lehreinheitgruppe.gruppe_kurzbz, tbl_kalender_lehreinheit.kalender_id
+ FROM lehre.tbl_kalender_lehreinheit
+ JOIN lehre.tbl_lehreinheit ON tbl_lehreinheit.lehreinheit_id = tbl_kalender_lehreinheit.lehreinheit_id
+ JOIN lehre.tbl_lehreinheitgruppe ON tbl_lehreinheitgruppe.lehreinheit_id = tbl_lehreinheit.lehreinheit_id
+ ". $union_event ."
+ ";
- JOIN (
- SELECT tbl_lehreinheitgruppe.studiengang_kz, tbl_lehreinheitgruppe.semester, tbl_lehreinheitgruppe.verband, tbl_lehreinheitgruppe.gruppe,
- tbl_lehreinheitgruppe.gruppe_kurzbz, tbl_kalender_lehreinheit.kalender_id
- FROM lehre.tbl_kalender_lehreinheit
- JOIN lehre.tbl_lehreinheit ON tbl_lehreinheit.lehreinheit_id = tbl_kalender_lehreinheit.lehreinheit_id
- JOIN lehre.tbl_lehreinheitgruppe ON tbl_lehreinheitgruppe.lehreinheit_id = tbl_lehreinheit.lehreinheit_id
- ". $union_event ."
- ) current_lehreinheitguppe ON current_lehreinheitguppe.kalender_id = current_kalender.kalender_id
-
- LEFT JOIN public.tbl_gruppe current_gruppe
- ON current_gruppe.gruppe_kurzbz = current_lehreinheitguppe.gruppe_kurzbz
-
- JOIN lehre.tbl_kalender other_kalender
- ON other_kalender.kalender_id != current_kalender.kalender_id
- AND other_kalender.von < ?
- AND other_kalender.bis > ?
-
- JOIN (
- SELECT tbl_lehreinheitgruppe.studiengang_kz, tbl_lehreinheitgruppe.semester, tbl_lehreinheitgruppe.verband, tbl_lehreinheitgruppe.gruppe,
- tbl_lehreinheitgruppe.gruppe_kurzbz, tbl_kalender_lehreinheit.kalender_id
- FROM lehre.tbl_kalender_lehreinheit
- JOIN lehre.tbl_lehreinheit ON tbl_lehreinheit.lehreinheit_id = tbl_kalender_lehreinheit.lehreinheit_id
- JOIN lehre.tbl_lehreinheitgruppe ON tbl_lehreinheitgruppe.lehreinheit_id = tbl_lehreinheit.lehreinheit_id
- ". $union_event ."
- ) other_lehreinheitguppe ON other_lehreinheitguppe.kalender_id = other_kalender.kalender_id
-
- LEFT JOIN public.tbl_gruppe other_gruppe
- ON other_gruppe.gruppe_kurzbz = other_lehreinheitguppe.gruppe_kurzbz
-
- LEFT JOIN public.tbl_studiengang stg
- ON stg.studiengang_kz = other_lehreinheitguppe.studiengang_kz
-
- WHERE current_kalender.kalender_id = ?
- AND other_kalender.status_kurzbz NOT IN ('archived', 'deleted', 'to_delete')
- AND current_lehreinheitguppe.studiengang_kz = other_lehreinheitguppe.studiengang_kz
- AND current_lehreinheitguppe.semester = other_lehreinheitguppe.semester
- AND (
- (
- current_lehreinheitguppe.gruppe_kurzbz IS NULL
- AND other_lehreinheitguppe.gruppe_kurzbz IS NULL
- AND (
- current_lehreinheitguppe.verband IS NULL
- OR (
- current_lehreinheitguppe.verband = other_lehreinheitguppe.verband
- AND (current_lehreinheitguppe.gruppe IS NULL OR other_lehreinheitguppe.gruppe IS NULL OR current_lehreinheitguppe.gruppe = other_lehreinheitguppe.gruppe)
- )
- )
+ if (isset($data->kalender_id))
+ {
+ $sql_gruppen = "
+ SELECT
+ other_kalender.von,
+ other_kalender.bis,
+ COALESCE(
+ other_lehreinheitguppe.gruppe_kurzbz,
+ UPPER(stg.typ::text || stg.kurzbz::text) || '-' || other_lehreinheitguppe.semester ||
+ COALESCE(other_lehreinheitguppe.verband::text, '') ||
+ COALESCE(other_lehreinheitguppe.gruppe::text, '')
+ ) AS gruppenname
+ FROM lehre.tbl_kalender current_kalender
+
+ JOIN (
+ ". $other_lehreinheitgruppe_subquery ."
+ ) current_lehreinheitguppe ON current_lehreinheitguppe.kalender_id = current_kalender.kalender_id
+
+ LEFT JOIN public.tbl_gruppe current_gruppe
+ ON current_gruppe.gruppe_kurzbz = current_lehreinheitguppe.gruppe_kurzbz
+
+ JOIN lehre.tbl_kalender other_kalender
+ ON other_kalender.kalender_id != current_kalender.kalender_id
+ AND other_kalender.von < ?
+ AND other_kalender.bis > ?
+
+ JOIN (
+ ". $other_lehreinheitgruppe_subquery ."
+ ) other_lehreinheitguppe ON other_lehreinheitguppe.kalender_id = other_kalender.kalender_id
+
+ LEFT JOIN public.tbl_gruppe other_gruppe
+ ON other_gruppe.gruppe_kurzbz = other_lehreinheitguppe.gruppe_kurzbz
+
+ LEFT JOIN public.tbl_studiengang stg
+ ON stg.studiengang_kz = other_lehreinheitguppe.studiengang_kz
+
+ WHERE current_kalender.kalender_id = ?
+ AND other_kalender.status_kurzbz NOT IN ('archived', 'deleted', 'to_delete', 'to_delete_live', 'to_delete_preview')
+ AND current_lehreinheitguppe.studiengang_kz = other_lehreinheitguppe.studiengang_kz
+ AND current_lehreinheitguppe.semester = other_lehreinheitguppe.semester
+ AND ". $group_matching_qry ."
+ AND other_kalender.kalender_id NOT IN (
+ SELECT vorgaenger_kalender_id
+ FROM lehre.tbl_kalender
+ WHERE vorgaenger_kalender_id IS NOT NULL
)
- OR
- (
- current_lehreinheitguppe.gruppe_kurzbz IS NOT NULL
- AND other_lehreinheitguppe.gruppe_kurzbz IS NOT NULL
- AND current_gruppe.direktinskription IS NOT TRUE
- AND other_gruppe.direktinskription IS NOT TRUE
- )
- OR
- (
- (
- current_lehreinheitguppe.gruppe_kurzbz IS NULL
- AND other_lehreinheitguppe.gruppe_kurzbz IS NOT NULL
- AND other_gruppe.direktinskription IS NOT TRUE
- )
- OR
- (
- current_lehreinheitguppe.gruppe_kurzbz IS NOT NULL
- AND other_lehreinheitguppe.gruppe_kurzbz IS NULL
- AND current_gruppe.direktinskription IS NOT TRUE
- )
- )
- )
- AND other_kalender.kalender_id NOT IN (
- SELECT vorgaenger_kalender_id
- FROM lehre.tbl_kalender
- WHERE vorgaenger_kalender_id IS NOT NULL
- )
- ";
+ ";
- $result = $dbModel->execReadOnlyQuery($sql_gruppen, [
- $data->bis,
- $data->von,
- $data->kalender_id,
- ]);
+ $params = [$data->bis, $data->von, $data->kalender_id];
+ }
+ else
+ {
+ $sql_gruppen = "
+ SELECT
+ other_kalender.von,
+ other_kalender.bis,
+ COALESCE(
+ other_lehreinheitguppe.gruppe_kurzbz,
+ UPPER(stg.typ::text || stg.kurzbz::text) || '-' || other_lehreinheitguppe.semester ||
+ COALESCE(other_lehreinheitguppe.verband::text, '') ||
+ COALESCE(other_lehreinheitguppe.gruppe::text, '')
+ ) AS gruppenname
+ FROM lehre.tbl_lehreinheitgruppe current_lehreinheitguppe
+ LEFT JOIN public.tbl_gruppe current_gruppe
+ ON current_gruppe.gruppe_kurzbz = current_lehreinheitguppe.gruppe_kurzbz
+
+ JOIN lehre.tbl_kalender other_kalender
+ ON other_kalender.von < ?
+ AND other_kalender.bis > ?
+
+ JOIN (
+ ". $other_lehreinheitgruppe_subquery ."
+ ) other_lehreinheitguppe ON other_lehreinheitguppe.kalender_id = other_kalender.kalender_id
+
+ LEFT JOIN public.tbl_gruppe other_gruppe
+ ON other_gruppe.gruppe_kurzbz = other_lehreinheitguppe.gruppe_kurzbz
+
+ LEFT JOIN public.tbl_studiengang stg
+ ON stg.studiengang_kz = other_lehreinheitguppe.studiengang_kz
+
+ WHERE current_lehreinheitguppe.lehreinheit_id = ?
+ AND other_kalender.status_kurzbz NOT IN ('archived', 'deleted', 'to_delete', 'to_delete_live', 'to_delete_preview')
+ AND current_lehreinheitguppe.studiengang_kz = other_lehreinheitguppe.studiengang_kz
+ AND current_lehreinheitguppe.semester = other_lehreinheitguppe.semester
+ AND ". $group_matching_qry ."
+ AND other_kalender.kalender_id NOT IN (
+ SELECT vorgaenger_kalender_id
+ FROM lehre.tbl_kalender
+ WHERE vorgaenger_kalender_id IS NOT NULL
+ )
+ ";
+
+ $params = [$data->bis, $data->von, $data->lehreinheit_id];
+ }
+
+ $result = $dbModel->execReadOnlyQuery($sql_gruppen, $params);
if (!isError($result) && hasData($result))
{
@@ -187,7 +238,7 @@ class VerbandCollisionCheck implements ICollisionCheck
WHERE current_event_teilnehmer.kalender_id = ?
AND current_event_teilnehmer.rolle_kurzbz = 'teilnehmer'
- AND other_kalender.status_kurzbz NOT IN ('archived', 'deleted', 'to_delete')
+ AND other_kalender.status_kurzbz NOT IN ('archived', 'deleted', 'to_delete', 'to_delete_live', 'to_delete_preview')
AND current_event_teilnehmer.studiengang_kz = other_event_teilnehmer.studiengang_kz
AND current_event_teilnehmer.semester = other_event_teilnehmer.semester
AND (
@@ -301,7 +352,7 @@ class VerbandCollisionCheck implements ICollisionCheck
ON other_gruppe.gruppe_kurzbz = other_lehreinheitguppe.gruppe_kurzbz
WHERE current_kalender.kalender_id IN ({$placeholders})
- AND other_kalender.status_kurzbz NOT IN ('archived', 'deleted', 'to_delete')
+ AND other_kalender.status_kurzbz NOT IN ('archived', 'deleted', 'to_delete', 'to_delete_live', 'to_delete_preview')
AND current_lehreinheitguppe.studiengang_kz = other_lehreinheitguppe.studiengang_kz
AND current_lehreinheitguppe.semester = other_lehreinheitguppe.semester
AND (
diff --git a/application/models/ressource/Kalendersyncstatus_model.php b/application/models/ressource/Kalendersyncstatus_model.php
new file mode 100644
index 000000000..f1cd8aedf
--- /dev/null
+++ b/application/models/ressource/Kalendersyncstatus_model.php
@@ -0,0 +1,14 @@
+dbTable = 'lehre.tbl_kalender_syncstatus';
+ $this->pk = 'kalender_syncstatus_id';
+ }
+}
diff --git a/application/views/Tempus.php b/application/views/Tempus.php
index d82be1426..db49c57db 100644
--- a/application/views/Tempus.php
+++ b/application/views/Tempus.php
@@ -7,11 +7,14 @@
'vue3' => true,
'primevue3' => true,
'tabulator5' => true,
+ 'filtercomponent' => true,
+ 'navigationcomponent' => true,
'vuedatepicker11' => true,
+ 'tags' => true,
'phrases' => array(
'global',
'ui',
- 'notiz',
+ 'lehre'
),
'customCSSs' => [
'public/css/components/vue-datepicker.css',
diff --git a/public/css/Studentenverwaltung.css b/public/css/Studentenverwaltung.css
index 870a58bdf..470ea03e9 100644
--- a/public/css/Studentenverwaltung.css
+++ b/public/css/Studentenverwaltung.css
@@ -8,6 +8,8 @@
@import './components/Notiz.css';
@import './components/Messages.css';
@import './components/AppMenu.css';
+@import './components/horizontalsplit.css';
+
html {
font-size: .875em;
diff --git a/public/css/Tempus.css b/public/css/Tempus.css
index 3e32789e1..6ca71025b 100644
--- a/public/css/Tempus.css
+++ b/public/css/Tempus.css
@@ -110,15 +110,23 @@ body {
background-color: var(--event-bg);
}
-.parkingevent {
+.course-parker {
+ position: relative;
border: 1px dashed !important;
- border-color: #AAAAAA;
padding: 0.5rem;
- margin-bottom: 0.5rem;
- margin-left: 0.5rem;
- margin-right: 0.5rem;
+ margin: 0.5rem;
+ background-color: var(--event-bg);
+ color: var(--event-color, var(--fhc-calendar-color-event, #000));
}
+.unpark-btn {
+ position: absolute;
+ top: 2px;
+ right: 2px;
+ padding: 0 4px;
+ border: none;
+ background: transparent;
+}
:root{
--fhc-calendar-pane-height: calc(100vh - 120px);
}
@@ -171,6 +179,21 @@ body {
}
+.bg-raumvorschlag-slot {
+ opacity: .70;
+}
+
+.bg-raumvorschlag-slot.rating-good {
+ background-color: var(--bs-success);
+}
+.bg-raumvorschlag-slot.rating-mid {
+ background-color: var(--bs-warning);
+}
+.bg-raumvorschlag-slot.rating-bad {
+ background-color: var(--bs-danger);
+}
+
+
.wish-w--2 {
background-color: #FF2200;
}
@@ -185,3 +208,9 @@ body {
}
+.room-option {
+ display: grid;
+ grid-template-columns: 20px minmax(80px, 110px) minmax(100px, 1fr) auto 20px 50px;
+ align-items: center;
+ width: 100%;
+}
diff --git a/public/css/components/calendar/events.css b/public/css/components/calendar/events.css
index 8946c1856..f224c6f7b 100644
--- a/public/css/components/calendar/events.css
+++ b/public/css/components/calendar/events.css
@@ -86,6 +86,9 @@
pointer-events: none;
background-color: transparent !important;
border: none !important;
+ width: fit-content;
+ margin-left: auto;
+ margin-right: auto;
}
.fhc-resize-bar--top {
diff --git a/public/css/components/calendar/frame.css b/public/css/components/calendar/frame.css
index d17cc06b4..68b907179 100644
--- a/public/css/components/calendar/frame.css
+++ b/public/css/components/calendar/frame.css
@@ -68,9 +68,16 @@
/* Grid */
.fhc-calendar-base-grid {
--fhc-calendar-axis-collapsible: .2fr;
+ --fhc-calendar-axis-collapsible-manual: .1fr;
+
background-color: var(--fhc-calendar-bg-gap);
grid-gap: var(--fhc-calendar-gap-header, calc(var(--fhc-calendar-gap, 1px) * 2));
}
+
+.fhc-calendar-base-grid-line-collapsed {
+ overflow: hidden;
+}
+
.fhc-calendar-base-grid .grid-header {
background-color: var(--fhc-calendar-bg-header-gap, var(--fhc-calendar-bg-gap));
}
diff --git a/public/css/tags.css b/public/css/tags.css
index 0230cfbda..ec32f0c69 100644
--- a/public/css/tags.css
+++ b/public/css/tags.css
@@ -17,6 +17,13 @@
transform: scale(1.15);
}
+/* add prop that disasbles hover and cursor*/
+.tag.disabled:hover
+{
+ transform: none;
+ cursor: default;
+}
+
.tag_rot {
background-color: #ff0000ff;
}
diff --git a/public/js/api/factory/tempus/coursepicker.js b/public/js/api/factory/tempus/coursepicker.js
index 92ff4fa6b..80b201a00 100644
--- a/public/js/api/factory/tempus/coursepicker.js
+++ b/public/js/api/factory/tempus/coursepicker.js
@@ -1,16 +1,9 @@
export default {
- search(query) {
- return {
- method: 'get',
- url: 'api/frontend/v1/tempus/coursepicker/search',
- params: { query }
- };
- },
- getByStg(studiengaenge, studiensemester_kurzbz) {
+ getCourses(filter, studiensemester_kurzbz) {
return {
method: 'post',
- url: 'api/frontend/v1/tempus/coursepicker/getByStg',
- params: { studiengaenge, studiensemester_kurzbz }
+ url: 'api/frontend/v1/tempus/coursepicker/getCourses',
+ params: { ...filter, studiensemester_kurzbz }
};
},
diff --git a/public/js/api/factory/tempus/kalender.js b/public/js/api/factory/tempus/kalender.js
index 4afe587e0..046aec385 100644
--- a/public/js/api/factory/tempus/kalender.js
+++ b/public/js/api/factory/tempus/kalender.js
@@ -14,7 +14,15 @@ export default {
}
};
},
- getPlanLecturer(start_date, end_date, collisionCheck = true, maxDailyEventLimit = null)
+ getRaumvorschlagSlots(lehreinheit_id, start_date, end_date)
+ {
+ return {
+ method: 'get',
+ url: '/api/frontend/v1/tempus/Kalender/getRaumvorschlagSlots',
+ params: { lehreinheit_id, start_date, end_date }
+ };
+ },
+ getPlanLecturer(start_date, end_date)
{
return {
method: 'get',
diff --git a/public/js/api/factory/tempus/reservierung.js b/public/js/api/factory/tempus/reservierung.js
index 7f91a6817..d0aa76bb8 100644
--- a/public/js/api/factory/tempus/reservierung.js
+++ b/public/js/api/factory/tempus/reservierung.js
@@ -48,11 +48,11 @@ export default {
url: '/api/frontend/v1/tempus/Reservierung/getRollen',
};
},
- addReservierung(titel, beschreibung, ort_kurzbz, start_date, end_date, teilnehmer, specialFinalGroups, specialGroups, groups) {
+ addReservierung(titel, beschreibung, orte, start_date, end_date, teilnehmer, specialFinalGroups, specialGroups, groups) {
return {
method: 'post',
url: '/api/frontend/v1/tempus/Reservierung/addReservierung',
- params: { titel, beschreibung, ort_kurzbz, start_date, end_date, teilnehmer, specialFinalGroups, specialGroups, groups}
+ params: { titel, beschreibung, orte, start_date, end_date, teilnehmer, specialFinalGroups, specialGroups, groups}
};
},
diff --git a/public/js/api/factory/tempus/sync.js b/public/js/api/factory/tempus/sync.js
new file mode 100644
index 000000000..0dd8d9eb5
--- /dev/null
+++ b/public/js/api/factory/tempus/sync.js
@@ -0,0 +1,83 @@
+/**
+ * Copyright (C) 2026 fhcomplete.org
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+export default {
+ getSyncs(studiensemester_kurzbz) {
+ return {
+ method: 'get',
+ url: 'api/frontend/v1/tempus/KalenderSync/getSyncs',
+ params: { studiensemester_kurzbz }
+ };
+ },
+ loadSync(kalender_syncstatus_id) {
+ return {
+ method: 'get',
+ url: 'api/frontend/v1/tempus/KalenderSync/loadSync',
+ params: { kalender_syncstatus_id }
+ };
+ },
+ getSyncStatus() {
+ return {
+ method: 'get',
+ url: 'api/frontend/v1/tempus/KalenderSync/getSyncStatus',
+ };
+ },
+ add(formData) {
+ return {
+ method: 'post',
+ url: 'api/frontend/v1/tempus/KalenderSync/add',
+ params: { formData }
+ };
+ },
+ delete(kalender_syncstatus_id) {
+ return {
+ method: 'post',
+ url: 'api/frontend/v1/tempus/KalenderSync/delete',
+ params: { kalender_syncstatus_id }
+ };
+ },
+ start(formData) {
+ return {
+ method: 'post',
+ url: 'api/frontend/v1/tempus/KalenderSync/start',
+ params: { formData }
+ };
+ },
+ updateSync(formData) {
+ return {
+ method: 'post',
+ url: 'api/frontend/v1/tempus/KalenderSync/updateSync',
+ params: { formData }
+ };
+ },
+ getStudienplan(oe_kurzbz, studiensemester_kurzbz, ausbildungssemester) {
+ return {
+ method: 'get',
+ url: 'api/frontend/v1/tempus/KalenderSync/getStudienplan',
+ params: { oe_kurzbz, studiensemester_kurzbz, ausbildungssemester }
+ };
+ },
+
+ getMaxSemester(studiengang_kzs) {
+ return {
+ method: 'post',
+ url: 'api/frontend/v1/stv/status/getMaxSemester/',
+ params: { studiengang_kzs }
+ };
+ },
+
+};
\ No newline at end of file
diff --git a/public/js/api/factory/tempus/tag.js b/public/js/api/factory/tempus/tag.js
new file mode 100644
index 000000000..6b8a4ce01
--- /dev/null
+++ b/public/js/api/factory/tempus/tag.js
@@ -0,0 +1,59 @@
+export default {
+
+ getTag(data)
+ {
+ return {
+ method: 'get',
+ url: '/api/frontend/v1/tempus/Tags/getTag',
+ params: data
+ };
+ },
+
+ getTags(data)
+ {
+ return {
+ method: 'get',
+ url: '/api/frontend/v1/tempus/Tags/getTags'
+ };
+ },
+ getTagsByCalendar(calenderGroupId)
+ {
+ return {
+ method: 'get',
+ url: '/api/frontend/v1/tempus/Tags/getTagsByCalendar/' + calenderGroupId,
+ };
+ },
+ addTag(data)
+ {
+ return {
+ method: 'post',
+ url: '/api/frontend/v1/tempus/Tags/addTag',
+ params: data
+ };
+ },
+ updateTag(data)
+ {
+ return {
+ method: 'post',
+ url: '/api/frontend/v1/tempus/Tags/updateTag',
+ params: data
+ };
+ },
+ doneTag(data)
+ {
+ return {
+ method: 'post',
+ url: '/api/frontend/v1/tempus/Tags/doneTag',
+ params: data
+ };
+ },
+
+ deleteTag(data)
+ {
+ return {
+ method: 'post',
+ url: '/api/frontend/v1/tempus/Tags/deleteTag',
+ params: data
+ };
+ },
+};
diff --git a/public/js/apps/Tempus.js b/public/js/apps/Tempus.js
index 160aa3383..b1159da02 100644
--- a/public/js/apps/Tempus.js
+++ b/public/js/apps/Tempus.js
@@ -16,6 +16,7 @@
*/
import FhcTempus from "../components/Tempus/Tempus.js";
+import FhcTempusSync from "../components/Tempus/TempusSync.js";
import Phrasen from "../plugins/Phrasen.js";
import {capitalize} from "../helpers/StringHelpers.js";
@@ -27,6 +28,7 @@ const router = VueRouter.createRouter({
history: VueRouter.createWebHistory(),
routes: [
{ path: `/${ciPath}/Tempus`, component: FhcTempus },
+ { path: `/${ciPath}/Tempus/sync`, component: FhcTempusSync },
]
});
diff --git a/public/js/components/Calendar/Base.js b/public/js/components/Calendar/Base.js
index 85c64ac18..360c9afa2 100644
--- a/public/js/components/Calendar/Base.js
+++ b/public/js/components/Calendar/Base.js
@@ -137,12 +137,14 @@ export default {
"update:date",
"update:mode",
"update:range",
+ "update:date-range",
"drop"
],
data() {
return {
internalView: null,
internalDate: null,
+ pickedRangeEnd: null,
modalEvent: null
};
},
@@ -272,6 +274,10 @@ export default {
}
},
+ handleDateRange({ start, end }) {
+ this.pickedRangeEnd = end;
+ this.$emit('update:date-range', { start, end });
+ },
showEventModal(eventObj) {
this.modalEvent = eventObj;
this.$refs.modal.show();
@@ -300,6 +306,7 @@ export default {
class="card-header"
v-model:date="cDate"
v-model:mode="cMode"
+ @update:date-range="handleDateRange"
@prev="clickPrev"
@next="clickNext"
@click:mode="$emit('click:mode', $event)"
@@ -316,6 +323,7 @@ export default {
:is="modes ? modes[cMode] : null || 'div'"
ref="mode"
v-model:current-date="cDate"
+ :range-end="pickedRangeEnd"
@update:range="$emit('update:range', $event)"
@request-modal-open="showEventModal"
@request-modal-close="hideEventModal"
diff --git a/public/js/components/Calendar/Base/Grid.js b/public/js/components/Calendar/Base/Grid.js
index d63a8d7e6..df322c151 100644
--- a/public/js/components/Calendar/Base/Grid.js
+++ b/public/js/components/Calendar/Base/Grid.js
@@ -61,7 +61,11 @@ export default {
flipAxis: Boolean,
allDayEvents: Boolean,
axisMainCollapsible: Boolean,
- snapToGrid: Boolean
+ snapToGrid: Boolean,
+ dayVisibility: {
+ type: Array,
+ default: null
+ }
},
data() {
return {
@@ -160,7 +164,17 @@ export default {
hasValidEvents() {
return this.events.find(e => e.length);
},
- styleGridCols() {
+ styleGridCols()
+ {
+ if (this.dayVisibility)
+ {
+ let anyVisible = this.dayVisibility.some(day => day);
+ if (anyVisible)
+ {
+ return this.axisMain.map((day, i) => this.isDayCollapsed(i) ? 'var(--fhc-calendar-axis-collapsible-manual, 0.1fr)' : '1fr').join(' ');
+ }
+ }
+
let cols = 'repeat(' + this.axisMain.length + ', 1fr)';
if (this.axisMainCollapsible) {
if (this.hasValidEvents)
@@ -479,6 +493,16 @@ export default {
}
});
},
+ isDayCollapsed(index)
+ {
+ if (this.dayVisibility)
+ {
+ let anyVisible = this.dayVisibility.some(day => day);
+ if (anyVisible)
+ return !this.dayVisibility[index];
+ }
+ return this.axisMainCollapsible && this.hasValidEvents && !this.events[index].length;
+ }
},
setup()
{
@@ -504,7 +528,7 @@ export default {
v-for="(date, index) in axisMain"
:key="index"
class="main-header"
- :class="{'collapsed-header': axisMainCollapsible && hasValidEvents && !events[index].length}"
+ :class="{'collapsed-header': isDayCollapsed(index)}"
:style="'grid-' + axisCol + ':' + (2+index)"
>
@@ -592,6 +616,7 @@ export default {
:date="date"
:events="eventsNormal[index]"
:backgrounds="backgrounds[index]"
+ :class="{ 'fhc-calendar-base-grid-line-collapsed': isDayCollapsed(index) }"
style="position:relative"
@resize-start="handleResizeStart"
:style="'grid-' + axisRow + ':1/-1;grid-' + axisCol + ':' + (1+index)"
diff --git a/public/js/components/Calendar/Base/Grid/Line/EventCard.js b/public/js/components/Calendar/Base/Grid/Line/EventCard.js
index 3851fea14..2a78ed348 100644
--- a/public/js/components/Calendar/Base/Grid/Line/EventCard.js
+++ b/public/js/components/Calendar/Base/Grid/Line/EventCard.js
@@ -9,6 +9,10 @@ export default {
event: { type: Object, required: true },
parked: Boolean
},
+ emits: [
+ 'select-kw',
+ 'unpark'
+ ],
computed: {
dragKalenderCollection() {
return this.event
@@ -16,21 +20,48 @@ export default {
topicString() {
return Array.isArray(this.event.orig.topic) ? this.event.orig.topic.join(', ') : this.event.orig.topic;
},
+ datum() {
+ return luxon.DateTime.fromISO(this.event.orig.datum).toFormat('dd.MM.yyyy')
+ },
+ kw() {
+ return luxon.DateTime.fromISO(this.event.orig.datum).startOf('week', { useLocaleWeeks: true }).localWeekNumber
+ },
},
+ methods: {
+ eventStyle(event) {
+ if (!event.farbe)
+ return undefined;
+ return '--event-bg:#' + event.farbe;
+ },
+ jumpToKw()
+ {
+ this.$emit('select-kw', this.kw);
+ },
+ unpark(evt)
+ {
+ this.$emit('unpark', this.event);
+ }
+ },
+
template: `
+
+
+
{{ topicString || event.orig.titel || event.orig.lehrfach }}
- {{ event.orig.datum }} {{ event.orig.beginn }}–{{ event.orig.ende }}
- · {{ event.orig.ort_kurzbz }}
+ KW: {{ kw }} {{ datum }}
+
+
+ {{ event.orig.beginn }}–{{ event.orig.ende }}
`
-};
+};
\ No newline at end of file
diff --git a/public/js/components/Calendar/Base/Header.js b/public/js/components/Calendar/Base/Header.js
index 39eea114b..7c3fcafcb 100644
--- a/public/js/components/Calendar/Base/Header.js
+++ b/public/js/components/Calendar/Base/Header.js
@@ -32,7 +32,8 @@ export default {
"prev",
"click:mode",
"update:date",
- "update:mode"
+ "update:mode",
+ "update:date-range"
],
data() {
return {
@@ -109,6 +110,7 @@ export default {
class="btn btn-outline-secondary border-0"
@click="$emit('prev')"
:disabled="open"
+ v-if="mode !== 'tableList'"
>
@@ -116,6 +118,7 @@ export default {
:mode="mode"
:date="date"
@update:date="$emit('update:date', $event)"
+ @update:date-range="$emit('update:date-range', $event)"
@open="open = true"
@closed="open = false"
:list-length="modeOptions.length"
@@ -124,6 +127,7 @@ export default {
class="btn btn-outline-secondary border-0"
@click="$emit('next')"
:disabled="open"
+ v-if="mode !== 'tableList'"
>
@@ -131,4 +135,4 @@ export default {
`
-}
+}
\ No newline at end of file
diff --git a/public/js/components/Calendar/Base/Header/Datepicker.js b/public/js/components/Calendar/Base/Header/Datepicker.js
index 98a26945b..3e2ec8327 100644
--- a/public/js/components/Calendar/Base/Header/Datepicker.js
+++ b/public/js/components/Calendar/Base/Header/Datepicker.js
@@ -24,8 +24,14 @@ export default {
}
},
emits: [
- "update:date"
+ "update:date",
+ "update:date-range"
],
+ data() {
+ return {
+ rangeEnd: null
+ };
+ },
computed: {
convertedDate() {
// convert to target TZ then strip TZ Information
@@ -34,34 +40,37 @@ export default {
},
current() {
switch (this.mode) {
- case "month":
- return {month: this.convertedDate.month-1, year: this.convertedDate.year};
- case "list":
- return [this.convertedDate.startOf('day').ts, this.convertedDate.startOf('day').plus({ days: this.listLength }).ts - 1];
- case "week":
- case "tableList":
- return [this.convertedDate.startOf('week', { useLocaleWeeks: true }).ts, this.convertedDate.endOf('week', { useLocaleWeeks: true }).ts];
- case "day":
- return this.convertedDate;
- default:
- return null;
+ case "month":
+ return {month: this.convertedDate.month-1, year: this.convertedDate.year};
+ case "list":
+ return [this.convertedDate.startOf('day').ts, this.convertedDate.startOf('day').plus({ days: this.listLength }).ts - 1];
+ case "week":
+ return [this.convertedDate.startOf('week', { useLocaleWeeks: true }).ts, this.convertedDate.endOf('week', { useLocaleWeeks: true }).ts];
+ case "tableList":
+ return [this.convertedDate.startOf('day').ts, (this.rangeEnd || this.convertedDate).endOf('day').ts];
+ case "day":
+ return this.convertedDate;
+ default:
+ return null;
}
},
title() {
switch (this.mode) {
- case "month":
- return this.date.toLocaleString({ month: 'long', year: 'numeric' });
- case "week":
- case "tableList":
- var year = this.date.localWeekYear;
- var week = this.date.toFormat('nn');
- return this.$p.t('calendar/year_kw', { year, week });
- case "list":
- return this.date.toLocaleString(luxon.DateTime.DATE_FULL) + '-' + this.date.plus({ days: this.listLength - 1 }).toLocaleString(luxon.DateTime.DATE_FULL);
- case "day":
- return this.date.toLocaleString(luxon.DateTime.DATE_FULL);
- default:
- return 'View not Supported';
+ case "month":
+ return this.date.toLocaleString({ month: 'long', year: 'numeric' });
+ case "week":
+ var year = this.date.localWeekYear;
+ var week = this.date.toFormat('nn');
+ return this.$p.t('calendar/year_kw', { year, week });
+ case "tableList":
+ const end = this.rangeEnd ? this.rangeEnd.setZone(this.timezone, { keepLocalTime: true }) : this.date;
+ return this.date.toLocaleString(luxon.DateTime.DATE_FULL) + '-' + end.toLocaleString(luxon.DateTime.DATE_FULL);
+ case "list":
+ return this.date.toLocaleString(luxon.DateTime.DATE_FULL) + '-' + this.date.plus({ days: this.listLength - 1 }).toLocaleString(luxon.DateTime.DATE_FULL);
+ case "day":
+ return this.date.toLocaleString(luxon.DateTime.DATE_FULL);
+ default:
+ return 'View not Supported';
}
},
weekStart() {
@@ -72,20 +81,28 @@ export default {
update(value) {
let date;
switch (this.mode) {
- case "month":
- value.month++;
- date = luxon.DateTime.fromObject(value).setZone(this.timezone, { keepLocalTime: true }).setLocale(this.locale);
- break;
- case "list":
- case "week":
- case "tableList":
- date = luxon.DateTime.fromJSDate(value[0]).setZone(this.timezone, { keepLocalTime: true }).setLocale(this.locale);
- break;
- case "day":
- date = luxon.DateTime.fromJSDate(value).setZone(this.timezone, { keepLocalTime: true }).setLocale(this.locale);
- break;
- default:
- return; // Don't update if the value is invalid!
+ case "month":
+ value.month++;
+ date = luxon.DateTime.fromObject(value).setZone(this.timezone, { keepLocalTime: true }).setLocale(this.locale);
+ break;
+ case "list":
+ case "week":
+ date = luxon.DateTime.fromJSDate(value[0]).setZone(this.timezone, { keepLocalTime: true }).setLocale(this.locale);
+ break;
+ case "tableList":
+ if (!Array.isArray(value) || !value[0] || !value[1])
+ return;
+ this.rangeEnd = luxon.DateTime.fromJSDate(value[1]);
+ const start = luxon.DateTime.fromJSDate(value[0]).setZone(this.timezone, { keepLocalTime: true }).setLocale(this.locale);
+ const end = this.rangeEnd.setZone(this.timezone, { keepLocalTime: true }).setLocale(this.locale);
+ this.$emit('update:date', start);
+ this.$emit('update:date-range', { start, end });
+ return;
+ case "day":
+ date = luxon.DateTime.fromJSDate(value).setZone(this.timezone, { keepLocalTime: true }).setLocale(this.locale);
+ break;
+ default:
+ return; // Don't update if the value is invalid!
}
this.$emit("update:date", date);
},
@@ -99,8 +116,8 @@ export default {
@update:model-value="update"
:format="() => title"
:month-picker="mode == 'month'"
- :week-picker="mode == 'week' || mode == 'tableList'"
- :range="mode == 'list' ? { autoRange: listLength - 1 } : false"
+ :week-picker="mode == 'week'"
+ :range="mode == 'list' ? { autoRange: listLength - 1 } : (mode == 'tableList' ? { partialRange: false } : false)"
:text-input="mode == 'day'"
:week-start="weekStart"
:week-numbers="{ type: weekNumbers }"
diff --git a/public/js/components/Calendar/Mode/Table.js b/public/js/components/Calendar/Mode/Table.js
index 3676114af..529927d5f 100644
--- a/public/js/components/Calendar/Mode/Table.js
+++ b/public/js/components/Calendar/Mode/Table.js
@@ -1,109 +1,57 @@
-import BaseSlider from '../Base/Slider.js';
import TableView from './Table/View.js';
export default {
name: "ModeTable",
components: {
- BaseSlider,
TableView
},
props: {
currentDate: {
type: luxon.DateTime,
required: true
+ },
+ rangeEnd: {
+ type: luxon.DateTime,
+ default: null
}
},
emits: [
- "update:currentDate",
"update:range",
- "click",
- "requestModalOpen"
],
data() {
return {
focusDate: this.currentDate,
- rangeOffset: 0
+ focusEnd: this.rangeEnd || this.currentDate
};
},
computed: {
range() {
- let first = this.focusDate.startOf('week', { useLocaleWeeks: true });
- let last = this.focusDate.endOf('week', { useLocaleWeeks: true });
-
- if (this.rangeOffset != 0) {
- if (this.rangeOffset < 0) {
- first = first.plus({ weeks: this.rangeOffset });
- } else {
- last = last.plus({ weeks: this.rangeOffset });
- }
- }
-
- return luxon.Interval.fromDateTimes(first, last);
+ return luxon.Interval.fromDateTimes(this.focusDate.startOf('day'), this.focusEnd.endOf('day'));
}
},
watch: {
- currentDate() {
- if (this.currentDate.locale != this.focusDate.locale) {
- this.focusDate = this.currentDate;
- this.$emit('update:range', this.range);
- } else {
- this.rangeOffset = this.currentDate.startOf('week', { useLocaleWeeks: true }).diff(this.focusDate.startOf('week', { useLocaleWeeks: true }), 'weeks').weeks;
- if (this.rangeOffset) {
- this.$emit('update:range', this.range);
- this.$refs.slider.slidePages(this.rangeOffset).then(this.updatePage);
- }
- }
+ currentDate(newDate) {
+ this.focusDate = newDate;
+ if (!this.rangeEnd)
+ this.focusEnd = newDate;
+ this.$emit('update:range', this.range);
+ },
+ rangeEnd(newEnd) {
+ this.focusEnd = newEnd || this.focusDate;
+ this.$emit('update:range', this.range);
}
},
methods: {
- prevPage() {
- this.rangeOffset = this.$refs.slider.target - 1;
- this.$emit('update:range', this.range);
- this.$refs.slider.prevPage().then(this.updatePage);
- },
- nextPage() {
- this.rangeOffset = this.$refs.slider.target + 1;
- this.$emit('update:range', this.range);
- this.$refs.slider.nextPage().then(this.updatePage);
- },
- updatePage(weeks) {
- const newFocusDate = this.focusDate.plus({ weeks });
- this.focusDate = newFocusDate;
- this.rangeOffset = 0;
- this.$emit('update:currentDate', this.focusDate);
- this.$emit('update:range', this.range);
- },
- viewAttrs(weeks) {
- const day = this.focusDate.plus({ weeks });
- return { ...this.$attrs, day };
- },
- handleClickDefaults(evt) {
- switch (evt.detail.source) {
- case 'day':
- // default: Set current-date
- this.$emit('update:currentDate', evt.detail.value);
- break;
- case 'event':
- // default: Request Modal
- this.$emit('requestModalOpen', { event: evt.detail.value });
- break;
- }
- }
},
mounted() {
this.$emit('update:range', this.range);
},
template: `
-
-
-
-
-
-
+
`
-}
+}
\ No newline at end of file
diff --git a/public/js/components/Calendar/Mode/Table/View.js b/public/js/components/Calendar/Mode/Table/View.js
index cee1bfe7d..6f3553f7b 100644
--- a/public/js/components/Calendar/Mode/Table/View.js
+++ b/public/js/components/Calendar/Mode/Table/View.js
@@ -1,8 +1,6 @@
import {CoreFilterCmpt} from "../../../../components/filter/Filter.js";
import BsModal from '../../../Bootstrap/Modal.js';
import FormInput from "../../../Form/Input.js";
-import ApiDetails from "../../../../api/lehrveranstaltung/details.js";
-
export default {
name: "TableView",
@@ -20,6 +18,10 @@ export default {
day: {
type: luxon.DateTime,
required: true
+ },
+ end: {
+ type: luxon.DateTime,
+ required: true
}
},
data()
@@ -30,10 +32,13 @@ export default {
},
computed: {
start() {
- return this.day.startOf('week', { useLocaleWeeks: true });
+ return this.day.startOf('day');
+ },
+ tableEnd() {
+ return this.end.endOf('day');
},
preparedEvents() {
- const end = this.start.plus({ days: 7 });
+ const end = this.tableEnd;
return this.events
.filter(e => e.start < end && e.end > this.start)
.sort((a, b) => a.start.ts - b.start.ts)
@@ -112,15 +117,6 @@ export default {
this.$refs.tableViewTable?.tabulator?.setData(newData);
}
},
-/* mounted() {
-
- this.$api.call(ApiDetails.getRaumtyp())
- .then(result => {
- this.raumtyp_array = result.data;
- })
- .catch(this.$fhcAlert.handleSystemError);
-
- },*/
template: /* html */`
luxon.Duration.fromObject({ hours: i + start }));
}
+ },
+ dayVisibility() {
+ return this.axisMain.map(date => this.visibleDays[date.weekday -1])
}
},
methods: {
isToday(date) {
return date.hasSame(luxon.DateTime.now().setZone(this.timezone), 'day');
+ },
+ toggleDay(day)
+ {
+ this.visibleDays.splice(day, 1, !this.visibleDays[day])
+ localStorage.setItem('fhc-calender-visible-days', JSON.stringify(this.visibleDays))
}
},
template: /* html */`
@@ -60,9 +76,10 @@ export default {
:axis-main-collapsible="collapseEmptyDays"
:snap-to-grid="!!timeGrid"
all-day-events
+ :day-visibility="dayVisibility"
>
-
-
+
+
evt.detail.source = 'day'"
@@ -70,6 +87,13 @@ export default {
+
+
diff --git a/public/js/components/Calendar/Tempus.js b/public/js/components/Calendar/Tempus.js
index 7f191569a..7654f1c5b 100644
--- a/public/js/components/Calendar/Tempus.js
+++ b/public/js/components/Calendar/Tempus.js
@@ -16,6 +16,10 @@ export default {
},
inject: {
renderers: {from: 'renderers'},
+ canToggleGrid: {
+ from: 'canToggleGrid',
+ default: false
+ },
appConfig: {
from: 'appConfig',
default: {
@@ -63,15 +67,21 @@ export default {
type: Array,
default: () => ['all']
},
+ showEvents: {
+ type: Boolean,
+ default: true
+ }
},
emits: [
"update:date",
"update:mode",
"update:range",
+ "update:date-range",
"drop",
"resize",
"event-hover",
- "event-unhover"
+ "event-unhover",
+ "open-reservierung"
],
data() {
@@ -150,9 +160,18 @@ export default {
return '--event-bg:#' + event.farbe;
},
updateRange(rangeInterval) {
+ if (this.currentMode === 'tableList')
+ return;
+
this.rangeInterval = rangeInterval;
this.$emit('update:range', rangeInterval);
},
+ handleDateRange({ start, end }) {
+ this.rangeInterval = luxon.Interval.fromDateTimes(start.startOf('day'), end.endOf('day'));
+ this.reset();
+ this.$emit('update:range', this.rangeInterval);
+ this.$emit('update:date-range', { start, end });
+ },
ondrop(payload){
this.$emit('drop', payload);
},
@@ -232,10 +251,11 @@ export default {
@update:date="(newDate, newMode) => $emit('update:date', newDate, newMode)"
@update:mode="(newMode, newDate) => { currentMode = newMode; $emit('update:mode', newMode, newDate) }"
@update:range="updateRange"
+ @update:date-range="handleDateRange"
>
Stundenraster
diff --git a/public/js/components/Tag/Tag.js b/public/js/components/Tag/Tag.js
index fc20213c9..f85b57134 100644
--- a/public/js/components/Tag/Tag.js
+++ b/public/js/components/Tag/Tag.js
@@ -15,6 +15,10 @@ export default {
'deleted',
],
props: {
+ isListItemShown: {
+ type: Boolean,
+ default: true
+ },
endpoint: {
type: Object,
required: true
@@ -79,7 +83,7 @@ export default {
'id': tag_id
};
- this.$api.call(this.endpoint.getTag(getData))
+ await this.$api.call(this.endpoint.getTag(getData))
.then(result => result.data)
.then(result => this.openModal(result))
},
@@ -132,7 +136,7 @@ export default {
{
postData.id = this.selectedTagId;
this.tagData.id = this.selectedTagId;
- this.$api.call(this.endpoint.updateTag(postData));
+ await this.$api.call(this.endpoint.updateTag(postData));
this.$emit("updated", this.tagData);
this.$refs.tagModal.hide();
}
@@ -144,7 +148,7 @@ export default {
return;
}
- this.$api.call(this.endpoint.addTag(postData))
+ await this.$api.call(this.endpoint.addTag(postData))
.then(response => response.data)
.then(response => {
if (typeof response === 'number') {
@@ -171,7 +175,7 @@ export default {
done: !this.tagData.done,
notiz: this.tagData.notiz,
}
- this.$api.call(this.endpoint.doneTag(postData))
+ await this.$api.call(this.endpoint.doneTag(postData))
this.$emit("updated", this.tagData);
this.$refs.tagModal.hide();
},
@@ -180,7 +184,7 @@ export default {
let postData = {
id: this.selectedTagId
}
- this.$api.call(this.endpoint.deleteTag(postData))
+ await this.$api.call(this.endpoint.deleteTag(postData))
this.$emit("deleted", this.selectedTagId)
this.$refs.tagModal.hide();
},
@@ -228,7 +232,7 @@ export default {
}
},
template: `
-
-
+
{{$p.t('lehre', 'cptitleempty')}}
{{$p.t('lehre', 'cpempty')}}
@@ -264,7 +301,7 @@ export default {
style="cursor:grab"
:style="courseStyle(course)"
class="course-picker-row rounded-3 mb-2 p-2"
- :class="{ 'course-picker-row-pinned': isPinned(course) }"
+ :class="{ 'course-picker-row-pinned': isPinned(course), 'course-picker-row-preview': isRaumvorschlagActive(course) }"
v-draggable:move.noimage="dragLehreinheitCollection(course)"
tabindex="0"
>
@@ -284,6 +321,19 @@ export default {
v-tooltip="isMultiWeek(course) ? 'MultiWeek aktiv' : 'MultiWeek Verplanung aktivieren'"
@click.stop="toggleMultiWeek(course)"
>
+
+
diff --git a/public/js/components/Tempus/Filters/LectureSelection.js b/public/js/components/Tempus/Filters/LectureSelection.js
index a25856b86..d08a35774 100644
--- a/public/js/components/Tempus/Filters/LectureSelection.js
+++ b/public/js/components/Tempus/Filters/LectureSelection.js
@@ -47,6 +47,10 @@ export default {
Zeitwünsche
+
+
+ Course Picker
+
diff --git a/public/js/components/Tempus/Filters/searchbarOptions.js b/public/js/components/Tempus/Filters/searchbarOptions.js
index 42150ed41..8ca4fe7a5 100644
--- a/public/js/components/Tempus/Filters/searchbarOptions.js
+++ b/public/js/components/Tempus/Filters/searchbarOptions.js
@@ -43,6 +43,25 @@ export function getTempusSearchbarOptions(self)
self.addToFilter(data, 'mitarbeiter');
}
},
+ {
+ label: "zum Course Picker hinzufügen",
+ icon: "fas fa-plus",
+ type: "function",
+ action: (data) => {
+ self.addToCoursePicker(data);
+ }
+ },
+ {
+ label: "LVVerwaltung",
+ icon: "fas fa-up-right-from-square",
+ type: "function",
+ action: (data) => {
+ const link = FHC_JS_DATA_STORAGE_OBJECT.app_root +
+ FHC_JS_DATA_STORAGE_OBJECT.ci_router +
+ "/LVVerwaltung/stdsem/" + self.selectedStudiensemester + "/emp/" + data.uid;
+ window.open(link, "_blank");
+ }
+ },
]
},
}
diff --git a/public/js/components/Tempus/Header.js b/public/js/components/Tempus/Header.js
new file mode 100644
index 000000000..d1dbc2dfa
--- /dev/null
+++ b/public/js/components/Tempus/Header.js
@@ -0,0 +1,108 @@
+import CoreSearchbar from "../searchbar/searchbar.js";
+import NavLanguage from "../navigation/Language.js";
+
+export default {
+ name: "TempusHeader",
+ components: {
+ CoreSearchbar,
+ NavLanguage,
+ },
+ props: {
+ tempusRoot: String,
+ avatarUrl: String,
+ logoutUrl: String,
+ searchbaroptions: {
+ type: Object,
+ required: true,
+ },
+ searchfunction: {
+ type: Function,
+ required: true,
+ },
+ },
+ emits: ["language-changed"],
+ methods: {
+ focusSearchbar() {
+ this.$refs.searchbar?.$refs?.input?.focus();
+ },
+ },
+ template: /* html */ `
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `,
+};
diff --git a/public/js/components/Tempus/HistoryModal.js b/public/js/components/Tempus/HistoryModal.js
new file mode 100644
index 000000000..9716bc1f1
--- /dev/null
+++ b/public/js/components/Tempus/HistoryModal.js
@@ -0,0 +1,68 @@
+/**
+ * Copyright (C) 2024 fhcomplete.org
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+import BsModal from "../Bootstrap/Modal.js";
+
+export default {
+ name: "HistoryModal",
+ components: {
+ BsModal,
+ },
+ props: {
+ entries: {
+ type: Array,
+ default: () => [],
+ },
+ },
+ methods: {
+ show() {
+ this.$refs.modal.show();
+ },
+ hide() {
+ this.$refs.modal.hide();
+ },
+ },
+ template: `
+
+ History
+
+
+
+
+ Von
+ Bis
+ Status
+ Ort
+
+
+
+
+ {{ entry.von }}
+ {{ entry.bis }}
+ {{ entry.status_kurzbz }}
+ {{ entry.ort }}
+
+
+
+
+
+ `,
+};
diff --git a/public/js/components/Tempus/Modal.js b/public/js/components/Tempus/Modal.js
new file mode 100644
index 000000000..8792a2eed
--- /dev/null
+++ b/public/js/components/Tempus/Modal.js
@@ -0,0 +1,353 @@
+import BsModal from '../Bootstrap/Modal.js';
+import CoreForm from '../Form/Form.js';
+import FormValidation from '../Form/Validation.js';
+import FormInput from '../Form/Input.js';
+
+import ApiTempusSync from '../../api/factory/tempus/sync.js';
+
+export default {
+ components: {
+ BsModal,
+ CoreForm,
+ FormValidation,
+ FormInput
+ },
+ props: {
+ config: Object,
+ stsem_kurzbz: String
+ },
+ emits: [
+ 'saved'
+ ],
+ data() {
+ return {
+ mode: 'new',
+ formData: {},
+ studienplaene: [],
+ max_semester: null,
+ syncStati: [],
+ loading: false
+ };
+ },
+ computed: {
+ semester() {
+ if (!this.max_semester)
+ return [];
+
+ return Array.from({length: this.max_semester}, (_, index) => index);
+ },
+ studienplanTrigger() {
+ return [
+ this.formData.oe_kurzbz,
+ this.formData.studiensemester_kurzbz,
+ this.formData.ausbildungssemester
+ ].join('|');
+ }
+ },
+ watch: {
+ 'formData.oe_kurzbz'(newOrganisation)
+ {
+ if (!newOrganisation) {
+ this.max_semester = null;
+ this.studienplaene = [];
+ return;
+ }
+
+ let filtered_org = (this.config.organisationen ?? []).filter(org => org.oe_kurzbz === newOrganisation)[0];
+
+ if (filtered_org && filtered_org.studiengang_kz)
+ this.getSemester(filtered_org.studiengang_kz);
+ else
+ this.max_semester = null;
+ },
+ studienplanTrigger() {
+ this.getStudienplan();
+ }
+ },
+ methods: {
+
+ openNew() {
+ this.$refs.modal.hide();
+
+ this.mode = 'new';
+ this.max_semester = null;
+ this.studienplaene = [];
+ this.formData = {
+ oe_kurzbz: null,
+ studiensemester_kurzbz: this.stsem_kurzbz ?? null,
+ datum_bis: null,
+ studienplan_id: null,
+ ausbildungssemester: null,
+ sync_status_kurzbz: null,
+ mail: false
+ };
+
+ this.$refs.form.clearValidation()
+ this.$refs.modal.show()
+ },
+ openEdit(entry) {
+ if (!entry || entry.kalender_syncstatus_id === undefined || entry.kalender_syncstatus_id === null)
+ {
+ return;
+ }
+
+ this.$refs.modal.hide();
+ this.mode = 'edit';
+
+ this.$api
+ .call(ApiTempusSync.loadSync(entry.kalender_syncstatus_id))
+ .then(result => {
+ this.formData = result.data;
+ return this.$refs.form.clearValidation();
+ })
+ .then(() => this.$refs.modal.show())
+ .catch(this.$fhcAlert.handleSystemError);
+ },
+
+ openStart() {
+ this.$refs.modal.hide();
+
+ this.mode = 'start';
+ this.max_semester = null;
+ this.studienplaene = [];
+ this.formData = {
+ oe_kurzbz: null,
+ studiensemester_kurzbz: this.stsem_kurzbz ?? null,
+ datum_bis: null,
+ sync_status_kurzbz: null,
+ studienplan_id: null,
+ ausbildungssemester: null,
+ mail: false
+ };
+
+ this.$refs.form.clearValidation()
+ this.$refs.modal.show()
+ },
+ getSemester(stg_kz) {
+ this.$api.call(ApiTempusSync.getMaxSemester([stg_kz]))
+ .then(response => response.data)
+ .then(response => {
+ this.max_semester = response ?? null;
+
+ let found = this.semester.filter(semester => semester === this.formData.ausbildungssemester);
+ if (found.length === 0)
+ this.formData.ausbildungssemester = null;
+ })
+ .catch(error => {
+ this.$fhcAlert.handleSystemError(error);
+ });
+ },
+ getStudienplan() {
+ if (!this.formData.oe_kurzbz || !this.formData.studiensemester_kurzbz) {
+ this.studienplaene = [];
+ return;
+ }
+
+ this.$api.call(ApiTempusSync.getStudienplan(
+ this.formData.oe_kurzbz,
+ this.formData.studiensemester_kurzbz,
+ this.formData.ausbildungssemester ?? null
+ ))
+ .then(response => response.data)
+ .then(response => {
+ this.studienplaene = response ?? [];
+ let found = this.studienplaene.filter(plan => plan.studienplan_id === this.formData.studienplan_id);
+ if (found.length === 0)
+ this.formData.studienplan_id = null;
+ })
+ .catch(error => {
+ this.$fhcAlert.handleSystemError(error);
+ });
+ },
+ save() {
+ if (this.mode === 'start')
+ this.start();
+ else if (this.mode === 'new')
+ this.insert();
+ else
+ this.update();
+ },
+ insert() {
+ this.$refs.form
+ .call(ApiTempusSync.add(this.formData))
+ .then(() => {
+ this.$fhcAlert.alertSuccess(this.$p.t('ui', 'successSave'));
+ this.$emit('saved', this.formData.studiensemester_kurzbz);
+ this.$refs.modal.hide();
+ })
+ .catch(this.$fhcAlert.handleSystemError);
+ },
+ update() {
+ this.$refs.form
+ .call(ApiTempusSync.updateSync(this.formData))
+ .then(() => {
+ this.$fhcAlert.alertSuccess(this.$p.t('ui', 'successSave'));
+ this.$emit('saved', this.formData.studiensemester_kurzbz);
+ this.$refs.modal.hide();
+ })
+ .catch(this.$fhcAlert.handleSystemError);
+ },
+ start(data) {
+ this.loading = true;
+ return this.$refs.form
+ .call(ApiTempusSync.start(data ?? this.formData))
+ .then(() => {
+ this.$fhcAlert.alertSuccess(this.$p.t('ui', 'successStart'));
+ this.$refs.modal.hide();
+ this.loading = false;
+ })
+ .catch(this.$fhcAlert.handleSystemError)
+ .finally(() => {
+ this.loading = false;
+ });
+ },
+ hideModal()
+ {
+ this.formData = {}
+ }
+ },
+ created() {
+
+ this.$api
+ .call(ApiTempusSync.getSyncStatus())
+ .then(result => this.syncStati = result.data ?? [])
+ .catch(error => this.$fhcAlert.handleSystemError(error));
+ },
+ template: `
+
+
+ {{ $p.t('lehre', 'tempus_sync_new') }}
+ {{ $p.t('lehre', 'tempus_sync_edit') }}
+ {{ $p.t('lehre', 'tempus_sync_start') }}
+
+
+
+
+
+
+
+ [{{ organisation.organisationseinheittyp_kurzbz }}] {{ organisation.bezeichnung }} {{ ['b', 'm'].includes(organisation.typ) ? organisation.stgbezeichnung : '' }}
+
+
+
+
+
+ {{ studiensemester.studiensemester_kurzbz }}
+
+
+
+
+ -- {{ $p.t('fehlermonitoring', 'keineAuswahl') }} --
+
+ {{ number }}
+
+
+
+
+ -- {{ $p.t('fehlermonitoring', 'keineAuswahl') }} --
+
+ {{ plan.bezeichnung }}-{{plan.orgform_kurzbz}}
+
+
+
+
+
+
+
+ -- {{ $p.t('fehlermonitoring', 'keineAuswahl') }} --
+
+ {{ status.bezeichnung }}
+
+
+
+
+
+
+
+
+
+
+ {{ $p.t('global', 'jetztStarten') }}
+ {{ $p.t('ui', 'ok') }}
+
+
+ `
+};
\ No newline at end of file
diff --git a/public/js/components/Tempus/ParkingSlot.js b/public/js/components/Tempus/ParkingSlot.js
index 6bc7bbf95..2016a925f 100644
--- a/public/js/components/Tempus/ParkingSlot.js
+++ b/public/js/components/Tempus/ParkingSlot.js
@@ -9,7 +9,7 @@ export default {
directives: {
drop
},
- emits: ['update:parkedKeys'],
+ emits: ['update:parkedKeys', 'select-kw'],
data() {
return {
parked: [],
@@ -66,11 +66,12 @@ export default {
>
diff --git a/public/js/components/Tempus/RaumauswahlModal.js b/public/js/components/Tempus/RaumauswahlModal.js
index 4e20e1c17..68c955992 100644
--- a/public/js/components/Tempus/RaumauswahlModal.js
+++ b/public/js/components/Tempus/RaumauswahlModal.js
@@ -1,11 +1,14 @@
import BsModal from "../Bootstrap/Modal.js";
import ApiKalender from "../../api/factory/tempus/kalender.js";
+import FormInput from "../../../js/components/Form/Input.js";
+
export default {
name: "RaumauswahlModal",
components: {
BsModal,
Listbox: primevue.listbox,
+ FormInput
},
emits: ["saved"],
data() {
@@ -14,6 +17,9 @@ export default {
selectedRooms: [],
event: null,
loading: false,
+ mode: 'edit',
+ pendingEvent: null,
+ location: null,
};
},
methods: {
@@ -22,10 +28,12 @@ export default {
if (!orig?.kalender_id)
return;
+ this.mode = 'edit';
this.event = orig;
this.loading = true;
this.entries = [];
this.selectedRooms = [];
+ this.location = this.event.location
this.$refs.modal.show();
@@ -53,13 +61,51 @@ export default {
},
hide() {
this.selectedRooms = [];
+ this.pendingEvent = null;
this.$refs.modal.hide();
},
- async confirm() {
- let ort_kurzbz = this.selectedRooms.map((room) => room.ort_kurzbz);
+ showForNew(lehreinheit_id, start_time, end_time, vorschlaege)
+ {
+ this.mode = 'new'
+ this.pendingEvent = {lehreinheit_id, start_time, end_time}
+ this.event = null;
+ this.loading = false;
+ this.entries = vorschlaege ?? [];
+ this.selectedRooms = [];
- if (ort_kurzbz.length < 1)
+ this.$refs.modal.show();
+ },
+
+ async confirm() {
+
+ if (this.mode === 'new')
+ {
+ const result = await this.$api.call(
+ ApiKalender.addKalenderEvent(
+ this.pendingEvent.lehreinheit_id,
+ this.selectedRooms.map((room) => room.ort_kurzbz),
+ this.pendingEvent.start_time,
+ this.pendingEvent.end_time
+ )
+ ).then((result) => result.data)
+
+ if (result.needs_room_selection)
+ {
+ this.showForNew(this.pendingEvent.lehreinheit_id, this.pendingEvent.start_time, this.pendingEvent.end_time, result.raum_vorschlaege);
+ return;
+ }
+
+ this.hide();
+ this.$emit('saved');
+ return;
+ }
+ let orte = {};
+
+ orte.ort_kurzbz = this.selectedRooms.map((room) => room.ort_kurzbz);
+ orte.location = this.location;
+
+ if (orte.ort_kurzbz.length < 1 && orte.location?.trim().length < 1)
{
await this.$api.call(
ApiKalender.deleteOrtEntry(this.event.kalender_id),
@@ -68,7 +114,7 @@ export default {
else
{
await this.$api.call(
- ApiKalender.updateKalenderEvent(this.event.kalender_id, {ort_kurzbz}),
+ ApiKalender.updateKalenderEvent(this.event.kalender_id, {orte}),
);
}
@@ -119,16 +165,38 @@ export default {
listStyle="max-height: 300px"
:emptyMessage="$p.t('ui', 'keineEintraegeGefunden')"
>
-
-
- {{ option.ort_kurzbz }}
- {{ option.score }}
-
-
+
+
+
+
+
+ [{{ option.raumtyp_kurzbz }}]
+
+
+
+ {{ option.ort_kurzbz }}
+
+
+ {{ option.max_person }} Pers.
+
+
+ {{ option.score }}
+
+
+
+
+
diff --git a/public/js/components/Tempus/Renderer/Lehreinheit/calendarEvent.js b/public/js/components/Tempus/Renderer/Lehreinheit/calendarEvent.js
index ae375da8d..4d2a92fc4 100644
--- a/public/js/components/Tempus/Renderer/Lehreinheit/calendarEvent.js
+++ b/public/js/components/Tempus/Renderer/Lehreinheit/calendarEvent.js
@@ -1,13 +1,19 @@
export default {
- props:{
+ props: {
event: {
type: Object,
- required: true
- }
+ required: true,
+ },
},
- computed:{
+ computed: {
classes() {
- const classes = ['cis-renderer-lehreinheit-calendar-event', 'calendar-event-default', 'h-100', 'w-100', 'p-1'];
+ const classes = [
+ 'cis-renderer-lehreinheit-calendar-event',
+ 'calendar-event-default',
+ 'h-100',
+ 'w-100',
+ 'p-1',
+ ];
if (this.event.collisions) {
classes.push('calendar-event-collisions');
@@ -16,79 +22,147 @@ export default {
return classes;
},
topicString() {
- return Array.isArray(this.event.topic) ? this.event.topic.join(', ') : this.event.topic;
+ return Array.isArray(this.event.topic)
+ ? this.event.topic.join(', ')
+ : this.event.topic;
},
ortString() {
- return Array.isArray(this.event.ort_kurzbz) ? this.event.ort_kurzbz.join(', ') : this.event.ort_kurzbz;
+ let orte = [
+ ...(this.event.ort_kurzbz || []),
+ this.event.location?.trim(),
+ ].filter(Boolean);
+ return orte.join(', ');
},
gruppeString() {
return Array.isArray(this.event.gruppe)
- ? this.event.gruppe.map(gruppe => gruppe.bezeichnung).join(', ')
+ ? this.event.gruppe.map((gruppe) => gruppe.bezeichnung).join(', ')
: this.event.gruppe;
},
+ statusIcon()
+ {
+ if (['planning', 'sync_preview'].includes(this.event.status_kurzbz))
+ return 'fa-solid fa-pen-ruler text-muted';
+ else if (['preview', 'sync_live', 'to_delete_preview'].includes(this.event.status_kurzbz))
+ return 'fa-solid fa-chalkboard-user text-muted';
+ else if (['live', 'to_delete_live'].includes(this.event.status_kurzbz))
+ return 'fa-solid fa-user-graduate text-muted'
+ },
tooltipString() {
const tooltipArray = [];
- tooltipArray.push([
- this.$p.t('global/uhrzeit'),
- [this.start, this.end].join(' - ')
- ].join(": "));
+ tooltipArray.push(
+ [this.$p.t('global/uhrzeit'), [this.start, this.end].join(' - ')].join(
+ ': ',
+ ),
+ );
- tooltipArray.push([
- this.$p.t('profilUpdate/topic'),
- this.topicString
- ].join(": "));
+ tooltipArray.push(
+ [this.$p.t('profilUpdate/topic'), this.topicString].join(': '),
+ );
- tooltipArray.push([
- this.$p.t('person/ort'),
- this.ortString
- ].join(": "));
+ tooltipArray.push([this.$p.t('person/ort'), this.ortString].join(': '));
if (this.gruppeString) {
- tooltipArray.push([
- this.$p.t('lehre/gruppe'),
- this.gruppeString
- ].join(": "));
+ tooltipArray.push(
+ [this.$p.t('lehre/gruppe'), this.gruppeString].join(': '),
+ );
}
if (Array.isArray(this.event.lektor) && this.event.lektor.length > 0) {
if (this.event.lektor.length > 3) {
- tooltipArray.push([
- this.$p.t('lehre/lektor'),
- this.event.lektor.slice(0, 3).map(lektor => lektor.kurzbz).join("\n")
- + "\n" + this.$p.t('lehre/weitereLektoren', [this.event.lektor.length - 3])
- ].join(": "));
+ tooltipArray.push(
+ [
+ this.$p.t('lehre/lektor'),
+ this.event.lektor
+ .slice(0, 3)
+ .map((lektor) => lektor.kurzbz)
+ .join('\n') +
+ '\n' +
+ this.$p.t('lehre/weitereLektoren', [
+ this.event.lektor.length - 3,
+ ]),
+ ].join(': '),
+ );
} else {
- tooltipArray.push([
- this.$p.t('lehre/lektor'),
- this.event.lektor.map(lektor => lektor.kurzbz).join("\n")
- ].join(": "));
+ tooltipArray.push(
+ [
+ this.$p.t('lehre/lektor'),
+ this.event.lektor.map((lektor) => lektor.kurzbz).join('\n'),
+ ].join(': '),
+ );
}
}
- return tooltipArray.join("\n");
+ return tooltipArray.join('\n');
},
start() {
- return luxon.Duration
- .fromISOTime(this.event.beginn)
- .toISOTime({ suppressSeconds: true });
+ return luxon.Duration.fromISOTime(this.event.beginn).toISOTime({
+ suppressSeconds: true,
+ });
},
end() {
- return luxon.Duration
- .fromISOTime(this.event.ende)
- .toISOTime({ suppressSeconds: true });
- }
+ return luxon.Duration.fromISOTime(this.event.ende).toISOTime({
+ suppressSeconds: true,
+ });
+ },
+ tags() {
+ if (typeof this.event.tags === 'string') {
+ try {
+ return JSON.parse(this.event.tags);
+ } catch (e) {
+ console.error('Failed to parse tags:', e);
+ return [];
+ }
+ }
+
+ return this.event.tags || [];
+ },
+ tagsTooltip() {
+ if (this.tags.length === 0) {
+ return '';
+ }
+
+ return this.tags.map((tag) => tag.beschreibung).join('\n');
+ },
+ resourcesTooltip() {
+ if (!this.event.resources || this.event.resources.length === 0) {
+ return '';
+ }
+
+ let resources = this.event.resources;
+ if (typeof this.event.resources === 'string') {
+ try {
+ resources = JSON.parse(this.event.resources);
+ } catch (e) {
+ console.error('Failed to parse resources:', e);
+ return '';
+ }
+ }
+
+ return resources.map((resource) => resource.beschreibung).join('\n');
+ },
},
- template: /*html*/`
+ template: /*html*/ `
-
+
+
+
+
+
+ {{event.verplante_stunden}}
`,
-}
+};
diff --git a/public/js/components/Tempus/Renderer/Lehreinheit/modalContent.js b/public/js/components/Tempus/Renderer/Lehreinheit/modalContent.js
index 47e7e0a73..aebf1455a 100644
--- a/public/js/components/Tempus/Renderer/Lehreinheit/modalContent.js
+++ b/public/js/components/Tempus/Renderer/Lehreinheit/modalContent.js
@@ -53,11 +53,33 @@ export default {
ortString() {
return Array.isArray(this.event.ort_kurzbz) ? this.event.ort_kurzbz.join(', ') : this.event.ort_kurzbz;
},
+ tags() {
+ if (typeof this.event.tags === 'string') {
+ try {
+ return JSON.parse(this.event.tags);
+ } catch (e) {
+ console.error('Failed to parse tags:', e);
+ return [];
+ }
+ }
+
+ return this.event.tags || [];
+ },
+ resourcesValue() {
+ let resources = this.event.resources;
+ if (typeof this.event.resources === 'string') {
+ try {
+ resources = JSON.parse(this.event.resources);
+ } catch (e) {
+ console.error('Failed to parse resources:', e);
+ return null;
+ }
+ }
+
+ return resources.map(resource => resource.beschreibung).join(', ');
+ },
},
methods: {
- mehtodNumberPadding: function (number) {
- return numberPadding(number);
- },
methodFormatDate: function (d) {
return formatDate(d);
},
@@ -78,7 +100,7 @@ export default {
});
}
},
- template: `
+ template: /*html*/`
{{$p.t('lvinfo','lehrveranstaltungsinformationen')}}
@@ -143,9 +165,36 @@ export default {
}}
{{event.organisationseinheit}}
+
+ {{
+ $p.t('ui','betriebsmittel')?
+ $p.t('ui','betriebsmittel')+':'
+ :''
+ }}
+
+ {{ resourcesValue }}
+
+
+
+ {{
+ $p.t('ui','tags')?
+ $p.t('ui','tags')+':'
+ :''
+ }}
+
+
+ {{ tag.beschreibung }}
+
+
+
`,
-}
\ No newline at end of file
+}
diff --git a/public/js/components/Tempus/Renderer/Reservierungen/calendarEvent.js b/public/js/components/Tempus/Renderer/Reservierungen/calendarEvent.js
index 5a511a902..750efb417 100644
--- a/public/js/components/Tempus/Renderer/Reservierungen/calendarEvent.js
+++ b/public/js/components/Tempus/Renderer/Reservierungen/calendarEvent.js
@@ -83,7 +83,8 @@ export default {
.toISOTime({ suppressSeconds: true });
},
ortString() {
- return Array.isArray(this.event.ort_kurzbz) ? this.event.ort_kurzbz.join(', ') : this.event.ort_kurzbz;
+ let orte = [...(this.event.ort_kurzbz || []), this.event.location?.trim()].filter(Boolean)
+ return orte.join(', ')
},
topicString() {
return Array.isArray(this.event.titel) ? this.event.titel.join(', ') : this.event.titel;
diff --git a/public/js/components/Tempus/Renderer/Reservierungen/modalContent.js b/public/js/components/Tempus/Renderer/Reservierungen/modalContent.js
index 121c70935..69dbca70e 100644
--- a/public/js/components/Tempus/Renderer/Reservierungen/modalContent.js
+++ b/public/js/components/Tempus/Renderer/Reservierungen/modalContent.js
@@ -52,9 +52,6 @@ export default {
},
},
methods: {
- mehtodNumberPadding: function (number) {
- return numberPadding(number);
- },
methodFormatDate: function (d) {
return formatDate(d);
},
@@ -133,4 +130,4 @@ export default {
`,
-}
\ No newline at end of file
+}
diff --git a/public/js/components/Tempus/Reservierung.js b/public/js/components/Tempus/Reservierung.js
index c0fe64aba..0c39059d3 100644
--- a/public/js/components/Tempus/Reservierung.js
+++ b/public/js/components/Tempus/Reservierung.js
@@ -33,7 +33,8 @@ export default {
show_all_fields: false,
filteredUsers: [],
filteredGroups: [],
- abortController: null
+ abortController: null,
+ location: null
};
},
created()
@@ -141,11 +142,16 @@ export default {
},
save()
{
+
+ let orte = {
+ ort_kurzbz: this.selected_rooms.map(r => r.ort_kurzbz),
+ location: this.location
+ }
this.$api.call(
ApiReservierung.addReservierung(
this.titel,
this.beschreibung,
- this.selected_rooms.map(r => r.ort_kurzbz),
+ orte,
luxon.DateTime.fromFormat(this.start, 'yyyy-MM-dd HH:mm').toISO(),
luxon.DateTime.fromFormat(this.end, 'yyyy-MM-dd HH:mm').toISO(),
this.teilnehmer.filter(t => t.uid && t.rolle).map(nehmer => ({ uid: nehmer.uid, rolle: nehmer.rolle })),
@@ -220,6 +226,14 @@ export default {
+
+
+
.
+ */
+import BsModal from "../Bootstrap/Modal.js";
+import FormInput from "../Form/Input.js";
+import ApiOperationalResourceToCalender from "../../api/factory/operationalResourceToCalender.js";
+
+export default {
+ name: "ResourcesAssignmentModal",
+ components: {
+ BsModal,
+ FormInput,
+ },
+ emits: ["saveFinished"],
+ data() {
+ return {
+ calendar: null,
+ availableResources: [],
+ filteredAvailableResources: [],
+ selectedAvailableResource: null,
+ assignedResources: [],
+ areFormButtonsDisplayed: false,
+ };
+ },
+ computed: {
+ dropdownParsedAvailableResources() {
+ return this.availableResources
+ .map((unit) => ({
+ label: unit.beschreibung,
+ value: unit.betriebsmittel_id,
+ data: unit,
+ }))
+ .sort((a, b) => a.label?.localeCompare(b.label));
+ },
+ },
+ watch: {
+ selectedAvailableResource(newValue) {
+ if (!newValue) return;
+
+ this.assignedResources.push({
+ betriebsmittel_kalender_id: null,
+ betriebsmittel_id: newValue.data.betriebsmittel_id,
+ beschreibung: newValue.data.beschreibung,
+ anmerkung: "",
+ isNoteTextareaShown: false,
+ });
+
+ this.areFormButtonsDisplayed = true;
+ },
+ },
+ methods: {
+ async open(calendar) {
+ if (!calendar?.kalender_id) return;
+
+ this.calendar = calendar;
+ this.availableResources =
+ await this.fetchSchedulableResourcesByCalender(calendar.kalender_id);
+ this.filteredAvailableResources = [
+ ...this.dropdownParsedAvailableResources,
+ ];
+ this.assignedResources =
+ await this.fetchAssignedResourcesByCalender(calendar.kalender_id);
+
+ this.show();
+ },
+ show() {
+ this.$refs.modal.show();
+ },
+ hide() {
+ this.$refs.modal.hide();
+ },
+ reset() {
+ this.calendar = null;
+ this.availableResources = [];
+ this.filteredAvailableResources = [];
+ this.selectedAvailableResource = null;
+ this.assignedResources = [];
+ this.areFormButtonsDisplayed = false;
+ },
+ async fetchAssignedResourcesByCalender(calenderId) {
+ const result = await this.$api.call(
+ ApiOperationalResourceToCalender.getAssignedResourcesByCalender(
+ calenderId,
+ ),
+ );
+
+ if (result.meta.status === "success") {
+ return result.data
+ .filter((unit) => !!unit)
+ .map((unit) => ({
+ isNoteTextareaShown:
+ unit.anmerkung && unit.anmerkung.trim() !== "",
+ ...unit,
+ }));
+ }
+
+ this.$fhcAlert.alertError(
+ this.$p.t("ui", "failed_assigned_resources_fetch_error_message"),
+ );
+ return [];
+ },
+ async fetchSchedulableResourcesByCalender(calendarId) {
+ const result = await this.$api.call(
+ ApiOperationalResourceToCalender.getSchedulableResourcesByCalendar(
+ calendarId,
+ ),
+ );
+
+ if (result.meta.status === "success") return result.data;
+
+ this.$fhcAlert.alertError(
+ this.$p.t("ui", "failed_schedulable_resources_fetch_error_message"),
+ );
+ return [];
+ },
+ filterAvailableResources(event) {
+ const query = event.query.toLowerCase();
+ const unassignedResources = this.dropdownParsedAvailableResources.filter(
+ (unit) =>
+ !this.assignedResources.some(
+ (assigned) => assigned.betriebsmittel_id === unit.value,
+ ),
+ );
+
+ this.filteredAvailableResources = query
+ ? unassignedResources.filter((unit) =>
+ unit.label.toLowerCase().includes(query),
+ )
+ : unassignedResources;
+ },
+ toggleAssignedResourceNoteInput(resource) {
+ const assignedResource = this.assignedResources.find(
+ (assigned) => assigned.betriebsmittel_id === resource.betriebsmittel_id,
+ );
+
+ if (assignedResource) {
+ assignedResource.isNoteTextareaShown =
+ !assignedResource.isNoteTextareaShown;
+ }
+
+ this.areFormButtonsDisplayed = true;
+ },
+ removeAssignedResource(resource) {
+ this.assignedResources = this.assignedResources.filter(
+ (assigned) =>
+ assigned.betriebsmittel_id !== resource.betriebsmittel_id,
+ );
+ this.areFormButtonsDisplayed = true;
+ },
+ async refreshResourcesAssignmentModalData() {
+ if (!this.calendar?.kalender_id) return;
+
+ this.availableResources =
+ await this.fetchSchedulableResourcesByCalender(
+ this.calendar.kalender_id,
+ );
+ this.filteredAvailableResources = [
+ ...this.dropdownParsedAvailableResources,
+ ];
+ this.assignedResources = await this.fetchAssignedResourcesByCalender(
+ this.calendar.kalender_id,
+ );
+ this.selectedAvailableResource = null;
+ this.areFormButtonsDisplayed = false;
+ },
+ async saveAssignedResourcesToCalendarItem() {
+ if (!this.calendar?.kalender_id) return;
+
+ const result = await this.$api.call(
+ ApiOperationalResourceToCalender.storeResourcesToCalendarRelationship(
+ this.calendar.kalender_id,
+ this.assignedResources,
+ ),
+ );
+
+ if (result.meta.status === "success") {
+ this.$fhcAlert.alertSuccess(
+ this.$p.t("ui", "assigned_resources_save_success_message"),
+ );
+ await this.refreshResourcesAssignmentModalData();
+ } else {
+ this.$fhcAlert.alertError(
+ this.$p.t("ui", "failed_assigned_resources_save_error_message"),
+ );
+ }
+
+ this.$emit("saveFinished");
+ this.hide();
+ },
+ },
+ template: `
+
+ {{ $p.t('ui', 'resource_assignment_modal_title') }}
+
+
+
+
+
+
+
+ {{ $p.t('ui', 'assigned_resources_subtitle') }}
+
+
+
+
+
+
{{ resource.beschreibung }}
+
+
+
+
+
+
+
{{ $p.t('ui', 'no_assigned_resources') }}
+
+
+
+ {{ $p.t('ui', 'abbrechen') }}
+
+
+ {{ $p.t('ui', 'speichern') }}
+
+
+
+
+
+ `,
+};
diff --git a/public/js/components/Tempus/SidebarMenu.js b/public/js/components/Tempus/SidebarMenu.js
new file mode 100644
index 000000000..264105d56
--- /dev/null
+++ b/public/js/components/Tempus/SidebarMenu.js
@@ -0,0 +1,190 @@
+import FhcCoursepicker from './Coursepicker.js';
+import LectureSelection from './Filters/LectureSelection.js';
+import VerbandSelection from './Filters/VerbandSelection.js';
+import RoomSelection from './Filters/RoomSelection.js';
+import ParkingSlot from './ParkingSlot.js';
+import StvStudiensemester from '../Stv/Studentenverwaltung/Studiensemester.js';
+
+export default {
+ name: 'TempusSidebarMenu',
+ components: {
+ FhcCoursepicker,
+ LectureSelection,
+ VerbandSelection,
+ RoomSelection,
+ ParkingSlot,
+ StvStudiensemester,
+ },
+ props: {
+ previewRole: {
+ type: String,
+ required: true,
+ },
+ rooms: {
+ type: Array,
+ required: true,
+ },
+ studiengaenge: {
+ type: Array,
+ required: true,
+ },
+ studiengaengeAll: {
+ type: Array,
+ default: () => [],
+ },
+ lecturers: {
+ type: Array,
+ required: true,
+ },
+ selectedStudiensemester: {
+ type: String,
+ required: true,
+ },
+ raumvorschlagPreview: {
+ type: Object,
+ default: null,
+ },
+ raumvorschlagLoading: {
+ type: Boolean,
+ default: false,
+ },
+ },
+ emits: [
+ 'update:previewRole',
+ 'update:rooms',
+ 'update:studiengaenge',
+ 'update:parkedKeys',
+ 'update:selectedStudiensemester',
+ 'remove-lecturer',
+ 'select-lecturer',
+ 'select-kw',
+ 'sync',
+ 'preview-raumvorschlag',
+ ],
+ methods: {
+ reloadCoursepicker() {
+ return this.$refs.coursepicker?.reload();
+ },
+ isParked(id) {
+ return this.$refs.parking?.isParked(id) ?? false;
+ },
+ park(items) {
+ return this.$refs.parking?.park(null, items);
+ },
+ unpark(item) {
+ return this.$refs.parking?.unpark(item);
+ },
+ },
+ computed: {
+ courseLecturers() {
+ if (!this.lecturers.length) return [];
+ return this.lecturers
+ .filter((lecture) => lecture.showCoursePicker)
+ .map((lecture) => lecture.uid);
+ },
+ },
+ template: /* html */ `
+
+ `,
+};
diff --git a/public/js/components/Tempus/TagsAssignmentModal.js b/public/js/components/Tempus/TagsAssignmentModal.js
new file mode 100644
index 000000000..9874d7b46
--- /dev/null
+++ b/public/js/components/Tempus/TagsAssignmentModal.js
@@ -0,0 +1,239 @@
+/**
+ * Copyright (C) 2024 fhcomplete.org
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
.
+ */
+import BsModal from "../Bootstrap/Modal.js";
+import FormInput from "../Form/Input.js";
+import CoreTag from "../Tag/Tag.js";
+import ApiTempusTag from "../../api/factory/tempus/tag.js";
+
+export default {
+ name: "TagsAssignmentModal",
+ components: {
+ BsModal,
+ FormInput,
+ CoreTag,
+ },
+ emits: ["tagsChanged"],
+ data() {
+ return {
+ calendar: null,
+ availableTags: [],
+ filteredAvailableTags: [],
+ selectedAvailableTag: null,
+ assignedTags: [],
+ tagEndpoint: ApiTempusTag,
+ tagModalEscapeHandler: null,
+ };
+ },
+ computed: {
+ dropdownParsedAvailableTags() {
+ return this.availableTags.map((tag) => ({
+ label: tag.bezeichnung,
+ value: tag.tag_typ_kurzbz,
+ data: tag,
+ }));
+ },
+ },
+ methods: {
+ async open(calendar) {
+ if (!calendar?.kalender_id) return;
+
+ this.calendar = calendar;
+ this.availableTags = await this.fetchAvailableTags();
+ this.filteredAvailableTags = [...this.dropdownParsedAvailableTags];
+ this.assignedTags = await this.fetchAssignedTagsByCalender(
+ calendar.eindeutige_kalender_gruppen_id,
+ );
+
+ this.show();
+ },
+ show() {
+ this.$refs.modal.show();
+ },
+ hide() {
+ this.$refs.modal.hide();
+ },
+ reset() {
+ this.removeTagModalEscapeHandler();
+ this.calendar = null;
+ this.availableTags = [];
+ this.filteredAvailableTags = [];
+ this.selectedAvailableTag = null;
+ this.assignedTags = [];
+ },
+ prepareTagModal() {
+ const tagModal = this.$refs.tagComponent?.$refs.tagModal;
+ const tagModalElement = tagModal?.$el;
+
+ if (!tagModalElement) return;
+
+ tagModalElement.addEventListener(
+ "shown.bs.modal",
+ () => {
+ this.tagModalEscapeHandler = (event) => {
+ if (event.key !== "Escape") return;
+
+ event.preventDefault();
+ event.stopImmediatePropagation();
+ tagModal.hide();
+ };
+ document.addEventListener("keydown", this.tagModalEscapeHandler, true);
+ },
+ { once: true },
+ );
+ tagModalElement.addEventListener(
+ "hidden.bs.modal",
+ () => this.restoreAssignmentModalFocus(),
+ { once: true },
+ );
+ },
+ removeTagModalEscapeHandler() {
+ if (!this.tagModalEscapeHandler) return;
+
+ document.removeEventListener(
+ "keydown",
+ this.tagModalEscapeHandler,
+ true,
+ );
+ this.tagModalEscapeHandler = null;
+ },
+ restoreAssignmentModalFocus() {
+ this.removeTagModalEscapeHandler();
+ this.$refs.modal?.$el?.focus();
+ },
+ async fetchAvailableTags() {
+ const result = await this.$api.call(ApiTempusTag.getTags());
+
+ if (result.meta.status === "success") return result.data;
+
+ this.$fhcAlert.alertError(
+ this.$p.t("ui", "failed_available_tags_fetch_error_message"),
+ );
+ return [];
+ },
+ async fetchAssignedTagsByCalender(calendarGroupId) {
+ const result = await this.$api.call(
+ ApiTempusTag.getTagsByCalendar(calendarGroupId),
+ );
+
+ if (result.meta.status === "success") {
+ return result.data.filter((tag) => !!tag);
+ }
+
+ this.$fhcAlert.alertError(
+ this.$p.t("ui", "failed_assigned_tags_fetch_error_message"),
+ );
+ return [];
+ },
+ filterAvailableTags(event) {
+ const query = event.query.toLowerCase();
+ const unassignedTags = this.dropdownParsedAvailableTags.filter(
+ (tag) =>
+ !this.assignedTags.some(
+ (assigned) => assigned.tag_id === tag.value,
+ ),
+ );
+
+ this.filteredAvailableTags = query
+ ? unassignedTags.filter((tag) =>
+ tag.label.toLowerCase().includes(query),
+ )
+ : unassignedTags;
+ },
+ selectTag(event) {
+ this.selectedAvailableTag = event.value;
+ this.prepareTagModal();
+ this.$refs.tagComponent?.openModal(event.value.data);
+ },
+ editTag(tag) {
+ this.prepareTagModal();
+ this.$refs.tagComponent?.editTag(tag.notiz_id);
+ },
+ async handleCalendarTagChange() {
+ this.removeTagModalEscapeHandler();
+
+ const calendarGroupId = this.calendar?.eindeutige_kalender_gruppen_id;
+ if (!calendarGroupId) return;
+
+ this.assignedTags =
+ await this.fetchAssignedTagsByCalender(calendarGroupId);
+ this.$emit("tagsChanged");
+ },
+ },
+ template: /* html */`
+
+ {{ $p.t('ui', 'tags_assignment_modal_title') }}
+
+
+
+
+ {{ option.label }}
+
+
+
+
+
+
+ {{ $p.t('ui', 'assigned_tags_subtitle') }}
+
+
+
+ {{ tag.bezeichnung }}
+
+
+
{{ $p.t('ui', 'no_assigned_tags') }}
+
+
+
+
+
+ `,
+};
diff --git a/public/js/components/Tempus/Tempus.js b/public/js/components/Tempus/Tempus.js
index 5abeffd98..4cd6e8966 100644
--- a/public/js/components/Tempus/Tempus.js
+++ b/public/js/components/Tempus/Tempus.js
@@ -14,65 +14,49 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see
.
*/
-import CoreSearchbar from "../searchbar/searchbar.js";
-import NavLanguage from "../navigation/Language.js";
-import VerticalSplit from "../verticalsplit/verticalsplit.js";
-import FhcCalendar from "../Calendar/Tempus.js";
-import FhcCoursepicker from "./Coursepicker.js";
-import LectureSelection from "./Filters/LectureSelection.js";
-import VerbandSelection from "./Filters/VerbandSelection.js";
-import RoomSelection from "./Filters/RoomSelection.js";
-import ParkingSlot from "./ParkingSlot.js";
-import ApiKalender from "../../api/factory/tempus/kalender.js";
-import ApiSearchbar from "../../api/factory/searchbar.js";
-import ApiRenderers from "../../api/factory/renderers.js";
-import ApiTempusConfig from "../../api/factory/tempus/config.js";
-import ApiOperationalResourceToCalender from "../../api/factory/operationalResourceToCalender.js";
-import AppMenu from "../AppMenu.js";
-import drop from "../../directives/drop.js";
-import AppConfig from "../AppConfig.js";
-
-import BsModal from "../Bootstrap/Modal.js";
-
-import BaseTreemenu from '../Base/Treemenu.js';
-
-import ApiStudiengangTree from "../../api/factory/tempus/studiengangtree.js";
-import ApiInfo from "../../api/factory/tempus/info.js";
-import StvStudiensemester from "../Stv/Studentenverwaltung/Studiensemester.js";
-import FormInput from "../../../js/components/Form/Input.js";
-import Reservierung from "./Reservierung.js";
-import { getTempusShortcuts } from "./shortcuts.js";
-import KeyboardShortcuts from "./KeyboardShortcuts.js";
-import { useContextMenuActions } from "../../composables/Tempus/ContextMenuActions.js";
-import MultiWeekPlanModal from "./MultiWeekPlanModal.js";
-import { getTempusSearchbarOptions } from "./Filters/searchbarOptions.js";
-import RaumauswahlModal from "./RaumauswahlModal.js";
-import LehreinheitModal from "./LehreinheitModal.js";
+import FhcCalendar from '../Calendar/Tempus.js';
+import ApiKalender from '../../api/factory/tempus/kalender.js';
+import ApiSearchbar from '../../api/factory/searchbar.js';
+import ApiRenderers from '../../api/factory/renderers.js';
+import ApiTempusConfig from '../../api/factory/tempus/config.js';
+import drop from '../../directives/drop.js';
+import AppConfig from '../AppConfig.js';
+import ApiInfo from '../../api/factory/tempus/info.js';
+import Reservierung from './Reservierung.js';
+import { getTempusShortcuts } from './shortcuts.js';
+import KeyboardShortcuts from './KeyboardShortcuts.js';
+import { useContextMenuActions } from '../../composables/Tempus/ContextMenuActions.js';
+import MultiWeekPlanModal from './MultiWeekPlanModal.js';
+import HistoryModal from './HistoryModal.js';
+import ResourcesAssignmentModal from './ResourcesAssignmentModal.js';
+import TagsAssignmentModal from './TagsAssignmentModal.js';
+import { getTempusSearchbarOptions } from './Filters/searchbarOptions.js';
+import TempusHeader from './Header.js';
+import TempusAppMenu from './AppMenu.js';
+import TempusVerbandMenu from './VerbandMenu.js';
+import TempusSidebarMenu from './SidebarMenu.js';
+import RaumauswahlModal from './RaumauswahlModal.js';
+import LehreinheitModal from './LehreinheitModal.js';
+import HorizontalSplit from '../horizontalsplit/horizontalsplit.js';
export default {
- name: "Tempus",
+ name: 'Tempus',
components: {
- CoreSearchbar,
- VerticalSplit,
FhcCalendar,
- FhcCoursepicker,
- LectureSelection,
- VerbandSelection,
- RoomSelection,
- ParkingSlot,
AppConfig,
- AppMenu,
- NavLanguage,
- BsModal,
- BaseTreemenu,
- StvStudiensemester,
- Multiselect: primevue.multiselect,
- FormInput,
+ TempusAppMenu,
+ TempusHeader,
+ TempusVerbandMenu,
+ TempusSidebarMenu,
Reservierung,
KeyboardShortcuts,
MultiWeekPlanModal,
+ HistoryModal,
+ ResourcesAssignmentModal,
+ TagsAssignmentModal,
RaumauswahlModal,
- LehreinheitModal
+ LehreinheitModal,
+ HorizontalSplit,
},
props: {
defaultSemester: String,
@@ -98,7 +82,9 @@ export default {
contextMenuActions: useContextMenuActions({
openRaumauswahl: (orig) => this.$refs.raumModal.show(orig),
openLehreinheit: (orig) => this.$refs.lehreinheitModal.show(orig),
- openResourcesAssignmentModal: (orig) => this.openResourcesAssignmentModal(orig),
+ openResourcesAssignmentModal: (orig) =>
+ this.$refs.resourcesAssignmentModal?.open(orig),
+ openTagsModal: (orig) => this.$refs.tagsAssignmentModal?.open(orig),
openHistory: (orig) => this.openHistory(orig),
deleteEntry: (orig) => this.deleteEntry(orig),
syncToLecturer: (orig) => this.syncToLecturer(orig),
@@ -108,52 +94,35 @@ export default {
deleteEntries: (origList) => this.deleteEntries(origList),
openRaumauswahl: (orig) => this.$refs.raumModal.show(orig),
},
+ canToggleGrid: this.permissions.stundenraster,
};
},
data() {
return {
appconfig: {},
- currentMode: "week",
+ currentMode: 'week',
configEndpoints: ApiTempusConfig,
- endpoint: ApiStudiengangTree,
- raumVorschlaege: [],
- selected: [],
-
- lv_id: null,
- events: null,
- minimized: false,
- currentlySelectedEvent: null,
hoveredEvent: null,
- //currentDay: new Date(),
- studiensemesterKurzbz: this.defaultSemester,
- lists: {
- nations: [],
- sprachen: [],
- geschlechter: [],
- },
- renderers: null,
+ renderers: {},
ort_kurzbz: null,
- view: "room",
parkedKeys: new Set(),
lecturers: [],
studiengaenge: [],
rooms: [],
- overlayCache: [],
+ overlayCache: {},
extraBackgrounds: [],
lastRange: null,
- stg: null,
- semester: null,
- studiensemester_kurzbz: null,
-
- visibleStatusArray: {},
- visibleStatus: ["all"],
- selectedStudiensemester:
- this.studiensemester_kurzbz ?? this.defaultSemester,
- calendarDate: luxon.DateTime.now()
- .setZone(this.config.timezone)
- .toISODate(),
+ visibleStatus: ['all'],
+ selectedStudiensemester: this.defaultSemester,
+ calendarDatesByMode: {
+ week: luxon.DateTime.now().setZone(this.config.timezone).toISODate(),
+ month: luxon.DateTime.now().setZone(this.config.timezone).toISODate(),
+ tableList: luxon.DateTime.now()
+ .setZone(this.config.timezone)
+ .toISODate(),
+ },
historyEntries: [],
- previewRole: "planer",
+ previewRole: 'planer',
multiWeekModal: {
show: false,
lehreinheitId: null,
@@ -162,20 +131,20 @@ export default {
endTime: null,
},
studiengaenge_all: [],
- resourcesAssignmentModal: {
- calendar: null,
- availableResources: [],
- filteredAvailableResources: [],
- selectedAvailableResource: null,
- assignedResources: [],
- areFormButtonsDisplayed: false,
- },
maxDailyEventLimitForMonthView: 10,
+ raumvorschlagPreview: null,
+ raumvorschlagLoading: false,
+ showEvents: true,
+ reservierungPending: false,
+ bcc: null,
};
},
computed: {
- currentDay() {
- return luxon.DateTime.now().setZone(this.config.timezone).toISODate();
+ calendarDate() {
+ return (
+ this.calendarDatesByMode[this.currentMode] ??
+ this.calendarDatesByMode.week
+ );
},
visibleLecturerUids() {
if (!this.lecturers.length) return null;
@@ -183,62 +152,21 @@ export default {
.filter((lecture) => lecture.showEvents)
.map((lecture) => lecture.uid);
},
- visibleStatusOptions() {
- return Object.entries(this.visibleStatusArray).map(([key, label]) => ({
- key,
- label,
- }));
- },
- visibleStatusValue() {
- if (this.visibleStatus.includes("all"))
- return this.visibleStatusOptions.filter(
- (visibleStatus) => visibleStatus.key === "all",
- );
- return this.visibleStatus.map((status) => ({
- key: status,
- label: this.visibleStatusArray[status],
- }));
- },
keyboardShortcuts() {
return getTempusShortcuts(this);
},
searchbaroptions() {
return getTempusSearchbarOptions(this);
},
- dropdownParsedAvailableResources() {
- return this.resourcesAssignmentModal.availableResources
- .map((unit) => {
- return {
- label: unit.beschreibung,
- value: unit.betriebsmittel_id,
- data: unit,
- };
- })
- .sort((a, b) => a.label?.localeCompare(b.label));
- },
},
methods: {
- async openResourcesAssignmentModal(orig) {
- if (!orig?.kalender_id) return;
-
- this.resourcesAssignmentModal.calendar = orig;
- this.resourcesAssignmentModal.availableResources =
- await this.fetchSchedulableResourcesByCalender(orig.kalender_id);
- this.resourcesAssignmentModal.filteredAvailableResources = [
- ...this.dropdownParsedAvailableResources,
- ];
-
- this.resourcesAssignmentModal.assignedResources =
- await this.fetchAssignedResourcesByCalender(orig.kalender_id);
-
- this.$refs.resourcesAssignmentModal.show();
- },
async deleteEntry(orig) {
if (!orig?.kalender_id) return;
await this.deleteEntryCall(orig);
this.$refs.calendar.resetEventLoader();
- this.$refs.coursepicker.reload();
+ this.$refs.sidebar.reloadCoursepicker();
+ this.rebuildRaumvorschlag();
},
async deleteEntries(origList) {
let validList = (origList ?? []).filter((orig) => orig?.kalender_id);
@@ -248,13 +176,13 @@ export default {
validList.map((orig) => this.deleteEntryCall(orig)),
);
this.$refs.calendar.resetEventLoader();
- this.$refs.coursepicker.reload();
+ this.$refs.sidebar.reloadCoursepicker();
},
async deleteEntryCall(orig) {
await this.$api
.call(ApiKalender.deleteEntry(orig.kalender_id))
.then(() => {
- this.$refs.parking.unpark({ type: orig.type, id: orig.kalender_id });
+ this.$refs.sidebar.unpark({ type: orig.type, id: orig.kalender_id });
});
},
async openHistory(orig) {
@@ -263,7 +191,7 @@ export default {
.call(ApiKalender.getHistory(orig.kalender_id))
.then((result) => {
this.historyEntries = result.data ?? [];
- this.$refs.historyModel.show();
+ this.$refs.historyModal.show();
});
},
syncToLecturer(orig) {
@@ -278,17 +206,103 @@ export default {
.call(ApiKalender.syncToStudent(orig.kalender_id))
.then(() => this.$refs.calendar.resetEventLoader());
},
- setOrt: function (data) {
+ rebuildRaumvorschlag() {
+ if (this.raumvorschlagPreview)
+ this.previewRaumvorschlag({
+ lehreinheit_id: this.raumvorschlagPreview?.lehreinheit_id,
+ });
+ },
+
+ clearRaumvorschlagPreview() {
+ this.raumvorschlagPreview = null;
+ this.raumvorschlagLoading = false;
+ this.rebuildExtraBackgrounds();
+ this.showEvents = true;
+ },
+ async previewRaumvorschlag(orig) {
+ if (!orig) {
+ this.clearRaumvorschlagPreview();
+ return;
+ }
+ if (!this.lastRange) return;
+
+ this.raumvorschlagLoading = true;
+ this.raumvorschlagPreview = {
+ lehreinheit_id: orig.lehreinheit_id,
+ slots: [],
+ };
+
+ await this.$api
+ .call(
+ ApiKalender.getRaumvorschlagSlots(
+ orig.lehreinheit_id,
+ this.lastRange.start.toISODate(),
+ this.lastRange.end.toISODate(),
+ ),
+ )
+ .then((result) => {
+ this.raumvorschlagPreview.slots = (result.data ?? []).map((slot) => ({
+ class: `rounded-3 bg-raumvorschlag-slot rating-${slot.rating}`,
+ start: slot.isostart,
+ matchEnd: slot.isoend,
+ end: slot.marker_isoend,
+ label: slot.label,
+ raeume: slot.raeume,
+ }));
+
+ if (this.raumvorschlagPreview.slots.length > 0)
+ this.showEvents = false;
+ })
+ .catch(this.$fhcAlert.handleSystemError)
+ .finally(() => {
+ this.raumvorschlagLoading = false;
+ this.rebuildExtraBackgrounds();
+ });
+ },
+ findRaumvorschlagSlot(lehreinheit_id, startDT, endDT) {
+ if (this.raumvorschlagPreview?.lehreinheit_id !== lehreinheit_id)
+ return null;
+
+ return (
+ this.raumvorschlagPreview.slots.find((slot) => {
+ const slotStart = luxon.DateTime.fromISO(slot.start);
+ const slotEnd = luxon.DateTime.fromISO(slot.matchEnd);
+ return (
+ slotStart.toMillis() === startDT.toMillis() &&
+ slotEnd.toMillis() === endDT.toMillis()
+ );
+ }) ?? null
+ );
+ },
+ setOrt(data) {
this.ort_kurzbz = data.ort_kurzbz;
this.rooms = [{ ort_kurzbz: data.ort_kurzbz }];
},
+ setEmp(data) {
+ const uid = data.uid;
+ const label = data.name;
+
+ this.lecturers = [
+ {
+ uid,
+ label,
+ showEvents: true,
+ overlays: { blocks: true, wishes: true },
+ showCoursePicker: false,
+ },
+ ];
+
+ this.$refs.calendar.resetEventLoader();
+ if (this.lastRange) this.handleRange(this.lastRange);
+ },
onSelectVerbandAndClose(payload) {
this.onSelectVerband(payload);
- bootstrap.Offcanvas.getOrCreateInstance(this.$refs.verbandMenu).hide();
+
+ const verbandMenu = this.$refs.verbandMenu?.$el;
+ if (verbandMenu)
+ bootstrap.Offcanvas.getOrCreateInstance(verbandMenu).hide();
},
-
- onSelectVerband({ path, stg_kz, semester, orgform_kurzbz, name }) {
-
+ onSelectVerband({ stg_kz, semester, orgform_kurzbz, name }) {
let exists = this.studiengaenge.some(
(stg) =>
stg.stg_kz == stg_kz &&
@@ -303,7 +317,7 @@ export default {
];
}
},
- setEmp: function (data) {
+ setEmp(data) {
const uid = data.uid;
const label = data.name;
@@ -315,28 +329,57 @@ export default {
label,
showEvents: true,
overlays: { blocks: true, wishes: true },
+ showCoursePicker: false,
},
];
this.$refs.calendar.resetEventLoader();
if (this.lastRange) this.handleRange(this.lastRange);
},
- addToFilter: function (filter, type) {
- if (type === "ort") {
+ addToCoursePicker(data) {
+ const uid = data.uid;
+ let lecturer = this.lecturers.find((lecture) => lecture.uid === uid);
+
+ if (!lecturer) {
+ this.addToFilter(data, 'mitarbeiter');
+ lecturer = this.lecturers.find((lecture) => lecture.uid === uid);
+
+ if (lecturer) {
+ lecturer.overlays = {
+ blocks: false,
+ wishes: false,
+ };
+ }
+ }
+
+ if (lecturer) {
+ lecturer.showCoursePicker = true;
+ }
+ },
+
+ addToFilter(filter, type) {
+ if (type === 'ort') {
const ort_kurzbz = filter.ort_kurzbz;
if (!this.rooms.some((room) => room.ort_kurzbz === ort_kurzbz)) {
this.rooms.push({ ort_kurzbz });
}
- } else if (type === "mitarbeiter") {
+ } else if (type === 'mitarbeiter') {
const uid = filter.uid;
const label = filter.name;
- if (!this.lecturers.some((l) => l.uid === uid)) {
+
+ let lecturer = this.lecturers.find((lecture) => lecture.uid === uid);
+
+ if (!lecturer) {
this.lecturers.push({
uid,
label,
showEvents: true,
overlays: { blocks: true, wishes: true },
+ showCoursePicker: false,
});
+ } else {
+ lecturer.showEvents = true;
+ lecturer.overlays = { blocks: true, wishes: true };
}
}
@@ -355,42 +398,42 @@ export default {
},
{ zone: this.config.timezone },
);
- this.calendarDate = date.toISODate();
+ this.calendarDatesByMode = {
+ ...this.calendarDatesByMode,
+ week: date.toISODate(),
+ };
+ this.currentMode = 'week';
},
handleChangeDate(newDate) {
- if (newDate && luxon.DateTime.isDateTime(newDate) && newDate.isValid)
- this.calendarDate = newDate.toISODate();
+ if (!(newDate && luxon.DateTime.isDateTime(newDate) && newDate.isValid))
+ return;
+
+ this.calendarDatesByMode = {
+ ...this.calendarDatesByMode,
+ [this.currentMode]: newDate.toISODate(),
+ };
},
handleChangeMode(newMode, newDate) {
if (!newMode) return;
+
+ if (newDate && luxon.DateTime.isDateTime(newDate) && newDate.isValid) {
+ this.calendarDatesByMode = {
+ ...this.calendarDatesByMode,
+ [this.currentMode]: newDate.toISODate(),
+ };
+ }
+
this.currentMode = newMode;
- if (newDate && luxon.DateTime.isDateTime(newDate) && newDate.isValid)
- this.calendarDate = newDate.toISODate();
},
updateCollision() {
this.$api
.call(ApiTempusConfig.updateCollision())
.then(() => {
this.$refs.config.reload();
- this.$fhcAlert.alertSuccess(this.$p.t("ui/settings_saved"));
+ this.$fhcAlert.alertSuccess(this.$p.t('ui/settings_saved'));
})
.catch(this.$fhcAlert.handleSystemErrors);
},
- toggleStatus(selected) {
- if (!selected || selected.length === 0) {
- this.visibleStatus = ["all"];
- return;
- }
- const hasAll = selected.includes("all");
- const hadAll = this.visibleStatus.includes("all");
-
- if (hasAll && !hadAll) {
- this.visibleStatus = ["all"];
- return;
- }
- this.visibleStatus = selected.filter((k) => k !== "all");
- if (this.visibleStatus.length === 0) this.visibleStatus = ["all"];
- },
searchfunction(params) {
return this.$api.call(ApiSearchbar.search(params));
},
@@ -417,7 +460,7 @@ export default {
if (hasLektoren)
filter.uid = this.lecturers.map((lecture) => lecture.uid);
- if (this.previewRole === "lektor")
+ if (this.previewRole === 'lektor')
return [
this.$api.call(
ApiKalender.getPlanLecturer(
@@ -429,7 +472,7 @@ export default {
),
];
- if (this.previewRole === "student")
+ if (this.previewRole === 'student')
return [
this.$api.call(
ApiKalender.getPlanStudent(
@@ -453,65 +496,20 @@ export default {
),
];
},
- toDateTime(value, timezone) {
- if (luxon.DateTime.isDateTime(value)) return value;
-
- if (value?.date?.isValid) return value.date;
-
- if (typeof value === "number")
- return luxon.DateTime.fromMillis(value, { zone: timezone });
-
- if (value instanceof Date)
- return luxon.DateTime.fromJSDate(value, { zone: timezone });
-
- if (typeof value === "string")
- return luxon.DateTime.fromISO(value, { zone: timezone });
-
- return luxon.DateTime.invalid("invalid datetime");
- },
- getLastEndOfSameDay(startDT, ends) {
- if (!ends?.length) return null;
-
- const dayKey = startDT.toISODate();
- let lastSameDay = null;
-
- for (const end of ends) {
- const dt = luxon.DateTime.isDateTime(end)
- ? end
- : luxon.DateTime.fromISO(String(end), { zone: startDT.zoneName });
-
- if (!dt.isValid) continue;
-
- if (dt.toISODate() === dayKey) lastSameDay = dt;
- }
-
- return lastSameDay;
- },
- clampEndToGrid(startDT, durationMin, ends) {
- const calculatedEnd = startDT.plus({ minutes: durationMin });
-
- const lastGridEndSameDay = this.getLastEndOfSameDay(startDT, ends);
-
- if (!lastGridEndSameDay) return calculatedEnd;
-
- return calculatedEnd > lastGridEndSameDay
- ? lastGridEndSameDay
- : calculatedEnd;
- },
_parseDates(start, end) {
const startDT = luxon.DateTime.fromISO(start);
const endDT = luxon.DateTime.fromISO(end);
if (!startDT.isValid || !endDT.isValid) {
- alert("Ungültiges Datum");
+ alert('Ungültiges Datum');
return null;
}
return {
startDT,
endDT,
- start_time: startDT.toFormat("yyyy-MM-dd HH:mm"),
- end_time: endDT.toFormat("yyyy-MM-dd HH:mm"),
+ start_time: startDT.toFormat('yyyy-MM-dd HH:mm'),
+ end_time: endDT.toFormat('yyyy-MM-dd HH:mm'),
};
},
@@ -528,7 +526,7 @@ export default {
const updatedInfos = {
ort_kurzbz: this.rooms.length
? this.rooms.map((room) => room.ort_kurzbz)
- : obj.orig.ort_kurzbz ?? [],
+ : (obj.orig.ort_kurzbz ?? []),
start_time,
end_time,
};
@@ -543,13 +541,13 @@ export default {
},
resizeHandler(payload) {
- if (this.previewRole !== "planer")
+ if (this.previewRole !== 'planer')
//TODO (david) testzweck
return;
const { item, start, end } = payload;
const obj = item[0];
if (!obj?.orig?.kalender_id)
- return alert("Kein gültiges Kalender-Event zum Resizen");
+ return alert('Kein gültiges Kalender-Event zum Resizen');
const dates = this._parseDates(start, end);
@@ -563,25 +561,26 @@ export default {
dates.end_time,
() => {
this.$refs.calendar.resetEventLoader();
- this.$refs.coursepicker.reload();
+ this.$refs.sidebar.reloadCoursepicker();
+ this.rebuildRaumvorschlag();
},
);
},
dropHandler(payload) {
- if (this.previewRole !== "planer")
+ if (this.previewRole !== 'planer')
//TODO (david) testzweck
return;
const { item, start, end } = payload;
- if (!item?.length) return alert("Keine Daten gedroppt");
+ if (!item?.length) return alert('Keine Daten gedroppt');
const obj = item[0];
- if (!obj?.type) return alert("Unbekannter Drop-Typ");
+ if (!obj?.type) return alert('Unbekannter Drop-Typ');
if (payload.targetKalenderId) {
- if (obj.type !== "lehreinheit" || !obj.orig?.lehreinheit_id)
+ if (obj.type !== 'lehreinheit' || !obj.orig?.lehreinheit_id)
return alert(
- "Nur Lehreinheiten können einem Termin hinzugefügt werden",
+ 'Nur Lehreinheiten können einem Termin hinzugefügt werden',
);
return this.$api
@@ -593,7 +592,9 @@ export default {
)
.then(() => {
this.$refs.calendar.resetEventLoader();
- this.bcc.postMessage("dropped");
+ this.$refs.sidebar.reloadCoursepicker();
+ this.rebuildRaumvorschlag();
+ this.bcc.postMessage('dropped');
});
}
@@ -602,10 +603,10 @@ export default {
const { startDT, endDT, start_time, end_time } = dates;
- if (obj.type === "reservierung") {
+ if (obj.type === 'reservierung') {
this.reservierungPending = true;
this.$refs.reservierung.show(start_time, end_time);
- } else if (obj.type === "lehreinheit") {
+ } else if (obj.type === 'lehreinheit') {
if (obj.multiweek) {
this.openMultiWeekPreview(
obj.orig.lehreinheit_id,
@@ -616,24 +617,69 @@ export default {
return;
}
- return this.$api
- .call(
- ApiKalender.addKalenderEvent(
- obj.orig.lehreinheit_id,
- this.rooms.length
- ? this.rooms.map((r) => r.ort_kurzbz)
- : obj.orig.ort_kurzbz ?? [],
- start_time,
- end_time,
- ),
- )
- .then((result) => result.data)
- .then((result) => {
- this.$refs.calendar.resetEventLoader();
- this.$refs.coursepicker.reload();
- this.bcc.postMessage("dropped");
- });
- } else if (obj.type === "kalender") {
+ const matchedSlot = this.findRaumvorschlagSlot(
+ obj.orig.lehreinheit_id,
+ startDT,
+ endDT,
+ );
+ const raeume = matchedSlot?.raeume ?? null;
+
+ if (!raeume) {
+ return this.$api
+ .call(
+ ApiKalender.addKalenderEvent(
+ obj.orig.lehreinheit_id,
+ this.rooms.length
+ ? this.rooms.map((r) => r.ort_kurzbz)
+ : (obj.orig.ort_kurzbz ?? []),
+ start_time,
+ end_time,
+ ),
+ )
+ .then((result) => result.data)
+ .then((result) => {
+ if (result.needs_room_selection) {
+ this.$refs.raumModal.showForNew(
+ obj.orig.lehreinheit_id,
+ start_time,
+ end_time,
+ result.raum_vorschlaege,
+ );
+ return;
+ }
+
+ this.$refs.calendar.resetEventLoader();
+ this.$refs.sidebar.reloadCoursepicker();
+ this.rebuildRaumvorschlag();
+ this.bcc.postMessage('dropped');
+ });
+ }
+
+ if (raeume.length === 1) {
+ return this.$api
+ .call(
+ ApiKalender.addKalenderEvent(
+ obj.orig.lehreinheit_id,
+ raeume[0].ort_kurzbz,
+ start_time,
+ end_time,
+ ),
+ )
+ .then(() => {
+ this.$refs.calendar.resetEventLoader();
+ this.$refs.sidebar.reloadCoursepicker();
+ this.bcc.postMessage('dropped');
+ this.rebuildRaumvorschlag();
+ });
+ }
+
+ this.$refs.raumModal.showForNew(
+ obj.orig.lehreinheit_id,
+ start_time,
+ end_time,
+ raeume,
+ );
+ } else if (obj.type === 'kalender') {
return this._updateKalenderEvent(
obj,
startDT,
@@ -641,16 +687,17 @@ export default {
start_time,
end_time,
() => {
- this.$refs.parking.unpark({
+ this.$refs.sidebar.unpark({
type: obj.type,
id: obj.orig.kalender_id,
});
this.$refs.calendar.resetEventLoader();
- this.bcc.postMessage("dropped");
+ this.rebuildRaumvorschlag();
+ this.bcc.postMessage('dropped');
},
);
} else {
- alert("Unbekannter Drop-Typ: " + obj.type);
+ alert('Unbekannter Drop-Typ: ' + obj.type);
}
},
openMultiWeekPreview(lehreinheit_id, ort_kurzbz, start_time, end_time) {
@@ -665,18 +712,19 @@ export default {
},
onMultiWeekConfirmed() {
this.$refs.calendar.resetEventLoader();
- this.$refs.coursepicker.reload();
- this.bcc.postMessage("dropped");
+ this.$refs.sidebar.reloadCoursepicker();
+ this.rebuildRaumvorschlag();
+ this.bcc.postMessage('dropped');
},
handleRange(range) {
if (!range?.start || !range?.end) return;
- if (this.currentMode === "week") {
+ if (this.currentMode === 'week') {
//Workaround because, updateRange is emitting 2 times
- const startDay = range.start.startOf("day");
- const endDay = range.end.startOf("day");
+ const startDay = range.start.startOf('day');
+ const endDay = range.end.startOf('day');
- const days = Math.round(endDay.diff(startDay, "days").days) + 1;
+ const days = Math.round(endDay.diff(startDay, 'days').days) + 1;
if (days > 8) return;
}
@@ -689,6 +737,11 @@ export default {
}
this.rebuildExtraBackgrounds();
+
+ if (this.raumvorschlagPreview)
+ this.previewRaumvorschlag({
+ lehreinheit_id: this.raumvorschlagPreview.lehreinheit_id,
+ });
},
getOverlays(uid, range, rangeKey) {
@@ -775,141 +828,12 @@ export default {
if (lect.overlays.wishes) res.push(...(entry.wishes || []));
}
+ if (this.raumvorschlagPreview)
+ res.push(...this.raumvorschlagPreview.slots);
+
this.extraBackgrounds = res;
},
- async fetchAssignedResourcesByCalender(calenderId) {
- let getAssignedResources = await this.$api.call(
- ApiOperationalResourceToCalender.getAssignedResourcesByCalender(
- calenderId,
- ),
- );
- if (getAssignedResources.meta.status === "success") {
- return getAssignedResources.data
- .filter((unit) => !!unit)
- .map((unit) => {
- return {
- isNoteTextareaShown:
- unit.anmerkung && unit.anmerkung.trim() !== "",
- ...unit,
- };
- })
- .filter((unit) => !!unit);
- } else {
- this.$fhcAlert.alertError(
- this.$p.t("ui", "failed_assigned_resources_fetch_error_message"),
- );
- }
-
- return [];
- },
- async fetchSchedulableResourcesByCalender(calendarID) {
- let getSchedulableResourcesByCalendar = await this.$api.call(
- ApiOperationalResourceToCalender.getSchedulableResourcesByCalendar(
- calendarID,
- ),
- );
- if (getSchedulableResourcesByCalendar.meta.status === "success") {
- return getSchedulableResourcesByCalendar.data;
- } else {
- this.$fhcAlert.alertError(
- this.$p.t("ui", "failed_schedulable_resources_fetch_error_message"),
- );
- }
-
- return [];
- },
- filterAvailableResources(event) {
- this.resourcesAssignmentModal.filteredAvailableResources;
- const query = event.query.toLowerCase();
- if (!query) {
- return (this.resourcesAssignmentModal.filteredAvailableResources = [
- ...this.dropdownParsedAvailableResources.filter((unit) => {
- return !this.resourcesAssignmentModal.assignedResources.some(
- (assigned) => assigned.betriebsmittel_id === unit.value,
- );
- }),
- ]);
- }
-
- return (this.resourcesAssignmentModal.filteredAvailableResources =
- this.dropdownParsedAvailableResources
- .filter((unit) => {
- return !this.resourcesAssignmentModal.assignedResources.some(
- (assigned) => assigned.betriebsmittel_id === unit.value,
- );
- })
- .filter((unit) => {
- return unit.label.toLowerCase().includes(query);
- }));
- },
- toggleAssignedResourceNoteInput(resource) {
- const index = this.resourcesAssignmentModal.assignedResources.findIndex(
- (assigned) => assigned.betriebsmittel_id === resource.betriebsmittel_id,
- );
- if (index !== -1) {
- this.resourcesAssignmentModal.assignedResources[
- index
- ].isNoteTextareaShown =
- !this.resourcesAssignmentModal.assignedResources[index]
- .isNoteTextareaShown;
- }
-
- this.resourcesAssignmentModal.areFormButtonsDisplayed = true;
- },
- removeAssignedResource(resource) {
- this.resourcesAssignmentModal.assignedResources =
- this.resourcesAssignmentModal.assignedResources.filter(
- (assigned) =>
- assigned.betriebsmittel_id !== resource.betriebsmittel_id,
- );
-
- this.resourcesAssignmentModal.areFormButtonsDisplayed = true;
- },
- async refreshResourcesAssignmentModalData(calenderItem) {
- this.resourcesAssignmentModal.availableResources =
- await this.fetchSchedulableResourcesByCalender(
- calenderItem.kalender_id,
- );
- this.resourcesAssignmentModal.filteredAvailableResources = [
- ...this.dropdownParsedAvailableResources,
- ];
-
- this.resourcesAssignmentModal.assignedResources =
- await this.fetchAssignedResourcesByCalender(calenderItem.kalender_id);
- this.resourcesAssignmentModal.selectedAvailableResource = null;
- this.resourcesAssignmentModal.areFormButtonsDisplayed = false;
- },
- async saveAssignedResourcesToCalendarItem(calenderItem, assignedResources) {
- let getSchedulableResourcesByCalendar = await this.$api.call(
- ApiOperationalResourceToCalender.storeResourcesToCalendarRelationship(
- calenderItem.kalender_id,
- assignedResources,
- ),
- );
- if (getSchedulableResourcesByCalendar.meta.status === "success") {
- this.$fhcAlert.alertSuccess(
- this.$p.t("ui", "assigned_resources_save_success_message"),
- );
- await this.refreshResourcesAssignmentModalData(calenderItem);
- } else {
- this.$fhcAlert.alertError(
- this.$p.t("ui", "failed_assigned_resources_save_error_message"),
- );
- }
-
- this.$refs.calendar.resetEventLoader();
- this.$refs.resourcesAssignmentModal.hide();
- },
- closeResourcesAssignmentModal() {
- this.resourcesAssignmentModal = {
- availableResources: [],
- filteredAvailableResources: [],
- selectedAvailableResource: null,
- assignedResources: [],
- areFormButtonsDisplayed: false,
- };
- },
removeLecturer(uid) {
if (uid == null) {
for (const lect of this.lecturers) delete this.overlayCache[lect.uid];
@@ -924,10 +848,18 @@ export default {
this.$refs.calendar.resetEventLoader();
}
},
+ handlePreviewRoleChange(role) {
+ this.previewRole = role;
+ this.$refs.calendar.resetEventLoader();
+ },
+ openReservierung() {
+ this.reservierungPending = false;
+ this.$refs.reservierung?.show();
+ },
triggerSync() {
this.$api
.call(ApiKalender.sync())
- .then(this.$refs.calendar.resetEventLoader());
+ .then(() => this.$refs.calendar.resetEventLoader());
},
onEventHover(event) {
this.hoveredEvent = event;
@@ -941,11 +873,11 @@ export default {
const event = this.hoveredEvent;
if (!event?.kalender_id) return;
- if (this.$refs.parking.isParked(event.kalender_id)) {
- this.$refs.parking.unpark({ type: event.type, id: event.kalender_id });
+ if (this.$refs.sidebar.isParked(event.kalender_id)) {
+ this.$refs.sidebar.unpark({ type: event.type, id: event.kalender_id });
} else {
- this.$refs.parking.park(null, {
- type: "kalender",
+ this.$refs.sidebar.park({
+ type: 'kalender',
id: event.kalender_id,
orig: event,
});
@@ -957,11 +889,8 @@ export default {
this.deleteEntry(event);
},
- clearHoveredEvent() {
- this.hoveredEvent = null;
- },
focusSearchbar() {
- this.$refs.searchbar?.$refs?.input.focus();
+ this.$refs.header?.focusSearchbar();
},
},
watch: {
@@ -977,33 +906,21 @@ export default {
studiengaenge: {
deep: true,
handler() {
+ this.clearRaumvorschlagPreview();
this.$refs.calendar.resetEventLoader();
},
},
- "resourcesAssignmentModal.selectedAvailableResource": function (newVal) {
- if (!newVal) return;
-
- this.resourcesAssignmentModal.assignedResources.push({
- betriebsmittel_kalender_id: null,
- betriebsmittel_id: newVal.data.betriebsmittel_id,
- beschreibung: newVal.data.beschreibung,
- anmerkung: "",
- isNoteTextareaShown: false,
- });
-
- this.resourcesAssignmentModal.areFormButtonsDisplayed = true;
- },
},
mounted() {
this.reservierungPending = false;
- this.bcc = new BroadcastChannel("fhc-dnd");
- this.bcc.addEventListener("message", (e) => {
- if (e.data === "dropped" && !this.reservierungPending)
+ this.bcc = new BroadcastChannel('fhc-dnd');
+ this.bcc.addEventListener('message', (e) => {
+ if (e.data === 'dropped' && !this.reservierungPending)
this.$refs.calendar.resetEventLoader();
});
},
beforeUnmount() {
- this.bcc.close();
+ this.bcc?.close();
},
async created() {
await this.$api
@@ -1040,17 +957,14 @@ export default {
`link[href="${data[rendertype].calendarEventStyles}"]`,
)
) {
- var link = document.createElement("link");
- link.type = "text/css";
- link.rel = "stylesheet";
+ var link = document.createElement('link');
+ link.type = 'text/css';
+ link.rel = 'stylesheet';
link.href = data[rendertype].calendarEventStyles;
head.appendChild(link);
}
}
- if (this.renderers === null) {
- this.renderers = {};
- }
if (!this.renderers[rendertype]) {
this.renderers[rendertype] = {};
}
@@ -1060,333 +974,97 @@ export default {
}
});
- this.$api.call(ApiTempusConfig.getHeader()).then((res) => {
- this.visibleStatusArray = res.data.visible_status;
- this.visibleStatus = ["all"];
- });
this.$api.call(ApiInfo.getStudiengaenge()).then((res) => {
this.studiengaenge_all = res.data;
});
},
- template: `
+ template: /* html */ `
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
- Planer
-
-
- Lektor
-
-
- Student
-
-
- Sync
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
+
-
-
-
- {{$p.t('ui', 'resource_assignment_modal_title')}}
-
-
- { resourcesAssignmentModal.selectedAvailableResource = option.value; }"
- :label="$p.t('ui', 'available_resources_label')"
- :suggestions="resourcesAssignmentModal.filteredAvailableResources"
- :optionValue="(option) => option.value"
- :optionLabel="(option) => option.label"
- @complete="filterAvailableResources"
- dropdown
- forceSelection
- type="autocomplete"
- name="availableResources"
- :closeOnSelect="false"
- >
-
-
-
-
-
{{$p.t('ui', 'assigned_resources_subtitle')}}
-
-
-
-
-
{{ resource.beschreibung }}
-
-
-
-
-
-
-
-
{{$p.t('ui', 'no_assigned_resources')}}
-
-
- {{$p.t('ui', 'abbrechen')}}
- {{$p.t('ui', 'speichern')}}
-
-
-
-
-
- History
-
-
-
-
- Von
- Bis
- Status
- Ort
-
-
-
-
- {{ entry.von }}
- {{ entry.bis }}
- {{ entry.status_kurzbz }}
- {{ entry.ort }}
-
-
-
-
-
+
{$refs.calendar.resetEventLoader(); $refs.sidebar.reloadCoursepicker(); rebuildRaumvorschlag();}"/>
+
+
+
+
this.$api.call(ApiTempusSync.getSyncs(this.studiensemester_kurzbz)),
+ ajaxResponse: (url, params, response) => {
+ return response.data || [];
+ },
+ layout: 'fitDataStretch',
+ placeholder: "Keine Daten verfügbar",
+ persistenceID: "2026_07_21_tempus_sync_v1",
+ locale: true,
+ columns: [
+ {
+ formatter: 'rowSelection',
+ titleFormatter: 'rowSelection',
+ headerSort: false,
+ width: 40
+ },
+ {titlePhrase: 'lehre/organisationseinheit', field: 'oebezeichnung'},
+ {titlePhrase: 'lehre/studiensemester', field: 'studiensemester_kurzbz'},
+ {titlePhrase: 'lehre/bis_title', field: 'datum_bis',
+ formatter: (cell) => {
+ const value = cell.getValue();
+ if (!value) return ''
+ const date = luxon.DateTime.fromSQL(value)
+ return date.isValid ? date.toFormat('dd.MM.yyyy') : value
+ },
+ minWidth: 100
+ },
+ {titlePhrase: 'lehre/studienplan', field: 'studienplanbezeichnung', minWidth: 200},
+ {titlePhrase: 'lehre/ausbildungssemester', field: 'ausbildungssemester'},
+ {titlePhrase: 'global/status', field: 'sync_status'},
+ {titlePhrase: 'lehre/mail_benachrichtigung', field: 'mail', formatter: 'tickCross', hozAlign: 'center', minWidth: 80},
+ {titlePhrase:'global/insertamum', field: 'insertamum',
+ formatter: (cell) => {
+ const value = cell.getValue();
+ if (!value) return ''
+ const date = luxon.DateTime.fromSQL(value)
+ return date.isValid ? date.toFormat('dd.MM.yyyy HH:mm') : value
+ },
+ visible: false
+ },
+ {titlePhrase: 'global/insertvon', field: 'insertvon', visible: false},
+ {titlePhrase: 'global/updateamum', field: 'updateamum',
+ formatter: (cell) => {
+ const value = cell.getValue();
+ if (!value) return ''
+ const date = luxon.DateTime.fromSQL(value)
+ return date.isValid ? date.toFormat('dd.MM.yyyy HH:mm') : value
+ },
+ visible: false
+ },
+ {titlePhrase: 'global/updatevon', field: 'updatevon', visible: false},
+ {
+ titlePhrase: 'global/aktionen',
+ field: 'actions',
+ headerSort: false,
+ hozAlign: 'center',
+ width: 120,
+ formatter: (cell) => {
+ let container = document.createElement('div');
+ container.className = 'd-flex gap-3';
+
+ const editButton = document.createElement('button');
+ editButton.className = 'btn btn-outline-secondary';
+ editButton.innerHTML = ' ';
+ editButton.addEventListener('click', () => {
+ this.$refs.syncModal.openEdit(cell.getRow().getData());
+ });
+ container.append(editButton);
+
+
+ const startButton = document.createElement('button');
+ startButton.className = 'btn btn-outline-secondary';
+ startButton.innerHTML = ' ';
+
+ startButton.addEventListener('click', () => {
+ startButton.disabled = true;
+ this.$refs.loader.show();
+
+ this.$refs.syncModal
+ .start(cell.getRow().getData())
+ .then(() => {
+ startButton.disabled = false;
+ this.$refs.loader.hide();
+
+ })
+ .catch(() => {
+ this.$refs.loader.hide();
+ startButton.disabled = false;
+ });
+ });
+
+ container.append(startButton);
+
+
+ const deleteButton = document.createElement('button');
+ deleteButton.className = 'btn btn-outline-secondary';
+ deleteButton.innerHTML = ' ';
+
+ deleteButton.addEventListener('click', evt => {
+ evt.stopPropagation();
+ this.$fhcAlert
+ .confirmDelete()
+ .then(result =>
+ result
+ ? cell.getData().kalender_syncstatus_id
+ : Promise.reject({ handled: true })
+ )
+ .then(kalender_syncstatus_id =>
+ this.$api.call(ApiTempusSync.delete(kalender_syncstatus_id))
+ )
+ .then(() => {
+ this.reloadTable();
+ })
+ .catch(this.$fhcAlert.handleSystemError);
+ });
+ container.append(deleteButton);
+
+ return container;
+ }
+ }
+ ],
+ }
+ }
+ },
+
+ methods: {
+ openNewModal()
+ {
+ this.$refs.syncModal.openNew();
+ },
+ reloadTable()
+ {
+ this.$refs.syncTable.reloadTable();
+ },
+ openStartModal()
+ {
+ this.$refs.syncModal.openStart();
+ },
+ onSaved(studiensemester_kurzbz)
+ {
+ if (studiensemester_kurzbz && studiensemester_kurzbz !== this.studiensemester_kurzbz)
+ {
+ this.studiensemester_kurzbz = studiensemester_kurzbz;
+ }
+ else
+ {
+ this.reloadTable();
+ }
+ }
+ },
+ template: `
+
+
+
+
+
+
+
+
+
+ -- {{ $p.t('lehre', 'studiensemester') }} --
+ {{ $p.t('ui', 'alleAnzeigen') }}
+
+ {{ studiensemester.studiensemester_kurzbz }}
+
+
+
+ {{ $p.t('global', 'jetztStarten') }}
+
+
+
+
+
+
+
+
+
+
+ `
+};
\ No newline at end of file
diff --git a/public/js/components/Tempus/VerbandMenu.js b/public/js/components/Tempus/VerbandMenu.js
new file mode 100644
index 000000000..ecf56e60b
--- /dev/null
+++ b/public/js/components/Tempus/VerbandMenu.js
@@ -0,0 +1,25 @@
+import BaseTreemenu from '../Base/Treemenu.js';
+
+export default {
+ name: 'TempusVerbandMenu',
+ components: {
+ BaseTreemenu,
+ },
+ emits: ['select-verband-and-close'],
+ methods: {
+ onSelectVerbandAndClose(payload) {
+ this.$emit('select-verband-and-close', payload);
+ },
+ },
+ template: /* html */ `
+
+
+
+
+ `,
+};
diff --git a/public/js/composables/Tempus/ContextMenuActions.js b/public/js/composables/Tempus/ContextMenuActions.js
index 1c6144902..5f6495ed1 100644
--- a/public/js/composables/Tempus/ContextMenuActions.js
+++ b/public/js/composables/Tempus/ContextMenuActions.js
@@ -18,6 +18,11 @@ export function useContextMenuActions(handlers)
icon: 'fa-solid fa-table-list',
action: handlers.openResourcesAssignmentModal
},
+ {
+ label: 'Tags',
+ icon: 'fa-solid fa-tags',
+ action: handlers.openTagsModal
+ },
{
label: 'Freischalten für Voransicht',
icon: 'fa-solid fa-chalkboard-user',
diff --git a/system/dbupdate_3.4.php b/system/dbupdate_3.4.php
index dc4507d5f..4b1468906 100644
--- a/system/dbupdate_3.4.php
+++ b/system/dbupdate_3.4.php
@@ -99,11 +99,17 @@ require_once('dbupdate_3.4/76150_perm_other_lv_plan.php');
require_once('dbupdate_3.4/68957_dashboard_bookmark_neue_Spalte_sort.php');
require_once('dbupdate_3.4/68530_Dashboard_Cleanup.php');
require_once('dbupdate_3.4/77375_ressourcen.php');
+require_once('dbupdate_3.4/78052_tempus_tag_events.php');
+require_once('dbupdate_3.4/78318_rename_column_of_eindeutige_gruppen_id_to_eindeutige_kalender_gruppen_id_in_tbl_kalender.php');
require_once('dbupdate_3.4/78318_rename_column_of_eindeutige_gruppen_id_to_eindeutige_kalender_gruppen_id_in_tbl_kalender.php');
require_once('dbupdate_3.4/75959_StudVw_Automatische_Tags.php');
require_once('dbupdate_3.4/76160_lvv_favorites.php');
require_once('dbupdate_3.4/77080_tabulator_presets_table.php');
require_once('dbupdate_3.4/78292_gehaltsanpassungtyp.php');
+require_once('dbupdate_3.4/77949_tempus_synchronisation.php');
+require_once('dbupdate_3.4/78287_tempus_raumzuordnung.php');
+
+require_once('dbupdate_3.4/78285_tempus_stundenraster.php');
// *** Pruefung und hinzufuegen der neuen Attribute und Tabellen
echo 'Pruefe Tabellen und Attribute! ';
diff --git a/system/dbupdate_3.4/46975_tempus.php b/system/dbupdate_3.4/46975_tempus.php
index 68d5cdcf8..921bbb4e6 100644
--- a/system/dbupdate_3.4/46975_tempus.php
+++ b/system/dbupdate_3.4/46975_tempus.php
@@ -249,3 +249,32 @@ if ($result = @$db->db_query("SELECT 1 FROM public.tbl_variablenname WHERE name
echo 'public.tbl_variablenname: Added name "tempus_favorites" ';
}
}
+
+
+if(!$result = @$db->db_query("SELECT status_kurzbz_max FROM lehre.tbl_kalender LIMIT 1"))
+{
+ $qry = "ALTER TABLE lehre.tbl_kalender ADD COLUMN status_kurzbz_max character varying(32);
+
+ UPDATE lehre.tbl_kalender_status
+ SET status_kurzbz = 'to_delete',
+ bezeichnung = 'to_delete'
+ WHERE status_kurzbz = 'todelete';
+ UPDATE lehre.tbl_kalender_status SET sort = 9 WHERE status_kurzbz = 'deleted';
+ UPDATE lehre.tbl_kalender_status SET sort = 10 WHERE status_kurzbz = 'archived';
+ INSERT INTO lehre.tbl_kalender_status (status_kurzbz, bezeichnung, bezeichnung_mehrsprachig, sort) VALUES (E'to_delete_preview', E'to_delete_preview', E'{\"Zur Löschung in Voransicht\", \"Marked for Deletion in Preview\"}', 7);
+ INSERT INTO lehre.tbl_kalender_status (status_kurzbz, bezeichnung, bezeichnung_mehrsprachig, sort) VALUES (E'to_delete_live', E'to_delete_live', E'{\"Zur Löschung in Live\", \"Marked for Deletion in Live\"}', 8);
+
+ ALTER TABLE lehre.tbl_kalender ADD CONSTRAINT tbl_kalender_status_max_fk FOREIGN KEY (status_kurzbz_max)
+ REFERENCES lehre.tbl_kalender_status (status_kurzbz) MATCH FULL ON DELETE RESTRICT ON UPDATE CASCADE;
+";
+
+
+ if(!$db->db_query($qry))
+ echo 'lehre.tbl_kalender: '.$db->db_last_error().' ';
+ else
+ echo ' lehre.tbl_kalender: Spalte status_kurzbz_max hinzugefuegt.
+ lehre.tbl_kalender_status: status_kurzbz von todelete auf to_delete umbenannt.
+ lehre.tbl_kalender_status: status_kurzbz to_delete_preview hinzugefuegt.
+ lehre.tbl_kalender_status: status_kurzbz to_delete_live hinzugefuegt.
+ ';
+}
\ No newline at end of file
diff --git a/system/dbupdate_3.4/77949_tempus_synchronisation.php b/system/dbupdate_3.4/77949_tempus_synchronisation.php
new file mode 100644
index 000000000..5773a31d0
--- /dev/null
+++ b/system/dbupdate_3.4/77949_tempus_synchronisation.php
@@ -0,0 +1,38 @@
+db_query("SELECT kalender_syncstatus_id FROM lehre.tbl_kalender_syncstatus LIMIT 1"))
+{
+ $qry = "CREATE TABLE lehre.tbl_kalender_syncstatus
+ (
+ kalender_syncstatus_id bigserial NOT NULL,
+ oe_kurzbz character varying(32),
+ studiensemester_kurzbz character varying(32),
+ datum_bis date,
+ studienplan_id integer,
+ ausbildungssemester smallint,
+ sync_status_kurzbz character varying(32),
+ mail boolean DEFAULT false NOT NULL,
+ insertamum timestamp DEFAULT now(),
+ insertvon character varying(32),
+ updateamum timestamp,
+ updatevon character varying(32),
+ CONSTRAINT tbl_kalender_syncstatus_pk PRIMARY KEY (kalender_syncstatus_id)
+ );
+
+ ALTER TABLE lehre.tbl_kalender_syncstatus ADD CONSTRAINT tbl_kalender_syncstatus_oe_kurzbz_fk FOREIGN KEY (oe_kurzbz) REFERENCES public.tbl_organisationseinheit (oe_kurzbz) ON DELETE RESTRICT ON UPDATE CASCADE;
+ ALTER TABLE lehre.tbl_kalender_syncstatus ADD CONSTRAINT tbl_kalender_syncstatus_studiensemester_kurzbz_fk FOREIGN KEY (studiensemester_kurzbz) REFERENCES public.tbl_studiensemester(studiensemester_kurzbz) ON DELETE RESTRICT ON UPDATE CASCADE;
+ ALTER TABLE lehre.tbl_kalender_syncstatus ADD CONSTRAINT tbl_kalender_syncstatus_studienplan_id_fk FOREIGN KEY (studienplan_id) REFERENCES lehre.tbl_studienplan (studienplan_id) ON DELETE RESTRICT ON UPDATE CASCADE;
+ ALTER TABLE lehre.tbl_kalender_syncstatus ADD CONSTRAINT tbl_kalender_syncstatus_status_sync_status_kurzbz_fk FOREIGN KEY (sync_status_kurzbz) REFERENCES lehre.tbl_kalender_status (status_kurzbz) ON DELETE RESTRICT ON UPDATE CASCADE;
+
+ GRANT SELECT, UPDATE ON lehre.tbl_kalender_syncstatus_kalender_syncstatus_id_seq TO vilesci;
+ GRANT SELECT ON lehre.tbl_kalender_syncstatus_kalender_syncstatus_id_seq TO web;
+ GRANT SELECT, UPDATE, INSERT, DELETE ON lehre.tbl_kalender_syncstatus to vilesci;
+ GRANT SELECT ON lehre.tbl_kalender_typ to web;";
+
+ if(!$db->db_query($qry))
+ echo 'lehre.tbl_kalender_syncstatus: '.$db->db_last_error().' ';
+ else
+ echo ' lehre.tbl_kalender_syncstatus: Tabelle hinzugefuegt ';
+
+}
\ No newline at end of file
diff --git a/system/dbupdate_3.4/78052_tempus_tag_events.php b/system/dbupdate_3.4/78052_tempus_tag_events.php
new file mode 100644
index 000000000..64f47d57b
--- /dev/null
+++ b/system/dbupdate_3.4/78052_tempus_tag_events.php
@@ -0,0 +1,19 @@
+db_query("SELECT 1
+ FROM information_schema.columns
+ WHERE table_schema = 'public'
+ AND table_name = 'tbl_notizzuordnung'
+ AND column_name = 'eindeutige_kalender_gruppen_id'");
+
+if ($db->db_num_rows($result) === 0)
+{
+ $qry = "ALTER TABLE public.tbl_notizzuordnung
+ ADD COLUMN eindeutige_kalender_gruppen_id UUID NULL DEFAULT NULL";
+
+ if (!$db->db_query($qry))
+ echo 'public.tbl_notizzuordnung: '.$db->db_last_error().' ';
+ else
+ echo ' public.tbl_notizzuordnung: eindeutige_kalender_gruppen_id column added';
+}
diff --git a/system/dbupdate_3.4/78285_tempus_stundenraster.php b/system/dbupdate_3.4/78285_tempus_stundenraster.php
new file mode 100644
index 000000000..e38e47a81
--- /dev/null
+++ b/system/dbupdate_3.4/78285_tempus_stundenraster.php
@@ -0,0 +1,16 @@
+db_query("SELECT * FROM system.tbl_berechtigung WHERE berechtigung_kurzbz='basis/tempus_stundenraster'"))
+{
+ if($db->db_num_rows($result)==0)
+ {
+ $qry = "INSERT INTO system.tbl_berechtigung(berechtigung_kurzbz, beschreibung)
+ VALUES('basis/tempus_stundenraster','Berechtigung, um den Stundenraster zu aktivieren/deaktivieren');";
+
+ if(!$db->db_query($qry))
+ echo 'Berechtigung: '.$db->db_last_error().' ';
+ else
+ echo 'Neue Berechtigung basis/tempus_stundenraster hinzugefuegt! ';
+ }
+}
diff --git a/system/dbupdate_3.4/78287_tempus_raumzuordnung.php b/system/dbupdate_3.4/78287_tempus_raumzuordnung.php
new file mode 100644
index 000000000..3e9d83fb5
--- /dev/null
+++ b/system/dbupdate_3.4/78287_tempus_raumzuordnung.php
@@ -0,0 +1,17 @@
+db_query("SELECT 1 FROM public.tbl_variablenname WHERE name = 'roomless_planning';"))
+{
+ if ($db->db_num_rows($result) == 0)
+ {
+ $qry = "INSERT INTO public.tbl_variablenname(name, defaultwert) VALUES('roomless_planning', false);
+ INSERT INTO public.tbl_variablenname(name, defaultwert) VALUES('priority_room_planning', false);
+ INSERT INTO public.tbl_variablenname(name, defaultwert) VALUES('dialog_room_planning', false);";
+
+ if (!$db->db_query($qry))
+ echo 'public.tbl_variablenname '.$db->db_last_error().' ';
+ else
+ echo 'public.tbl_variablenname: Added "roomless_planning", "priority_room_planning", "dialog_room_planning" ';
+ }
+}
\ No newline at end of file
diff --git a/system/phrasesupdate.php b/system/phrasesupdate.php
index 7e4f6d6f4..97b0d475c 100644
--- a/system/phrasesupdate.php
+++ b/system/phrasesupdate.php
@@ -3363,6 +3363,126 @@ $phrases = array(
)
)
),
+ array(
+ 'app' => 'core',
+ 'category' => 'lehre',
+ 'phrase' => 'mail_benachrichtigung',
+ 'insertvon' => 'system',
+ 'phrases' => array(
+ array(
+ 'sprache' => 'German',
+ 'text' => 'Mail-Benachrichtigung',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ array(
+ 'sprache' => 'English',
+ 'text' => 'Email notification',
+ 'description' => '',
+ 'insertvon' => 'system'
+ )
+ )
+ ),
+ array(
+ 'app' => 'core',
+ 'category' => 'lehre',
+ 'phrase' => 'bis_title',
+ 'insertvon' => 'system',
+ 'phrases' => array(
+ array(
+ 'sprache' => 'German',
+ 'text' => 'Bis',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ array(
+ 'sprache' => 'English',
+ 'text' => 'Until',
+ 'description' => '',
+ 'insertvon' => 'system'
+ )
+ )
+ ),
+ array(
+ 'app' => 'core',
+ 'category' => 'lehre',
+ 'phrase' => 'errorDeleting',
+ 'insertvon' => 'system',
+ 'phrases' => array(
+ array(
+ 'sprache' => 'German',
+ 'text' => 'Fehler beim Löschen.',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ array(
+ 'sprache' => 'English',
+ 'text' => 'Error deleting.',
+ 'description' => '',
+ 'insertvon' => 'system'
+ )
+ )
+ ),
+ array(
+ 'app' => 'core',
+ 'category' => 'lehre',
+ 'phrase' => 'tempus_sync_start',
+ 'insertvon' => 'system',
+ 'phrases' => array(
+ array(
+ 'sprache' => 'German',
+ 'text' => 'Synchronisierung manuell starten',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ array(
+ 'sprache' => 'English',
+ 'text' => 'Start synchronization manually',
+ 'description' => '',
+ 'insertvon' => 'system'
+ )
+ )
+ ),
+ array(
+ 'app' => 'core',
+ 'category' => 'lehre',
+ 'phrase' => 'tempus_sync_edit',
+ 'insertvon' => 'system',
+ 'phrases' => array(
+ array(
+ 'sprache' => 'German',
+ 'text' => 'Sync-Eintrag bearbeiten',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ array(
+ 'sprache' => 'English',
+ 'text' => 'Edit sync entry',
+ 'description' => '',
+ 'insertvon' => 'system'
+ )
+ )
+ ),
+ array(
+ 'app' => 'core',
+ 'category' => 'lehre',
+ 'phrase' => 'tempus_sync_new',
+ 'insertvon' => 'system',
+ 'phrases' => array(
+ array(
+ 'sprache' => 'German',
+ 'text' => 'Neuer Sync-Eintrag',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ array(
+ 'sprache' => 'English',
+ 'text' => 'New sync entry',
+ 'description' => '',
+ 'insertvon' => 'system'
+ )
+ )
+ ),
array(
'app' => 'core',
'category' => 'lehre',
@@ -37385,6 +37505,86 @@ array(
)
)
),
+ array(
+ 'app' => 'core',
+ 'category' => 'ui',
+ 'phrase' => 'roomless_planning_error',
+ 'insertvon' => 'system',
+ 'phrases' => array(
+ array(
+ 'sprache' => 'German',
+ 'text' => 'Raumloseplanung deaktiviert',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ array(
+ 'sprache' => 'English',
+ 'text' => 'Roomless planning is disabled.',
+ 'description' => '',
+ 'insertvon' => 'system'
+ )
+ )
+ ),
+ array(
+ 'app' => 'core',
+ 'category' => 'ui',
+ 'phrase' => 'roomless_planning',
+ 'insertvon' => 'system',
+ 'phrases' => array(
+ array(
+ 'sprache' => 'German',
+ 'text' => 'Raumlos planen',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ array(
+ 'sprache' => 'English',
+ 'text' => 'Plan without room',
+ 'description' => '',
+ 'insertvon' => 'system'
+ )
+ )
+ ),
+ array(
+ 'app' => 'core',
+ 'category' => 'ui',
+ 'phrase' => 'priority_room_planning',
+ 'insertvon' => 'system',
+ 'phrases' => array(
+ array(
+ 'sprache' => 'German',
+ 'text' => ' Prioritätsraum verwenden',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ array(
+ 'sprache' => 'English',
+ 'text' => 'Use priority room',
+ 'description' => '',
+ 'insertvon' => 'system'
+ )
+ )
+ ),
+ array(
+ 'app' => 'core',
+ 'category' => 'ui',
+ 'phrase' => 'dialog_room_planning',
+ 'insertvon' => 'system',
+ 'phrases' => array(
+ array(
+ 'sprache' => 'German',
+ 'text' => 'Raumauswahl anzeigen',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ array(
+ 'sprache' => 'English',
+ 'text' => 'Show room selection',
+ 'description' => '',
+ 'insertvon' => 'system'
+ )
+ )
+ ),
array(
'app' => 'softwarebereitstellung',
'category' => 'ui',
@@ -39128,6 +39328,26 @@ array(
)
)
),
+ array(
+ 'app' => 'core',
+ 'category' => 'ui',
+ 'phrase' => 'successStart',
+ 'insertvon' => 'system',
+ 'phrases' => array(
+ array(
+ 'sprache' => 'German',
+ 'text' => '"Erfolgreich gestartet',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ array(
+ 'sprache' => 'English',
+ 'text' => 'Started successfully',
+ 'description' => '',
+ 'insertvon' => 'system'
+ )
+ )
+ ),
array(
'app' => 'core',
'category' => 'ui',
@@ -62178,6 +62398,86 @@ I have been informed that I am under no obligation to consent to the transmissio
)
)
),
+ array(
+ 'app' => 'core',
+ 'category' => 'ui',
+ 'phrase' => 'tags_assignment_modal_title',
+ 'insertvon' => 'system',
+ 'phrases' => array(
+ array(
+ 'sprache' => 'German',
+ 'text' => 'Tag-Zuweisung',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ array(
+ 'sprache' => 'English',
+ 'text' => 'Tag Assignment',
+ 'description' => '',
+ 'insertvon' => 'system'
+ )
+ )
+ ),
+ array(
+ 'app' => 'core',
+ 'category' => 'ui',
+ 'phrase' => 'assigned_tags_subtitle',
+ 'insertvon' => 'system',
+ 'phrases' => array(
+ array(
+ 'sprache' => 'German',
+ 'text' => 'Zugewiesene Tags',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ array(
+ 'sprache' => 'English',
+ 'text' => 'Assigned Tags',
+ 'description' => '',
+ 'insertvon' => 'system'
+ )
+ )
+ ),
+ array(
+ 'app' => 'core',
+ 'category' => 'ui',
+ 'phrase' => 'no_assigned_tags',
+ 'insertvon' => 'system',
+ 'phrases' => array(
+ array(
+ 'sprache' => 'German',
+ 'text' => 'Keine zugewiesenen Tags',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ array(
+ 'sprache' => 'English',
+ 'text' => 'No Assigned Tags',
+ 'description' => '',
+ 'insertvon' => 'system'
+ )
+ )
+ ),
+ array(
+ 'app' => 'core',
+ 'category' => 'ui',
+ 'phrase' => 'tags',
+ 'insertvon' => 'system',
+ 'phrases' => array(
+ array(
+ 'sprache' => 'German',
+ 'text' => 'Tags',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ array(
+ 'sprache' => 'English',
+ 'text' => 'Tags',
+ 'description' => '',
+ 'insertvon' => 'system'
+ )
+ )
+ ),
// generic tabulator phrases END
array(
'app' => 'core',
diff --git a/system/seeders/015_kalender.sql b/system/seeders/015_kalender.sql
index 2ba23c754..a4bbc6a41 100644
--- a/system/seeders/015_kalender.sql
+++ b/system/seeders/015_kalender.sql
@@ -177,6 +177,17 @@ VALUES
(51006, 31),
(51004, 32);
+-- Calendar setup for tags tests
+INSERT INTO lehre.tbl_kalender
+(von, bis, typ, status_kurzbz, vorgaenger_kalender_id, eindeutige_kalender_gruppen_id, insertamum, insertvon, updateamum, updatevon)
+VALUES
+(date_trunc('week', CURRENT_DATE) + INTERVAL '6 day' + TIME '16:55:00', date_trunc('week', CURRENT_DATE) + INTERVAL '6 day' + TIME '17:40:00', 'lehreinheit', 'live', NULL, 'ec1bd001-facf-4b21-a671-bcb2d4f037fd', now(), 'demoadmin', NULL, 'demoadmin');
+
+INSERT INTO lehre.tbl_kalender_lehreinheit
+(lehreinheit_id, kalender_id)
+VALUES
+(51004, 33);
+
-- INSERT INTO public.tbl_variablenname
-- (name, defaultwert)
-- VALUES('ignore_resources_collisions', 'false');
diff --git a/system/seeders/016_tag.sql b/system/seeders/016_tag.sql
new file mode 100644
index 000000000..9fef2cf1c
--- /dev/null
+++ b/system/seeders/016_tag.sql
@@ -0,0 +1,5 @@
+INSERT INTO public.tbl_notiz_typ
+(typ_kurzbz, bezeichnung_mehrsprachig, beschreibung, automatisiert, aktiv, zuordnung, tag, style, vorrueckung, prioritaet/*, taglib*/)
+VALUES
+('prioone', '{Prio 1,Prio 1}', '', false, true, NULL, false, 'tag_rot', false, 1/*, NULL*/),
+('priotwo', '{Prio 2,Prio 2}', '', false, true, NULL, false, 'tag_gelb', false, 2/*, NULL*/);
\ No newline at end of file
diff --git a/tests/cypress/cypress/e2e/specs/ui/tempus/tempus.smoke.cy.js b/tests/cypress/cypress/e2e/specs/ui/tempus/tempus.smoke.cy.js
index 454c8686a..45fde5243 100644
--- a/tests/cypress/cypress/e2e/specs/ui/tempus/tempus.smoke.cy.js
+++ b/tests/cypress/cypress/e2e/specs/ui/tempus/tempus.smoke.cy.js
@@ -71,6 +71,20 @@ context("Tempus smoke tests", () => {
tempusPage.getResourcesModal().should("be.visible");
});
+ it("shows tags modal when selecting Tags from event context menu", () => {
+ tempusPage.waitForCalendarToFinishLoading();
+ tempusPage
+ .getCalendarEventsByWeekdayAndStartTime("Sunday", "16:55:00")
+ .should("have.length.greaterThan", 0);
+
+ tempusPage.getCalendarEventsByWeekdayAndStartTime("Sunday", "16:55:00").first().rightclick();
+ tempusPage.getEventContextMenuOption("Tags").click({ force: true });
+ waitForOk("@fetchTags");
+ waitForOk("@fetchTagsByCalendar");
+
+ tempusPage.getTagsModal().should("be.visible");
+ });
+
it("shows history modal when selecting History from event context menu", () => {
tempusPage.waitForCalendarToFinishLoading();
tempusPage.getCalendarEvents().should("have.length.greaterThan", 0);
diff --git a/tests/cypress/cypress/support/pages/tempus.po.js b/tests/cypress/cypress/support/pages/tempus.po.js
index 7eeeab146..3f8dd1e42 100644
--- a/tests/cypress/cypress/support/pages/tempus.po.js
+++ b/tests/cypress/cypress/support/pages/tempus.po.js
@@ -72,6 +72,14 @@ class TempusPage {
method: "GET",
url: "**/tempus/OperationalResourceToCalenderAPI/getAssignedResourcesByCalender/**",
}).as("fetchAssignedResources");
+ cy.intercept({
+ method: "GET",
+ url: "**/tempus/Tags/getTags**",
+ }).as("fetchTags");
+ cy.intercept({
+ method: "GET",
+ url: "**/tempus/Tags/getTagsByCalendar/**",
+ }).as("fetchTagsByCalendar");
cy.intercept({
method: "POST",
url: "**/tempus/coursepicker/getByStg**",
@@ -345,6 +353,8 @@ class TempusPage {
cy
.get("[data-cy='resourcesAssignmentModal']")
.closest(".bootstrap-modal.show");
+ getTagsModal = () =>
+ cy.get("[data-cy='tagsAssignmentModal']").closest(".bootstrap-modal.show")
getCalendarEventRoom = (id) =>
this.getCalendarEventById(id).find("[data-cy='calendar-event-room']");
getStundenrasterToggle = () =>