Conflict resolved

This commit is contained in:
Ivymaster
2026-09-07 18:00:00 +02:00
78 changed files with 5280 additions and 1519 deletions
+2
View File
@@ -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))
+8 -1
View File
@@ -2,4 +2,11 @@
$config['send_update_mails'] = false;
$config['calendar_start'] = 7;
$config['calendar_end'] = 23;
$config['calendar_end'] = 23;
$config['tempus_tags'] = [
'prioone' => ['readonly' => false],
'priotwo' => ['readonly' => true],
'hinweis' => ['readonly' => false],
];
+38 -9
View File
@@ -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)
]);
}
}
}
@@ -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();
}
@@ -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;
}
}
@@ -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
@@ -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();
@@ -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()
@@ -0,0 +1,304 @@
<?php
if (!defined('BASEPATH'))
exit('No direct script access allowed');
class KalenderSync extends FHCAPI_Controller
{
private $_ci;
const ALLOWED_STATUS = ['preview', 'live'];
public function __construct()
{
parent::__construct([
'getSyncs' => ['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();
}
}
@@ -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');
@@ -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))
@@ -0,0 +1,53 @@
<?php
if (!defined('BASEPATH'))
exit('No direct script access allowed');
class Tags extends Tag_Controller
{
const BERECHTIGUNG_KURZBZ = ['admin:rw', 'assistenz:rw'];
public function __construct()
{
parent::__construct([
'getTag' => 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'));
}
}
@@ -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))
+31 -3
View File
@@ -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);
+438 -223
View File
@@ -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;
}
}
@@ -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') . '<hr/>';
//TODO (david) richtigen Empfänger
if (!empty($lektor_entries))
$this->_ci->maillib->send('', '[email protected]', 'Lektor Tempus Update', $lektor_entries);
if (!empty($student_entries))
+429
View File
@@ -0,0 +1,429 @@
<?php
if (! defined("BASEPATH")) exit("No direct script access allowed");
class KalenderSyncLib
{
private $_ci;
private $status_map = array(
'preview' => 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);
}
}
+291 -28
View File
@@ -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();
}
}
@@ -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)',
@@ -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();
@@ -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();
@@ -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
@@ -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 (
@@ -0,0 +1,14 @@
<?php
class Kalendersyncstatus_model extends DB_Model
{
/**
* Constructor
*/
public function __construct()
{
parent::__construct();
$this->dbTable = 'lehre.tbl_kalender_syncstatus';
$this->pk = 'kalender_syncstatus_id';
}
}
+4 -1
View File
@@ -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',
+2
View File
@@ -8,6 +8,8 @@
@import './components/Notiz.css';
@import './components/Messages.css';
@import './components/AppMenu.css';
@import './components/horizontalsplit.css';
html {
font-size: .875em;
+34 -5
View File
@@ -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%;
}
@@ -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 {
+7
View File
@@ -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));
}
+7
View File
@@ -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;
}
+3 -10
View File
@@ -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 }
};
},
+9 -1
View File
@@ -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',
+2 -2
View File
@@ -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}
};
},
+83
View File
@@ -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 <https://www.gnu.org/licenses/>.
*/
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 }
};
},
};
+59
View File
@@ -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
};
},
};
+2
View File
@@ -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 },
]
});
+8
View File
@@ -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"
+28 -3
View File
@@ -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)"
>
<slot name="main-header" v-bind="{ index, date }" />
@@ -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)"
@@ -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: `
<div
class="fhc-calendar-base-grid-line-event event"
class="course-parker event"
v-draggable:move.noimage="dragKalenderCollection"
style="border:1px"
:style="eventStyle(event.orig)"
data-cy="calendar-event"
>
<button v-if="parked" class="unpark-btn" type="button" @click="unpark">
<i class="fa-solid fa-xmark"></i>
</button>
<div class="title">
{{ topicString || event.orig.titel || event.orig.lehrfach }}
</div>
<div>
{{ event.orig.datum }} {{ event.orig.beginn }}–{{ event.orig.ende }}
<span v-if="event.ort_kurzbz">· {{ event.orig.ort_kurzbz }}</span>
<span class="text-decoration-underline" @click="jumpToKw">KW: {{ kw }}</span> {{ datum }}
</div>
<div>
{{ event.orig.beginn }}–{{ event.orig.ende }}
</div>
</div>
`
};
};
+6 -2
View File
@@ -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'"
>
<i class="fa fa-chevron-left"></i>
</button>
@@ -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'"
>
<i class="fa fa-chevron-right"></i>
</button>
@@ -131,4 +135,4 @@ export default {
</div>
</div>
`
}
}
@@ -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 }"
+20 -72
View File
@@ -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: `
<div
class="fhc-calendar-mode-week flex-grow-1 position-relative"
@cal-click-default.capture="handleClickDefaults"
>
<base-slider ref="slider" v-slot="slot">
<table-view ref="view" v-bind="viewAttrs(slot.offset)">
<template v-slot="slot"><slot v-bind="slot" mode="week" /></template>
</table-view>
</base-slider>
<div>
<table-view ref="view" v-bind="$attrs" :day="focusDate" :end="focusEnd">
<template v-slot="slot"><slot v-bind="slot" mode="week" /></template>
</table-view>
</div>
`
}
}
@@ -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 */`
<div class="fhc-calendar-mode-table-view h-100 overflow-auto">
<core-filter-cmpt
@@ -2,6 +2,8 @@ import CalendarGrid from '../../Base/Grid.js';
import LabelDay from '../../Base/Label/Day.js';
import LabelDow from '../../Base/Label/Dow.js';
import LabelTime from '../../Base/Label/Time.js';
import FormInput from "../../../Form/Input.js";
export default {
name: "WeekView",
@@ -9,7 +11,8 @@ export default {
CalendarGrid,
LabelDay,
LabelDow,
LabelTime
LabelTime,
FormInput
},
inject: {
timeGrid: "timeGrid",
@@ -23,6 +26,11 @@ export default {
},
collapseEmptyDays: Boolean
},
data() {
return {
visibleDays: JSON.parse(localStorage.getItem('fhc-calender-visible-days')) ?? [true, true, true, true, true, true, true]
}
},
computed: {
start() {
return this.day.startOf('week', { useLocaleWeeks: true });
@@ -44,11 +52,19 @@ export default {
const end = this.hoursPlan?.end ?? 23;
return Array.from({ length: end - start + 1 }, (e, i) => 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"
>
<template #main-header="{ date }">
<div :class="{ today: isToday(date) }">
<template #main-header="{ date, index }">
<div :class="{ today: isToday(date) }" class="d-flex flex-column align-items-center">
<label-dow
v-bind="{ date }"
@cal-click="evt => evt.detail.source = 'day'"
@@ -70,6 +87,13 @@ export default {
<label-day
v-bind="{ date }"
/>
<form-input
type="checkbox"
:checked="visibleDays[date.weekday - 1]"
@change="toggleDay(date.weekday - 1)"
@click.stop
>
</form-input>
</div>
</template>
<template #part-header="{ part }">
+23 -2
View File
@@ -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"
>
<template v-slot="{ event, mode }">
<div
:class="['event-type-' + event.type + ' ' + mode + 'PageContainer', { 'event--parked': parkedEvents.has(String(event.kalender_id)) }]"
:class="['event-type-' + event.type + ' ' + mode + 'PageContainer', { 'event--parked': parkedEvents.has(String(event.kalender_id)) }, {'event--opacity': !showEvents}]"
:type="mode == 'day' ? 'button' : undefined"
:style="eventStyle(event)"
@mouseenter="$emit('event-hover', event)"
@@ -264,6 +284,7 @@ export default {
class="d-flex align-items-center gap-2"
style="cursor:pointer"
@click="showRaster = !showRaster"
v-if="canToggleGrid"
>
<i :class="showRaster ? 'fa-solid fa-toggle-on text-primary' : 'fa-solid fa-toggle-off text-muted'"></i>
<span class="form-check-label">Stundenraster</span>
+10 -6
View File
@@ -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: `
<div class="plus_button_container" @mouseleave="hideList">
<div v-if="isListItemShown" class="plus_button_container" @mouseleave="hideList">
<span :title="values.length === 0 ? 'Bitte Zeilen markieren' : ''">
<button @mouseover="showList = true"
:disabled="!values || values.length === 0"
+24
View File
@@ -0,0 +1,24 @@
import AppMenu from "../AppMenu.js";
export default {
name: "TempusAppMenu",
components: {
AppMenu,
},
template: /* html */ `
<aside id="appMenu" class="bg-light offcanvas offcanvas-start col-md p-md-0 h-100">
<div class="offcanvas-header">
Tempus
<button
type="button"
class="btn-close text-reset"
data-bs-dismiss="offcanvas"
:aria-label="$p.t('ui/schliessen')"
></button>
</div>
<div class="offcanvas-body">
<app-menu app-identifier="tempus" />
</div>
</aside>
`,
};
+71 -21
View File
@@ -14,19 +14,32 @@ export default {
type: Array,
default: [],
},
lecturers: {
type: Array,
default: [],
},
studiensemester: {
type: String,
default: null
},
previewLehreinheitId: {
type: [String, Number],
default: null
},
previewLoading: {
type: Boolean,
default: false
},
},
emits: ['select-lecturer', 'select-kw'],
emits: ['select-lecturer', 'select-kw', 'preview-raumvorschlag'],
data() {
return {
searchparam: '',
allCourses: [],
sortBy: null,
multiWeekIds: new Set(),
favorites: new Set()
favorites: new Set(),
filter: {}
}
},
computed: {
@@ -86,32 +99,45 @@ export default {
studiengaenge: {
deep: true,
handler(val) {
this.searchparam = '';
this.loadCoursesByStg(val);
this.loadCourses();
}
},
lecturers: {
deep: true,
handler() {
this.loadCourses();
}
},
studiensemester() {
if (this.studiengaenge)
this.loadCoursesByStg(this.studiengaenge);
this.loadCourses();
},
},
methods: {
async loadCoursesByStg(stg) {
if (stg.length <= 0)
{
this.allCourses = [];
return;
async loadCourses()
{
const hasLektoren = this.lecturers.length > 0;
const hasStg = this.studiengaenge.length > 0;
this.filter = {};
if (hasLektoren)
this.filter.uid = this.lecturers;
if (hasStg) {
this.filter.stg = this.studiengaenge.map(
({ stg_kz, semester, orgform_kurzbz }) => ({
stg_kz,
semester,
orgform_kurzbz,
}),
);
}
let payload = stg.map(({ stg_kz, semester, orgform_kurzbz }) => ({
stg_kz,
semester,
orgform_kurzbz
}));
this.$api.call(ApiCoursePicker.getByStg(payload, this.studiensemester))
if (Object.keys(this.filter).length === 0)
return this.allCourses = [];
this.$api.call(ApiCoursePicker.getCourses(this.filter, this.studiensemester))
.then(result => {
this.allCourses = result.data.map(e => ({
this.allCourses = result?.data?.map(e => ({
lehreinheit_id: e.lehreinheit_id,
lektoren: e.lektoren,
raumtyp: e.raumtyp,
@@ -177,7 +203,7 @@ export default {
},
reload()
{
this.loadCoursesByStg(this.studiengaenge);
this.loadCourses();
},
toggleMultiWeek(course)
{
@@ -210,6 +236,17 @@ export default {
{
return this.favorites.has(course.lehreinheit_id);
},
selectRaumvorschlag(course)
{
if (this.isRaumvorschlagActive(course))
this.$emit('preview-raumvorschlag', null);
else
this.$emit('preview-raumvorschlag', course.orig);
},
isRaumvorschlagActive(course)
{
return this.previewLehreinheitId != null && this.previewLehreinheitId === course.lehreinheit_id;
},
},
mounted() {
this.favorites = new Set(JSON.parse(localStorage.getItem('tempus_coursepicker_favs') || '[]'));
@@ -253,7 +290,7 @@ export default {
</button>
</div>
</div>
<div v-if="studiengaenge.length <= 0" class="d-flex flex-column align-items-center justify-content-center text-center text-muted py-5 px-3 h-100">
<div v-if="Object.keys(filter).length <= 0" class="d-flex flex-column align-items-center justify-content-center text-center text-muted py-5 px-3 h-100">
<span class="small fw-semibold mb-1">{{$p.t('lehre', 'cptitleempty')}}</span>
<span class="small">{{$p.t('lehre', 'cpempty')}}</span>
</div>
@@ -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)"
></i>
<i
v-if="!(previewLoading && isRaumvorschlagActive(course))"
class="fa fa-door-open"
:class="isRaumvorschlagActive(course) ? 'text-primary' : 'text-muted'"
style="cursor:pointer"
v-tooltip="isRaumvorschlagActive(course) ? 'Raumvorschlag ausblenden' : 'Raumvorschlag einblenden'"
@click.stop="selectRaumvorschlag(course)"
></i>
<i
v-else
class="fa fa-spinner fa-spin text-primary"
v-tooltip="'Räume werden geprüft...'"
></i>
</div>
<!--TODO(david) entfernen, dient nur für das mappen mit der lvverwaltung-->
@@ -47,6 +47,10 @@ export default {
<i :class="l.overlays.wishes ? 'fa-solid fa-toggle-on text-primary' : 'fa-solid fa-toggle-off text-muted'"></i>
<span class="form-check-label">Zeitwünsche</span>
</div>
<div class="d-flex align-items-center gap-2" @click="l.showCoursePicker = !l.showCoursePicker" style="cursor:pointer">
<i :class="l.showCoursePicker ? 'fa-solid fa-toggle-on text-primary' : 'fa-solid fa-toggle-off text-muted'"></i>
<span class="form-check-label">Course Picker</span>
</div>
</div>
</div>
</div>
@@ -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");
}
},
]
},
}
+108
View File
@@ -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 */ `
<header class="navbar navbar-expand-lg navbar-dark bg-dark flex-md-nowrap p-0 shadow">
<div class="col-md-4 col-lg-3 col-xl-2 d-flex align-items-center">
<button
class="btn btn-outline-light border-0 m-1 collapsed"
type="button"
data-bs-toggle="offcanvas"
data-bs-target="#appMenu"
aria-controls="appMenu"
aria-expanded="false"
:aria-label="$p.t('ui/toggle_nav')"
>
<span class="svg-icon svg-icon-apps"></span>
</button>
<a class="navbar-brand me-0" :href="tempusRoot">Tempus</a>
</div>
<button
class="btn btn-outline-light border-0 d-md-none m-1 collapsed"
type="button"
data-bs-toggle="offcanvas"
data-bs-target="#sidebarMenu"
aria-controls="sidebarMenu"
aria-expanded="false"
:aria-label="$p.t('ui/toggle_nav')"
>
<span class="fa-solid fa-table-list"></span>
</button>
<core-searchbar
ref="searchbar"
:searchoptions="searchbaroptions"
:searchfunction="searchfunction"
class="searchbar position-relative w-100"
show-btn-submit
></core-searchbar>
<div id="nav-user" class="dropdown">
<button
id="nav-user-btn"
class="btn btn-link rounded-0 py-0"
type="button"
data-bs-toggle="dropdown"
data-bs-target="#nav-user-menu"
aria-expanded="false"
aria-controls="nav-user-menu"
>
<img
:src="avatarUrl"
:alt="$p.t('profilUpdate/profilBild')"
class="bg-light avatar rounded-circle border border-light"
/>
</button>
<ul
ref="navUserDropdown"
class="dropdown-menu dropdown-menu-dark dropdown-menu-end rounded-0 text-center m-0"
aria-labelledby="nav-user-btn"
>
<li>
<button
type="button"
class="dropdown-item"
data-bs-toggle="modal"
data-bs-target="#configModal"
>
{{ $p.t('ui/settings') }}
</button>
</li>
<li><hr class="dropdown-divider m-0"/></li>
<li>
<nav-language @changed="$emit('language-changed', $event)" item-class="dropdown-item border-left-dark" />
</li>
<li><hr class="dropdown-divider m-0"/></li>
<li>
<a class="dropdown-item" :href="logoutUrl">
{{ $p.t('ui/logout') }}
</a>
</li>
</ul>
</div>
</header>
`,
};
@@ -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 <https://www.gnu.org/licenses/>.
*/
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: `
<bs-modal
ref="modal"
class="bootstrap-prompt"
dialog-class="modal-lg"
data-cy="historyModal"
>
<template #title>History</template>
<template #default>
<table v-if="entries.length" class="table table-bordered table-hover">
<thead class="table-light">
<tr>
<th>Von</th>
<th>Bis</th>
<th>Status</th>
<th>Ort</th>
</tr>
</thead>
<tbody>
<tr v-for="entry in entries" :key="entry.id">
<td>{{ entry.von }}</td>
<td>{{ entry.bis }}</td>
<td>{{ entry.status_kurzbz }}</td>
<td>{{ entry.ort }}</td>
</tr>
</tbody>
</table>
</template>
</bs-modal>
`,
};
+353
View File
@@ -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: `
<bs-modal ref="modal" @hideBsModal="hideModal">
<template #title>
<template v-if="mode === 'new'">{{ $p.t('lehre', 'tempus_sync_new') }}</template>
<template v-else-if="mode === 'edit'">{{ $p.t('lehre', 'tempus_sync_edit') }}</template>
<template v-else>{{ $p.t('lehre', 'tempus_sync_start') }}</template>
</template>
<core-form ref="form">
<form-validation></form-validation>
<form-input
container-class="mb-3"
type="select"
v-model="formData.oe_kurzbz"
name="oe_kurzbz"
:label="$p.t('lehre/organisationseinheit')"
required
>
<option
v-for="organisation in config.organisationen"
:key="organisation.oe_kurzbz"
:value="organisation.oe_kurzbz"
>
[{{ organisation.organisationseinheittyp_kurzbz }}] {{ organisation.bezeichnung }} {{ ['b', 'm'].includes(organisation.typ) ? organisation.stgbezeichnung : '' }}
</option>
</form-input>
<form-input
container-class="mb-3"
type="select"
v-model="formData.studiensemester_kurzbz"
name="studiensemester_kurzbz"
:label="$p.t('lehre/studiensemester')"
required
>
<option
v-for="studiensemester in config.studiensemestern"
:key="studiensemester.studiensemester_kurzbz"
:value="studiensemester.studiensemester_kurzbz"
>
{{ studiensemester.studiensemester_kurzbz }}
</option>
</form-input>
<form-input
container-class="mb-3"
type="select"
v-model="formData.ausbildungssemester"
name="ausbildungssemester"
:label="$p.t('lehre/ausbildungssemester')"
:disabled="!max_semester"
>
<option :value="null">-- {{ $p.t('fehlermonitoring', 'keineAuswahl') }} --</option>
<option
v-for="number in semester"
:key="number"
:value="number"
>
{{ number }}
</option>
</form-input>
<form-input
container-class="mb-3"
type="select"
v-model="formData.studienplan_id"
name="studienplan_id"
:label="$p.t('lehre/studienplan')"
:disabled="!studienplaene.length"
>
<option :value="null">-- {{ $p.t('fehlermonitoring', 'keineAuswahl') }} --</option>
<option
v-for="plan in studienplaene"
:key="plan.studienplan_id"
:value="plan.studienplan_id"
>
{{ plan.bezeichnung }}-{{plan.orgform_kurzbz}}
</option>
</form-input>
<form-input
container-class="mb-3"
type="DatePicker"
v-model="formData.datum_bis"
name="datum_bis"
:label="$p.t('ui/dateTo')"
auto-apply
:enable-time-picker="false"
text-input
format="dd.MM.yyyy"
preview-format="dd.MM.yyyy"
:teleport="true"
>
</form-input>
<form-input
container-class="mb-3"
type="select"
v-model="formData.sync_status_kurzbz"
name="sync_status_kurzbz"
:label="$p.t('global/status')"
>
<option :value="null">-- {{ $p.t('fehlermonitoring', 'keineAuswahl') }} --</option>
<option
v-for="status in syncStati"
:key="status.status_kurzbz"
:value="status.status_kurzbz"
>
{{ status.bezeichnung }}
</option>
</form-input>
<form-input
container-class="mb-3"
type="checkbox"
v-model="formData.mail"
name="mail"
:label="$p.t('lehre/mail_benachrichtigung')"
>
</form-input>
</core-form>
<template #footer>
<button
type="button"
class="btn btn-primary"
@click="save"
:disabled="loading"
>
<template v-if="mode === 'start'">{{ $p.t('global', 'jetztStarten') }}</template>
<template v-else>{{ $p.t('ui', 'ok') }}</template>
</button>
</template>
</bs-modal>`
};
+3 -2
View File
@@ -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 {
>
<i v-if="!parked.length" class="fa-solid fa-square-parking"></i>
<event-card
class="parkingevent"
v-for="parkedEvent in parked"
:key="parkedEvent.id"
:event="parkedEvent"
parked
@select-kw="$emit('select-kw', $event)"
@unpark="unpark"
/>
</div>
</div>
+81 -13
View File
@@ -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')"
>
<template #option="{ option }">
<div class="d-flex justify-content-between w-100">
<span><i class="fa-solid fa-door-open me-2"></i>{{ option.ort_kurzbz }}</span>
<span
class="text-muted"
v-tooltip="{ value: (option.details || []).join('\\n'), class: 'custom-tooltip' }"
>{{ option.score }}</span>
</div>
</template>
<template #option="{ option }">
<div class="room-option">
<i class="fa-solid fa-door-open text-muted"></i>
<span v-if="option.raumtyp_kurzbz">
[{{ option.raumtyp_kurzbz }}]
</span>
<span v-else></span>
<span class="fw-semibold">
{{ option.ort_kurzbz }}
</span>
<span class="text-muted small text-nowrap">
{{ option.max_person }} Pers.
</span>
<span
class="text-muted text-end"
v-tooltip="{ value: (option.details || []).join('\\n'), class: 'custom-tooltip' }"
>
{{ option.score }}
</span>
</div>
</template>
</Listbox>
<form-input
v-if="mode === 'edit'"
label="Location"
type="textarea"
container-class="col-12"
name="location"
v-model="location"
></form-input>
<div class="d-flex justify-content-end gap-2 mt-3">
<button type="button" class="btn btn-secondary" @click="hide">
@@ -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*/ `
<div
:class="classes"
class="position-relative"
@wheel.stop
>
<div v-if="event.has_assigned_resources" class="position-absolute top-0 start-0 m-1" >
<div class="position-absolute top-0 start-0 m-1 d-flex gap-1" >
<i :class="statusIcon"></i>
<i
class="fa-solid fa-table-list text-muted"
v-tooltip="resourcesTooltip"
v-if="event.has_assigned_resources"
class="fa-solid fa-table-list text-muted"
></i>
<i
v-tooltip="tagsTooltip"
v-if="tags?.length"
class="fa-solid fa-tags"
></i>
</div>
<div class="position-absolute bottom-0 start-0 m-1">
{{event.verplante_stunden}}
</div>
<div
v-if="!event.allDayEvent && event?.beginn && event?.ende"
@@ -119,4 +193,4 @@ export default {
</div>
</div>
`,
}
};
@@ -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*/`
<div>
<h5>
{{$p.t('lvinfo','lehrveranstaltungsinformationen')}}
@@ -143,9 +165,36 @@ export default {
}}</th>
<td>{{event.organisationseinheit}}</td>
</tr>
<tr v-if="resourcesValue">
<th>{{
$p.t('ui','betriebsmittel')?
$p.t('ui','betriebsmittel')+':'
:''
}}</th>
<td>
{{ resourcesValue }}
</td>
</tr>
<tr v-if="tags.length">
<th>{{
$p.t('ui','tags')?
$p.t('ui','tags')+':'
:''
}}</th>
<td>
<div>
<span
v-for="tag in tags"
:key="tag.tag_typ_kurzbz"
:class="[tag.style, { tag_done: tag.done }]"
class="tag disabled"
>{{ tag.beschreibung }}</span>
</div>
</td>
</tr>
</tbody>
</table>
<lv-menu :containerStyles="['p-0']" :rowStyles="['m-0']" v-if="lvMenu.length" :menu="lvMenu" />
</div>`,
}
}
@@ -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;
@@ -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 {
</table>
</div>`,
}
}
+16 -2
View File
@@ -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 {
</template>
</Multiselect>
</div>
<div class="col-6">
<form-input
:label="$p.t('global', 'raum')"
type="textarea"
name="location"
v-model="location"
></form-input>
</div>
<div class="col-12" v-if="show_all_fields">
<div v-for="(nehmer, i) in teilnehmer" :key="i" class="d-flex gap-2 mb-2 align-items-end">
<form-input
@@ -0,0 +1,282 @@
/**
* 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 <https://www.gnu.org/licenses/>.
*/
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: `
<bs-modal
ref="modal"
@hide-bs-modal="reset"
class="bootstrap-prompt"
data-cy="resourcesAssignmentModal"
>
<template #title>{{ $p.t('ui', 'resource_assignment_modal_title') }}</template>
<template #default>
<div class="mb-5">
<form-input
v-if="availableResources.length"
@item-select="selectedAvailableResource = $event.value"
:label="$p.t('ui', 'available_resources_label')"
:suggestions="filteredAvailableResources"
:option-value="option => option.value"
:option-label="option => option.label"
@complete="filterAvailableResources"
dropdown
force-selection
type="autocomplete"
name="availableResources"
:close-on-select="false"
/>
</div>
<div>
<div class="d-flex align-items-center justify-content-between mb-2">
<h6 class="mb-2 mx-auto text-bold fw-1">
{{ $p.t('ui', 'assigned_resources_subtitle') }}
</h6>
</div>
<div v-if="assignedResources.length" class="mb-4">
<div
v-for="resource in assignedResources"
:key="resource.betriebsmittel_id"
class="shadow-sm p-2 mb-2 bg-body rounded"
>
<div class="d-flex justify-content-between align-items-center mb-1">
<p class="m-0">{{ resource.beschreibung }}</p>
<div class="d-flex justify-content-between align-items-center gap-2">
<a href="#" @click.prevent="toggleAssignedResourceNoteInput(resource)" class="ms-auto">
<i class="fa fa-edit text-primary"></i>
</a>
<a href="#" @click.prevent="removeAssignedResource(resource)" class="ms-auto">
<i class="fa fa-trash text-danger"></i>
</a>
</div>
</div>
<form-input
v-if="resource.isNoteTextareaShown"
v-model="resource.anmerkung"
@input="areFormButtonsDisplayed = true"
:placeholder="$capitalize($p.t('global/anmerkung'))"
:rows="1"
class="flex-grow-1"
type="textarea"
name="anmerkung"
/>
</div>
</div>
<div v-else class="d-flex align-items-center justify-content-center mb-2">
<p class="text-muted mb-0">{{ $p.t('ui', 'no_assigned_resources') }}</p>
</div>
<div
v-if="areFormButtonsDisplayed"
class="d-flex justify-content-end gap-2"
>
<button type="button" class="btn btn-secondary" @click="refreshResourcesAssignmentModalData">
{{ $p.t('ui', 'abbrechen') }}
</button>
<button type="button" class="btn btn-primary" @click="saveAssignedResourcesToCalendarItem">
{{ $p.t('ui', 'speichern') }}
</button>
</div>
</div>
</template>
</bs-modal>
`,
};
+190
View File
@@ -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 */ `
<nav
id="sidebarMenu"
class="bg-light offcanvas offcanvas-start col-md p-md-0 h-100 w-100"
>
<div class="sidebar-icons d-flex flex-row align-items-start py-2 gap-1 ps-2">
<button
class="btn btn-outline-secondary"
type="button"
data-bs-toggle="offcanvas"
data-bs-target="#verbandMenu"
aria-controls="verbandMenu"
aria-expanded="false"
title="Verband"
>
<span class="fa-solid fa-university"></span>
</button>
<button
class="btn btn-outline-secondary"
type="button"
data-bs-toggle="offcanvas"
data-bs-target="#verbandMenu"
aria-controls="verbandMenu"
aria-expanded="false"
title="Verband"
>
<span class="fa-solid fa-door-open"></span>
</button>
</div>
<div class="px-2 py-1 w-100">
<div
class="d-flex gap-1 py-1"
data-cy="previewRoleOptionsHolder"
>
<button
class="btn btn-sm"
:class="previewRole === 'planer' ? 'btn-dark' : 'btn-outline-dark'"
@click="$emit('update:previewRole', 'planer')"
>
<i class="fa-solid fa-pen-ruler me-1"></i>Planer
</button>
<button
class="btn btn-sm"
:class="previewRole === 'lektor' ? 'btn-primary' : 'btn-outline-primary'"
@click="$emit('update:previewRole', 'lektor')"
>
<i class="fa-solid fa-chalkboard-user me-1"></i>Lektor
</button>
<button
class="btn btn-sm"
:class="previewRole === 'student' ? 'btn-success' : 'btn-outline-success'"
@click="$emit('update:previewRole', 'student')"
>
<i class="fa-solid fa-user-graduate me-1"></i>Student
</button>
<button
class="btn btn-sm btn-outline-danger"
@click="$emit('sync')"
>
<i class="fa-solid fa-rotate me-1"></i>Sync
</button>
</div>
</div>
<room-selection
v-if="rooms.length"
:rooms="rooms"
@update:rooms="$emit('update:rooms', $event)"
></room-selection>
<verband-selection
v-if="studiengaenge.length"
:studiengaenge="studiengaenge"
:studiengaenge-all="studiengaengeAll"
@update:studiengaenge="$emit('update:studiengaenge', $event)"
></verband-selection>
<lecture-selection
v-if="lecturers.length"
:lecturers="lecturers"
@remove="$emit('remove-lecturer', $event)"
></lecture-selection>
<div class="d-flex flex-column flex-grow-1" style="min-height: 0">
<parking-slot
ref="parking"
@update:parked-keys="$emit('update:parkedKeys', $event)"
@select-kw="$emit('select-kw', $event)"
></parking-slot>
<fhc-coursepicker
ref="coursepicker"
:studiengaenge="studiengaenge"
:studiensemester="selectedStudiensemester"
:lecturers="courseLecturers"
:preview-lehreinheit-id="raumvorschlagPreview?.lehreinheit_id ?? null"
:preview-loading="raumvorschlagLoading"
@preview-raumvorschlag="$emit('preview-raumvorschlag', $event)"
@select-lecturer="$emit('select-lecturer', $event)"
@select-kw="$emit('select-kw', $event)"
></fhc-coursepicker>
</div>
<stv-studiensemester
:studiensemester-kurzbz="selectedStudiensemester"
@update:studiensemester-kurzbz="$emit('update:selectedStudiensemester', $event)"
></stv-studiensemester>
</nav>
`,
};
@@ -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 <https://www.gnu.org/licenses/>.
*/
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 */`
<bs-modal
ref="modal"
@hide-bs-modal="reset"
body-class="p-4"
class="bootstrap-prompt"
data-cy="tagsAssignmentModal"
>
<template #title>{{ $p.t('ui', 'tags_assignment_modal_title') }}</template>
<template #default>
<div class="mb-5">
<form-input
v-if="availableTags.length"
@item-select="selectTag"
:label="$capitalize($p.t('ui', 'tags'))"
:suggestions="filteredAvailableTags"
:option-value="option => option.value"
:option-label="option => option.label"
@complete="filterAvailableTags"
dropdown
force-selection
type="autocomplete"
name="availableTags"
:close-on-select="false"
>
<template #option="{ option }">
<span :class="['tag', option.data.style]">{{ option.label }}</span>
</template>
</form-input>
</div>
<div>
<div class="d-flex align-items-center justify-content-between mb-2">
<h6 class="mb-1 mx-auto text-bold fw-1">
{{ $p.t('ui', 'assigned_tags_subtitle') }}
</h6>
</div>
<div v-if="assignedTags.length">
<span
v-for="tag in assignedTags"
:key="tag.tag_typ_kurzbz"
:class="[tag.style, { tag_done: tag.done }]"
@click="editTag(tag)"
class="tag"
>{{ tag.bezeichnung }}</span>
</div>
<div v-else class="d-flex align-items-center justify-content-center mb-2">
<p class="text-muted mb-0">{{ $p.t('ui', 'no_assigned_tags') }}</p>
</div>
</div>
</template>
</bs-modal>
<core-tag
v-if="calendar?.eindeutige_kalender_gruppen_id"
ref="tagComponent"
:is-list-item-shown="false"
:endpoint="tagEndpoint"
:values="[calendar.eindeutige_kalender_gruppen_id]"
zuordnung_typ="eindeutige_kalender_gruppen_id"
@added="handleCalendarTagChange"
@deleted="handleCalendarTagChange"
@updated="handleCalendarTagChange"
/>
`,
};
File diff suppressed because it is too large Load Diff
+243
View File
@@ -0,0 +1,243 @@
import {CoreFilterCmpt} from '../filter/Filter.js';
import {CoreNavigationCmpt} from '../navigation/Navigation.js';
import FormInput from "../Form/Input.js";
import CoreBaseLayout from '../../components/layout/BaseLayout.js';
import ApiTempusSync from '../../api/factory/tempus/sync.js';
import TempusSyncModal from './Modal.js';
import FhcLoader from '../../components/Loader.js';
export default {
props: {
config: Object,
defaultSemester: String
},
components: {
CoreFilterCmpt,
CoreBaseLayout,
CoreNavigationCmpt,
FormInput,
TempusSyncModal,
FhcLoader
},
data() {
return {
studiensemester_kurzbz: this.defaultSemester ?? null
}
},
watch: {
studiensemester_kurzbz()
{
this.reloadTable();
}
},
computed: {
tabulatorOptions() {
return {
index: "kalender_syncstatus_id",
ajaxURL: 'dummy',
ajaxRequestFunc: () => 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 = '<i class="fa fa-edit"></i>';
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 = '<i class="fa fa-play"></i>';
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 = '<i class="fa fa-trash"></i>';
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: `
<core-navigation-cmpt></core-navigation-cmpt>
<core-base-layout>
<template #main>
<fhc-loader ref="loader" :timeout="0"></fhc-loader>
<core-filter-cmpt
ref="syncTable"
:tabulator-options="tabulatorOptions"
:table-only=true
:side-menu="false"
:reload="true"
:useSelectionSpan="false"
new-btn-label="Hinzufügen"
new-btn-show
@click:new="openNewModal">
<template #actions>
<form-input
type="select"
v-model="studiensemester_kurzbz"
name="studiensemester_kurzbz"
>
<option :value="null" disabled>-- {{ $p.t('lehre', 'studiensemester') }} --</option>
<option value="all">{{ $p.t('ui', 'alleAnzeigen') }}</option>
<option
v-for="studiensemester in config.studiensemestern"
:key="studiensemester.studiensemester_kurzbz"
:value="studiensemester.studiensemester_kurzbz"
>
{{ studiensemester.studiensemester_kurzbz }}
</option>
</form-input>
<button
type="button"
class="btn btn-outline-secondary"
@click="openStartModal"
>
{{ $p.t('global', 'jetztStarten') }}
</button>
</template>
</core-filter-cmpt>
<tempus-sync-modal
ref="syncModal"
:config="config"
:stsem_kurzbz="studiensemester_kurzbz"
@saved="onSaved"
>
</tempus-sync-modal>
</template>
</core-base-layout>
`
};
@@ -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 */ `
<div id="verbandMenu" class="offcanvas offcanvas-start col-md p-md-0 h-100" tabindex="-1" data-cy="verbandMenu">
<div class="offcanvas-header justify-content-end px-1 d-md-none">
<h5 class="offcanvas-title" id="verbandMenuLabel">
<i class="fa-solid fa-university me-2"></i>Verband
</h5>
<button type="button" class="btn-close text-reset" data-bs-dismiss="offcanvas" :aria-label="$p.t('ui/schliessen')"></button>
</div>
<base-treemenu config="tempus" @select-entry="onSelectVerbandAndClose" class="col" style="height:0%"></base-treemenu>
</div>
`,
};
@@ -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',
+6
View File
@@ -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 '<H2>Pruefe Tabellen und Attribute!</H2>';
+29
View File
@@ -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"<br>';
}
}
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 '<strong>lehre.tbl_kalender: '.$db->db_last_error().'</strong><br>';
else
echo '<br />lehre.tbl_kalender: Spalte status_kurzbz_max hinzugefuegt.
<br />lehre.tbl_kalender_status: status_kurzbz von todelete auf to_delete umbenannt.
<br />lehre.tbl_kalender_status: status_kurzbz to_delete_preview hinzugefuegt.
<br />lehre.tbl_kalender_status: status_kurzbz to_delete_live hinzugefuegt.
';
}
@@ -0,0 +1,38 @@
<?php
if (! defined('DB_NAME')) exit('No direct script access allowed');
if(!$result = @$db->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 '<strong>lehre.tbl_kalender_syncstatus: '.$db->db_last_error().'</strong><br>';
else
echo ' lehre.tbl_kalender_syncstatus: Tabelle hinzugefuegt<br>';
}
@@ -0,0 +1,19 @@
<?php
if (! defined('DB_NAME')) exit('No direct script access allowed');
$result = $db->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 '<strong>public.tbl_notizzuordnung: '.$db->db_last_error().'</strong><br>';
else
echo '<br>public.tbl_notizzuordnung: eindeutige_kalender_gruppen_id column added';
}
@@ -0,0 +1,16 @@
<?php
if (! defined('DB_NAME')) exit('No direct script access allowed');
if($result = $db->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 '<strong>Berechtigung: '.$db->db_last_error().'</strong><br>';
else
echo 'Neue Berechtigung basis/tempus_stundenraster hinzugefuegt!<br>';
}
}
@@ -0,0 +1,17 @@
<?php
if (! defined('DB_NAME')) exit('No direct script access allowed');
if ($result = @$db->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 '<strong>public.tbl_variablenname '.$db->db_last_error().'</strong><br>';
else
echo 'public.tbl_variablenname: Added "roomless_planning", "priority_room_planning", "dialog_room_planning"<br>';
}
}
+300
View File
@@ -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',
+11
View File
@@ -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');
+5
View File
@@ -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*/);
@@ -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);
@@ -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 = () =>