diff --git a/application/config/Events.php b/application/config/Events.php index 80a8f03b3..84c610c63 100644 --- a/application/config/Events.php +++ b/application/config/Events.php @@ -23,6 +23,15 @@ Events::on('loadRenderers', function ($renderers) { ); }); +Events::on('loadRenderers', function ($renderers) { + $fhc_core_renderers =& $renderers(); + $fhc_core_renderers["slot_room"] = array( + 'modalTitle' => APP_ROOT.'public/js/components/Cis/Renderer/Slot/roomModalTitle.js', + 'modalContent' => APP_ROOT.'public/js/components/Cis/Renderer/Slot/roomModalContent.js', + 'calendarEventStyles' => APP_ROOT.'public/css/Cis4/CoreCalendarEvents.css' + ); +}); + Events::on('loadRenderers', function ($renderers) { $fhc_core_renderers =& $renderers(); $fhc_core_renderers["ferien"] = array( diff --git a/application/config/noten.php b/application/config/noten.php new file mode 100644 index 000000000..0eedde2ae --- /dev/null +++ b/application/config/noten.php @@ -0,0 +1,6 @@ + wirken sich nicht auf Antritte aus +$config['NOTEN_OHNE_ANTRITT'] = [9, 17]; // tbl_note pk \ No newline at end of file diff --git a/application/config/routes.php b/application/config/routes.php index aa4ba9db8..0eb2839cd 100644 --- a/application/config/routes.php +++ b/application/config/routes.php @@ -64,6 +64,10 @@ $route['api/v1/system/[S|s]prache/(:any)'] = 'api/v1/system/sprache2/$1'; $route['Cis/LvPlan/.*'] = 'Cis/LvPlan/index/$1'; $route['Cis/MyLvPlan/.*'] = 'Cis/MyLvPlan/index/$1'; $route['Cis/MyLv/.*'] = 'Cis/MyLv/index/$1'; +$route['Cis/OtherLvPlan/.*'] = 'Cis/OtherLvPlan/index/$1'; +//Route for LV Plan Stg/Semester/Verband/Gruppe +$route['Cis/StgOrgLvPlan/.*'] = 'Cis/StgOrgLvPlan/index/$1'; +$route['Cis/Benotungstool/.*'] = 'Cis/Benotungstool/index/$1'; $route['Abgabetool/Assistenz'] = 'Cis/Abgabetool/Assistenz'; $route['Abgabetool/Assistenz/(:any)'] = 'Cis/Abgabetool/Assistenz/$1'; diff --git a/application/controllers/Cis/Abgabetool.php b/application/controllers/Cis/Abgabetool.php index 04338b1a9..28414dafa 100644 --- a/application/controllers/Cis/Abgabetool.php +++ b/application/controllers/Cis/Abgabetool.php @@ -31,12 +31,8 @@ class Abgabetool extends Auth_Controller { // TODO: routing from index based on berechtigung? - $viewData = array( - 'uid'=>getAuthUID(), - ); - if(defined('CIS4') && CIS4) { - $this->load->view('CisRouterView/CisRouterView.php', ['viewData' => $viewData, 'route' => 'Abgabetool']); + $this->load->view('CisRouterView/CisRouterView.php', ['route' => 'Abgabetool']); } else { $this->load->view('Cis/Abgabetool.php', ['uid' => getAuthUID(), 'route' => 'Abgabetool']); } @@ -44,12 +40,8 @@ class Abgabetool extends Auth_Controller public function Student($student_uid_prop = '') { - $viewData = array( - 'uid'=>getAuthUID(), - ); - if(defined('CIS4') && CIS4) { - $this->load->view('CisRouterView/CisRouterView.php', ['viewData' => $viewData, 'route' => 'AbgabetoolStudent']); + $this->load->view('CisRouterView/CisRouterView.php', ['route' => 'AbgabetoolStudent']); } else { $this->load->view('Cis/Abgabetool.php', ['uid' => getAuthUID(), 'route' => 'AbgabetoolStudent', 'student_uid_prop' => $student_uid_prop]); } @@ -57,12 +49,8 @@ class Abgabetool extends Auth_Controller public function Mitarbeiter() { - $viewData = array( - 'uid'=>getAuthUID(), - ); - if(defined('CIS4') && CIS4) { - $this->load->view('CisRouterView/CisRouterView.php', ['viewData' => $viewData, 'route' => 'AbgabetoolMitarbeiter']); + $this->load->view('CisRouterView/CisRouterView.php', ['route' => 'AbgabetoolMitarbeiter']); } else { $this->load->view('Cis/Abgabetool.php', ['uid' => getAuthUID(), 'route' => 'AbgabetoolMitarbeiter']); } @@ -70,13 +58,8 @@ class Abgabetool extends Auth_Controller public function Assistenz($stg_kz_prop = '') { - - $viewData = array( - 'uid'=>getAuthUID(), - ); - if(defined('CIS4') && CIS4) { - $this->load->view('CisRouterView/CisRouterView.php', ['viewData' => $viewData, 'route' => 'AbgabetoolAssistenz']); + $this->load->view('CisRouterView/CisRouterView.php', ['route' => 'AbgabetoolAssistenz']); } else { $this->load->view('Cis/Abgabetool.php', ['uid' => getAuthUID(), 'route' => 'AbgabetoolAssistenz', 'stg_kz_prop' => $stg_kz_prop]); } @@ -84,12 +67,8 @@ class Abgabetool extends Auth_Controller public function Deadlines() { - $viewData = array( - 'uid'=>getAuthUID(), - ); - if(defined('CIS4') && CIS4) { - $this->load->view('CisRouterView/CisRouterView.php', ['viewData' => $viewData, 'route' => 'DeadlinesOverview']); + $this->load->view('CisRouterView/CisRouterView.php', ['route' => 'DeadlinesOverview']); } else { $this->load->view('Cis/Abgabetool.php', ['uid' => getAuthUID(), 'route' => 'DeadlinesOverview']); } diff --git a/application/controllers/Cis/Auth.php b/application/controllers/Cis/Auth.php index 67267ebf6..87ef5d3da 100644 --- a/application/controllers/Cis/Auth.php +++ b/application/controllers/Cis/Auth.php @@ -40,7 +40,7 @@ class Auth extends FHC_Controller if ($this->form_validation->run()) { - redirect($this->authlib->getLandingPage('/CisVue/Dashboard')); + redirect($this->authlib->getLandingPage('/Cis4')); } else { diff --git a/application/controllers/Cis/Benotungstool.php b/application/controllers/Cis/Benotungstool.php new file mode 100644 index 000000000..94faeff98 --- /dev/null +++ b/application/controllers/Cis/Benotungstool.php @@ -0,0 +1,37 @@ + self::PERM_LOGGED + ]); + + $this->_ci =& get_instance(); + } + + // ----------------------------------------------------------------------------------------------------------------- + // Public methods + + /** + * @return void + */ + public function index() + { + $viewData = array( + 'uid'=>getAuthUID(), + ); + + $this->load->view('CisRouterView/CisRouterView.php', ['viewData' => $viewData, 'route' => 'Benotungstool']); + } + +} \ No newline at end of file diff --git a/application/controllers/Cis/LvPlan.php b/application/controllers/Cis/LvPlan.php index 884c8a9a0..32e622f5a 100644 --- a/application/controllers/Cis/LvPlan.php +++ b/application/controllers/Cis/LvPlan.php @@ -28,12 +28,6 @@ class LvPlan extends Auth_Controller */ public function index() { - - $viewData = array( - 'uid'=>getAuthUID(), - 'timezone' => $this->config->item('timezone') - ); - - $this->load->view('CisRouterView/CisRouterView.php', ['viewData' => $viewData, 'route' => 'LvPlan']); + $this->load->view('CisRouterView/CisRouterView.php', ['route' => 'LvPlan']); } } diff --git a/application/controllers/Cis/MyLv.php b/application/controllers/Cis/MyLv.php index 819d56b05..0f24d3a80 100644 --- a/application/controllers/Cis/MyLv.php +++ b/application/controllers/Cis/MyLv.php @@ -26,11 +26,6 @@ class MyLv extends Auth_Controller */ public function index() { - - $viewData = array( - - ); - - $this->load->view('CisRouterView/CisRouterView.php', ['viewData' => $viewData, 'route' => 'MyLv']); + $this->load->view('CisRouterView/CisRouterView.php', ['route' => 'MyLv']); } } diff --git a/application/controllers/Cis/MyLvPlan.php b/application/controllers/Cis/MyLvPlan.php index 366ce8e65..a7f21aa20 100644 --- a/application/controllers/Cis/MyLvPlan.php +++ b/application/controllers/Cis/MyLvPlan.php @@ -27,13 +27,7 @@ class MyLvPlan extends Auth_Controller * @return void */ public function index() - { - - $viewData = array( - 'uid'=>getAuthUID(), - 'timezone' => $this->config->item('timezone') - ); - - $this->load->view('CisRouterView/CisRouterView.php', ['viewData' => $viewData, 'route' => 'MyLvPlan']); + { + $this->load->view('CisRouterView/CisRouterView.php', ['route' => 'MyLvPlan']); } } diff --git a/application/controllers/Cis/OtherLvPlan.php b/application/controllers/Cis/OtherLvPlan.php new file mode 100644 index 000000000..e62644dd1 --- /dev/null +++ b/application/controllers/Cis/OtherLvPlan.php @@ -0,0 +1,34 @@ + ['basis/other_lv_plan:r'] + ]); + + // Load Config + $this->load->config('calendar'); + } + + // ----------------------------------------------------------------------------------------------------------------- + // Public methods + + /** + * @return void + */ + public function index() + { + $this->load->view('CisRouterView/CisRouterView.php', ['route' => 'OtherLvPlan']); + } +} diff --git a/application/controllers/Cis/Profil.php b/application/controllers/Cis/Profil.php index c287d87d0..dd7963228 100644 --- a/application/controllers/Cis/Profil.php +++ b/application/controllers/Cis/Profil.php @@ -55,15 +55,7 @@ class Profil extends Auth_Controller */ public function index() { - - $this->load->library('ProfilLib'); - $profil_data = $this->profillib->getView(getAuthUID()); - $profil_data = hasData($profil_data) ? getData($profil_data) : null; - $viewData = array( - 'editable'=>true, - 'profil_data' => $profil_data, - ); - $this->load->view('CisRouterView/CisRouterView.php',['viewData' => $viewData, 'route' => 'profilIndex']); + $this->load->view('CisRouterView/CisRouterView.php', ['route' => 'profilIndex']); } /** @@ -73,23 +65,13 @@ class Profil extends Auth_Controller */ public function View($uid) { - $this->load->library('ProfilLib'); - $profil_data = $this->profillib->getView($uid); - $profil_data = hasData($profil_data) ? getData($profil_data) : null; - $viewData = array ( - 'uid' => $uid, - 'profil_data'=>$profil_data, - ); - if($uid == getAuthUID()){ - $viewData['editable'] = true; - } - $this->load->view('CisRouterView/CisRouterView.php',['viewData' => $viewData, 'route' => 'profilViewUid']); + $this->load->view('CisRouterView/CisRouterView.php', ['route' => 'profilViewUid']); } /** - * checks whether a specific userID is a mitarbeiter or not (foreword declaration of the function isMitarbeiter in Mitarbeiter_model.php) + * checks whether a specific userID is a student or not (foreword declaration of the function isStudent in Student_model.php) * @access public - * @param $uid the userID used to check if it is a mitarbeiter + * @param $uid the userID used to check if it is a student * @return boolean */ public function isStudent($uid) @@ -119,7 +101,7 @@ class Profil extends Auth_Controller } /** - * gets the adressen that are marked as zustell from the currenlty logged in user + * gets the adressen that are marked as zustell from the currently logged in user * @access public * @return array a list of adresse_id's */ @@ -262,23 +244,23 @@ class Profil extends Auth_Controller $this->GemeindeModel->addDistinct(); $this->GemeindeModel->addSelect(["name"]); if ($nation == "A") { - if (isset($zip) && $zip > 999 && $zip < 32000) { + if (isset($zip) && $zip > 999 && $zip < 32000) { - $gemeinde_res = $this->GemeindeModel->loadWhere(['plz' => $zip]); - if (isError($gemeinde_res)) { - show_error("error while trying to query bis.tbl_gemeinde"); - } - $gemeinde_res = hasData($gemeinde_res) ? getData($gemeinde_res) : null; - $gemeinde_res = array_map(function ($obj) { - return $obj->name; - }, $gemeinde_res); - echo json_encode($gemeinde_res); - - } else { - echo json_encode(error("ortschaftskennziffer code was not valid")); + $gemeinde_res = $this->GemeindeModel->loadWhere(['plz' => $zip]); + if (isError($gemeinde_res)) { + show_error("error while trying to query bis.tbl_gemeinde"); } + $gemeinde_res = hasData($gemeinde_res) ? getData($gemeinde_res) : null; + $gemeinde_res = array_map(function ($obj) { + return $obj->name; + }, $gemeinde_res); + echo json_encode($gemeinde_res); + + } else { + echo json_encode(error("ortschaftskennziffer code was not valid")); + } } else { - echo json_encode(error("Nation was not 'A' (Austria)")); + echo json_encode(error("Nation was not 'A' (Austria)")); } } @@ -750,6 +732,4 @@ class Profil extends Auth_Controller $zutrittskarte_ausgegebenam = str_replace("-", ".", $zutrittskarte_ausgegebenam); return $zutrittskarte_ausgegebenam; } - - } diff --git a/application/controllers/CisVue/Dashboard.php b/application/controllers/Cis/ProjektabgabeUebersicht.php similarity index 56% rename from application/controllers/CisVue/Dashboard.php rename to application/controllers/Cis/ProjektabgabeUebersicht.php index ee830cb8b..62c46dd5f 100644 --- a/application/controllers/CisVue/Dashboard.php +++ b/application/controllers/Cis/ProjektabgabeUebersicht.php @@ -5,18 +5,16 @@ if (! defined('BASEPATH')) exit('No direct script access allowed'); /** * */ -class Dashboard extends Auth_Controller +class ProjektabgabeUebersicht extends Auth_Controller { /** * Constructor */ public function __construct() { - parent::__construct( - array( - 'index' => 'dashboard/benutzer:r' - ) - ); + parent::__construct([ + 'index' => ['basis/cis:r'] + ]); } // ----------------------------------------------------------------------------------------------------------------- @@ -27,17 +25,12 @@ class Dashboard extends Auth_Controller */ public function index() { - - $this->load->model('person/Person_model','PersonModel'); - $personData = getData($this->PersonModel->getByUid(getAuthUID()))[0]; - + // TODO create permission $viewData = array( 'uid' => getAuthUID(), - 'name' => $personData->vorname, - 'person_id' => $personData->person_id + 'showEdit' => true ); - - $this->load->view('CisRouterView/CisRouterView.php', ['viewData' => $viewData]); + $this->load->view('CisRouterView/CisRouterView.php', ['viewData' => $viewData, 'route' => 'ProjektabgabeUebersicht']); } -} \ No newline at end of file +} diff --git a/application/controllers/Cis/Raumsuche.php b/application/controllers/Cis/Raumsuche.php index 055038275..f48beb0f3 100644 --- a/application/controllers/Cis/Raumsuche.php +++ b/application/controllers/Cis/Raumsuche.php @@ -25,11 +25,6 @@ class Raumsuche extends Auth_Controller */ public function index() { - - $viewData = array( - 'uid'=>getAuthUID(), - ); - - $this->load->view('CisRouterView/CisRouterView.php', ['viewData' => $viewData, 'route' => 'Raumsuche']); + $this->load->view('CisRouterView/CisRouterView.php', ['route' => 'Raumsuche']); } } diff --git a/application/controllers/Cis/StgOrgLvPlan.php b/application/controllers/Cis/StgOrgLvPlan.php new file mode 100644 index 000000000..bf329fcb8 --- /dev/null +++ b/application/controllers/Cis/StgOrgLvPlan.php @@ -0,0 +1,33 @@ + ['basis/cis:r'] + ]); + + // Load Config + $this->load->config('calendar'); + } + + // ----------------------------------------------------------------------------------------------------------------- + // Public methods + + /** + * @return void + */ + public function index() + { + $this->load->view('CisRouterView/CisRouterView.php', ['route' => 'StgOrgLvPlan']); + } +} diff --git a/application/controllers/Cis/Studium.php b/application/controllers/Cis/Studium.php index a298de648..20f323dff 100644 --- a/application/controllers/Cis/Studium.php +++ b/application/controllers/Cis/Studium.php @@ -29,10 +29,7 @@ class Studium extends Auth_Controller */ public function index() { - $viewData = array( - - ); - $this->load->view('CisRouterView/CisRouterView.php',['viewData' => $viewData, 'route' => 'studium']); + $this->load->view('CisRouterView/CisRouterView.php',['route' => 'studium']); } diff --git a/application/controllers/Cis/Zeitsperren.php b/application/controllers/Cis/Zeitsperren.php new file mode 100644 index 000000000..baa09b8c6 --- /dev/null +++ b/application/controllers/Cis/Zeitsperren.php @@ -0,0 +1,30 @@ + ['basis/cis:r'], + ]); + + // Load Libraries + $this->load->library('VariableLib', ['uid' => getAuthUID()]); + } + + /** + * index loads the view Zeitsperren + * @access public + * @return void + */ + public function index() + { + $viewData = array( + 'uid'=>getAuthUID(), + ); + + $this->load->view('CisRouterView/CisRouterView.php', ['viewData' => $viewData, 'route' => 'zeitsperren']); + } +} diff --git a/application/controllers/Cis4.php b/application/controllers/Cis4.php index b7ba2029d..48fd6c240 100644 --- a/application/controllers/Cis4.php +++ b/application/controllers/Cis4.php @@ -1,6 +1,7 @@ 'basis/cis:r' - ) + array( + 'index' => 'basis/cis:r' + ) ); // Load Config @@ -30,16 +31,6 @@ class Cis4 extends Auth_Controller */ public function index() { - $this->load->model('person/Person_model', 'PersonModel'); - $personData = getData($this->PersonModel->getByUid(getAuthUID()))[0]; - - $viewData = array( - 'uid' => getAuthUID(), - 'name' => $personData->vorname, - 'person_id' => $personData->person_id, - 'timezone' => $this->config->item('timezone') - ); - - $this->load->view('CisRouterView/CisRouterView.php', ['viewData' => $viewData, 'route' => 'FhcDashboard']); + $this->load->view('CisRouterView/CisRouterView.php', ['route' => 'FhcDashboard']); } } diff --git a/application/controllers/api/frontend/v1/Bookmark.php b/application/controllers/api/frontend/v1/Bookmark.php index 3e646bb51..aa45709d4 100644 --- a/application/controllers/api/frontend/v1/Bookmark.php +++ b/application/controllers/api/frontend/v1/Bookmark.php @@ -18,6 +18,8 @@ if (! defined('BASEPATH')) exit('No direct script access allowed'); +use \DateTime as DateTime; + class Bookmark extends FHCAPI_Controller { @@ -28,111 +30,162 @@ class Bookmark extends FHCAPI_Controller { parent::__construct([ 'getBookmarks' => self::PERM_LOGGED, - 'delete' => self::PERM_LOGGED, - 'insert' => self::PERM_LOGGED, + 'delete' => self::PERM_LOGGED, + 'insert' => self::PERM_LOGGED, 'update' => self::PERM_LOGGED, - ]); + 'changeOrder' => self::PERM_LOGGED + ]); $this->load->model('dashboard/Bookmark_model', 'BookmarkModel'); $this->uid = getAuthUID(); $this->pid = getAuthPersonID(); - } //------------------------------------------------------------------------------------------------------------------ // Public methods - /** - * gets the bookmarks associated to a user + /** + * gets the bookmarks associated to a user * @access public * @return void */ public function getBookmarks() { - $this->BookmarkModel->addOrder("bookmark_id"); + $this->BookmarkModel->addOrder("sort"); $bookmarks = $this->BookmarkModel->loadWhere(["uid"=>$this->uid]); - $bookmarks = $this->getDataOrTerminateWithError($bookmarks); + $bookmarks = $this->getDataOrTerminateWithError($bookmarks); - $this->terminateWithSuccess($bookmarks); - } - - /** - * deletes bookmark from associated user - * @access public - * @return void - */ - public function delete($bookmark_id) - { - $bookmark = $this->BookmarkModel->load($bookmark_id); - - $bookmark = current($this->getDataOrTerminateWithError($bookmark)); - - // only delete bookmark if the user is the owner of the bookmark - if($bookmark->uid == $this->uid || $this->permissionlib->isBerechtigt('admin')){ - - $delete_result = $this->BookmarkModel->delete($bookmark_id); - - $delete_result = $this->getDataOrTerminateWithError($delete_result); - - $this->terminateWithSuccess($delete_result); - }else{ - $this->_outputAuthError(['delete' => ['admin:rw']]); - } - } - - /** - * inserts new bookmark into the bookmark table - * @access public - * @return void - */ - public function insert() - { - // form validation - $this->load->library('form_validation'); - $this->form_validation->set_rules('url', 'URL', 'required|valid_url|max_length[511]'); - $this->form_validation->set_rules('title', 'Title', 'required|max_length[255]'); - if($this->form_validation->run() == FALSE) $this->terminateWithValidationErrors($this->form_validation->error_array()); - - $url = $this->input->post('url',true); - $title = $this->input->post('title',true); - $tag = $this->input->post('tag', true); - - $insert_into_result = $this->BookmarkModel->insert(['uid'=>$this->uid, 'url'=>$url, 'title'=>$title,'tag'=>$tag, 'insertvon'=>$this->uid, 'updateamum'=>NULL, 'updatevon'=>NULL]); - - $insert_into_result = $this->getDataOrTerminateWithError($insert_into_result); - - $this->terminateWithSuccess($insert_into_result); - - } + $this->terminateWithSuccess($bookmarks); + } /** - * updates bookmark in the bookmark table + * deletes bookmark from associated user * @access public * @return void */ - public function update($bookmark_id) + public function delete($bookmark_id) { - // form validation - $this->load->library('form_validation'); - $this->form_validation->set_rules('url', 'URL', 'required|valid_url|max_length[511]'); - $this->form_validation->set_rules('title', 'Title', 'required|max_length[255]'); - if($this->form_validation->run() == FALSE) $this->terminateWithValidationErrors($this->form_validation->error_array()); + $bookmark = $this->BookmarkModel->load($bookmark_id); - $url = $this->input->post('url',true); - $title = $this->input->post('title',true); + $bookmark = current($this->getDataOrTerminateWithError($bookmark)); + + // only delete bookmark if the user is the owner of the bookmark + if ($bookmark->uid == $this->uid || $this->permissionlib->isBerechtigt('admin')) { + $delete_result = $this->BookmarkModel->delete($bookmark_id); + + $delete_result = $this->getDataOrTerminateWithError($delete_result); + + $this->terminateWithSuccess($delete_result); + } else { + $this->_outputAuthError(['delete' => ['admin:rw']]); + } + } + + /** + * inserts new bookmark into the bookmark table + * @access public + * @return void + */ + public function insert() + { + // form validation + $this->load->library('form_validation'); + $this->form_validation->set_rules('url', 'URL', 'required|valid_url|max_length[511]'); + $this->form_validation->set_rules('title', 'Title', 'required|max_length[255]'); + if (!$this->form_validation->run()) + $this->terminateWithValidationErrors($this->form_validation->error_array()); + + $url = $this->input->post('url', true); + $title = $this->input->post('title', true); + $tag = $this->input->post('tag', true); + if (is_array($tag)) { + $tag = json_encode($tag); // convert PHP array to JSON string + } + $sort = $this->input->post('sort', true); + + $insert_into_result = $this->BookmarkModel->insert([ + 'uid' => $this->uid, + 'url' => $url, + 'title' => $title, + 'tag' => $tag, + 'insertvon' => $this->uid, + 'updateamum' => null, + 'updatevon' => null, + 'sort' => $sort + ]); + + $insert_into_result = $this->getDataOrTerminateWithError($insert_into_result); + + $this->terminateWithSuccess($insert_into_result); + } + + /** + * updates bookmark in the bookmark table + * @access public + * @return void + */ + public function update($bookmark_id) + { + // form validation + $this->load->library('form_validation'); + $this->form_validation->set_rules('url', 'URL', 'required|valid_url|max_length[511]'); + $this->form_validation->set_rules('title', 'Title', 'required|max_length[255]'); + if (!$this->form_validation->run()) + $this->terminateWithValidationErrors($this->form_validation->error_array()); + + $url = $this->input->post('url', true); + $title = $this->input->post('title', true); + $tag = $this->input->post('tag', true); + if (is_array($tag)) { + $tag = json_encode($tag); + } $now = new DateTime(); $now = $now->format('Y-m-d H:i:s'); - $update_result = $this->BookmarkModel->update($bookmark_id,['url'=>$url, 'title'=>$title,'updateamum'=>$now]); + $update_result = $this->BookmarkModel->update($bookmark_id, [ + 'url' => $url, + 'title' => $title, + 'tag' => $tag, + 'updateamum' => $now + ]); - $update_result = $this->getDataOrTerminateWithError($update_result); + $update_result = $this->getDataOrTerminateWithError($update_result); - $this->terminateWithSuccess($update_result); + $this->terminateWithSuccess($update_result); + } - } + /** + * changes sort of two bookmarks in the bookmark table + * @access public + * @return void + */ + public function changeOrder($bookmark_id1, $bookmark_id2) + { + $update_result = []; + + $result1 = $this->BookmarkModel->load($bookmark_id1); + $data1 = $this->getDataOrTerminateWithError($result1); + $sort1 = current($data1)->sort; + + $result2 = $this->BookmarkModel->load(["bookmark_id"=>$bookmark_id2]); + $data2 = $this->getDataOrTerminateWithError($result2); + $sort2 = current($data2)->sort; + + $update_result1 = $this->BookmarkModel->update($bookmark_id1, [ + 'sort' => $sort2 + ]); + $update_result[] = $this->getDataOrTerminateWithError($update_result1); + + $update_result2 = $this->BookmarkModel->update($bookmark_id2, [ + 'sort' => $sort1 + ]); + $update_result[] = $this->getDataOrTerminateWithError($update_result2); + + $this->terminateWithSuccess($update_result); + } } diff --git a/application/controllers/api/frontend/v1/Cis4FhcApi.php b/application/controllers/api/frontend/v1/Cis4FhcApi.php index 372e4bfaa..4d0f906f0 100644 --- a/application/controllers/api/frontend/v1/Cis4FhcApi.php +++ b/application/controllers/api/frontend/v1/Cis4FhcApi.php @@ -27,7 +27,7 @@ class Cis4FhcApi extends FHCAPI_Controller public function __construct() { parent::__construct([ - 'getViewData' => self::PERM_LOGGED, + 'dashboardViewData' => self::PERM_LOGGED, ]); } @@ -36,17 +36,22 @@ class Cis4FhcApi extends FHCAPI_Controller // Public methods /** - * fetches ViewData - */ - public function getViewData() + * retrieves view data for dashboard view + * @access public + * @param $uid the userID for which profile is being viewed, null or missing value implies one's own profile + */ + public function dashboardViewData() { $this->load->model('person/Person_model','PersonModel'); $personData = getData($this->PersonModel->getByUid(getAuthUID()))[0]; + $this->load->config('calendar'); + $viewData = array( 'uid' => getAuthUID(), 'name' => $personData->vorname, - 'person_id' => $personData->person_id + 'person_id' => $personData->person_id, + 'timezone' => $this->config->item('timezone'), ); $this->terminateWithSuccess($viewData); diff --git a/application/controllers/api/frontend/v1/CisMenu.php b/application/controllers/api/frontend/v1/CisMenu.php index 4f4f2573e..163c337ae 100644 --- a/application/controllers/api/frontend/v1/CisMenu.php +++ b/application/controllers/api/frontend/v1/CisMenu.php @@ -16,12 +16,13 @@ * along with this program. If not, see . */ -if (! defined('BASEPATH')) exit('No direct script access allowed'); +if (!defined('BASEPATH')) + exit('No direct script access allowed'); class CisMenu extends FHCAPI_Controller { - + /** * Object initialization */ @@ -31,28 +32,95 @@ class CisMenu extends FHCAPI_Controller 'getMenu' => self::PERM_LOGGED, ]); - + } //------------------------------------------------------------------------------------------------------------------ // Public methods - /** + /** * fetches the menu for CIS from the database based on the userLanguage */ - public function getMenu() + public function getMenu() { $this->load->model('content/Content_model', 'ContentModel'); $this->load->config('cis'); - $cis4_content_id =$this->config->item('cis_menu_root_content_id'); - $result = $this->ContentModel->getMenu($cis4_content_id, getAuthUID(),getUserLanguage()); + $cis4_content_id = $this->config->item('cis_menu_root_content_id'); + $result = $this->ContentModel->getMenu($cis4_content_id, getAuthUID(), getUserLanguage()); $result = $this->getDataOrTerminateWithError($result); $menu = $result->childs ?? []; - $this->terminateWithSuccess($menu); - } - - + $menu = $this->generateUrlsForMenuItems($menu); + $this->terminateWithSuccess($menu); + } + + private function generateUrlsForMenuItems($menuItems) + { + return array_map( + function ($menuItem) { + return $this->generateUrlForMenuItem($menuItem); + }, + $menuItems + ); + } + + private function generateUrlForMenuItem($menuItem) + { + $menuItem->url = $this->menuItemUrlHelper($menuItem); + unset($menuItem->content); + + if ($menuItem->childs && count($menuItem->childs)) { + $menuItem->childs = $this->generateUrlsForMenuItems($menuItem->childs); + } + + return $menuItem; + } + + private function menuItemUrlHelper($menuItem) + { + if ($menuItem->template_kurzbz !== 'redirect') { + return site_url("/CisVue/Cms/content/" . $menuItem->content_id); + } + + if (!$menuItem->content || !mb_strlen($menuItem->content)) { + return ''; + } + + $doc = new DOMDocument(); + $doc->loadXML($menuItem->content); + $urlElem = $doc->getElementsByTagName('url')->item(0); + + if (!$urlElem) { + return ''; + } + + $url = $urlElem->textContent; + + if (strpos($url, '../cms/news.php') !== false) { + $newsRegex = '/^\.\.\/cms\/news\.php/'; + $url = preg_replace($newsRegex, site_url("/CisVue/Cms/news"), $url); + } + + if (strpos($url, '../cms/content.php?') !== false) { + $contentRegex = '/^\.\.\/cms\/content\.php\?content_id=([0-9]+)/'; + $matches = []; + preg_match($contentRegex, $url, $matches); + $url = site_url('/CisVue/Cms/content/' . $matches[1]); + } + + if (strpos($url, '../index.ci.php') !== false) { + $indexRegex = '/^\.\.\/index\.ci\.php/'; + $url = preg_replace($indexRegex, site_url(), $url); + } + + if (strpos($url, '../') !== false) { + $relativeRegex = '/^\.\.\//'; + $url = preg_replace($relativeRegex, base_url(), $url); + } + + return $url; + } + } diff --git a/application/controllers/api/frontend/v1/Lehre.php b/application/controllers/api/frontend/v1/Lehre.php index 10d945a3e..fae2f2f3b 100644 --- a/application/controllers/api/frontend/v1/Lehre.php +++ b/application/controllers/api/frontend/v1/Lehre.php @@ -18,18 +18,9 @@ if (! defined('BASEPATH')) exit('No direct script access allowed'); -//require_once('../../../include/studiengang.class.php'); -//require_once('../../../include/student.class.php'); -//require_once('../../../include/datum.class.php'); -//require_once('../../../include/mail.class.php'); -//require_once('../../../include/benutzerberechtigung.class.php'); -//require_once('../../../include/phrasen.class.php'); -//require_once('../../../include/projektarbeit.class.php'); -//require_once('../../../include/projektbetreuer.class.php'); - class Lehre extends FHCAPI_Controller { - + /** * Object initialization */ @@ -38,39 +29,58 @@ class Lehre extends FHCAPI_Controller parent::__construct([ 'lvStudentenMail' => self::PERM_LOGGED, 'LV' => self::PERM_LOGGED, - 'Pruefungen' => self::PERM_LOGGED + 'Pruefungen' => self::PERM_LOGGED, + 'semesterAverageGrade' => self::PERM_LOGGED, + 'getZugewieseneLv' => self::PERM_LOGGED, + 'getLeForLv' => self::PERM_LOGGED ]); - + + $this->load->library('PhrasesLib'); + + $this->loadPhrases( + array( + 'global', + 'ui', + 'abgabetool' + ) + ); + + $this->load->helper('hlp_sancho_helper'); + + require_once(FHCPATH . 'include/studiengang.class.php'); + require_once(FHCPATH . 'include/student.class.php'); + require_once(FHCPATH . 'include/projektarbeit.class.php'); + require_once(FHCPATH . 'include/projektbetreuer.class.php'); } //------------------------------------------------------------------------------------------------------------------ // Public methods - /** + /** * constructs the emails of the groups from a lehrveranstaltung */ - public function lvStudentenMail() + public function lvStudentenMail() { - $lehreinheit_id = $this->input->get("lehreinheit_id",TRUE); - - // return early if the required parameter is missing - if(!isset($lehreinheit_id)) - { - $this->terminateWithError('Missing required parameter', self::ERROR_TYPE_GENERAL); - } + $lehreinheit_id = $this->input->get("lehreinheit_id",TRUE); - $this->load->model('education/Lehreinheit_model', 'LehreinheitModel'); - - $studentenMails = $this->LehreinheitModel->getStudentenMail($lehreinheit_id); + // return early if the required parameter is missing + if(!isset($lehreinheit_id)) + { + $this->terminateWithError('Missing required parameter', self::ERROR_TYPE_GENERAL); + } - $studentenMails = $this->getDataOrTerminateWithError($studentenMails); + $this->load->model('education/Lehreinheit_model', 'LehreinheitModel'); + + $studentenMails = $this->LehreinheitModel->getStudentenMail($lehreinheit_id); + + $studentenMails = $this->getDataOrTerminateWithError($studentenMails); //convert array of objects into array of strings $studentenMails = array_map(function($element){ return $element->mail; }, $studentenMails); - $this->terminateWithSuccess($studentenMails); + $this->terminateWithSuccess($studentenMails); } public function LV($studiensemester_kurzbz, $lehrveranstaltung_id) @@ -80,13 +90,13 @@ class Lehre extends FHCAPI_Controller $result = $this->LehrveranstaltungModel->getLvsByStudentWithGrades(getAuthUID(), $studiensemester_kurzbz, getUserLanguage(), $lehrveranstaltung_id); $result = current($this->getDataOrTerminateWithError($result)); - + $this->terminateWithSuccess($result); } /** * fetches all Pruefungen of a student for a specific lehrveranstaltung - * if the student passed the Pruefung on the first attempt, no information about the Pruefungen is stored in the database + * if the student passed the Pruefung on the first attempt, no information about the Pruefungen is stored in the database * @param mixed $lehrveranstaltung_id * @return void */ @@ -100,5 +110,90 @@ class Lehre extends FHCAPI_Controller $this->terminateWithSuccess($result); } -} + /** + * calculates and returns the grade average and weighted average for a specific semester + * @param string $studiensemester_kurzbz + * @return void + */ + + public function semesterAverageGrade($studiensemester_kurzbz) + { + $this->load->model('education/Lehrveranstaltung_model', 'LehrveranstaltungModel'); + $semesterLvs = $this->LehrveranstaltungModel->getLvsByStudentWithGrades(getAuthUID(), $studiensemester_kurzbz, getUserLanguage()); + + if (isError($semesterLvs)) + return $this->outputJsonError(getError($semesterLvs)); + + $semesterLvsData = getData($semesterLvs); + + $doGradesExist = false; + $sum = 0; + $count = 0; + $sumWeighted = 0; + $sumEcts = 0; + + foreach ($semesterLvsData as $lv) { + if (!$lv->znote || $lv->znote < 1 || $lv->znote > 5) + continue; + + $doGradesExist = true; + + $sum += $lv->znote; + $count++; + $sumWeighted += $lv->znote * floatval($lv->ects); + $sumEcts += floatval($lv->ects); + } + + $averageGrade = null; + $weightedAverageGrade = null; + if ($doGradesExist) { + $averageGrade = $sum/$count; + $weightedAverageGrade = $sumWeighted/$sumEcts; + } + + $this->terminateWithSuccess(['average_grade' => $averageGrade, 'weighted_average_grade' => $weightedAverageGrade]); + } + + /** + * fetches all assigned lehrveranstaltungen of a mitarbeiter for a given semester + * @param mixed $uid + * @param mixed $sem_kurzbz + * @return void + */ + public function getZugewieseneLv() { + $uid = $this->input->get("uid",TRUE); + $sem_kurzbz = $this->input->get("sem_kurzbz",TRUE); + + // TODO: error messages + + if(!isset($sem_kurzbz) || isEmptyString($sem_kurzbz)) + $this->terminateWithError($this->p->t('global', 'wrongParameters'), 'general'); + + if (!isset($uid) || isEmptyString($uid)) + $uid = getAuthUID(); + + // querying other ma_uids data requires admin permission + if($uid !== getAuthUID()) { + $this->load->library('PermissionLib'); + $isAdmin = $this->permissionlib->isBerechtigt('admin'); + if(!$isAdmin) $this->terminateWithError($this->p->t('ui', 'keineBerechtigung'), 'general'); + } + + $this->load->model('education/Lehrveranstaltung_model', 'LehrveranstaltungModel'); + $result = $this->LehrveranstaltungModel->getLvForLektorInSemester($sem_kurzbz, $uid); + $data = $this->getDataOrTerminateWithError($result); + $this->terminateWithSuccess($data); + } + + public function getLeForLv() { + $lv_id = $this->input->get("lv_id",TRUE); + $sem_kurzbz = $this->input->get("sem_kurzbz",TRUE); + + $this->load->model('education/Lehreinheit_model', 'LehreinheitModel'); + +// $this->terminateWithSuccess($this->LehreinheitModel->getLesForLv($lv_id, $sem_kurzbz)); + $this->terminateWithSuccess($this->LehreinheitModel->getAllLehreinheitenForLvaAndMaUid($lv_id, getAuthUID(), $sem_kurzbz)); + } + +} diff --git a/application/controllers/api/frontend/v1/LvMenu.php b/application/controllers/api/frontend/v1/LvMenu.php index 45936d9f5..27cbba1c2 100644 --- a/application/controllers/api/frontend/v1/LvMenu.php +++ b/application/controllers/api/frontend/v1/LvMenu.php @@ -36,7 +36,8 @@ class LvMenu extends FHCAPI_Controller public function __construct() { parent::__construct([ - 'getLvMenu' => self::PERM_LOGGED + 'getLvMenu' => self::PERM_LOGGED, + 'getMultipleLvMenu' => self::PERM_LOGGED ]); $this->load->model("ressource/Mitarbeiter_model"); @@ -61,24 +62,23 @@ class LvMenu extends FHCAPI_Controller /** * alternative function to get multiple lvMenus with a single http request + * not yet working as intended as the menu_lv.inc.php scripts called by the + * lvMenuBuild event have logic coupled to require_once import which results in + * a wrong logic after the first invocation -> faulty results for lvinfo, moodle + * and several others */ - public function getMultipleLvMenu($lvMenuOptionList){ + public function getMultipleLvMenu(){ + $lvMenuOptionList = $this->input->post('lvMenuOptionList', true); $result =[]; foreach($lvMenuOptionList as $lvMenuOptions){ - $lvMenu = $this->getLvMenu($lvMenuOptions['lvid'],$lvMenuOptions['studiensemester_kurzbz']); - if(isError($lvMenu)){ - // TODO: some lvMenu threw an error, handle error here - } + $lvMenu = $this->getLvMenuInternal($lvMenuOptions['lvid'],$lvMenuOptions['studiensemester_kurzbz']); + $result[$lvMenuOptions['lvid']]=$lvMenu; } $this->terminateWithSuccess($result); } - - /** - * - */ - public function getLvMenu($lvid, $studiensemester_kurzbz) - { + + private function getLvMenuInternal($lvid, $studiensemester_kurzbz) { // return early if parameters are missing if(!isset($lvid) || !isset($studiensemester_kurzbz)) @@ -89,14 +89,14 @@ class LvMenu extends FHCAPI_Controller // get the user if (!$user=getAuthUID()) - $this->terminateWithError($this->p->t('global', 'nichtAngemeldet')); + $this->terminateWithError($this->p->t('global', 'nichtAngemeldet')); // check if is_lector $is_lector = false; $mares = $this->Mitarbeiter_model->isMitarbeiter($user); if(hasData($mares)) { - $is_lector = getData($mares); + $is_lector = getData($mares); } // definition of user_is_allowed_to_upload @@ -105,7 +105,7 @@ class LvMenu extends FHCAPI_Controller // load lehrveranstaltung $lvres = $this->Lehrveranstaltung_model->load($lvid); - if(!hasData($lvres)) + if(!hasData($lvres)) { $this->terminateWithError('LV ' . $lvid . ' not found.'); } @@ -124,7 +124,7 @@ class LvMenu extends FHCAPI_Controller $stgres = $this->Studiengang_model->load(strval($studiengang_kz)); if(!hasData($stgres)) { - $this->terminateWithError('Stg ' . $lv->studiengang_kz . ' not found.'); + $this->terminateWithError('Stg ' . $lv->studiengang_kz . ' not found.'); } $stg = (getData($stgres))[0]; $kurzbz = strtoupper($stg->typ . $stg->kurzbz); @@ -139,7 +139,7 @@ class LvMenu extends FHCAPI_Controller $angemeldet = false; $lesres = $this->Lehreinheit_model->getLehreinheitenForStudentAndStudienSemester( - $lvid, $user, $angezeigtes_stsem + $lvid, $user, $angezeigtes_stsem ); if(hasData($lesres) && count(getData($lesres)) > 0) @@ -148,7 +148,7 @@ class LvMenu extends FHCAPI_Controller // lehrfach $lehrfach_id=''; - + if(defined('CIS_LEHRVERANSTALTUNG_LEHRFACH_ANZEIGEN') && CIS_LEHRVERANSTALTUNG_LEHRFACH_ANZEIGEN) { // Wenn der eingeloggte User zu einer der Lehreinheiten zugeteilt ist @@ -211,8 +211,8 @@ class LvMenu extends FHCAPI_Controller foreach($fbs as $row) { $lehrfach_oe_kurzbz_arr[] = $row->oe_kurzbz; - if($this->PermissionLib->isBerechtigt('lehre', null, $row->oe_kurzbz) - || $this->PermissionLib->isBerechtigt('assistenz', null, $stg->oe_kurzbz)) + if($this->PermissionLib->isBerechtigt('lehre', null, $row->oe_kurzbz) + || $this->PermissionLib->isBerechtigt('assistenz', null, $stg->oe_kurzbz)) { $user_is_allowed_to_upload=true; } @@ -224,21 +224,21 @@ class LvMenu extends FHCAPI_Controller $menu = array(); $this->fhc_menu_lvinfo($menu, $lvid, $studiengang_kz, $lektor_der_lv, $is_lector, $lehrfach_oe_kurzbz_arr); - + $this->fhc_menu_feedback($menu, $angemeldet, $lvid); - + $this->fhc_menu_gesamtnote($menu, $angemeldet, $lvid, $lv, $is_lector, $angezeigtes_stsem); - + $this->fhc_menu_emailStudierende($menu, $user, $angemeldet, $lvid, $angezeigtes_stsem); - + $this->fhc_menu_abmeldung($menu, $user, $is_lector, $lvid, $angezeigtes_stsem); - + $this->fhc_menu_lehretools($menu, $lvid, $angezeigtes_stsem, $sprache); - + $this->fhc_menu_anrechnungStudent($menu, $lvid, $angezeigtes_stsem); - + $this->fhc_menu_anrechnungLector($menu, $angezeigtes_stsem); - + // Addons Menu Logic // ########################################################################################## @@ -272,18 +272,18 @@ class LvMenu extends FHCAPI_Controller 'permissionLib' => &$this->PermissionLib, 'phrasesLib' => &$this->PhrasesLib ]; - - Events::trigger('lvMenuBuild', - // passing $menu per reference - function & () use (&$menu) { - return $menu; - }, - $params + + Events::trigger('lvMenuBuild', + // passing $menu per reference + function & () use (&$menu) { + return $menu; + }, + $params ); // Menu sortieren // ########################################################################################## - + foreach ($menu as $key => $row){ // removes menu points that are not needed in the c4 lvUebersicht @@ -291,7 +291,7 @@ class LvMenu extends FHCAPI_Controller unset($menu[$key]); continue; } - + // fills pos array to sort the menu $pos[$key] = $row['position']; @@ -299,11 +299,18 @@ class LvMenu extends FHCAPI_Controller array_multisort($pos, SORT_ASC, SORT_NUMERIC, $menu); - // HTTP response - // ########################################################################################## + + return $menu; + } + + /** + * + */ + public function getLvMenu($lvid, $studiensemester_kurzbz) + { + $menu = $this->getLvMenuInternal($lvid, $studiensemester_kurzbz); $this->terminateWithSuccess($menu); - } private function fhc_menu_lvinfo(&$menu, $lvid, $studiengang_kz, $lektor_der_lv, $is_lector, $lehrfach_oe_kurzbz_arr){ diff --git a/application/controllers/api/frontend/v1/LvPlan.php b/application/controllers/api/frontend/v1/LvPlan.php index 28b48e3f1..dc87732b9 100644 --- a/application/controllers/api/frontend/v1/LvPlan.php +++ b/application/controllers/api/frontend/v1/LvPlan.php @@ -16,7 +16,8 @@ * along with this program. If not, see . */ -if (! defined('BASEPATH')) exit('No direct script access allowed'); +if (!defined('BASEPATH')) + exit('No direct script access allowed'); use CI3_Events as Events; use \DateTime as DateTime; @@ -33,19 +34,25 @@ class LvPlan extends FHCAPI_Controller parent::__construct([ 'getRoomplan' => self::PERM_LOGGED, - 'Stunden' => self::PERM_LOGGED, - 'getReservierungen' => self::PERM_LOGGED, + 'Stunden' => self::PERM_LOGGED, + 'getReservierungen' => self::PERM_LOGGED, 'LvPlanEvents' => self::PERM_LOGGED, 'eventsPersonal' => self::PERM_LOGGED, 'eventsLv' => self::PERM_LOGGED, 'getLehreinheitStudiensemester' => self::PERM_LOGGED, 'studiensemesterDateInterval' => self::PERM_LOGGED, 'getLvPlanForStudiensemester' => self::PERM_LOGGED, - 'getLv' => self::PERM_LOGGED + 'getLv' => self::PERM_LOGGED, + 'eventsStgOrg' => self::PERM_LOGGED, + 'fetchFerienEvents' => self::PERM_LOGGED, + 'getStudiengaenge' => self::PERM_LOGGED, + 'getLehrverband' => self::PERM_LOGGED, + 'permissionOtherLvPlan' => self::PERM_LOGGED, + 'compactibleEventTypes' => self::PERM_LOGGED, ]); - $this->load->library('LogLib'); - $this->loglib->setConfigs(array( + $this->load->library('LogLib'); + $this->loglib->setConfigs(array( 'classIndex' => 5, 'functionIndex' => 5, 'lineIndex' => 4, @@ -53,17 +60,17 @@ class LvPlan extends FHCAPI_Controller 'dbExecuteUser' => 'RESTful API' )); - $this->load->library('form_validation'); + $this->load->library('form_validation'); } //------------------------------------------------------------------------------------------------------------------ // Public methods /** - * fetches LvPlan and Moodle events together - * @access public - * - */ + * fetches LvPlan and Moodle events together + * @access public + * + */ public function LvPlanEvents() { $hasLv = $this->input->post('lv_id'); @@ -83,24 +90,30 @@ class LvPlan extends FHCAPI_Controller // form validation $this->form_validation->set_rules('start_date', "start_date", "required"); $this->form_validation->set_rules('end_date', "end_date", "required"); - + if (!$this->form_validation->run()) $this->terminateWithValidationErrors($this->form_validation->error_array()); // storing the post parameter in local variables $start_date = $this->input->post('start_date', true); $end_date = $this->input->post('end_date', true); + $uid = $this->input->post('uid', true); + + // disallow accessing other user's lv plan if missing permission + if ($uid && $uid !== getAuthUID() && !$this->permissionlib->isBerechtigt('basis/other_lv_plan')) { + $this->terminateWithError("Missing permission to view other users' timetables!"); + } // fetching lvplan events - $result = $this->stundenplanlib->getEventsUser($start_date, $end_date); + $result = $this->stundenplanlib->getEventsUser($start_date, $end_date, $uid); $lvplanEvents = $this->getDataOrTerminateWithError($result); // fetching moodle events - $moodleEvents = $this->fetchMoodleEvents($start_date, $end_date); + $moodleEvents = $uid ? [] : $this->fetchMoodleEvents($start_date, $end_date); // fetching ferien events - $ferienEvents = $this->fetchFerienEvents($start_date, $end_date); - + $ferienEvents = $this->fetchFerienEvents($start_date, $end_date, $uid); + $this->terminateWithSuccess(array_merge( $lvplanEvents, @@ -109,6 +122,45 @@ class LvPlan extends FHCAPI_Controller )); } + /** + * fetches LvPlan for studiengang / semester / verband / gruppe + * + * @access public + */ + public function eventsStgOrg() + { + $this->load->library('StundenplanLib'); + + // form validation + $this->form_validation->set_rules('start_date', "start_date", "required"); + $this->form_validation->set_rules('end_date', "end_date", "required"); + //$this->form_validation->set_rules('stg_kz', "stg_kz", "required"); //no validation show empty calendar + + if (!$this->form_validation->run()) { + $this->terminateWithValidationErrors($this->form_validation->error_array()); + $stgOrgEvents = []; + $ferienEvents = []; + } else { + $start_date = $this->input->post('start_date', true); + $end_date = $this->input->post('end_date', true); + $stg_kz = $this->input->post('stg_kz', true); + $sem = $this->input->post('sem', true); + $verband = $this->input->post('verband', true); + $gruppe = $this->input->post('gruppe', true); + + $result = $this->stundenplanlib->getEventsStgOrg($start_date, $end_date, $stg_kz, $sem, $verband, $gruppe); + $stgOrgEvents = $this->getDataOrTerminateWithError($result); + + $result = $this->stundenplanlib->fetchFerienTageEvents($start_date, $end_date, $stg_kz); + $ferienEvents = $this->getDataOrTerminateWithError($result); + } + + $this->terminateWithSuccess(array_merge( + $stgOrgEvents, + $ferienEvents + )); + } + /** * fetches LvPlan and Ferien events together for the lv * @@ -122,7 +174,7 @@ class LvPlan extends FHCAPI_Controller $this->form_validation->set_rules('start_date', "start_date", "required"); $this->form_validation->set_rules('end_date', "end_date", "required"); $this->form_validation->set_rules('lv_id', "lv_id", "required|integer"); - + if (!$this->form_validation->run()) $this->terminateWithValidationErrors($this->form_validation->error_array()); @@ -137,7 +189,6 @@ class LvPlan extends FHCAPI_Controller // fetching ferien events $ferienEvents = $this->fetchFerienEvents($start_date, $end_date); - $this->terminateWithSuccess(array_merge( $lvplanEvents, @@ -146,40 +197,42 @@ class LvPlan extends FHCAPI_Controller } //TODO: delete this function if we don't use the old calendar export endpoints anymore - public function studiensemesterDateInterval($date){ - $this->load->model('organisation/Studiensemester_model','StudiensemesterModel'); - $studiensemester =$this->StudiensemesterModel->getByDate(date_format(date_create($date),'Y-m-d')); - $studiensemester =current($this->getDataOrTerminateWithError($studiensemester)); + public function studiensemesterDateInterval($date) + { + $this->load->model('organisation/Studiensemester_model', 'StudiensemesterModel'); + $studiensemester = $this->StudiensemesterModel->getByDate(date_format(date_create($date), 'Y-m-d')); + $studiensemester = current($this->getDataOrTerminateWithError($studiensemester)); $this->terminateWithSuccess($studiensemester); } - public function getLvPlanForStudiensemester($studiensemester,$lvid){ + public function getLvPlanForStudiensemester($studiensemester, $lvid) + { $this->load->library('StundenplanLib'); - $this->load->model('organisation/Studiensemester_model','StudiensemesterModel'); - - $studiensemester_result = $this->StudiensemesterModel->loadWhere(["studiensemester_kurzbz"=>$studiensemester]); + $this->load->model('organisation/Studiensemester_model', 'StudiensemesterModel'); + + $studiensemester_result = $this->StudiensemesterModel->loadWhere(["studiensemester_kurzbz" => $studiensemester]); $studiensemester_result = current($this->getDataOrTerminateWithError($studiensemester_result)); $timespan_start = new DateTime($studiensemester_result->start); $timespan_ende = new DateTime($studiensemester_result->ende); - $lvplan = $this->stundenplanlib->getStundenplan(date_format($timespan_start, 'Y-m-d'),date_format($timespan_ende, 'Y-m-d'), $lvid); + $lvplan = $this->stundenplanlib->getStundenplan(date_format($timespan_start, 'Y-m-d'), date_format($timespan_ende, 'Y-m-d'), $lvid); $this->terminateWithSuccess($lvplan); - - } - - /** - * fetches Stunden layout from database - * @access public - * - */ - public function Stunden() + } + + + /** + * fetches Stunden layout from database + * @access public + * + */ + public function Stunden() { $this->load->model('ressource/Stunde_model', 'StundeModel'); $this->StundeModel->addOrder('stunde', 'ASC'); $stunden = $this->StundeModel->load(); - $stunden = $this->getDataOrTerminateWithError($stunden); + $stunden = $this->getDataOrTerminateWithError($stunden); $this->terminateWithSuccess($stunden); } @@ -210,10 +263,10 @@ class LvPlan extends FHCAPI_Controller $roomplan_data = $this->stundenplanlib->getRoomplan($ort_kurzbz, $start_date, $end_date); $roomplan_data = $this->getDataOrTerminateWithError($roomplan_data); - + $this->terminateWithSuccess($roomplan_data); } - + /** * gets the reservierungen of a room if the ort_kurzbz parameter is * supplied otherwise gets the reservierungen of the lvplan of a student @@ -226,25 +279,27 @@ class LvPlan extends FHCAPI_Controller { $this->form_validation->set_rules('start_date', "StartDate", "required"); $this->form_validation->set_rules('end_date', "EndDate", "required"); - + if (!$this->form_validation->run()) $this->terminateWithValidationErrors($this->form_validation->error_array()); // storing the post parameter in local variables $start_date = $this->input->post('start_date', true); $end_date = $this->input->post('end_date', true); + $uid = $this->input->post('uid', true); // get data $this->load->library('StundenplanLib'); - $result = $this->stundenplanlib->getReservierungen($start_date, $end_date, $ort_kurzbz); + $result = $this->stundenplanlib->getReservierungen($start_date, $end_date, $ort_kurzbz, $uid); $result = $this->getDataOrTerminateWithError($result); $this->terminateWithSuccess($result); } - public function getLehreinheitStudiensemester($lehreinheit_id){ + public function getLehreinheitStudiensemester($lehreinheit_id) + { $this->load->model('education/Lehreinheit_model', 'LehreinheitModel'); $this->LehreinheitModel->addSelect(["studiensemester_kurzbz"]); $result = $this->LehreinheitModel->load($lehreinheit_id); @@ -287,6 +342,68 @@ class LvPlan extends FHCAPI_Controller return $this->terminateWithSuccess(current($result)); } + public function getStudiengaenge() + { + $this->load->model('organisation/Studiengang_model', 'StudiengangModel'); + + $this->StudiengangModel->addOrder('typ'); + $this->StudiengangModel->addOrder('kurzbz'); + $result = $this->StudiengangModel->loadWhere([ + 'aktiv' => true + ]); + + $data = $this->getDataOrTerminateWithError($result); + + return $this->terminateWithSuccess($data); + } + + public function getLehrverband($studiengang_kz, $semester = null, $verband = null) + { + $this->load->model('organisation/Lehrverband_model', 'LehrverbandModel'); + + $where = [ + 'aktiv' => true, + 'studiengang_kz' => $studiengang_kz, + ]; + + if ($semester !== null && $semester !== 'null' && $semester !== 'undefined') { + $where['semester'] = $semester; + } + if ($verband !== null && $verband !== 'null' && $verband !== 'undefined') { + $where['verband'] = $verband; + } + + $this->LehrverbandModel->addOrder('studiengang_kz'); + $this->LehrverbandModel->addOrder('semester'); + $this->LehrverbandModel->addOrder('verband'); + $this->LehrverbandModel->addOrder('gruppe'); + $result = $this->LehrverbandModel->loadWhere($where); + + $data = $this->getDataOrTerminateWithError($result); + + return $this->terminateWithSuccess($data); + } + + /** + * Checks if the current user has permission to view other users' timetables + * + * @return void + */ + public function permissionOtherLvPlan() + { + $this->terminateWithSuccess($this->permissionlib->isBerechtigt('basis/other_lv_plan')); + } + + /** + * get event types which can be compacted in lv plan display + * + * @return void + */ + public function compactibleEventTypes() + { + $this->terminateWithSuccess(["lehreinheit", "reservierung"]); + } + /** * fetch moodle events * @@ -299,19 +416,19 @@ class LvPlan extends FHCAPI_Controller $this->load->config('calendar'); $tz = new DateTimeZone($this->config->item('timezone')); - + $start = new DateTime($start_date); $start->setTimezone($tz); - + $end = new DateTime($end_date); $end->setTimezone($tz); $end->modify('+1 day -1 second'); - + $moodle_events = []; - + Events::trigger( 'moodleCalendarEvents', - function & () use (&$moodle_events) { + function &() use (&$moodle_events) { return $moodle_events; }, [ @@ -331,23 +448,23 @@ class LvPlan extends FHCAPI_Controller * @param string $end_date * @return array */ - private function fetchFerienEvents($start_date, $end_date) + private function fetchFerienEvents($start_date, $end_date, $uid = null) { $this->load->model('organisation/Studiensemester_model', 'StudiensemesterModel'); $this->load->model('education/Studentlehrverband_model', 'StudentLehrverbandModel'); $currentStudiensemester = $this->StudiensemesterModel->getByDate($start_date); $currentStudiensemester = $this->getDataOrTerminateWithError($currentStudiensemester); - + if ($currentStudiensemester) { $studentsemester_kurzbz = current($currentStudiensemester)->studiensemester_kurzbz; $studiengang = $this->StudentLehrverbandModel->loadWhere([ - "student_uid" => getAuthUID(), + "student_uid" => $uid ?? getAuthUID(), "studiensemester_kurzbz" => $studentsemester_kurzbz ]); $studiengang = $this->getDataOrTerminateWithError($studiengang); - + if ($studiengang) $studiengang_kz = current($studiengang)->studiengang_kz; else @@ -357,7 +474,7 @@ class LvPlan extends FHCAPI_Controller } $ferienEvents = $this->stundenplanlib->fetchFerienTageEvents($start_date, $end_date, $studiengang_kz); - + return $this->getDataOrTerminateWithError($ferienEvents); } } diff --git a/application/controllers/api/frontend/v1/Noten.php b/application/controllers/api/frontend/v1/Noten.php new file mode 100644 index 000000000..0d385a5d2 --- /dev/null +++ b/application/controllers/api/frontend/v1/Noten.php @@ -0,0 +1,1144 @@ +. + */ + +if (! defined('BASEPATH')) exit('No direct script access allowed'); + +use CI3_Events as Events; + +class Noten extends FHCAPI_Controller +{ + + /** + * Object initialization + */ + public function __construct() + { + parent::__construct([ + 'getStudentenNoten' => array('lehre/benotungstool:rw'), + 'getNoten' => array('lehre/benotungstool:rw'), + 'saveStudentenNoten' => array('lehre/benotungstool:rw'), + 'getNotenvorschlagStudent' => array('lehre/benotungstool:rw'), + 'saveNotenvorschlag' => array('lehre/benotungstool:rw'), + 'saveStudentPruefung' => array('lehre/benotungstool:rw'), + 'createPruefungen' => array('lehre/benotungstool:rw'), + 'saveNotenvorschlagBulk' => array('lehre/benotungstool:rw'), + 'savePruefungenBulk' => array('lehre/benotungstool:rw'), + 'getCisConfig' => array('lehre/benotungstool:rw'), + 'getNoteByPunkte' => array('lehre/benotungstool:rw') + ]); + + $this->load->library('AuthLib', null, 'AuthLib'); + $this->load->library('PhrasesLib'); + + // Loads LogLib with different debug trace levels to get data of the job that extends this class + // It also specify parameters to set database fields + $this->load->library('LogLib', array( + 'classIndex' => 5, + 'functionIndex' => 5, + 'lineIndex' => 4, + 'dbLogType' => 'API', // required + 'dbExecuteUser' => 'RESTful API', + 'requestId' => 'API', + 'requestDataFormatter' => function ($data) { + return json_encode($data); + } + ), 'logLib'); + + // Loads phrases system + $this->loadPhrases([ + 'global', + 'person', + 'benotungstool', + 'lehre', + 'ui' + ]); + + $this->load->model('education/LePruefung_model', 'LePruefungModel'); + $this->load->model('education/Lvgesamtnote_model', 'LvgesamtnoteModel'); + $this->load->model('education/Lehrveranstaltung_model', 'LehrveranstaltungModel'); + $this->load->model('education/Notenschluesselaufteilung_model', 'NotenschluesselaufteilungModel'); + $this->load->model('person/Person_model', 'PersonModel'); + $this->load->model('organisation/Studienplan_model', 'StudienplanModel'); + $this->load->model('crm/Student_model', 'StudentModel'); + $this->load->model('codex/Mobilitaet_model', 'MobilitaetModel'); + $this->load->model('organisation/Erhalter_model', 'ErhalterModel'); + + $this->load->config('noten'); + $this->load->helper('hlp_sancho_helper'); + + } + + public function getCisConfig() { + $NOTEN_OHNE_ANTRITT = $this->config->item('NOTEN_OHNE_ANTRITT'); + + $this->terminateWithSuccess( + array( + // Punkte bei der Noteneingabe anzeigen + 'CIS_GESAMTNOTE_PUNKTE' => CIS_GESAMTNOTE_PUNKTE, + + // basically on/of toggle for the points/grade col and the arrow button + 'CIS_GESAMTNOTE_UEBERSCHREIBEN' => CIS_GESAMTNOTE_UEBERSCHREIBEN, + + // only relevant in punkte calculation in backend + // 'CIS_GESAMTNOTE_GEWICHTUNG' => CIS_GESAMTNOTE_GEWICHTUNG, + + // this one should always be set true since fh prüfungsordnung requires at least 3 antritte (t1+t2+kP) + // send it anyway to use in maxAntritte calculation + 'CIS_GESAMTNOTE_PRUEFUNG_TERMIN2' => CIS_GESAMTNOTE_PRUEFUNG_TERMIN2, + + // should in 99% of cases be kept true to enable 4 antritte in total, but if a certain + // fh still works with 3 antritte per note this can limit the max number of antritte accordingly + 'CIS_GESAMTNOTE_PRUEFUNG_TERMIN3' => CIS_GESAMTNOTE_PRUEFUNG_TERMIN3, + + // used to toggle availability of kommPruef type pruefungen + 'CIS_GESAMTNOTE_PRUEFUNG_KOMMPRUEF' => CIS_GESAMTNOTE_PRUEFUNG_KOMMPRUEF, + + //technically exists but is never used, could be LE pendant to next flag + // 'CIS_GESAMTNOTE_PRUEFUNG_MOODLE_NOTE' => CIS_GESAMTNOTE_PRUEFUNG_MOODLE_NOTE, + + // basically a toggle for "use teilnoten" and the source is always moodle + // setting this to false breaks legacy tool and if that was fixed it wouldnt render any table at all + // anyway so not sure why this even is a config at all. placebo at best + + // toggles availability of the teilnoten column... existas but do we really need this? + 'CIS_GESAMTNOTE_PRUEFUNG_MOODLE_LE_NOTE' => CIS_GESAMTNOTE_PRUEFUNG_MOODLE_LE_NOTE, + + // send a mail when approving grades + 'CIS_GESAMTNOTE_FREIGABEMAIL_NOTE' => CIS_GESAMTNOTE_FREIGABEMAIL_NOTE, + + 'NOTEN_OHNE_ANTRITT' => $NOTEN_OHNE_ANTRITT + ) + ); + } + + /** + * GET METHOD + * expects 'lv_id', 'sem_kurzbz' + * returns List of all Students of given lehrveranstaltung and semester and fetches their grades. + * Loads LvGesamtnote aswell as Teilnoten from externalSources via getExternalGrades Event. + * Calculates the Notenvorschlag for every student based on averaging their Teilnoten. + * Finally also fetches all Prüfungen for every student which are linked to lva and semester. + */ + public function getStudentenNoten() { + $lv_id = $this->input->get("lv_id",TRUE); + $sem_kurzbz = $this->input->get("sem_kurzbz",TRUE); + + if (!isset($lv_id) || isEmptyString($lv_id) + || !isset($sem_kurzbz) || isEmptyString($sem_kurzbz)) + $this->terminateWithError($this->p->t('global', 'wrongParameters'), 'general'); + + // get studenten for lva & sem with zeugnisnote if available + $studenten = $this->LehrveranstaltungModel->getStudentsByLv($sem_kurzbz, $lv_id); + $studentenData = $this->getDataOrTerminateWithError($studenten); + + if(count($studentenData) == 0) { + $this->terminateWithError('No students found for lva and semester'); + } + + $func = function ($value) { + return $value->uid; + }; + + $grades = array(); + $student_uids = array_map($func, $studentenData); + + $funcpre = function ($value) { + return $value->prestudent_id; + }; + + $prestudent_ids = array_map($funcpre, $studentenData); + + if(count($student_uids) > 0) { + $mobres = $this->MobilitaetModel->getMobilityZusatzForUids($student_uids); + $mobData = $this->getDataOrTerminateWithError($mobres); + + $result = $this->ErhalterModel->load(); + $erhalter = getData($result)[0]; + + $erhalter_kz = '9' . sprintf("%03s", $erhalter->erhalter_kz); + foreach($mobData as $mob) { + $grades[$mob->uid]['mobility_zusatz'] = $this->MobilitaetModel->formatZusatz($mob, $erhalter_kz); + } + } + + foreach($student_uids as $uid) { + $grades[$uid]['grades'] = []; + + $result = $this->LvgesamtnoteModel->getLvGesamtNoten($lv_id, $uid, $sem_kurzbz); + $this->addMeta($uid.'getLvGesamtNoten', $result); + if(!isError($result) && hasData($result)) { + $lvgesamtnote = getData($result)[0]; + $grades[$uid]['note_lv'] = $lvgesamtnote->note; + $grades[$uid]['freigabedatum'] = $lvgesamtnote->freigabedatum; + $grades[$uid]['benotungsdatum'] = $lvgesamtnote->benotungsdatum; + $grades[$uid]['punkte_lv'] = $lvgesamtnote->punkte; + } else { + $grades[$uid]['note_lv'] = null; + $grades[$uid]['freigabedatum'] = null; + $grades[$uid]['benotungsdatum'] = null; + $grades[$uid]['punkte_lv'] = null; + } + } + + // send $grades reference to moodle addon + try { + Events::trigger( + 'getExternalGrades', + function & () use (&$grades) + { + return $grades; + }, + [ + 'lvid' => $lv_id, + 'stsem' => $sem_kurzbz + ] + ); + } catch (Throwable $t) { + $this->addMeta('getExternalGradesError', $t->getMessage()); + } + + // assign the anw% to the students in the studentData loop + $anwresult = $this->getAnwesenheiten($prestudent_ids, $lv_id, $sem_kurzbz); + + // calculate notenvorschläge from teilnoten + foreach($studentenData as $student) { + + $student->anwquote = $anwresult[$student->prestudent_id]; + + $g = $grades[$student->uid]['grades']; + $note_lv = $grades[$student->uid]['note_lv']; + + // overwrite any calculation with lv note once available + if(!is_null($note_lv)) { + $student->note_vorschlag = $note_lv; + } else if(count($g) > 0) { + + $notensumme = 0; + $notensumme_gewichtet = 0; + $gewichtsumme = 0; + $punktesumme = 0; + $punktesumme_gewichtet = 0; + $anzahlnoten = 0; + foreach($g as $teilnote) { + if (is_numeric($teilnote['grade']) || (is_null($teilnote['grade']) && is_numeric($teilnote['points']))) + { + $notensumme += $teilnote['grade']; + $punktesumme += $teilnote['points']; + $notensumme_gewichtet += $teilnote['grade'] * $teilnote['weight']; + $punktesumme_gewichtet += $teilnote['points'] * $teilnote['weight']; + $gewichtsumme += $teilnote['weight']; + $anzahlnoten += 1; + } + } + + if (CIS_GESAMTNOTE_PUNKTE) { + if (defined('CIS_GESAMTNOTE_GEWICHTUNG') && CIS_GESAMTNOTE_GEWICHTUNG) { + // Lehreinheitsgewichtung + $punkte_vorschlag = round($punktesumme_gewichtet / $gewichtsumme, 2); + $note_vorschlag_result = $this->NotenschluesselaufteilungModel->getNote($punkte_vorschlag, $lv_id, $sem_kurzbz); + $note_vorschlag = $this->getDataOrTerminateWithError($note_vorschlag_result); + } else { + $punkte_vorschlag = round($punktesumme / $anzahlnoten, 2); + $note_vorschlag_result = $this->NotenschluesselaufteilungModel->getNote($punkte_vorschlag, $lv_id, $sem_kurzbz); + $note_vorschlag = $this->getDataOrTerminateWithError($note_vorschlag_result); + } + } else { + if (defined('CIS_GESAMTNOTE_GEWICHTUNG') && CIS_GESAMTNOTE_GEWICHTUNG) { + $note_vorschlag = round($notensumme_gewichtet / $gewichtsumme); + } else { + $note_vorschlag = round($notensumme / $anzahlnoten); + } + } + + $student->note_vorschlag = $note_vorschlag; + } + } + + // get all prüfungen with noten held in that semester in that lva + $pruefungen = $this->LePruefungModel->getPruefungenByLvStudiensemester($lv_id, $sem_kurzbz); + $pruefungenData = getData($pruefungen); + + $this->terminateWithSuccess(array($studentenData, $pruefungenData, DOMAIN, $grades, $anwresult)); + } + + /** + * GET METHOD + * returns List of all available & active NotenOptions + */ + public function getNoten() { + $this->load->model('education/Note_model', 'NoteModel'); + + $result = $this->NoteModel->getAllActive(); + $noten = $this->getDataOrTerminateWithError($result); + $this->terminateWithSuccess($noten); + } + + /** + * POST METHOD + * expects 'lv_id', 'sem_kurzbz', 'password', 'noten' + * Notenfreigabe method which checks the users password as a security measure. + * Tries to load Lehrveranstaltung, Studiengang and Person via Model in order to validate the coherency of input parameters + * lv_id & sem_kurzbz in relation to the noten array delivered. + * Updates the LvGesamtnote note, aswell as freigabedatum, which is key in the logic of the freigegeben/offen/changed notenStatus + * Along this process builds a html table to be placed in a confirmation email (uid only and full variant depending on config) + * which is being sent to the Lektor, aswell as the assigned Assistenz. + */ + public function saveStudentenNoten() { + $result = $this->getPostJSON(); + + if(!property_exists($result, 'sem_kurzbz') || !property_exists($result, 'lv_id') || + !property_exists($result, 'password') || !property_exists($result, 'noten')) { + $this->terminateWithError($this->p->t('global', 'missingParameters'), 'general'); + } + + if(!$this->AuthLib->checkUserAuthByUsernamePassword(getAuthUID(), $result->password)->retval) { + $this->terminateWithError($this->p->t('global', 'wrongPassword'), 'general'); + } + + $lv_id = $result->lv_id; + $sem_kurzbz = $result->sem_kurzbz; + + $ret = []; + + $res = $this->LehrveranstaltungModel->load($lv_id); + if(isError($res) || !hasData($res)) { + $this->terminateWithError($this->p->t('benotungstool', 'noValidLvFoundForId', [$lv_id])); + } + + $lv = getData($res)[0]; + + $studiengang_kz = $lv->studiengang_kz; + $res = $this->StudiengangModel->load($studiengang_kz); + if(isError($res) || !hasData($res)) { + $this->terminateWithError($this->p->t('benotungstool', 'noValidStudiengangFoundForId', [$studiengang_kz])); + } + $sg = getData($res)[0]; + $lvaFullName = $sg->kurzbzlang . ' ' . $lv->semester . '.Semester + ' . $lv->bezeichnung . " - " .$lv->lehrform_kurzbz. " " . $lv->orgform_kurzbz . " - " . $sem_kurzbz; + + $emails = explode(', ', $sg->email); + + + $res = $this->PersonModel->load(getAuthPersonId()); + if(isError($res) || !hasData($res)) { + $this->terminateWithError($this->p->t('benotungstool', 'noValidPersonFoundForId', [getAuthPersonId()])); + } + $pers = getData($res)[0]; + $lektorFullName = $pers->anrede.' '.$pers->vorname.' '.$pers->nachname; //.' ('.$pers->kurzbz.')'; + + + $res = $this->StudienplanModel->getStudienplanByLvaSemKurzbz($lv_id, $sem_kurzbz); + $data = getData($res); + $studienplan_bezeichnung = ''; + foreach ($data as $row) { + $studienplan_bezeichnung .= $row->bezeichnung . ' '; + } + $betreff = $this->p->t('benotungstool','notenfreigabe').' ' . $lv->bezeichnung . ' ' . $lv->orgform_kurzbz . ' - ' . $studienplan_bezeichnung; + + $studlist = ""; + + if (defined('CIS_GESAMTNOTE_FREIGABEMAIL_NOTE') && CIS_GESAMTNOTE_FREIGABEMAIL_NOTE) { + $studlist .= "\n + \n + \n + \n"; + if(defined(CIS_GESAMTNOTE_PUNKTE) && CIS_GESAMTNOTE_PUNKTE) { + $studlist .= "\n"; + } + $studlist .= "\n"; + $studlist .= "\n"; + } else { + $studlist .= "\n"; + } + + foreach($result->noten as $note) { + + $resultLVGes = $this->LvgesamtnoteModel->getLvGesamtNoteVorschlag($lv_id, $note->uid, $sem_kurzbz); + $this->addMeta($note->uid.'$resultLVGes', $resultLVGes); + if (!isError($resultLVGes) && hasData($resultLVGes)) + { + $lvgesamtnote = getData($resultLVGes)[0]; + + if ($lvgesamtnote->benotungsdatum > $lvgesamtnote->freigabedatum) + { + + $id = $this->LvgesamtnoteModel->update( + [$lvgesamtnote->student_uid, $lvgesamtnote->studiensemester_kurzbz, $lvgesamtnote->lehrveranstaltung_id], + array( + 'note' => $lvgesamtnote->note, + 'freigabevon_uid' => getAuthUID(), + 'freigabedatum' => date("Y-m-d H:i:s"), + 'updateamum' => date("Y-m-d H:i:s"), + 'updatevon' => getAuthUID() + ) + ); + + if($id) { + $res = $this->LvgesamtnoteModel->load($id->retval); + if(hasData($res)) { + $lvgesamtnote = getData($res)[0]; + $ret[] = array('uid' => $note->uid, 'freigabedatum' => $lvgesamtnote->freigabedatum, 'benotungsdatum' => $lvgesamtnote->benotungsdatum); + } + } + + if (defined('CIS_GESAMTNOTE_FREIGABEMAIL_NOTE') && CIS_GESAMTNOTE_FREIGABEMAIL_NOTE) + { + $studlist .= ""; + $studlist .= ""; + $studlist .= ""; + $studlist .= ""; + + if(defined(CIS_GESAMTNOTE_PUNKTE) && CIS_GESAMTNOTE_PUNKTE) { + $studlist .= ""; + } + $studlist .= ""; + + $studlist .= ""; + } else { + $studlist .= "\n"; + } + } + } + } + $studlist .= "
" . $this->p->t('person','personenkennzeichen') . "" . $this->p->t('lehre','studiengang') . "" . $this->p->t('benotungstool','c4nachname') . "" . $this->p->t('benotungstool','c4vorname') . "" . $this->p->t('benotungstool','c4punkte') . "" . $this->p->t('benotungstool','c4grade') . "" . $this->p->t('ui','bearbeitetVon') . "
" . $this->p->t('person','uid') . "
" . trim($note->matrikelnr) . "" . trim($note->kuerzel) . "" . trim($note->nachname) . "" . trim($note->vorname) . "" . trim($lvgesamtnote->punkte) . "" .$note->noteBezeichnung. "" . $lvgesamtnote->mitarbeiter_uid; + if ($lvgesamtnote->updatevon != '') + $studlist .= " (" . $lvgesamtnote->updatevon . ")"; + $studlist .= "
" . trim($note->uid) . "
"; + + $this->logLib->logInfoDB(array('saveStudentenNoten', array( + 'updatevon' => getAuthUID(), + 'updateamum' => date('Y-m-d H:i:s') + ), getAuthUID(), getAuthPersonId(), array($result->noten, $lv_id, $sem_kurzbz))); + + // always send the mail, config toggles data contents + $this->sendFreigabeEmail($lektorFullName, $lvaFullName, count($result->noten), $emails, $studlist, $betreff); + + $this->terminateWithSuccess($ret); + } + + + private function sendFreigabeEmail($lektorFullName, $lvaFullName, $notenCount, $emailAdressen, $studlist, $betreff) + { + $emailAdressen[] = getAuthUID() . "@" . DOMAIN; // also send mail to lektors own adress + $adressen = implode(";", $emailAdressen); + + foreach ($emailAdressen as $email) + { + // Prepare mail content + $body_fields = array( + 'lektor' => $lektorFullName, + 'lvaname' => $lvaFullName, + 'studlist' => $studlist, + 'neuenotencount' => $notenCount, + 'adressen' => $adressen + ); + + // Send mail + sendSanchoMail( + 'Notenfreigabe', + $body_fields, + $email, + $betreff + ); + } + + } + + /** + * GET METHOD + * should return Notenvorschlag for single Students, not used anywhere but required as per + * https://openproject.technikum-wien.at/projects/fh-complete/work_packages/60873/activity + */ + public function getNotenvorschlagStudent() { + $uid = $this->input->get("uid",TRUE); + + // if uid is missing or empty, fall back to getAuthUID() + if ($uid === NULL || trim((string)$uid) === '') { + $uid = getAuthUID(); + } + + $sem_kurzbz = $this->input->get("sem_kurzbz",TRUE); + $lv_id = $this->input->get("lv_id",TRUE); + + if ($uid === NULL || trim((string)$uid) === '' + || $sem_kurzbz === NULL || trim((string)$sem_kurzbz) === '' + || $lv_id === NULL || trim((string)$lv_id) === '') { + $this->terminateWithError($this->p->t('global', 'missingParameters'), 'general'); + } + + // TODO: we need a zuordnungscheck here? any lektor can get any grades? + // what about assistenz with different rights doing lectors job once again? + // students checking their own grades? + + $result = $this->LvgesamtnoteModel->getLvGesamtNoteVorschlag($lv_id, $uid, $sem_kurzbz); + $data = $this->getDataOrTerminateWithError($result); + + // TODO: moodle teilnote but it seems they only work for a whole course? + + // get anw% of student by prestudent_id +// $anwresult = $this->getAnwesenheiten($prestudent_ids, $lv_id, $sem_kurzbz); + + + + $this->terminateWithSuccess($data); + } + + /** + * POST METHOD + * expects 'datum', 'lva_id', 'student_uid', 'note' + * Inserts or updates a pruefung for lva & student_uid at given datum (YYYY-MM-DD). When creating a new + * Pruefung, sets the provided (Prüfungs-) Note. + * Updates the LvGesamtnote of student. + * Can return 1 or 2 Prüfungen, since the original grade before the first prüfung is being saved as "Termin1" when + * a "Termin2" is being created. + */ + public function saveStudentPruefung() { // einzelne pruefung speichern + $result = $this->getPostJSON(); + + if(!property_exists($result, 'datum') || !property_exists($result, 'lva_id') || + !property_exists($result, 'student_uid') || !property_exists($result, 'note')) { + $this->terminateWithError($this->p->t('global', 'missingParameters'), 'general'); + } + + $student_uid = $result->student_uid; + $note = $result->note; + $punkte = $result->punkte; + $datum = $result->datum; + $lva_id = $result->lva_id; + $lehreinheit_id = $result->lehreinheit_id; + + $stsem = $result->sem_kurzbz; + $typ = $result->typ; + + $jetzt = date("Y-m-d H:i:s"); + + if(CIS_GESAMTNOTE_PUNKTE && isset($punkte) && $punkte >= 0) { + // Bei Punkteeingabe wird die Note nochmals geprueft und ggf korrigiert + $resultNote = $this->NotenschluesselaufteilungModel->getNote($punkte, $lva_id, $stsem); + if(isError($resultNote)) { + $this->terminateWithError(getError($resultNote)); + } else { + $data = getData($resultNote); + if($data != $note) + { + $note = $data; + } + } + + } + + // TODO: more sophisticated empty check + if($note=='') { + $this->load->model('education/Note_model', 'NoteModel'); + $result = $this->NoteModel->getNochNichtEingetragenNote(); + $note = getData($result)[0]->note; + } + + + $this->load->model('education/Lehrveranstaltung_model', 'LehrveranstaltungModel'); + $this->load->model('organisation/Studiengang_model', 'StudiengangModel'); + + $res = $this->LehrveranstaltungModel->load($lva_id); + if(isError($res) || !hasData($res)) { + $this->terminateWithError('Keine gültige Lehrveranstaltung gefunden für ID: '.$lva_id); + } + + $studiengang_kz = getData($res)[0]->studiengang_kz; + $res = $this->StudiengangModel->load($studiengang_kz); + if(isError($res) || !hasData($res)) { + $this->terminateWithError('Kein gültiger Studiengang gefunden für ID: '.$studiengang_kz); + } + + + $result = $this->LvgesamtnoteModel->getLvGesamtNoten($lva_id, $student_uid, $stsem); + if(!isError($result) && !hasData($result)) { + + $id = $this->LvgesamtnoteModel->insert( + array( + 'student_uid' => $student_uid, + 'lehrveranstaltung_id' => $lva_id, + 'studiensemester_kurzbz' => $stsem, + 'note' => $note, + 'punkte' => $punkte, + 'mitarbeiter_uid' => getAuthUID(), + 'benotungsdatum' => $jetzt, + 'freigabedatum' => null, + 'freigabevon_uid' => null, + 'bemerkung' => null, + 'updateamum' => null, + 'updatevon' => null, + 'insertamum' => $jetzt, + 'insertvon' => getAuthUID() + ) + ); + if($id) { + $res = $this->LvgesamtnoteModel->load($id->retval); + if(hasData($res)) $lvgesamtnote = getData($res)[0]; + } + + $this->logLib->logInfoDB(array('saveStudentPruefung insert lvnote', $res, array( + 'insertvon' => getAuthUID(), + 'insertamum' => date('Y-m-d H:i:s') + ), getAuthUID(), getAuthPersonId(), $student_uid, $lva_id, $stsem, $note,$punkte, $jetzt)); + + } + else if(!isError($result) && hasData($result)) + { + $lvgesamtnote = getData($result)[0]; + + $id = $this->LvgesamtnoteModel->update( + [$lvgesamtnote->student_uid, $lvgesamtnote->studiensemester_kurzbz, $lvgesamtnote->lehrveranstaltung_id], + array( + 'note' => $note, + 'punkte' => $punkte, + 'benotungsdatum' => $jetzt, + 'updateamum' => $jetzt, + 'updatevon' => getAuthUID() + ) + ); + + if($id) { + $res = $this->LvgesamtnoteModel->load($id->retval); + if(hasData($res)) $lvgesamtnote = getData($res)[0]; + } + + $this->logLib->logInfoDB(array('saveStudentPruefung update lvnote', $res, array( + 'updatevon' => getAuthUID(), + 'updateamum' => date('Y-m-d H:i:s') + ), getAuthUID(), getAuthPersonId(), $student_uid, $lva_id, $stsem, $note,$punkte, $jetzt)); + } + + // save pruefung after updating lvnote, since pruefungspunkte get loaded by lv punkte + $pruefungenChanged = $this->savePruefungstermin($typ, $student_uid, $lva_id, $stsem, $lehreinheit_id, $note, $punkte, $datum); + + $savedPruefung = $pruefungenChanged['savedPruefung'] ?? null; + $extraPruefung = $pruefungenChanged['extraPruefung'] ?? null; + + $savedPruefungData = count($savedPruefung) > 0 ? $savedPruefung[0] : null; + $extraPruefungData = count($extraPruefung) > 0 ? $extraPruefung[0] : null; + + $this->terminateWithSuccess(array($savedPruefungData, $lvgesamtnote, $extraPruefungData)); + } + + /** + * private helper method to update/insert pruefungstermine + */ + private function savePruefungstermin($typ, $student_uid, $lva_id, $stsem, $lehreinheit_id, $note, $punkte = '', $datum) + { + + // extra check if the student has lvnote and a zeugnisnote in the relevant lva + $resultLV = $this->LvgesamtnoteModel->getLvGesamtNoten($lva_id, $student_uid, $stsem); + $lvgesamtnoteData = getData($resultLV); + $this->addMeta('lvgesamtnoteData', $lvgesamtnoteData); + + // allocating pruefungen before lv note is forbidden + if($lvgesamtnoteData == null) return $this->p->t('benotungstool', 'c4keineLvNoteEingetragen'); + + $status = []; + + // send $grades reference to moodle addon + Events::trigger( + 'getEntschuldigungsStatusForStudentOnDate', + function & () use (&$status) + { + return $status; + }, + [ + 'student_uid' => $student_uid, + 'datum' => $datum + ] + ); + + if(count($status) > 0 && $status[0] == true) { + $this->load->model('education/Note_model', 'NoteModel'); + $result = $this->NoteModel->getEntschuldigtNote(); + $note = getData($result)[0]->note; + } + + $jetzt = date("Y-m-d H:i:s"); + + $pruefungenChanged = []; + + if($typ == "Termin2" && defined('CIS_GESAMTNOTE_PRUEFUNG_TERMIN2') && CIS_GESAMTNOTE_PRUEFUNG_TERMIN2) + { + + // Wenn eine Nachprüfung angelegt wird, wird zuerst eine Pruefung mit 1. Termin angelegt welche für die ursprüngliche Note + // vor den Prüfungsantritten zählt + + $result1 = $this->LePruefungModel->getPruefungenByUidTypLvStudiensemester($student_uid, "Termin1", $lva_id, $stsem); + + // if there is a termin 1 entry already do nothing + if(!isError($result1) && hasData($result1)) { + + } else if(!isError($result1) && !hasData($result1)) { + // new entry termin1 + + $resultLV = $this->LvgesamtnoteModel->getLvGesamtNoteVorschlag($lva_id, $student_uid, $stsem); + + // update Termin1 note + if (hasData($resultLV)) + { + $lvgesamtnote = getData($resultLV)[0]; + $pr_note = $lvgesamtnote->note; + $pr_punkte = $lvgesamtnote->punkte; + $benotungsdatum = $lvgesamtnote->benotungsdatum; + } + else if(!hasData($resultLV))// set Termin1 note to "noch nicht eingetragen" + { + $this->load->model('education/Note_model', 'NoteModel'); + $result = $this->NoteModel->getNochNichtEingetragenNote(); + $pr_note = getData($result)[0]->note; + $pr_punkte = ''; + $benotungsdatum = $jetzt; + } + + $id = $this->LePruefungModel->insert( + array( + 'lehreinheit_id' => $lehreinheit_id, + 'student_uid' => $student_uid, + 'mitarbeiter_uid' => getAuthUID(), + 'note' => $pr_note, + 'punkte' => $pr_punkte, + 'pruefungstyp_kurzbz' => "Termin1", + 'datum' => $benotungsdatum, + 'anmerkung' => "", + 'insertamum' => $jetzt, + 'insertvon' => getAuthUID(), + 'updateamum' => null, + 'updatevon' => null, + 'ext_id' => null + ) + ); + if($id) { + $res = $this->LePruefungModel->load($id->retval); + if(hasData($res)) $pruefungenChanged['extraPruefung'] = getData($res); + } + + $this->logLib->logInfoDB(array('termin1 created',$res, getAuthUID(), getAuthPersonId())); + + } + + + + + // Die Pruefung wird als Termin2 eingetragen + $result2 = $this->LePruefungModel->getPruefungenByUidTypLvStudiensemester($student_uid, "Termin2", $lva_id, $stsem); + // if there is a termin 2 entry already update it + if(!isError($result2) && hasData($result2)) { + // update + $termin2 = getData($result2)[0]; + $id = $this->LePruefungModel->update( + $termin2->pruefung_id, + array( + 'updateamum' => $jetzt, + 'updatevon' => getAuthUID(), + 'note' => $note, + 'punkte' => $punkte, + 'datum' => $datum, + 'anmerkung' => "" + ) + ); + if($id) { + $res = $this->LePruefungModel->load($id->retval); + if(hasData($res)) $pruefungenChanged['savedPruefung'] = getData($res); + } + + $this->logLib->logInfoDB(array('termin2 updated',$res, getAuthUID(), getAuthPersonId())); + + + } else if(!isError($result2) && !hasData($result2)) { + // new entry termin 2 + + $id = $this->LePruefungModel->insert( + array( + 'lehreinheit_id' => $lehreinheit_id, + 'student_uid' => $student_uid, + 'mitarbeiter_uid' => getAuthUID(), + 'note' => $note, + 'punkte' => $punkte, + 'pruefungstyp_kurzbz' => $typ, + 'datum' => $datum, + 'anmerkung' => "", + 'insertamum' => $jetzt, + 'insertvon' => getAuthUID(), + 'updateamum' => null, + 'updatevon' => null, + 'ext_id' => null + ) + ); + if($id) { + $res = $this->LePruefungModel->load($id->retval); + if(hasData($res)) $pruefungenChanged['savedPruefung'] = getData($res); + } + + $this->logLib->logInfoDB(array('termin2 inserted',$res, getAuthUID(), getAuthPersonId())); + + } + + } else if($typ == "Termin3" && defined('CIS_GESAMTNOTE_PRUEFUNG_TERMIN3') && CIS_GESAMTNOTE_PRUEFUNG_TERMIN3) + { + + $result3 = $this->LePruefungModel->getPruefungenByUidTypLvStudiensemester($student_uid, "Termin3", $lva_id, $stsem); + + if(!isError($result3) && hasData($result3)) { + // update + $termin3 = getData($result3)[0]; + + $id = $this->LePruefungModel->update( + $termin3->pruefung_id, + array( + 'updateamum' => $jetzt, + 'updatevon' => getAuthUID(), + 'note' => $note, + 'punkte' => $punkte, + 'datum' => $datum, + 'anmerkung' => "" + ) + ); + if($id) { + $res = $this->LePruefungModel->load($id->retval); + if(hasData($res)) $pruefungenChanged['savedPruefung'] = getData($res); + } + + $this->logLib->logInfoDB(array('termin3 updated',$res, getAuthUID(), getAuthPersonId())); + + } else if(!isError($result3) && !hasData($result3)) { + // insert new termin3 + + $id = $this->LePruefungModel->insert( + array( + 'lehreinheit_id' => $lehreinheit_id, + 'student_uid' => $student_uid, + 'mitarbeiter_uid' => getAuthUID(), + 'note' => $note, + 'punkte' => $punkte, + 'pruefungstyp_kurzbz' => $typ, + 'datum' => $datum, + 'anmerkung' => "", + 'insertamum' => $jetzt, + 'insertvon' => getAuthUID(), + 'updateamum' => null, + 'updatevon' => null, + 'ext_id' => null + ) + ); + if($id) { + $res = $this->LePruefungModel->load($id->retval); + if(hasData($res)) $pruefungenChanged['savedPruefung'] = getData($res); + } + + $this->logLib->logInfoDB(array('termin3 inserted',$res, getAuthUID(), getAuthPersonId())); + + } + } else { + $this->terminateWithError($this->p->t('benotungstool', 'wrongPruefungType', [$student_uid, $typ]), 'general'); + } + + return $pruefungenChanged; + } + + /** + * POST METHOD + * expects 'sem_kurzbz', 'lv_id', 'student_uid', 'note' + * Method that sets lv_note of student in lva and semester from provided Points/Grade Selection. + * Updates the note & benotungsdatum, which is key in the noten state offen/freigegeben/changed + */ + public function saveNotenvorschlag() { + $result = $this->getPostJSON(); + + if(!property_exists($result, 'lv_id') || !property_exists($result, 'sem_kurzbz') || + !property_exists($result, 'student_uid') || !property_exists($result, 'note')) { + $this->terminateWithError($this->p->t('global', 'missingParameters'), 'general'); + } + + $lv_id = $result->lv_id; + $student_uid = $result->student_uid; + $sem_kurzbz = $result->sem_kurzbz; + $note = $result->note; + $punkte = $result->punkte; + + $result = $this->LvgesamtnoteModel->getLvGesamtNoteVorschlag($lv_id, $student_uid, $sem_kurzbz); + + $this->addMeta('LvgesamtnoteModelresult', $result); + + if(!isError($result) && hasData($result)) { + $lvgesamtnote = getData($result)[0]; + + $id = $this->LvgesamtnoteModel->update( + [$lvgesamtnote->student_uid, $lvgesamtnote->studiensemester_kurzbz, $lvgesamtnote->lehrveranstaltung_id], + array( + 'note' => $note, + 'punkte' => $punkte, + 'benotungsdatum' => date("Y-m-d H:i:s"), + 'updateamum' => date("Y-m-d H:i:s"), + 'updatevon' => getAuthUID() + ) + ); + + if($id) { + $res = $this->LvgesamtnoteModel->load($id->retval); + if(hasData($res)) $lvgesamtnote = getData($res)[0]; + } + + $this->logLib->logInfoDB(array('saveNotenvorschlag update lv gesamtnote',$res, getAuthUID(), getAuthPersonId())); + + } else if(!isError($result) && !hasData($result)) { + $id = $this->LvgesamtnoteModel->insert( + array( + 'student_uid' => $student_uid, + 'lehrveranstaltung_id' => $lv_id, + 'studiensemester_kurzbz' => $sem_kurzbz, + 'note' => $note, + 'punkte' => $punkte, + 'mitarbeiter_uid' => getAuthUID(), + 'benotungsdatum' => date("Y-m-d H:i:s"), + 'freigabedatum' => null, + 'freigabevon_uid' => null, + 'bemerkung' => null, + 'updateamum' => null, + 'updatevon' => null, + 'insertamum' => date("Y-m-d H:i:s"), + 'insertvon' => getAuthUID() + ) + ); + if($id) { + $res = $this->LvgesamtnoteModel->load($id->retval); + if(hasData($res)) $lvgesamtnote = getData($res)[0]; + } + + $this->logLib->logInfoDB(array('saveNotenvorschlag insert lv gesamtnote',$res, getAuthUID(), getAuthPersonId())); + } + + $this->terminateWithSuccess(array($lvgesamtnote)); + } + + /** + * POST METHOD + * expects 'sem_kurzbz', 'lv_id', 'noten' + * Bulk variant of saveNotenvorschlag, used when importing grades from csv. + */ + public function saveNotenvorschlagBulk() { + $result = $this->getPostJSON(); + + if(!property_exists($result, 'lv_id') || !property_exists($result, 'sem_kurzbz') || + !property_exists($result, 'noten')) { + $this->terminateWithError($this->p->t('global', 'missingParameters'), 'general'); + } + + $lv_id = $result->lv_id; + $sem_kurzbz = $result->sem_kurzbz; + $noten = $result->noten; + + $retLvNoten = []; + + foreach($noten as $note) + { + + $result = $this->LvgesamtnoteModel->getLvGesamtNoteVorschlag($lv_id, $note->uid, $sem_kurzbz); + $this->addMeta($note->uid.'$result', $result); + + if(CIS_GESAMTNOTE_PUNKTE) { + $resultNote = $this->NotenschluesselaufteilungModel->getNote($note->punkte, $lv_id, $sem_kurzbz); + $note->note = $this->getDataOrTerminateWithError($resultNote); + $this->addMeta($note->uid.'note', $note); + } + + if(!isError($result) && hasData($result)) { + $lvgesamtnote = getData($result)[0]; + + $id = $this->LvgesamtnoteModel->update( + [$lvgesamtnote->student_uid, $lvgesamtnote->studiensemester_kurzbz, $lvgesamtnote->lehrveranstaltung_id], + array( + 'note' => trim($note->note), + 'punkte' => $note->punkte, + 'benotungsdatum' => date("Y-m-d H:i:s"), + 'updateamum' => date("Y-m-d H:i:s"), + 'updatevon' => getAuthUID() + ) + ); + + if($id) { + $res = $this->LvgesamtnoteModel->load($id->retval); + if(hasData($res)) $lvgesamtnote = getData($res)[0]; + } + + $this->logLib->logInfoDB(array('saveNotenvorschlagBulk update lv gesamtnote',$res, getAuthUID(), getAuthPersonId())); + + } else if(!isError($result) && !hasData($result)) { + $id = $this->LvgesamtnoteModel->insert( + array( + 'student_uid' => $note->uid, + 'lehrveranstaltung_id' => $lv_id, + 'studiensemester_kurzbz' => $sem_kurzbz, + 'note' => trim($note->note), + 'punkte' => $note->punkte, + 'mitarbeiter_uid' => getAuthUID(), + 'benotungsdatum' => date("Y-m-d H:i:s"), + 'freigabedatum' => null, + 'freigabevon_uid' => null, + 'bemerkung' => null, + 'updateamum' => null, + 'updatevon' => null, + 'insertamum' => date("Y-m-d H:i:s"), + 'insertvon' => getAuthUID() + ) + ); + if($id) { + $res = $this->LvgesamtnoteModel->load($id->retval); + if(hasData($res)) $lvgesamtnote = getData($res)[0]; + } + + $this->logLib->logInfoDB(array('saveNotenvorschlagBulk insert lv gesamtnote',$res, getAuthUID(), getAuthPersonId())); + } + + $retLvNoten[] = $lvgesamtnote; + } + + $this->terminateWithSuccess($retLvNoten); + } + + /** + * POST METHOD + * expects 'uids', 'datum' + * Bulk variant of saveStudentPruefung, used when creating a new Prüfung for several students. Always sets note to + * "noch nicht eingetragen" for the created Prüfung. + */ + public function createPruefungen() { + $result = $this->getPostJSON(); + + if(!property_exists($result, 'uids') || !property_exists($result, 'datum')) { + $this->terminateWithError($this->p->t('global', 'missingParameters'), 'general'); + } + + $uids = $result->uids; + $datum = $result->datum; + $lva_id = $result->lva_id; + + $stsem = $result->sem_kurzbz; + + $ret = []; + + $this->load->model('education/Note_model', 'NoteModel'); + $result = $this->NoteModel->getNochNichtEingetragenNote(); + $note = getData($result)[0]->note; + + foreach ($uids as $student) { + $student_uid = $student->uid; + $typ = $student->typ; + $punkte = null; // new pruefungen never have punkte, + + $lehreinheit_id = $student->lehreinheit_id; + $ret[$student->uid] = $this->savePruefungstermin($typ, $student_uid, $lva_id, $stsem, $lehreinheit_id, $note, $punkte, $datum); + } + + $this->logLib->logInfoDB(array('createPruefungen',$ret, getAuthUID(), getAuthPersonId())); + + $this->terminateWithSuccess($ret); + } + + /** + * POST METHOD + * expects 'lv_id', 'sem_kurzbz', 'pruefungen' + * Bulk variant of saveStudentPruefung, used when importing pruefungsdata from csv with available noten. + */ + public function savePruefungenBulk() { + $result = $this->getPostJSON(); + + if(!property_exists($result, 'lv_id') || !property_exists($result, 'sem_kurzbz') || + !property_exists($result, 'pruefungen')) { + $this->terminateWithError($this->p->t('global', 'missingParameters'), 'general'); + } + + $lv_id = $result->lv_id; + $sem_kurzbz = $result->sem_kurzbz; + $pruefungen = $result->pruefungen; + + $ret = []; + + foreach ($pruefungen as $pruefung) { + + if(CIS_GESAMTNOTE_PUNKTE) { + $result = $this->NotenschluesselaufteilungModel->getNote($pruefung->punkte, $lv_id, $sem_kurzbz); + $this->addMeta($pruefung->uid."result", $result); + $pruefung->note = $this->getDataOrTerminateWithError($result); + $this->addMeta($pruefung->uid."note", $pruefung->note); + } + + $student_uid = $pruefung->uid; + $typ = $pruefung->typ; + $note = $pruefung->note; // TODO: parameterize for import maybe + $datum = $pruefung->datum; + $punkte = $pruefung->punkte; + + $lehreinheit_id = $pruefung->lehreinheit_id; + $ret[$student_uid] = $this->savePruefungstermin($typ, $student_uid, $lv_id, $sem_kurzbz, $lehreinheit_id, $note, $punkte, $datum); + } + + $this->logLib->logInfoDB(array('savePruefungenBulk',$ret, getAuthUID(), getAuthPersonId())); + + $this->terminateWithSuccess($ret); + } + + private function getAnwesenheiten($prestudent_ids, $lv_id, $sem_kurzbz) { + + $anwesenheiten = []; + try { + $downloadFunc = function ($anwesenheitenResult) use (&$anwesenheiten) { + // map result rows by prestudent_uid to retrieve them by that key later on + foreach ($anwesenheitenResult as $anw) { + $anwesenheiten[$anw->prestudent_id] = $anw->sum; + } + }; + + Events::trigger( + 'getAnwesenheitenForLvAndSemester', + $prestudent_ids, + $lv_id, + $sem_kurzbz, + $downloadFunc + ); + } catch (Throwable $t) { + $this->addMeta('getAnwesenheitenForLvAndSemester', $t->getMessage()); + } + + return $anwesenheiten; + + } + + public function getNoteByPunkte() { + $result = $this->getPostJSON(); + + if(!property_exists($result, 'punkte') + || !property_exists($result, 'lv_id') + || !property_exists($result, 'sem_kurzbz')) { + $this->terminateWithError($this->p->t('global', 'missingParameters'), 'general'); + } + + $punkte = $result->punkte; + $lv_id = $result->lv_id; + $sem_kurzbz = $result->sem_kurzbz; + + $result = $this->NotenschluesselaufteilungModel->getNote($punkte, $lv_id, $sem_kurzbz); + $data = $this->getDataOrTerminateWithError($result); + + $this->terminateWithSuccess($data); + + } + +} + diff --git a/application/controllers/api/frontend/v1/OtherLvPlan.php b/application/controllers/api/frontend/v1/OtherLvPlan.php new file mode 100644 index 000000000..cad654f12 --- /dev/null +++ b/application/controllers/api/frontend/v1/OtherLvPlan.php @@ -0,0 +1,76 @@ +. + */ + +if (!defined('BASEPATH')) + exit('No direct script access allowed'); + +class OtherLvPlan extends FHCAPI_Controller +{ + + /** + * Object initialization + */ + public function __construct() + { + parent::__construct([ + 'otherLvPlanViewData' => ['basis/other_lv_plan:r'], + ]); + + $this->load->model('ressource/mitarbeiter_model', 'MitarbeiterModel'); + $this->load->model('person/Benutzer_model', 'BenutzerModel'); + + } + + //------------------------------------------------------------------------------------------------------------------ + // Public methods + + /** + * retrieves viewData for other lv plan view + * @access public + * @param $uid the userID for which the other lv plan is being viewed + */ + public function otherLvPlanViewData($uid) + { + $isMitarbeiterResult = $this->MitarbeiterModel->isMitarbeiter($uid); + $isMitarbeiter = getData($isMitarbeiterResult); + $isStudent = !$isMitarbeiter; + + $this->BenutzerModel->addSelect(["foto", "vorname", "nachname"]); + $this->BenutzerModel->addJoin("tbl_person", "person_id"); + $personResult = $this->BenutzerModel->load([$uid]); + $person = hasData($personResult) ? getData($personResult) : null; + + $viewData = [ + "user_data" => [ + "username" => $uid, + "is_student" => $isStudent, + "is_mitarbeiter" => $isMitarbeiter, + "foto" => $person[0]->foto, + "vorname" => $person[0]->vorname, + "nachname" => $person[0]->nachname, + ], + ]; + + $this->terminateWithSuccess($viewData); + } + + // ----------------------------------------------------------------------------------------------------------------- + // Private methods + +} + diff --git a/application/controllers/api/frontend/v1/Profil.php b/application/controllers/api/frontend/v1/Profil.php index 3133b107a..96cdea855 100644 --- a/application/controllers/api/frontend/v1/Profil.php +++ b/application/controllers/api/frontend/v1/Profil.php @@ -16,7 +16,8 @@ * along with this program. If not, see . */ -if (! defined('BASEPATH')) exit('No direct script access allowed'); +if (!defined('BASEPATH')) + exit('No direct script access allowed'); class Profil extends FHCAPI_Controller { @@ -27,13 +28,13 @@ class Profil extends FHCAPI_Controller public function __construct() { parent::__construct([ - 'fotoSperre' => self::PERM_LOGGED, + 'fotoSperre' => self::PERM_LOGGED, 'getGemeinden' => self::PERM_LOGGED, 'getAllNationen' => self::PERM_LOGGED, 'isMitarbeiter' => self::PERM_LOGGED, 'profilViewData' => self::PERM_LOGGED, ]); - + $this->load->library('PermissionLib'); $this->load->model('ressource/mitarbeiter_model', 'MitarbeiterModel'); @@ -48,28 +49,37 @@ class Profil extends FHCAPI_Controller //------------------------------------------------------------------------------------------------------------------ // Public methods - public function profilViewData($uid=null){ + + /** + * retrieves view data for profile view + * @access public + * @param $uid the userID for which profile is being viewed, null or missing value implies one's own profile + */ + public function profilViewData($uid = null) + { + $authUid = getAuthUID(); + $isProfilOfAuthUser = !$uid || $uid === $authUid; + $this->load->library('ProfilLib'); - $editable = false; - if(isset($uid) && $uid != null){ - $profil_data = $this->profillib->getView($uid); - if($uid == getAuthUID()){ - $editable = true; - } - }else{ - $editable = true; - $profil_data = $this->profillib->getView(getAuthUID()); + $profileData = $this->profillib->getView($uid ?? $authUid); + $profileData = hasData($profileData) ? getData($profileData) : null; + + $viewData = [ + 'editable' => $isProfilOfAuthUser, + 'profil_data' => $profileData, + 'permissions' => [ + 'basis/other_lv_plan' => $this->permissionlib->isBerechtigt(('basis/other_lv_plan')) + ] + ]; + + if ($isProfilOfAuthUser) { + $viewData['calendar_sync_urls'] = $this->getCalendarSyncUrlData(); } - - $profil_data = hasData($profil_data) ? getData($profil_data) : null; - $viewData = array( - 'editable'=>$editable, - 'profil_data' => $profil_data, - ); + $this->terminateWithSuccess($viewData); } - /** + /** * update column foto_sperre in public.tbl_person * @access public * @param boolean $value new value for the column @@ -77,9 +87,9 @@ class Profil extends FHCAPI_Controller */ public function fotoSperre($value) { - if(!isset($value)){ - $this->terminateWithError("Missing parameter", self::ERROR_TYPE_GENERAL); - } + if (!isset($value)) { + $this->terminateWithError("Missing parameter", self::ERROR_TYPE_GENERAL); + } $res = $this->PersonModel->update($this->pid, ["foto_sperre" => $value]); if (isError($res)) { @@ -87,10 +97,10 @@ class Profil extends FHCAPI_Controller } $this->PersonModel->addSelect("foto_sperre"); $res = $this->PersonModel->load($this->pid); - - $res = $this->getDataOrTerminateWithError($res); - - $this->terminateWithSuccess(current($res)); + + $res = $this->getDataOrTerminateWithError($res); + + $this->terminateWithSuccess(current($res)); } /** @@ -109,7 +119,7 @@ class Profil extends FHCAPI_Controller if (isError($nation_res)) { $this->terminateWithError("error while trying to query table codex.tbl_nation", self::ERROR_TYPE_GENERAL); } - + $nation_res = $this->getDataOrTerminateWithError($nation_res); $this->terminateWithSuccess($nation_res); @@ -117,30 +127,30 @@ class Profil extends FHCAPI_Controller public function getGemeinden($nation, $zip) { - if(!isset($nation) || !isset($zip)){ + if (!isset($nation) || !isset($zip)) { echo json_encode(error("Missing parameters")); return; } - + $this->load->model('codex/Gemeinde_model', "GemeindeModel"); - + $gemeinde_res = $this->GemeindeModel->getGemeindeByPlz($zip); - + if (isError($gemeinde_res)) { - $this->terminateWithError(getError($gemeinde_res),self::ERROR_TYPE_GENERAL); + $this->terminateWithError(getError($gemeinde_res), self::ERROR_TYPE_GENERAL); } $gemeinde_res = $this->getDataOrTerminateWithError($gemeinde_res); - + /* $gemeinde_res = array_map(function ($obj) { return $obj->ortschaftsname; }, $gemeinde_res); */ - $this->terminateWithSuccess($gemeinde_res); - + $this->terminateWithSuccess($gemeinde_res); + } - + /** * checks whether a specific userID is a mitarbeiter or not (foreword declaration of the function isMitarbeiter in Mitarbeiter_model.php) * @access public @@ -150,23 +160,48 @@ class Profil extends FHCAPI_Controller public function isMitarbeiter($uid) { - if(!$uid) $this->terminateWithError("No uid provided", self::ERROR_TYPE_GENERAL); - - + if (!$uid) + $this->terminateWithError("No uid provided", self::ERROR_TYPE_GENERAL); + + $result = $this->MitarbeiterModel->isMitarbeiter($uid); - + if (isError($result)) { $this->terminateWithError("error when calling Mitarbeiter_model function isMitarbeiter with uid " . $uid, self::ERROR_TYPE_GENERAL); } $result = $this->getDataOrTerminateWithError($result); - + $this->terminateWithSuccess($result); } // ----------------------------------------------------------------------------------------------------------------- // Private methods - + /** + * gets the identifier, phrase, and url for each calendar sync option + * @access private + * @return array array of arrays, where each child array is a sync option + */ + private function getCalendarSyncUrlData() + { + return [ + [ + "identifier" => "cal_dav", + "labelPhrase" => "profil/calendar_sync_cal_dav", + "url" => APP_ROOT . "webdav/lvplan.php/calendars/" . $this->uid . "/LVPlan-" . $this->uid, + ], + [ + "identifier" => "cal_dav_principal", + "labelPhrase" => "profil/calendar_sync_cal_dav_principal", + "url" => APP_ROOT . "webdav/lvplan.php/principals/" . $this->uid, + ], + [ + "identifier" => "i_cal", + "labelPhrase" => "profil/calendar_sync_i_cal", + "url" => APP_ROOT . "webdav/google.php?cal=" . encryptData($this->uid, LVPLAN_CYPHER_KEY) . "&" . microtime(true), + ], + ]; + } } diff --git a/application/controllers/api/frontend/v1/StgOrgLvPlan.php b/application/controllers/api/frontend/v1/StgOrgLvPlan.php new file mode 100644 index 000000000..ffbf85e57 --- /dev/null +++ b/application/controllers/api/frontend/v1/StgOrgLvPlan.php @@ -0,0 +1,64 @@ +. + */ + +if (!defined('BASEPATH')) + exit('No direct script access allowed'); + +class StgOrgLvPlan extends FHCAPI_Controller +{ + + /** + * Object initialization + */ + public function __construct() + { + parent::__construct([ + 'stgOrgLvPlanViewData' => self::PERM_LOGGED, + ]); + + } + + //------------------------------------------------------------------------------------------------------------------ + // Public methods + + /** + * fetches view data for stg org lv plan + * @access public + */ + public function stgOrgLvPlanViewData() + { + $this->load->model('organisation/Studiengang_model', 'StudiengangModel'); + $this->StudiengangModel->addOrder('typ'); + $this->StudiengangModel->addOrder('kurzbz'); + $result = $this->StudiengangModel->loadWhere([ + 'aktiv' => true + ]); + $studiengaenge = $this->getDataOrTerminateWithError($result); + + $viewData = array( + 'studiengaenge' => $studiengaenge, + ); + + $this->terminateWithSuccess($viewData); + } + + // ----------------------------------------------------------------------------------------------------------------- + // Private methods + +} + diff --git a/application/controllers/api/frontend/v1/Studiensemester.php b/application/controllers/api/frontend/v1/Studiensemester.php new file mode 100644 index 000000000..44f236a3c --- /dev/null +++ b/application/controllers/api/frontend/v1/Studiensemester.php @@ -0,0 +1,67 @@ +. + */ + +if (! defined('BASEPATH')) exit('No direct script access allowed'); +class Studiensemester extends FHCAPI_Controller +{ + + private $_ci; + + /** + * Object initialization + */ + public function __construct() + { + parent::__construct([ + 'getStudiensemester'=> self::PERM_LOGGED, + + ]); + + $this->_ci =& get_instance(); + + $this->_ci->load->model('organisation/Studiensemester_model', 'StudiensemesterModel'); + + + } + + //------------------------------------------------------------------------------------------------------------------ + // Public methods + + /** + * GET METHOD + * returns List of all studiensemester as well as current one + */ + public function getStudiensemester() + { + $this->_ci->StudiensemesterModel->addOrder("start", "DESC"); + $result = $this->_ci->StudiensemesterModel->load(); + + $studiensemester = getData($result); + $result = $this->_ci->StudiensemesterModel->getAkt(); + $aktuell = getData($result); + + $this->terminateWithSuccess(array($studiensemester, $aktuell)); + } + + //------------------------------------------------------------------------------------------------------------------ + // Private methods + + + +} + diff --git a/application/controllers/api/frontend/v1/Studium.php b/application/controllers/api/frontend/v1/Studium.php index d17f0c1a1..f53a4105c 100644 --- a/application/controllers/api/frontend/v1/Studium.php +++ b/application/controllers/api/frontend/v1/Studium.php @@ -62,21 +62,36 @@ class Studium extends FHCAPI_Controller if($this->getDataOrTerminateWithError($this->StudentModel->isStudent(getAuthUID()))){ $studentLehrverband =$this->StudentlehrverbandModel->loadWhere(["student_uid" => getAuthUID(), "studiensemester_kurzbz" => $aktuelles_studiensemester->studiensemester_kurzbz]); - $studentLehrverband = current($this->getDataOrTerminateWithError($studentLehrverband)); - - $student_studiensemester = $studentLehrverband->studiensemester_kurzbz; - $student_studiengang = $studentLehrverband->studiengang_kz; - $student_semester = $studentLehrverband->semester; + + //TODO(Manu) check if use Fallback or just comment out all paramschecks? + //add Fallback: if no LehrverbandData of actual semester, get Data of previous one + if(!hasData($studentLehrverband)) + { + $result= $this->StudiensemesterModel->getPreviousFrom($aktuelles_studiensemester->studiensemester_kurzbz); + + $data = $this->getDataOrTerminateWithError($result); + $vorheriges_studiensemester = current($data)->studiensemester_kurzbz; + $studentLehrverband =$this->StudentlehrverbandModel->loadWhere(["student_uid" => getAuthUID(), "studiensemester_kurzbz" => $vorheriges_studiensemester]); + } + $studentLehrverband = current(getData($studentLehrverband)); + $student_studienplan = $this->getStudienPlanFromPrestudentStatus(getAuthPersonId())->studienplan_id; - if(!isset($parameter_studiensemester)) - $parameter_studiensemester = $student_studiensemester; - if(!isset($parameter_studiengang)) - $parameter_studiengang = $student_studiengang; - if(!isset($parameter_semester)) - $parameter_semester = $student_semester; + if(!isset($parameter_studiensemester)) { + $student_studiensemester = $studentLehrverband->studiensemester_kurzbz; + $parameter_studiensemester = $student_studiensemester; + } + if(!isset($parameter_studiengang)) { + $student_studiengang = $studentLehrverband->studiengang_kz; + $parameter_studiengang = $student_studiengang; + } + if(!isset($parameter_semester)) { + $student_semester = $studentLehrverband->semester; + $parameter_semester = $student_semester; + } if(!isset($parameter_studienplan)) - $parameter_studienplan = $student_studienplan; + $parameter_studienplan = $student_studienplan; + } if(isset($parameter_studiensemester)){ @@ -96,8 +111,7 @@ class Studium extends FHCAPI_Controller // fetch studiensemester $allStudienSemester = $this->getDataOrTerminateWithError($this->StudiensemesterModel->load()); - - + if(isset($parameter_studiensemester) && !empty(array_filter($allStudienSemester, function($studiensemester) use($parameter_studiensemester){ return $studiensemester->studiensemester_kurzbz == $parameter_studiensemester->studiensemester_kurzbz; }))){ @@ -216,6 +230,8 @@ class Studium extends FHCAPI_Controller $studienplaene = array_map(function($studienplan){ $orgform = current($this->getDataOrTerminateWithError($this->OrgformModel->loadWhere(["orgform_kurzbz" => $studienplan->orgform_kurzbz]))); $studienplan->orgform_bezeichnung = $orgform->bezeichnung; + // bezeichnung_mehrsprachig + $studienplan->orgform_bezeichnung_english = $orgform->bezeichnung_mehrsprachig[1]; return $studienplan; },$studienplaene); return $studienplaene; diff --git a/application/controllers/api/frontend/v1/Zeitsperren.php b/application/controllers/api/frontend/v1/Zeitsperren.php new file mode 100644 index 000000000..66efa81a7 --- /dev/null +++ b/application/controllers/api/frontend/v1/Zeitsperren.php @@ -0,0 +1,367 @@ + self::PERM_LOGGED, + 'getTypenZeitsperren' => self::PERM_LOGGED, + 'getTypenErreichbarkeit' => self::PERM_LOGGED, + 'getStunden' => self::PERM_LOGGED, + 'loadZeitsperre' => self::PERM_LOGGED, + 'add' => self::PERM_LOGGED, + 'update' => self::PERM_LOGGED, + 'delete' => self::PERM_LOGGED, + ]); + + // Load Libraries + $this->load->library('VariableLib', ['uid' => getAuthUID()]); + $this->load->library('form_validation'); + + // Load language phrases + $this->loadPhrases([ + 'ui', + 'person', + 'zeitsperren' + ]); + + // Load models + $this->load->model('ressource/Zeitsperre_model', 'ZeitsperreModel'); + $this->load->model('ressource/Zeitsperretyp_model', 'ZeitsperretypModel'); + $this->load->model('ressource/Erreichbarkeit_model', 'ErreichbarkeitModel'); + $this->load->model('ressource/Stunde_model', 'StundeModel'); + $this->load->model('ressource/Zeitaufzeichnung_model', 'ZeitaufzeichnungModel'); + } + + public function getZeitsperrenUser($uid) + { + //check if $uid is passedUser + $loggedInUser = getAuthUID(); + if($loggedInUser != $uid) { + $this->load->library('PermissionLib'); + $isAdmin = $this->permissionlib->isBerechtigt('admin'); + if(!$isAdmin) { + $this->terminateWithError($this->p->t('ui', 'noAdmin'), self::ERROR_TYPE_GENERAL); + } + } + + $result = $this->ZeitsperreModel->getZeitsperrenUser($uid); + + if (isError($result)) { + $this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL); + } + $this->terminateWithSuccess((getData($result) ?: [])); + } + + public function getTypenZeitsperren() + { + $this->ZeitsperretypModel->addOrder('beschreibung', 'ASC'); + $result = $this->ZeitsperretypModel->load(); + if (isError($result)) { + $this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL); + } + $this->terminateWithSuccess((getData($result) ?: [])); + } + + public function getTypenErreichbarkeit() + { + $result = $this->ErreichbarkeitModel->load(); + if (isError($result)) { + $this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL); + } + $this->terminateWithSuccess((getData($result) ?: [])); + } + + public function getStunden() + { + $this->StundeModel->addOrder('stunde', 'ASC'); + $result = $this->StundeModel->load(); + if (isError($result)) { + $this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL); + } + $this->terminateWithSuccess((getData($result) ?: [])); + } + + public function loadZeitsperre($zeitsperre_id) + { + $this->ZeitsperreModel->addSelect( + 'campus.tbl_zeitsperre.*, typ.*, + ma.person_id AS ma_person_id, ma.vorname AS ma_vorname, ma.nachname AS ma_nachname, + ma.titelpre AS ma_titelpre, ma.titelpost AS ma_titelpost' + ); + $this->ZeitsperreModel->addJoin('campus.tbl_zeitsperretyp typ', 'ON (typ.zeitsperretyp_kurzbz = campus.tbl_zeitsperre.zeitsperretyp_kurzbz)'); + $this->ZeitsperreModel->addJoin('public.tbl_benutzer ben', 'ON (ben.uid = campus.tbl_zeitsperre.vertretung_uid)', 'LEFT'); + $this->ZeitsperreModel->addJoin('public.tbl_person ma', 'ON (ma.person_id = ben.person_id)', 'LEFT'); + $result = $this->ZeitsperreModel->loadWhere( + array('zeitsperre_id' => $zeitsperre_id) + ); + + if (isError($result)) { + $this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL); + } + $this->terminateWithSuccess((current(getData($result)) ?: [])); + } + + public function add($mitarbeiter_uid) + { + $loggedInUser = getAuthUID(); + + if($mitarbeiter_uid != $loggedInUser) + $this->terminateWithError($this->p->t('ui', 'noPermission'), self::ERROR_TYPE_GENERAL); + + $this->form_validation->set_rules('zeitsperretyp_kurzbz', 'Grund Zeitsperre', 'required', [ + 'required' => $this->p->t('ui', 'error_fieldRequired', ['field' => 'Grund Zeitsperre']) + ]); + + $this->form_validation->set_rules('vondatum', 'VonDatum', 'required|is_valid_date', [ + 'required' => $this->p->t('ui', 'error_fieldRequired', ['field' => 'VonDatum']), + 'is_valid_date' => $this->p->t('ui', 'error_notValidDate', ['field' => 'VonDatum']) + ]); + + $this->form_validation->set_rules('bisdatum', 'BisDatum', 'required|is_valid_date|callback_check_von_bis_datum|callback_check_diff_intval', [ + 'required' => $this->p->t('ui', 'error_fieldRequired', ['field' => 'BisDatum']), + 'is_valid_date' => $this->p->t('ui', 'error_notValidDate', ['field' => 'BisDatum']), + 'check_von_bis_datum' => $this->p->t('zeitsperre', 'error_VonDatumGroesserAlsBisDatum'), + 'check_diff_intval' => $this->p->t('zeitsperre', 'error_zeitraumAuffallendHoch') + ]); + + if ($this->form_validation->run() == false) + { + $this->terminateWithValidationErrors($this->form_validation->error_array()); + } + + $bezeichnung = $this->input->post('bezeichnung'); + $vondatum = $this->input->post('vondatum'); + $vonstunde = $this->input->post('vonstunde'); + $bisdatum = $this->input->post('bisdatum'); + $bisstunde = $this->input->post('bisstunde'); + //$vonIso = $this->input->post('vonISO'); //Timestamp für Stunde + //$bisIso = $this->input->post('bisISO'); //Timestamp für Stunde + $erreichbarkeit_kurzbz = $this->input->post('erreichbarkeit_kurzbz'); + $vertretung_uid = $this->input->post('vertretung_uid'); + $zeitsperretyp_kurzbz = $this->input->post('zeitsperretyp_kurzbz'); + + //check if existing zeitsperre + $result = $this->ZeitsperreModel->getSperreByDate($mitarbeiter_uid, $vondatum, $vonstunde, true); + $data = $this->getDataOrTerminateWithError($result); + + if(hasData($result)) + { + $this->terminateWithError($this->p->t('zeitsperren', 'error_existingZeitsperre', ['typ'=> current($data)->zeitsperretyp_kurzbz]), self::ERROR_TYPE_GENERAL); + } + + //check if existing zeitaufzeichnung + if(in_array($zeitsperretyp_kurzbz, Zeitsperre_model::BLOCKIERENDE_ZEITSPERREN)) + { + $result = $this->ZeitsperreModel->existsZeitaufzeichnung($mitarbeiter_uid, $vondatum, $bisdatum); + + if(hasData($result)) + $this->terminateWithError($this->p->t('zeitsperren', 'error_existingZeitaufzeichnung'), self::ERROR_TYPE_GENERAL); + } + + $result = $this->ZeitsperreModel->insert( + [ + 'mitarbeiter_uid' => $mitarbeiter_uid, + 'bezeichnung' => $bezeichnung, + 'vondatum' => $vondatum, + 'vonstunde' => $vonstunde, + 'bisdatum' => $bisdatum, + 'bisstunde' => $bisstunde, + 'erreichbarkeit_kurzbz' => $erreichbarkeit_kurzbz, + 'zeitsperretyp_kurzbz' => $zeitsperretyp_kurzbz, + 'vertretung_uid' => $vertretung_uid, + 'insertvon' => $loggedInUser, + 'insertamum' => date('c'), + ] + ); + $data = $this->getDataOrTerminateWithError($result); + + $this->terminateWithSuccess($data); + } + + public function update($zeitsperre_id) + { + //check if loggedin User is owner of the zeitsperre + $loggedInUser = getAuthUID(); + $result = $this->ZeitsperreModel->load($zeitsperre_id); + $data = $this->getDataOrTerminateWithError($result); + $uid = current($data)->mitarbeiter_uid; + + if($uid != $loggedInUser) + $this->terminateWithError($this->p->t('ui', 'noPermission'), self::ERROR_TYPE_GENERAL); + + if(!$zeitsperre_id) + { + return $this->terminateWithError($this->p->t('ui', 'error_missingId', ['id'=> 'Zeitsperre_id']), self::ERROR_TYPE_GENERAL); + } + //get current params + $array_update = [ + 'bezeichnung', + 'vondatum', + 'vonstunde', + 'bisdatum', + 'bisstunde', + // 'vonISO', //Timestamp für Stunde + // 'bisISO', //Timestamp für Stunde + 'erreichbarkeit_kurzbz', + 'vertretung_uid', + 'zeitsperretyp_kurzbz', + 'mitarbeiter_uid', + ]; + $post = $this->input->post(); + $update = []; + + foreach ($array_update as $prop) + { + if (array_key_exists($prop, $post)) + { + $update[$prop] = $post[$prop]; + } + } + + // Validation + $rulesDefined = false; //necessary, otherwise CI validation will always be triggered, even without rules + foreach ($update as $key => $val) { + switch ($key) { + case 'zeitsperretyp_kurzbz': + $this->form_validation->set_rules( + $key, + 'Grund Zeitsperre', + 'required', + ['required' => $this->p->t('ui', 'error_fieldRequired', ['field'=>'Grund Zeitsperre'])] + ); + $rulesDefined = true; + break; + case 'vondatum': + $this->form_validation->set_rules( + $key, + 'VonDatum', + 'required|is_valid_date', + [ + 'required' => $this->p->t('ui', 'error_fieldRequired', ['field'=>'VonDatum']), + 'is_valid_date' => $this->p->t('ui', 'error_notValidDate', ['field'=>'VonDatum']) + ] + ); + $rulesDefined = true; + break; + case 'bisdatum': + $rules = 'required|is_valid_date'; + if (array_key_exists('vondatum', $update)) { + $rules .= '|callback_check_von_bis_datum|callback_check_diff_intval'; + } + $this->form_validation->set_rules( + $key, + 'BisDatum', + $rules, + [ + 'required' => $this->p->t('ui', 'error_fieldRequired', ['field'=>'BisDatum']), + 'is_valid_date' => $this->p->t('ui', 'error_notValidDate', ['field'=>'BisDatum']), + 'check_von_bis_datum' => $this->p->t('zeitsperre', 'error_VonDatumGroesserAlsBisDatum'), + 'check_diff_intval' => $this->p->t('zeitsperre', 'error_zeitraumAuffallendHoch') + ] + ); + $rulesDefined = true; + break; + } + } + + if ($rulesDefined && $this->form_validation->run() == false) { + $this->terminateWithValidationErrors($this->form_validation->error_array()); + } + + if(array_key_exists('vondatum', $post) || array_key_exists('bisdatum', $post)) + { + $result = $this->ZeitsperreModel->load($zeitsperre_id); + $data = $this->getDataOrTerminateWithError($result); + $data = current($data); + + $mitarbeiter_uid = array_key_exists('mitarbeiter_uid', $post) ? $update['mitarbeiter_uid'] : $data->mitarbeiter_uid; + $vondatum = array_key_exists('vondatum', $post) ? $update['vondatum'] : $data->vondatum; + $bisdatum = array_key_exists('bisdatum', $post) ? $update['bisdatum'] : $data->bisdatum; + $vonstunde = array_key_exists('vonstunde', $post) ? $update['vonstunde'] : $data->vonstunde; + $zeitsperretyp_kurzbz = array_key_exists('zeitsperretyp_kurzbz', $post) ? $update['zeitsperretyp_kurzbz'] : $data->zeitsperretyp_kurzbz; + + $result = $this->ZeitsperreModel->getSperreByDate($mitarbeiter_uid, $vondatum, $vonstunde, true); + $data = $this->getDataOrTerminateWithError($result); + + if(hasData($result)) + { + $this->terminateWithError($this->p->t('zeitsperren', 'error_existingZeitsperre', ['typ'=> current($data)->zeitsperretyp_kurzbz]), self::ERROR_TYPE_GENERAL); + } + + //check if existing zeitaufzeichnung + if(in_array($zeitsperretyp_kurzbz, Zeitsperre_model::BLOCKIERENDE_ZEITSPERREN)) + { + $result = $this->ZeitsperreModel->existsZeitaufzeichnung($mitarbeiter_uid, $vondatum, $bisdatum); + + if(hasData($result)) + $this->terminateWithError($this->p->t('zeitsperren', 'error_existingZeitaufzeichnung'), self::ERROR_TYPE_GENERAL); + } + } + + if (!empty($update)) { + $update['updatevon'] = $loggedInUser; + $update['updateamum'] = date('c'); + $result = $this->ZeitsperreModel->update($zeitsperre_id, $update); + + $data = $this->getDataOrTerminateWithError($result); + + $this->terminateWithSuccess($data); + } + else + $this->terminateWithSuccess("no update"); + } + + public function delete($zeitsperre_id) + { + + if (!is_numeric($zeitsperre_id) || (int)$zeitsperre_id <= 0) + { + $this->terminateWithError($this->p->t('ui', 'error_missingId', ['id' => 'Zeitsperre_id']), self::ERROR_TYPE_GENERAL); + } + + //check if loggedin User is owner of the zeitsperre + $loggedInUser = getAuthUID(); + $result = $this->ZeitsperreModel->load($zeitsperre_id); + $data = $this->getDataOrTerminateWithError($result); + $uid = current($data)->mitarbeiter_uid; + + if($uid != $loggedInUser) + $this->terminateWithError($this->p->t('ui', 'noPermission'), self::ERROR_TYPE_GENERAL); + + $result = $this->ZeitsperreModel->delete( + array('zeitsperre_id' => $zeitsperre_id) + ); + + if (isError($result)) { + $this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL); + } + $this->terminateWithSuccess((getData($result) ?: [])); + } + + public function check_von_bis_datum($bisdatum) + { + $vondatum = $this->input->post('vondatum'); + + return $vondatum <= $bisdatum; + } + + public function check_diff_intval($bisdatum) + { + $vondatum = $this->input->post('vondatum'); + + // Intervall in days + $vonTs = strtotime($vondatum); + $bisTs = strtotime($bisdatum); + + $tage = ($bisTs - $vonTs) / 86400; + + // if intervall > 14 + return $tage <= 14; + } + + +} diff --git a/application/controllers/api/frontend/v1/calendar/RoomPlan.php b/application/controllers/api/frontend/v1/calendar/RoomPlan.php new file mode 100644 index 000000000..489d83b53 --- /dev/null +++ b/application/controllers/api/frontend/v1/calendar/RoomPlan.php @@ -0,0 +1,232 @@ +. + */ + +if (! defined('BASEPATH')) exit('No direct script access allowed'); + +class RoomPlan extends FHCAPI_Controller +{ + + /** + * Object initialization + */ + public function __construct() + { + parent::__construct([ + 'addRoomReservation' => self::PERM_LOGGED, + 'deleteRoomReservation' => self::PERM_LOGGED, + 'getRoomCreationInfo' => self::PERM_LOGGED, + 'getGruppen' => self::PERM_LOGGED, + 'getLektor' => self::PERM_LOGGED, + 'getReservableMap' => self::PERM_LOGGED, + ]); + + $this->load->library('LogLib'); + $this->loglib->setConfigs(array( + 'classIndex' => 5, + 'functionIndex' => 5, + 'lineIndex' => 4, + 'dbLogType' => 'API', + 'dbExecuteUser' => 'RESTful API' + )); + + $this->load->library('form_validation'); + $this->load->library('PermissionLib'); + $this->load->library('StundenplanLib'); + + $this->loadPhrases(['ui']); + } + + //------------------------------------------------------------------------------------------------------------------ + // Public methods + + + + public function addRoomReservation() + { + $this->form_validation->set_rules('selectedStart', "Start", "required"); + $this->form_validation->set_rules('selectedEnd', "End", "required"); + $this->form_validation->set_rules('title', "Title", "required|max_length[10]"); + $this->form_validation->set_rules('beschreibung', "Beschreibung", "required|max_length[32]"); + $this->form_validation->set_rules('ort_kurzbz', "Ort", "required|max_length[16]"); + $this->form_validation->set_rules('studiengang', 'Studiengang', 'numeric'); + $this->form_validation->set_rules('semester', 'Semester', 'integer|greater_than_equal_to[0]'); + $this->form_validation->set_rules('verband', 'Verband', 'trim'); + $this->form_validation->set_rules('gruppe', 'Gruppe', 'trim'); + $this->form_validation->set_rules('spezialgruppe', 'Spezialgruppe', 'max_length[32]'); + $this->form_validation->set_rules('lektoren', 'Lektoren'); + + if (!$this->form_validation->run()) + $this->terminateWithValidationErrors($this->form_validation->error_array()); + + $start = $this->input->post('selectedStart'); + $end = $this->input->post('selectedEnd'); + $title = $this->input->post('title'); + $beschreibung = $this->input->post('beschreibung'); + $ort_kurzbz = $this->input->post('ort_kurzbz'); + + $studiengang_kz = $this->input->post('studiengang'); + $semester = $this->input->post('semester'); + $verband = $this->input->post('verband'); + $gruppe = $this->input->post('gruppe'); + $spezialgruppe = $this->input->post('spezialgruppe'); + $lektoren = $this->input->post('lektoren'); + + + $result = $this->stundenplanlib->addReservation($start, $end, $title, $beschreibung, $ort_kurzbz, $lektoren, $studiengang_kz, $semester, $verband, $gruppe, $spezialgruppe); + + if (isError($result)) + $this->terminateWithError($result); + + $this->terminateWithSuccess($result); + } + + public function deleteRoomReservation() + { + $reservierung_id = $this->input->post('reservierung_id'); + + $result = $this->stundenplanlib->deleteReservation($reservierung_id); + + if (isError($result)) + $this->terminateWithError($result); + + $this->terminateWithSuccess($result); + } + + public function getRoomCreationInfo() + { + $return_array = array('berechtigt' => false, 'studiengaenge' => []); + if (!$this->permissionlib->isBerechtigt('lehre/reservierung')) + $this->terminateWithSuccess($return_array); + + $stg_berechtigungen = $this->permissionlib->getSTG_isEntitledFor('lehre/reservierung'); + if (isEmptyArray($stg_berechtigungen)) + $this->terminateWithSuccess($return_array); + + $this->load->model('organisation/Studiengang_model', 'StudiengangModel'); + $this->StudiengangModel->addSelect('studiengang_kz, UPPER(CONCAT(typ, kurzbz)) as kuerzel, kurzbzlang'); + $this->StudiengangModel->addOrder('typ, kurzbz'); + $this->StudiengangModel->db->where_in('studiengang_kz', $stg_berechtigungen); + $studiengaenge = $this->StudiengangModel->loadWhere(array('aktiv' => true)); + + if (isError($studiengaenge)) + $this->terminateWithError($studiengaenge); + + $return_array['studiengaenge'] = hasData($studiengaenge) ? getData($studiengaenge) : []; + $return_array['berechtigt'] = true; + + $this->terminateWithSuccess($return_array); + } + + public function getGruppen() + { + $query = $this->input->get('query'); + if (is_null($query)) + $this->terminateWithError($this->p->t('ui', 'ungueltigeParameter'), self::ERROR_TYPE_GENERAL); + + $stg_berechtigungen = $this->permissionlib->getSTG_isEntitledFor('lehre/reservierung'); + + if (isEmptyArray($stg_berechtigungen)) + $this->terminateWithSuccess([]); + + $this->load->model('organisation/gruppe_model', 'GruppeModel'); + + $query_words = explode(' ', urldecode($query)); + + $this->GruppeModel->addOrder('gruppe_kurzbz'); + $this->GruppeModel->db->group_start(); + foreach ($query_words as $word) + { + $this->GruppeModel->db->group_start(); + $this->GruppeModel->db->where('gruppe_kurzbz ILIKE', "%" . $word . "%"); + $this->GruppeModel->db->or_where('bezeichnung ILIKE', "%" . $word . "%"); + $this->GruppeModel->db->or_where('beschreibung ILIKE', "%" . $word . "%"); + $this->GruppeModel->db->or_where('orgform_kurzbz ILIKE', "%" . $word . "%"); + + if (is_numeric($word)) + { + $this->GruppeModel->db->or_where('studiengang_kz', $word); + } + $this->GruppeModel->db->group_end(); + } + $this->GruppeModel->db->group_end(); + $this->GruppeModel->db->where_in('studiengang_kz', $stg_berechtigungen); + $gruppen = $this->GruppeModel->loadWhere(array('sichtbar' => true, 'lehre' => true)); + if (isError($gruppen)) + $this->terminateWithError($gruppen); + + $this->terminateWithSuccess(hasData($gruppen) ? getData($gruppen) : []); + } + + public function getLektor() + { + + $query = $this->input->get('query'); + if (is_null($query)) + $this->terminateWithError($this->p->t('ui', 'ungueltigeParameter'), self::ERROR_TYPE_GENERAL); + + $stg_berechtigungen = $this->permissionlib->getSTG_isEntitledFor('lehre/reservierung'); + + if (isEmptyArray($stg_berechtigungen)) + $this->terminateWithSuccess([]); + + $this->load->model('ressource/Mitarbeiter_model', 'MitarbeiterModel'); + + $query_words = explode(' ', urldecode($query)); + + $this->MitarbeiterModel->addSelect('uid, person_id, vorname, nachname'); + $this->MitarbeiterModel->addJoin('public.tbl_benutzer', 'uid = mitarbeiter_uid'); + $this->MitarbeiterModel->addJoin('public.tbl_person', 'person_id'); + $this->MitarbeiterModel->db->where('public.tbl_benutzer.aktiv', true); + $this->MitarbeiterModel->db->group_start(); + foreach ($query_words as $word) + { + $this->MitarbeiterModel->db->group_start(); + $this->MitarbeiterModel->db->where('tbl_person.vorname ILIKE', "%" . $word . "%"); + $this->MitarbeiterModel->db->or_where('tbl_person.nachname ILIKE', "%" . $word . "%"); + $this->MitarbeiterModel->db->or_where('uid ILIKE', "%" . $word . "%"); + $this->MitarbeiterModel->db->group_end(); + } + $this->MitarbeiterModel->db->group_end(); + + $this->MitarbeiterModel->addOrder('nachname'); + $this->MitarbeiterModel->addOrder('vorname'); + $mitarbeiter = $this->MitarbeiterModel->load(); + if (isError($mitarbeiter)) + $this->terminateWithError($mitarbeiter); + + $this->terminateWithSuccess(hasData($mitarbeiter) ? getData($mitarbeiter) : []); + } + + public function getReservableMap($ort_kurzbz = null) + { + $this->form_validation->set_rules('start_date', "StartDate", "required"); + $this->form_validation->set_rules('end_date', "EndDate", "required"); + + if (!$this->form_validation->run()) + $this->terminateWithValidationErrors($this->form_validation->error_array()); + + // storing the post parameter in local variables + $start_date = $this->input->post('start_date', true); + $end_date = $this->input->post('end_date', true); + + $result = $this->stundenplanlib->getReservableMap($ort_kurzbz, $start_date, $end_date); + + $this->terminateWithSuccess(array('reservierbarMap' => hasData($result) ? getData($result) : [])); + } + +} diff --git a/application/controllers/api/frontend/v1/dashboard/Board.php b/application/controllers/api/frontend/v1/dashboard/Board.php index c50fec128..fdded61e3 100644 --- a/application/controllers/api/frontend/v1/dashboard/Board.php +++ b/application/controllers/api/frontend/v1/dashboard/Board.php @@ -40,11 +40,32 @@ class Board extends FHCAPI_Controller public function list() { + $this->DashboardModel->addSelect('dashboard_id'); + $this->DashboardModel->addSelect('dashboard_kurzbz'); + $this->DashboardModel->addSelect('tbl_dashboard.beschreibung'); + $this->DashboardModel->addSelect("( + SELECT json_agg(w.*) + FROM dashboard.tbl_widget w + JOIN dashboard.tbl_dashboard_widget dw + USING(widget_id) + WHERE dw.dashboard_id=tbl_dashboard.dashboard_id + ) AS \"widgetSetup\""); + $result = $this->DashboardModel->load(); $data = $this->getDataOrTerminateWithError($result); - $this->terminateWithSuccess($result); + $data = array_map(function ($dashboard) { + $tmpSetups = json_decode($dashboard->widgetSetup); + $tmpSetups = array_map(function ($widget) { + $widget->setup->file = absoluteJsImportUrl($widget->setup->file); + return $widget; + }, $tmpSetups); + $dashboard->widgetSetup = $tmpSetups; + return $dashboard; + }, $data); + + $this->terminateWithSuccess($data); } public function create() @@ -82,7 +103,7 @@ class Board extends FHCAPI_Controller $data = $this->getDataOrTerminateWithError($result); - $this->terminateWithSuccess($result); + $this->terminateWithSuccess($data); } public function delete() @@ -116,6 +137,6 @@ class Board extends FHCAPI_Controller $data = $this->getDataOrTerminateWithError($result); - $this->terminateWithSuccess($result); + $this->terminateWithSuccess($data); } } diff --git a/application/controllers/api/frontend/v1/dashboard/Preset.php b/application/controllers/api/frontend/v1/dashboard/Preset.php index 5983d9660..d9be307cf 100644 --- a/application/controllers/api/frontend/v1/dashboard/Preset.php +++ b/application/controllers/api/frontend/v1/dashboard/Preset.php @@ -120,10 +120,7 @@ class Preset extends FHCAPI_Controller $conf = $this->dashboardlib->getPreset($db, $funktion); if ($conf) { $preset = json_decode($conf->preset, true); - if (!isset($preset[$funktion]) || !isset($preset[$funktion]['widgets'])) - $result[$funktion] = []; - else - $result[$funktion] = $preset[$funktion]['widgets']; + $result[$funktion] = $preset; } else { $result[$funktion] = []; } @@ -154,7 +151,7 @@ class Preset extends FHCAPI_Controller $preset_decoded = json_decode($preset->preset, true); - $this->dashboardlib->addWidgetsToWidgets($preset_decoded, $dashboard_kurzbz, $funktion_kurzbz, [$widget]); + $preset_decoded[$widget['widgetid']] = $widget; $preset->preset = json_encode($preset_decoded); @@ -186,8 +183,10 @@ class Preset extends FHCAPI_Controller $preset_decoded = json_decode($preset->preset, true); - if (!$this->dashboardlib->removeWidgetFromWidgets($preset_decoded, $funktion_kurzbz, $widgetid)) + if (!isset($preset_decoded[$widgetid])) show_404(); + + unset($preset_decoded[$widgetid]); $preset->preset = json_encode($preset_decoded); diff --git a/application/controllers/api/frontend/v1/dashboard/User.php b/application/controllers/api/frontend/v1/dashboard/User.php index 9d020649e..e603573ed 100644 --- a/application/controllers/api/frontend/v1/dashboard/User.php +++ b/application/controllers/api/frontend/v1/dashboard/User.php @@ -48,25 +48,9 @@ class User extends FHCAPI_Controller $uid = $this->authlib->getAuthObj()->username; - /*$mergedconfig = $this->dashboardlib->getMergedConfig($dashboard->dashboard_id, $uid); + $mergedconfig = $this->dashboardlib->getMergedUserConfig($dashboard->dashboard_id, $uid); - $this->terminateWithSuccess([ - 'general' => call_user_func_array( - 'array_merge_recursive', - $mergedconfig - ) - ]);*/ - $defaultconfig = $this->dashboardlib->getDefaultConfig($dashboard->dashboard_id); - $userconfig = $this->dashboardlib->getUserConfig($dashboard->dashboard_id, $uid); - - $defaultconfig_squashed = $defaultconfig ? call_user_func_array('array_replace_recursive', $defaultconfig) : []; - $userconfig_squashed = $userconfig ? call_user_func_array('array_replace_recursive', $userconfig) : []; - - $mergedconfig = array_replace_recursive($defaultconfig_squashed, $userconfig_squashed); - - $this->terminateWithSuccess([ - DashboardLib::SECTION_IF_FUNKTION_KURZBZ_IS_NULL => $mergedconfig - ]); + $this->terminateWithSuccess($mergedconfig); } public function addWidget() @@ -86,26 +70,15 @@ class User extends FHCAPI_Controller if (!isset($widget['widgetid'])) $widget['widgetid'] = $this->dashboardlib->generateWidgetId($dashboard_kurzbz); + if (isset($widget['source'])) + unset($widget['source']); + $override = $this->dashboardlib->getOverrideOrCreateEmptyOverride($dashboard_kurzbz, $uid); - + $override_decoded = json_decode($override->override, true); - if (!isset($override_decoded['general']) || !is_array($override_decoded['general'])) - $override_decoded['general'] = []; + $override_decoded[$widget['widgetid']] = $widget; - if (!isset($override_decoded['general']['widgets'])) - $override_decoded['general']['widgets'] = []; - - $override_decoded['general']['widgets'][$widget['widgetid']] = $widget; - - // NOTE(chris): remove doubles in other funktionen - foreach ($override_decoded as $funktion => $array) { - if ($funktion == 'general') - continue; - if (isset($array['widgets']) && isset($array['widgets'][$widget['widgetid']])) - unset($override_decoded[$funktion]['widgets'][$widget['widgetid']]); - } - $override->override = json_encode($override_decoded); $result = $this->dashboardlib->insertOrUpdateOverride($override); @@ -135,18 +108,10 @@ class User extends FHCAPI_Controller $override_decoded = json_decode($override->override, true); - foreach (array_keys($override_decoded) as $k) { - if (!isset($override_decoded[$k]["widgets"])) { - unset($override_decoded[$k]); - continue; - } - if (isset($override_decoded[$k]["widgets"][$widget_id])) { - unset($override_decoded[$k]["widgets"][$widget_id]); - } - if (!$override_decoded[$k]["widgets"]) { - unset($override_decoded[$k]); - } - } + if (!isset($override_decoded[$widget_id])) + show_404(); + + unset($override_decoded[$widget_id]); $override->override = json_encode($override_decoded); diff --git a/application/controllers/api/frontend/v1/education/PaabgabeUebersicht.php b/application/controllers/api/frontend/v1/education/PaabgabeUebersicht.php new file mode 100644 index 000000000..13824d6df --- /dev/null +++ b/application/controllers/api/frontend/v1/education/PaabgabeUebersicht.php @@ -0,0 +1,206 @@ + array('lehre/abgabetool:r'), + 'getStudiengaenge' => array('lehre/abgabetool:r'), + 'getTermine' => array('lehre/abgabetool:r'), + 'getPaAbgabetypen' => array('lehre/abgabetool:r'), + 'downloadZip' => array('lehre/abgabetool:r') + //'downloadProjektarbeit' => array('lehre/abgabetool:r') + ]); + + $this->load->model('education/Paabgabe_model', 'PaabgabeModel'); + + $this->load->library('PermissionLib'); + + // Load Phrases + $this->loadPhrases([ + 'abgabetool' + ]); + } + + /** + * Get Projektabgaben for search criteria. + */ + public function getPaAbgaben() + { + $studiengang_kz = $this->input->get('studiengang_kz'); + $abgabetyp_kurzbz = $this->input->get('abgabetyp_kurzbz'); + $abgabedatum = $this->input->get('abgabedatum'); + $personSearchString = $this->input->get('personSearchString'); + + $result = $this->PaabgabeModel->getPaAbgaben(self::ABGABE_TYPES, $studiengang_kz, $abgabetyp_kurzbz, $abgabedatum, $personSearchString); + + if (isError($result)) $this->terminateWithError(getError($result), self::ERROR_TYPE_DB); + + // check wether Abgabe is in visual library + if (hasData($result)) + { + Events::trigger('in_visual_library', getData($result)); + } + + $this->terminateWithSuccess(getData($result) ?: []); + } + + /** + * Get all Studiengänge for which user is entitled for + */ + public function getStudiengaenge() + { + $studiengang_kz_arr = $this->permissionlib->getSTG_isEntitledFor(self::DOWNLOAD_PERMISSION); + + if (!$studiengang_kz_arr) $this->terminateWithSuccess([]); + + $this->load->model('organisation/Studiengang_model', 'StudiengangModel'); + + $this->StudiengangModel->addSelect('tbl_studiengang.*, UPPER(tbl_studiengang.typ || tbl_studiengang.kurzbz) AS kuerzel', $studiengang_kz_arr); + $this->StudiengangModel->db->where_in('studiengang_kz', $studiengang_kz_arr); + $this->StudiengangModel->addOrder('typ, kurzbz'); + $result = $this->StudiengangModel->load(); + + if (isError($result)) $this->terminateWithError(getError($result), self::ERROR_TYPE_DB); + + $this->terminateWithSuccess((getData($result) ?: [])); + } + + /** + * Get projekt work due dates, depending on search criteria. + */ + public function getTermine() + { + $studiengang_kz = $this->input->get('studiengang_kz'); + $abgabetyp_kurzbz = $this->input->get('abgabetyp_kurzbz'); + + $result = $this->PaabgabeModel->getTermine(self::ABGABE_TYPES, $studiengang_kz, $abgabetyp_kurzbz); + + if (isError($result)) $this->terminateWithError(getError($result), self::ERROR_TYPE_DB); + + $this->terminateWithSuccess((getData($result) ?: [])); + } + + /** + * Get all submission types. + */ + public function getPaAbgabetypen() + { + // Load model PaabgabetypModel + $this->load->model('education/Paabgabetyp_model', 'PaabgabetypModel'); + + $this->PaabgabetypModel->addOrder('bezeichnung'); + $result = $this->PaabgabetypModel->load(); + + if (isError($result)) $this->terminateWithError(getError($result), self::ERROR_TYPE_DB); + + $this->terminateWithSuccess((getData($result) ?: [])); + } + + /** + * Download zip files with project works matching submission search criteria. + */ + public function downloadZip() + { + $studiengang_kz = $this->input->get('studiengang_kz'); + $abgabetyp_kurzbz = $this->input->get('abgabetyp_kurzbz'); + $abgabedatum = $this->input->get('abgabedatum'); + $personSearchString = $this->input->get('personSearchString'); + + if (!isset($studiengang_kz) && !isset($abgabetyp_kurzbz) && !isset($abgabedatum) && !isset($personSearchString)) + $this->terminateWithFileOutput('text/plain', $this->p->t('abgabetool', 'nichtsAusgewaehlt')); + + $this->load->library('zip'); + + $result = $this->PaabgabeModel->getPaAbgaben(self::ABGABE_TYPES, $studiengang_kz, $abgabetyp_kurzbz, $abgabedatum, $personSearchString); + + if (isError($result)) $this->terminateWithFileOutput('text/plain', getError($result)); + + $fileExists = false; + $studiengang_kuerzel = null; + + if (!hasData($result)) $this->terminateWithFileOutput('text/plain', $this->p->t('abgabetool', 'keineDateienVorhanden')); + + $abgaben = getData($result); + + foreach ($abgaben as $abgabe) + { + $path = PAABGABE_PATH.$abgabe->paabgabe_id.'_'.$abgabe->uid.'.pdf'; + if (file_exists($path)) + { + $fileExists = true; + $studiengang_kuerzel = $abgabe->studiengang_kuerzel; + $this->zip->read_file($path); + } + } + + if (!$fileExists) $this->terminateWithFileOutput('text/plain', $this->p->t('abgabetool', 'keineDateienVorhanden')); + + $studiengang_kz = $this->input->get('studiengang_kz'); + $zipFileName = 'Abgabe'.(isset($studiengang_kz) && isset($studiengang_kuerzel) ? '_'.$studiengang_kuerzel : '').'.zip'; + $this->zip->download($zipFileName); + } + + /** + * Download Projektarbeit document. + */ + //~ public function downloadProjektarbeit() + //~ { + //~ $paabgabe_id = $this->input->get('paabgabe_id'); + + //~ if (!is_numeric($paabgabe_id)) + //~ $this->terminateWithError($this->p->t('ui', 'error_missingId', ['id' => 'Abgabe ID']), self::ERROR_TYPE_GENERAL); + + //~ //$abgabeRes = $this->PaabgabeModel->getEndabgabe($projektarbeit_id); + //~ $this->PaabgabeModel->addSelect("paabgabe_id, student_uid, tbl_paabgabe.datum, tbl_paabgabe.abgabedatum, projekttyp_kurzbz, titel, titel_english, + //~ paabgabe_id || '_' || student_uid || '.pdf' AS filename"); + //~ $this->PaabgabeModel->addJoin('lehre.tbl_projektarbeit', 'projektarbeit_id'); + //~ $abgabeRes = $this->PaabgabeModel->load($paabgabe_id); + + //~ if (isError($abgabeRes)) + //~ show_error(getError($abgabeRes)); + + //~ if (hasData($abgabeRes)) + //~ { + //~ $endabgabe = getData($abgabeRes)[0]; + //~ $filepath = PAABGABE_PATH.$endabgabe->filename; + + //~ if (file_exists($filepath)) + //~ { + //~ $this->output + //~ ->set_status_header(200) + //~ ->set_content_type('application/pdf', 'utf-8') + //~ ->set_header('Content-Disposition: attachment; filename="'.$endabgabe->filename.'"') + //~ ->set_output(file_get_contents($filepath)) + //~ ->_display(); + //~ } + //~ else + //~ { + //~ show_error("File does not exist."); + //~ } + //~ } + //~ } +} diff --git a/application/controllers/components/Cis/Mylv.php b/application/controllers/components/Cis/Mylv.php index 1fdb7e2a1..b6d10931c 100644 --- a/application/controllers/components/Cis/Mylv.php +++ b/application/controllers/components/Cis/Mylv.php @@ -13,12 +13,13 @@ class Mylv extends Auth_Controller */ public function __construct() { + parent::__construct([ - 'Student' => ['student/anrechnung_beantragen:r','user:r'], // TODO(chris): permissions? - 'Studiensemester' => ['student/anrechnung_beantragen:r','user:r'], // TODO(chris): permissions? - 'Lvs' => ['student/anrechnung_beantragen:r','user:r'], // TODO(chris): permissions? - 'Info' => ['student/anrechnung_beantragen:r','user:r'], // TODO(chris): permissions? - 'Pruefungen' => ['student/anrechnung_beantragen:r','user:r'] // TODO(chris): permissions? + 'Student' => ['student/anrechnung_beantragen:r','user:r', 'basis/cis:r'], // TODO(chris): permissions? + 'Studiensemester' => ['student/anrechnung_beantragen:r','user:r', 'basis/cis:r'], // TODO(chris): permissions? + 'Lvs' => ['student/anrechnung_beantragen:r','user:r', 'basis/cis:r'], // TODO(chris): permissions? + 'Info' => ['student/anrechnung_beantragen:r','user:r', 'basis/cis:r'], // TODO(chris): permissions? + 'Pruefungen' => ['student/anrechnung_beantragen:r','user:r', 'basis/cis:r'] // TODO(chris): permissions? ]); } @@ -44,13 +45,27 @@ class Mylv extends Auth_Controller public function Studiensemester() { $this->load->model('organisation/Studiensemester_model', 'StudiensemesterModel'); + $this->load->model('crm/Student_model', 'StudentModel'); + $this->load->model('ressource/Mitarbeiter_model', 'MitarbeiterModel'); - $result = $this->StudiensemesterModel->getWhereStudentHasLvs(getAuthUID()); + $isMitarbeiter = getData($this->MitarbeiterModel->isMitarbeiter(getAuthUID())) ?? false; + if($isMitarbeiter) { + $result = $this->StudiensemesterModel->getWhereMitarbeiterHasLvs(getAuthUID()); - if (isError($result)) - return $this->outputJsonError(getError($result)); + if (isError($result)) + return $this->outputJsonError(getError($result)); - $this->outputJsonSuccess(getData($result)); + $this->outputJsonSuccess(getData($result)); + } else if(getData($this->StudentModel->isStudent(getAuthUID())) ?? false) { // $isStudent + $result = $this->StudiensemesterModel->getWhereStudentHasLvs(getAuthUID()); + + if (isError($result)) + return $this->outputJsonError(getError($result)); + + $this->outputJsonSuccess(getData($result)); + } else { + $this->outputJsonError('neither student or mitarbeiter'); + } } /** @@ -58,13 +73,27 @@ class Mylv extends Auth_Controller public function Lvs($studiensemester_kurzbz) { $this->load->model('education/Lehrveranstaltung_model', 'LehrveranstaltungModel'); + $this->load->model('crm/Student_model', 'StudentModel'); + $this->load->model('ressource/Mitarbeiter_model', 'MitarbeiterModel'); - $result = $this->LehrveranstaltungModel->getLvsByStudentWithGrades(getAuthUID(), $studiensemester_kurzbz, getUserLanguage()); + $isMitarbeiter = getData($this->MitarbeiterModel->isMitarbeiter(getAuthUID())) ?? false; + if($isMitarbeiter) { + $result = $this->LehrveranstaltungModel->getLvsByMitarbeiterInSemester(getAuthUID(), $studiensemester_kurzbz); - if (isError($result)) - return $this->outputJsonError(getError($result)); + if (isError($result)) + return $this->outputJsonError(getError($result)); - $this->outputJsonSuccess(getData($result)); + $this->outputJsonSuccess(getData($result)); + } else if(getData($this->StudentModel->isStudent(getAuthUID())) ?? false) { // $isStudent + $result = $this->LehrveranstaltungModel->getLvsByStudentWithGrades(getAuthUID(), $studiensemester_kurzbz, getUserLanguage()); + + if (isError($result)) + return $this->outputJsonError(getError($result)); + + $this->outputJsonSuccess(getData($result)); + } else { + $this->outputJsonError('neither student or mitarbeiter'); + } } /** diff --git a/application/controllers/lehre/Pruefungsprotokoll.php b/application/controllers/lehre/Pruefungsprotokoll.php index 4d34b08ca..aa3eb4c73 100644 --- a/application/controllers/lehre/Pruefungsprotokoll.php +++ b/application/controllers/lehre/Pruefungsprotokoll.php @@ -215,8 +215,11 @@ class Pruefungsprotokoll extends Auth_Controller if (hasData($abschlusspruefung)) { $abschlusspruefung_data = getData($abschlusspruefung); - if ($this->permissionlib->isBerechtigt('admin') || - (isset($abschlusspruefung_data->studiengang_kz) && $this->permissionlib->isBerechtigt('assistenz', 'suid', $abschlusspruefung_data->studiengang_kz)) + if ($this->permissionlib->isBerechtigt('admin') + || ( + isset($abschlusspruefung_data->studiengang_kz) + && $this->permissionlib->isBerechtigt('assistenz', 'suid', $abschlusspruefung_data->studiengang_kz) + ) || $this->_uid === $abschlusspruefung_data->uid_vorsitz) $result = $abschlusspruefung; else diff --git a/application/development/config.php b/application/development/config.php new file mode 100644 index 000000000..d254722bb --- /dev/null +++ b/application/development/config.php @@ -0,0 +1,517 @@ +]+$/i +| +| DO NOT CHANGE THIS UNLESS YOU FULLY UNDERSTAND THE REPERCUSSIONS!! +| +*/ +$config['permitted_uri_chars'] = 'a-z 0-9~%.:_\-'; + +/* +|-------------------------------------------------------------------------- +| Enable Query Strings +|-------------------------------------------------------------------------- +| +| By default CodeIgniter uses search-engine friendly segment based URLs: +| example.com/who/what/where/ +| +| By default CodeIgniter enables access to the $_GET array. If for some +| reason you would like to disable it, set 'allow_get_array' to FALSE. +| +| You can optionally enable standard query string based URLs: +| example.com?who=me&what=something&where=here +| +| Options are: TRUE or FALSE (boolean) +| +| The other items let you set the query string 'words' that will +| invoke your controllers and its functions: +| example.com/index.php?c=controller&m=function +| +| Please note that some of the helpers won't work as expected when +| this feature is enabled, since CodeIgniter is designed primarily to +| use segment based URLs. +| +*/ +$config['allow_get_array'] = TRUE; +$config['enable_query_strings'] = FALSE; +$config['controller_trigger'] = 'c'; +$config['function_trigger'] = 'm'; +$config['directory_trigger'] = 'd'; + +/* +|-------------------------------------------------------------------------- +| Error Logging Threshold +|-------------------------------------------------------------------------- +| +| You can enable error logging by setting a threshold over zero. The +| threshold determines what gets logged. Threshold options are: +| +| 0 = Disables logging, Error logging TURNED OFF +| 1 = Error Messages (including PHP errors) +| 2 = Debug Messages +| 3 = Informational Messages +| 4 = All Messages +| +| You can also pass an array with threshold levels to show individual error types +| +| array(2) = Debug Messages, without Error Messages +| +| For a live site you'll usually only enable Errors (1) to be logged otherwise +| your log files will fill up very fast. +| +*/ +$config['log_threshold'] = 1; + +/* +|-------------------------------------------------------------------------- +| Error Logging Directory Path +|-------------------------------------------------------------------------- +| +| Leave this BLANK unless you would like to set something other than the default +| application/logs/ directory. Use a full server path with trailing slash. +| +*/ +$config['log_path'] = ''; + +/* +|-------------------------------------------------------------------------- +| Log File Extension +|-------------------------------------------------------------------------- +| +| The default filename extension for log files. The default 'php' allows for +| protecting the log files via basic scripting, when they are to be stored +| under a publicly accessible directory. +| +| Note: Leaving it blank will default to 'php'. +| +*/ +$config['log_file_extension'] = 'log'; + +/* +|-------------------------------------------------------------------------- +| Log File Permissions +|-------------------------------------------------------------------------- +| +| The file system permissions to be applied on newly created log files. +| +| IMPORTANT: This MUST be an integer (no quotes) and you MUST use octal +| integer notation (i.e. 0700, 0644, etc.) +*/ +$config['log_file_permissions'] = 0644; + +/* +|-------------------------------------------------------------------------- +| Date Format for Logs +|-------------------------------------------------------------------------- +| +| Each item that is logged has an associated date. You can use PHP date +| codes to set your own date formatting +| +*/ +$config['log_date_format'] = 'Y-m-d H:i:s'; + +/* +|-------------------------------------------------------------------------- +| Error Views Directory Path +|-------------------------------------------------------------------------- +| +| Leave this BLANK unless you would like to set something other than the default +| application/views/errors/ directory. Use a full server path with trailing slash. +| +*/ +$config['error_views_path'] = ''; + +/* +|-------------------------------------------------------------------------- +| Cache Directory Path +|-------------------------------------------------------------------------- +| +| Leave this BLANK unless you would like to set something other than the default +| application/cache/ directory. Use a full server path with trailing slash. +| +*/ +$config['cache_path'] = ''; + +/* +|-------------------------------------------------------------------------- +| Cache Include Query String +|-------------------------------------------------------------------------- +| +| Whether to take the URL query string into consideration when generating +| output cache files. Valid options are: +| +| FALSE = Disabled +| TRUE = Enabled, take all query parameters into account. +| Please be aware that this may result in numerous cache +| files generated for the same page over and over again. +| array('q') = Enabled, but only take into account the specified list +| of query parameters. +| +*/ +$config['cache_query_string'] = FALSE; + +/* +|-------------------------------------------------------------------------- +| Encryption Key +|-------------------------------------------------------------------------- +| +| If you use the Encryption class, you must set an encryption key. +| See the user guide for more info. +| +| http://codeigniter.com/user_guide/libraries/encryption.html +| +*/ +$config['encryption_key'] = ''; + +/* +|-------------------------------------------------------------------------- +| Session Variables +|-------------------------------------------------------------------------- +| +| 'sess_driver' +| +| The storage driver to use: files, database, redis, memcached +| +| 'sess_cookie_name' +| +| The session cookie name, must contain only [0-9a-z_-] characters +| +| 'sess_expiration' +| +| The number of SECONDS you want the session to last. +| Setting to 0 (zero) means expire when the browser is closed. +| +| 'sess_save_path' +| +| The location to save sessions to, driver dependent. +| +| For the 'files' driver, it's a path to a writable directory. +| WARNING: Only absolute paths are supported! +| +| For the 'database' driver, it's a table name. +| Please read up the manual for the format with other session drivers. +| +| IMPORTANT: You are REQUIRED to set a valid save path! +| +| 'sess_match_ip' +| +| Whether to match the user's IP address when reading the session data. +| +| 'sess_time_to_update' +| +| How many seconds between CI regenerating the session ID. +| NOTE: Keep it as it is to prevent security issues (https://en.wikipedia.org/wiki/Session_fixation) +| +| 'sess_regenerate_destroy' +| +| Whether to destroy session data associated with the old session ID +| when auto-regenerating the session ID. When set to FALSE, the data +| will be later deleted by the garbage collector. +| +| Other session cookie settings are shared with the rest of the application, +| except for 'cookie_prefix' and 'cookie_httponly', which are ignored here. +| +*/ +$config['sess_driver'] = 'files'; +$config['sess_cookie_name'] = 'sess_ci_session'; +$config['sess_expiration'] = 1800; // Session expires every 30 minutes +$config['sess_save_path'] = NULL; +$config['sess_match_ip'] = FALSE; +$config['sess_time_to_update'] = 300; +$config['sess_regenerate_destroy'] = FALSE; + +/* +|-------------------------------------------------------------------------- +| Cookie Related Variables +|-------------------------------------------------------------------------- +| +| 'cookie_prefix' = Set a cookie name prefix if you need to avoid collisions +| 'cookie_domain' = Set to .your-domain.com for site-wide cookies +| 'cookie_path' = Typically will be a forward slash +| 'cookie_secure' = Cookie will only be set if a secure HTTPS connection exists. +| 'cookie_httponly' = Cookie will only be accessible via HTTP(S) (no javascript) +| +| Note: These settings (with the exception of 'cookie_prefix' and +| 'cookie_httponly') will also affect sessions. +| +*/ +$config['cookie_prefix'] = ''; +$config['cookie_domain'] = ''; +$config['cookie_path'] = '/'; +$config['cookie_secure'] = FALSE; +$config['cookie_httponly'] = FALSE; + +/* +|-------------------------------------------------------------------------- +| Standardize newlines +|-------------------------------------------------------------------------- +| +| Determines whether to standardize newline characters in input data, +| meaning to replace \r\n, \r, \n occurrences with the PHP_EOL value. +| +| This is particularly useful for portability between UNIX-based OSes, +| (usually \n) and Windows (\r\n). +| +*/ +$config['standardize_newlines'] = FALSE; + +/* +|-------------------------------------------------------------------------- +| Global XSS Filtering +|-------------------------------------------------------------------------- +| +| Determines whether the XSS filter is always active when GET, POST or +| COOKIE data is encountered +| +| WARNING: This feature is DEPRECATED and currently available only +| for backwards compatibility purposes! +| +*/ +$config['global_xss_filtering'] = FALSE; + +/* +|-------------------------------------------------------------------------- +| Cross Site Request Forgery +|-------------------------------------------------------------------------- +| Enables a CSRF cookie token to be set. When set to TRUE, token will be +| checked on a submitted form. If you are accepting user data, it is strongly +| recommended CSRF protection be enabled. +| +| 'csrf_token_name' = The token name +| 'csrf_cookie_name' = The cookie name +| 'csrf_expire' = The number in seconds the token should expire. +| 'csrf_regenerate' = Regenerate token on every submission +| 'csrf_exclude_uris' = Array of URIs which ignore CSRF checks +*/ +$config['csrf_protection'] = FALSE; +$config['csrf_token_name'] = 'csrf_test_name'; +$config['csrf_cookie_name'] = 'csrf_cookie_name'; +$config['csrf_expire'] = 7200; +$config['csrf_regenerate'] = TRUE; +$config['csrf_exclude_uris'] = array(); + +/* +|-------------------------------------------------------------------------- +| Output Compression +|-------------------------------------------------------------------------- +| +| Enables Gzip output compression for faster page loads. When enabled, +| the output class will test whether your server supports Gzip. +| Even if it does, however, not all browsers support compression +| so enable only if you are reasonably sure your visitors can handle it. +| +| Only used if zlib.output_compression is turned off in your php.ini. +| Please do not use it together with httpd-level output compression. +| +| VERY IMPORTANT: If you are getting a blank page when compression is enabled it +| means you are prematurely outputting something to your browser. It could +| even be a line of whitespace at the end of one of your scripts. For +| compression to work, nothing can be sent before the output buffer is called +| by the output class. Do not 'echo' any values with compression enabled. +| +*/ +$config['compress_output'] = FALSE; + +/* +|-------------------------------------------------------------------------- +| Master Time Reference +|-------------------------------------------------------------------------- +| +| Options are 'local' or any PHP supported timezone. This preference tells +| the system whether to use your server's local time as the master 'now' +| reference, or convert it to the configured one timezone. See the 'date +| helper' page of the user guide for information regarding date handling. +| +*/ +$config['time_reference'] = 'local'; + +/* +|-------------------------------------------------------------------------- +| Rewrite PHP Short Tags +|-------------------------------------------------------------------------- +| +| If your PHP installation does not have short tag support enabled CI +| can rewrite the tags on-the-fly, enabling you to utilize that syntax +| in your view files. Options are TRUE or FALSE (boolean) +| +| Note: You need to have eval() enabled for this to work. +| +*/ +$config['rewrite_short_tags'] = FALSE; + +/* +|-------------------------------------------------------------------------- +| Reverse Proxy IPs +|-------------------------------------------------------------------------- +| +| If your server is behind a reverse proxy, you must whitelist the proxy +| IP addresses from which CodeIgniter should trust headers such as +| HTTP_X_FORWARDED_FOR and HTTP_CLIENT_IP in order to properly identify +| the visitor's IP address. +| +| You can use both an array or a comma-separated list of proxy addresses, +| as well as specifying whole subnets. Here are a few examples: +| +| Comma-separated: '10.0.1.200,192.168.5.0/24' +| Array: array('10.0.1.200', '192.168.5.0/24') +*/ +$config['proxy_ips'] = ''; + +/* +|-------------------------------------------------------------------------- +| FHComplete Build Version +|-------------------------------------------------------------------------- +| +| Version Number of the Current Build +| This is used to invalidate Cache for JS and CSS Files +| +| Example: 2019102901 +*/ +$config['fhcomplete_build_version'] = '2019102903'; diff --git a/application/development/database.php b/application/development/database.php new file mode 100644 index 000000000..fde6d6e68 --- /dev/null +++ b/application/development/database.php @@ -0,0 +1,122 @@ +db->last_query() and profiling of DB queries. +| When you run a query, with this setting set to TRUE (default), +| CodeIgniter will store the SQL statement for debugging purposes. +| However, this may cause high memory usage, especially if you run +| a lot of SQL queries ... disable this to avoid that problem. +| +| The $active_group variable lets you choose which connection group to +| make active. By default there is only one group (the 'default' group). +| +| The $query_builder variables lets you determine whether or not to load +| the query builder class. +*/ +$active_group = 'default'; +$query_builder = TRUE; + +$db['default'] = array( + 'dsn' => '', + 'hostname' => DB_HOST, + 'username' => DB_USER, + 'password' => DB_PASSWORD, + 'port' => DB_PORT, + 'database' => DB_NAME, + 'dbdriver' => 'postgre', + 'dbprefix' => '', + 'pconnect' => DB_CONNECT_PERSISTENT, + 'db_debug' => (ENVIRONMENT !== 'production'), + 'cache_on' => FALSE, + 'cachedir' => '', + 'char_set' => 'utf8', + 'dbcollat' => 'utf8_general_ci', + 'swap_pre' => '', + 'encrypt' => FALSE, + 'compress' => FALSE, + 'stricton' => FALSE, + 'failover' => array(), + 'save_queries' => TRUE +); + +$db['system'] = array( + 'dsn' => '', + 'hostname' => DB_HOST, + 'username' => 'fhcomplete', + 'password' => 'Fhcomplet3Onc4p1', + 'database' => DB_NAME, + 'port' => DB_PORT, + 'dbschema' => 'public', + 'dbdriver' => 'postgre', + 'dbprefix' => '', + 'pconnect' => DB_CONNECT_PERSISTENT, + 'db_debug' => (ENVIRONMENT !== 'production'), + 'cache_on' => FALSE, + 'cachedir' => '', + 'char_set' => 'utf8', + 'dbcollat' => 'utf8_general_ci', + 'swap_pre' => '', + 'encrypt' => FALSE, + 'compress' => FALSE, + 'stricton' => FALSE, + 'failover' => array(), + 'save_queries' => TRUE +); diff --git a/application/libraries/StundenplanLib.php b/application/libraries/StundenplanLib.php index 7ed64da2c..441bf0c75 100644 --- a/application/libraries/StundenplanLib.php +++ b/application/libraries/StundenplanLib.php @@ -40,13 +40,16 @@ class StundenplanLib * @return stdClass * @access public */ - public function getEventsUser($start, $end) + public function getEventsUser($start, $end, $uid = null) { $this->_ci =& get_instance(); $this->_ci->load->model('ressource/Mitarbeiter_model', 'MitarbeiterModel'); - $uid = getAuthUID(); + if (!$uid) { + $uid = getAuthUID(); + } + if (is_null($uid)) return error("No UID"); @@ -217,7 +220,7 @@ class StundenplanLib * @param string $ort_kurzbz * @return stdClass */ - public function getReservierungen($start_date, $end_date, $ort_kurzbz = '') + public function getReservierungen($start_date, $end_date, $ort_kurzbz = '', $uid = null) { $this->_ci =& get_instance(); @@ -228,14 +231,14 @@ class StundenplanLib $this->_ci->load->model('ressource/Reservierung_model', 'ReservierungModel'); $this->_ci->load->model('ressource/Stundenplan_model', 'StundenplanModel'); - $is_mitarbeiter = getData($this->_ci->MitarbeiterModel->isMitarbeiter(getAuthUID())); + $is_mitarbeiter = getData($this->_ci->MitarbeiterModel->isMitarbeiter($uid ?? getAuthUID())); if ($is_mitarbeiter && empty($ort_kurzbz)) { // request for personal lvplan show only reservations of logged in user - $reservierungen = $this->_ci->ReservierungModel->getReservierungenMitarbeiter($start_date, $end_date); + $reservierungen = $this->_ci->ReservierungModel->getReservierungenMitarbeiter($start_date, $end_date, $uid); } else { // querying the reservierungen - $reservierungen = $this->_ci->ReservierungModel->getReservierungen($start_date, $end_date, $ort_kurzbz); + $reservierungen = $this->_ci->ReservierungModel->getReservierungen($start_date, $end_date, $ort_kurzbz, $uid); } if (isError($reservierungen)) @@ -368,6 +371,32 @@ class StundenplanLib $item->gruppe = $gruppe_obj_array; $item->lektor = $lektor_obj_array; + $this->_ci->load->library('PermissionLib'); + $berechtigt_begrenzt = $this->_ci->permissionlib->isBerechtigt('lehre/reservierung:begrenzt', 'sui'); + + $now = time(); + $res_lektor_start = $this->jump_day($now, RES_TAGE_LEKTOR_MIN - 1); + $res_lektor_ende = mktime(0, 0, 0, date('m', $now), date('d', $now) + RES_TAGE_LEKTOR_BIS, date('Y', $now)); + + $start_date = is_numeric($item->beginn) ? $item->beginn : strtotime($item->beginn); + if (!date('w', $start_date)) { + $start_date = $this->jump_day($start_date, 1); + } + + $start_date_str = date('Y-m-d', $start_date); + $res_lektor_start_str = date('Y-m-d', $res_lektor_start); + $res_lektor_ende_str = date('Y-m-d', $res_lektor_ende); + + $show_delete = (($berechtigt_begrenzt && ($item->insertvon == getAuthUID() || in_array(getAuthUID(), $item->uids))) && + $start_date_str >= $res_lektor_start_str && + $start_date_str <= $res_lektor_ende_str + ); + + if ($show_delete) + $item->deletable = true; + else + $item->deletable = false; + } } @@ -445,6 +474,237 @@ class StundenplanLib return success($ferienEventsFlattened); } + public function getEventsStgOrg( $start, $end, $stg_kz, $sem, $verband, $gruppe) + { + $this->_ci =& get_instance(); + + $this->_ci->load->model('ressource/Stundenplan_model', 'StundenplanModel'); + + $stundenplan_data = $this->_ci->StundenplanModel->getStundenplanStudiengang($start, $end, $stg_kz, $sem, $verband, $gruppe); + if (isError($stundenplan_data)) + return $stundenplan_data; + $stundenplan_data = getData($stundenplan_data) ?? []; + + $function_error = $this->expandObjectInformation($stundenplan_data); + if ($function_error) + return $function_error; + + return success($stundenplan_data); + } + + public function addReservation($start, $end, $title, $beschreibung, $ort_kurzbz, $lektoren = null, $studiengang = null, $semester = null, $verband = null, $gruppe = null, $spezialgruppe = null) + { + $this->_ci =& get_instance(); + $this->_ci->load->model('ressource/Stunde_model', 'StundeModel'); + $this->_ci->load->model('ressource/Reservierung_model', 'ReservierungModel'); + $this->_ci->load->model('ressource/stundenplandev_model', 'StundenplandevModel'); + $this->_ci->load->model('ressource/stundenplan_model', 'StundenplanModel'); + $this->_ci->load->library('PermissionLib'); + + $startTime = new DateTime($start); + $endTime = new DateTime($end); + + $stunden = $this->_ci->StundeModel->loadWhere(array( + 'beginn <' => $endTime->format('H:i:s'), + 'ende >' => $startTime->format('H:i:s') + )); + + if (!hasData($stunden)) + { + return error("Keine Stunden vorhanden"); + } + + $stunden = array_column(getData($stunden), 'stunde'); + + $this->_ci->StundenplandevModel->db->select('1'); + $this->_ci->StundenplandevModel->db->where('datum', $startTime->format('Y-m-d')); + $this->_ci->StundenplandevModel->db->where('ort_kurzbz', $ort_kurzbz); + $this->_ci->StundenplandevModel->db->where_in('stunde', $stunden); + $stundenplandev_belegung = $this->_ci->StundenplandevModel->load(); + + $this->_ci->StundenplanModel->db->select('1'); + $this->_ci->StundenplanModel->db->where('ort_kurzbz', $ort_kurzbz); + $this->_ci->StundenplanModel->db->where('datum', $startTime->format('Y-m-d')); + $this->_ci->StundenplanModel->db->where_in('stunde', $stunden); + $stundenplan_belegung = $this->_ci->StundenplanModel->load(); + + if ((hasData($stundenplandev_belegung) || hasData($stundenplan_belegung)) + && !$this->_ci->permissionlib->isBerechtigt('lehre/reservierungAdvanced')) + return error ('lvplan/bereitsReserviert'); + + $this->_ci->ReservierungModel->addSelect('stunde'); + $reservation_hours = $this->_ci->ReservierungModel->loadWhere(array('datum' => $startTime->format('Y-m-d'), 'ort_kurzbz' => $ort_kurzbz)); + + + if (isError($reservation_hours)) + return $reservation_hours; + + $reservation_hours = hasData($reservation_hours) ? array_column(getData($reservation_hours), 'stunde') : array(); + + if (!empty(array_intersect($stunden, $reservation_hours)) + && !$this->_ci->permissionlib->isBerechtigt('lehre/reservierungAdvanced')) + return error("lvplan/bereitsReserviert"); + + + if (!empty($lektoren)) + { + foreach ($lektoren as $lektor) + { + $insert = array('ort_kurzbz' => $ort_kurzbz, + 'datum' => $startTime->format('Y-m-d'), + 'titel' => $title, + 'studiengang_kz' => is_null($studiengang) ? 0 : $studiengang, + 'beschreibung' => $beschreibung, + 'insertvon' => getAuthUID(), + 'uid' => $lektor, + 'semester' => is_null($semester) ? null : $semester, + 'verband' => is_null($verband) ? null : $verband, + 'gruppe' => is_null($gruppe) ? null : $gruppe, + 'gruppe_kurzbz' => is_null($spezialgruppe) ? null : $spezialgruppe, + ); + + foreach ($stunden as $stunde) + { + $insert['stunde'] = $stunde; + $check_insert = $this->_ci->ReservierungModel->insert($insert); + if (isError($check_insert)) + return $check_insert; + } + } + } + else + { + foreach ($stunden as $stunde) + { + $check_insert = $this->_ci->ReservierungModel->insert(array( + 'ort_kurzbz' => $ort_kurzbz, + 'uid' => getAuthUID(), + 'stunde' => $stunde, + 'datum' => $startTime->format('Y-m-d'), + 'titel' => $title, + 'studiengang_kz' => is_null($studiengang) ? 0 : $studiengang, + 'beschreibung' => $beschreibung, + 'insertvon' => getAuthUID() + )); + if (isError($check_insert)) + return $check_insert; + } + } + + return success("Erfolgreich"); + } + + public function deleteReservation($reservierung_id) + { + $this->_ci =& get_instance(); + $this->_ci->load->model('ressource/Reservierung_model', 'ReservierungModel'); + $this->_ci->load->model('ressource/Stunde_model', 'StundeModel'); + $this->_ci->load->library('PermissionLib'); + + + $this->_ci->ReservierungModel->db->where_in('reservierung_id', $reservierung_id); + $reservation = $this->_ci->ReservierungModel->load(); + if (isError($reservation)) + return $reservation; + + if (!hasData($reservation)) + return error("Reservierungen nicht gefunden"); + + $reservations = getData($reservation); + + $today = new DateTime(); + foreach ($reservations as $reservierung) + { + if ($today->format('Y-m-d') > $reservierung->datum) + return error("Vergangene Reservierungen können nicht gelöscht werden"); + + if (($this->_ci->permissionlib->isBerechtigt('lehre/reservierung:begrenzt')) && ($reservierung->insertvon == getAuthUID() || $reservierung->uid === getAuthUID())) + { + $delete_result = $this->_ci->ReservierungModel->delete($reservierung->reservierung_id); + + if (isError($delete_result)) + return $delete_result; + } + } + return success("Erfolgreich"); + } + + + public function getReservableMap($ort_kurzbz, $start_date, $end_date) + { + $this->_ci =& get_instance(); + $this->_ci->load->model('ressource/Ort_model', 'OrtModel'); + $this->_ci->load->library('PermissionLib'); + + $berechtigt_begrenzt = $this->_ci->permissionlib->isBerechtigt('lehre/reservierung:begrenzt', 'suid'); + $berechtigt_erweitert = $this->_ci->permissionlib->isBerechtigt('lehre/reservierung', 'suid'); + + $ort_data = $this->_ci->OrtModel->load($ort_kurzbz); + if (isError($ort_data) || !hasData($ort_data)) + return []; + + $ort_data = getData($ort_data)[0]; + + if (!$ort_data->reservieren) + return []; + + if (!$berechtigt_begrenzt && !$berechtigt_erweitert) + return []; + + $start_ts = is_numeric($start_date) ? (int)$start_date : strtotime($start_date); + $end_ts = is_numeric($end_date) ? (int)$end_date : strtotime($end_date); + + if (!$start_ts || !$end_ts) + return []; + + if ($end_ts < $start_ts) + { + $tmp = $start_ts; + $start_ts = $end_ts; + $end_ts = $tmp; + } + + $now = time(); + $tage_min = defined('RES_TAGE_LEKTOR_MIN') ? (int)RES_TAGE_LEKTOR_MIN : 0; + $tage_bis = defined('RES_TAGE_LEKTOR_BIS') ? (int)RES_TAGE_LEKTOR_BIS : 0; + + $datum_res_lektor_start = $this->jump_day($now, $tage_min - 1); + $datum_res_lektor_ende = $this->jump_day($now, $tage_bis); + + $start_ymd_allowed = date('Y-m-d', $datum_res_lektor_start); + $end_ymd_allowed = date('Y-m-d', $datum_res_lektor_ende); + + $result = []; + + $current = strtotime(date('Y-m-d', $start_ts) . ' 00:00:00'); + $end_day = strtotime(date('Y-m-d', $end_ts) . ' 00:00:00'); + + while ($current <= $end_day) + { + $ymd = date('Y-m-d', $current); + + if ((int)date('w', $current) === 0) + { + $result[$ymd] = false; + $current = $this->jump_day($current, 1); + continue; + } + + $result[$ymd] = ($ymd >= $start_ymd_allowed && $ymd <= $end_ymd_allowed) ? true : false; + + $current = $this->jump_day($current, 1); + } + + return success($result); + } + + private function jump_day($timestamp, $days) + { + $days = (int)$days; + $prefix = ($days >= 0 ? '+' : ''); + return strtotime($prefix . $days . ' days', $timestamp); + } + // start of the private functions ######################################################################################################## // function used to sort an array of studiensemester strings diff --git a/application/libraries/dashboard/DashboardLib.php b/application/libraries/dashboard/DashboardLib.php index 1c3983108..c9838f0e7 100644 --- a/application/libraries/dashboard/DashboardLib.php +++ b/application/libraries/dashboard/DashboardLib.php @@ -37,7 +37,9 @@ class DashboardLib public function getDashboardByKurzbz($dashboard_kurzbz) { - $result = $this->_ci->DashboardModel->getDashboardByKurzbz($dashboard_kurzbz); + $result = $this->_ci->DashboardModel->loadWhere([ + 'dashboard_kurzbz' => $dashboard_kurzbz + ]); if (hasData($result)) { @@ -47,17 +49,21 @@ class DashboardLib return null; } - public function getMergedConfig($dashboard_id, $uid) + public function getMergedUserConfig($dashboard_id, $uid) { - $defaultconfig = $this->getDefaultConfig($dashboard_id); - $userconfig = $this->getUserConfig($dashboard_id, $uid); + $defaultconfig = $this->getUserBaseConfig($dashboard_id); + $userconfig = $this->getUserOverrideConfig($dashboard_id, $uid); - $mergedconfig = array_replace_recursive($defaultconfig, $userconfig); + $sourceconfig = array_map(function ($value) { + return ['source' => $value['source']]; + }, $defaultconfig); + + $mergedconfig = array_replace_recursive($defaultconfig, $userconfig, $sourceconfig); return $mergedconfig; } - public function getDefaultConfig($dashboard_id) + protected function getUserBaseConfig($dashboard_id) { $funktion_kurzbzs = []; $rights = $this->_ci->permissionlib->getAccessRights(); @@ -87,7 +93,11 @@ class DashboardLib $preset = json_decode($presetobj->preset, true); if (null !== $preset) { - $defaultconfig = array_replace_recursive($defaultconfig, $preset); + $preset = array_map(function ($value) use ($presetobj) { + $value['source'] = $presetobj->funktion_kurzbz ?: self::SECTION_IF_FUNKTION_KURZBZ_IS_NULL; + return $value; + }, $preset); + $defaultconfig = array_merge_recursive($defaultconfig, $preset); } } } @@ -95,7 +105,7 @@ class DashboardLib return $defaultconfig; } - public function getUserConfig($dashboard_id, $uid) + protected function getUserOverrideConfig($dashboard_id, $uid) { $res_userconfig = $this->_ci->DashboardOverrideModel->getOverride($dashboard_id, $uid); @@ -124,7 +134,7 @@ class DashboardLib $emptyoverride = new stdClass(); $emptyoverride->dashboard_id = $dashboard->dashboard_id; $emptyoverride->uid = $uid; - $emptyoverride->override = '{"' . self::USEROVERRIDE_SECTION . '": {"widgets":{}}, "custom": { "widgets" : {}}}'; + $emptyoverride->override = '[]'; return $emptyoverride; } @@ -143,8 +153,7 @@ class DashboardLib $emptypreset = new stdClass(); $emptypreset->dashboard_id = $dashboard->dashboard_id; $emptypreset->funktion_kurzbz = $funktion_kurzbz; - $section = ($funktion_kurzbz !== null) ? $funktion_kurzbz : self::SECTION_IF_FUNKTION_KURZBZ_IS_NULL; - $emptypreset->preset = '{"' . $section . '": { "widgets" : {}},"custom": { "widgets" : {}}}'; + $emptypreset->preset = '[]'; return $emptypreset; } @@ -209,44 +218,4 @@ class DashboardLib return $result; } - - public function addWidgetsToWidgets(&$widgets, $dashboard_kurzbz, $section, $addwigets) - { - foreach ($addwigets as $widget) - { - if(!isset($widget['widgetid'])) - { - $widget['widgetid'] = $this->generateWidgetId($dashboard_kurzbz); - } - $this->addWidgetToWidgets($widgets, $section, $widget, $widget['widgetid']); - } - } - - public function addWidgetToWidgets(&$widgets, $section, $widget, $widgetid) - { - $section = ($section !== null) ? $section : self::SECTION_IF_FUNKTION_KURZBZ_IS_NULL; - if (!isset($widgets[$section]) || !isset($widgets[$section]["widgets"]) || !is_array($widgets[$section])) - { - $widgets[$section] = array(); - $widgets[$section]["widgets"] = array(); - } - - $widgets[$section]["widgets"][$widgetid] = $widget; - } - - public function removeWidgetFromWidgets(&$widgets, $section, $widgetid) - { - $section = ($section !== null) ? $section : self::SECTION_IF_FUNKTION_KURZBZ_IS_NULL; - if (isset($widgets[$section]) && isset($widgets[$section]["widgets"][$widgetid])) - { - unset($widgets[$section]["widgets"][$widgetid]); - if(empty($widgets[$section]["widgets"]) && $section !== self::USEROVERRIDE_SECTION) { - unset($widgets[$section]); - } - return true; - } - else { - return false; - } - } } diff --git a/application/models/codex/Mobilitaet_model.php b/application/models/codex/Mobilitaet_model.php index 13f966d50..107c1ebb1 100644 --- a/application/models/codex/Mobilitaet_model.php +++ b/application/models/codex/Mobilitaet_model.php @@ -11,4 +11,73 @@ class Mobilitaet_model extends DB_Model $this->dbTable = 'bis.tbl_mobilitaet'; $this->pk = 'mobilitaet_id'; } + + public function getMobilityZusatzForUids($uids) { + $qry = "SELECT distinct on(nachname, vorname, public.tbl_benutzer.person_id) uid, + tbl_mitarbeiter.mitarbeiter_uid, + tbl_note.lkt_ueberschreibbar, tbl_note.anmerkung, + tbl_mobilitaet.mobilitaetstyp_kurzbz, + (CASE WHEN bis.tbl_mobilitaet.studiensemester_kurzbz = vw_student_lehrveranstaltung.studiensemester_kurzbz THEN 1 ELSE 0 END) as doubledegree, + public.tbl_prestudent.gsstudientyp_kurzbz as ddtype, + (SELECT status_kurzbz FROM public.tbl_prestudentstatus + WHERE prestudent_id=tbl_student.prestudent_id + ORDER BY datum DESC, insertamum DESC, ext_id DESC LIMIT 1) as studienstatus + FROM + campus.vw_student_lehrveranstaltung + JOIN public.tbl_benutzer USING(uid) + JOIN public.tbl_person USING(person_id) + LEFT JOIN public.tbl_student ON(uid=student_uid) + LEFT JOIN public.tbl_mitarbeiter ON(uid=mitarbeiter_uid) + LEFT JOIN public.tbl_studentlehrverband USING(student_uid,studiensemester_kurzbz) + LEFT JOIN lehre.tbl_zeugnisnote on(vw_student_lehrveranstaltung.lehrveranstaltung_id=tbl_zeugnisnote.lehrveranstaltung_id + AND tbl_zeugnisnote.student_uid=tbl_student.student_uid + AND tbl_zeugnisnote.studiensemester_kurzbz=tbl_studentlehrverband.studiensemester_kurzbz) + LEFT JOIN lehre.tbl_note USING (note) + LEFT JOIN bis.tbl_bisio ON(uid=tbl_bisio.student_uid) + LEFT JOIN public.tbl_studiengang ON(tbl_student.studiengang_kz=tbl_studiengang.studiengang_kz) + LEFT JOIN bis.tbl_mobilitaet USING(prestudent_id) + LEFT JOIN public.tbl_prestudent USING(prestudent_id) + WHERE uid IN ?"; + + return $this->execReadOnlyQuery($qry, [$uids]); + } + + public function formatZusatz($entry, $erhalter_kz) { + $zusatz = ''; + + if (isset($entry->studienstatus) && $entry->studienstatus === 'Incoming') { + $zusatz = '(i)'; + } + + if (isset($entry->lkt_ueberschreibbar) && $entry->lkt_ueberschreibbar === false) { + $zusatz .= ' (' . ($entry->anmerkung ?? '') . ')'; + } + + if (isset($entry->mitarbeiter_uid) && $entry->mitarbeiter_uid !== null) { + $zusatz .= ' (ma)'; + } + + if (isset($entry->stg_kz_student) && $entry->stg_kz_student == $erhalter_kz) { + $zusatz .= ' (a.o.)'; + } + + if ( + isset($entry->mobilitaetstyp_kurzbz) && $entry->mobilitaetstyp_kurzbz && + isset($entry->doubledegree) && $entry->doubledegree === 1 + ) { + $zusatz .= ' (d.d.'; + + $ddtype = $entry->ddtype ?? null; + + if ($ddtype == 'Intern') { + $zusatz .= 'i.)'; + } elseif ($ddtype == 'Extern') { + $zusatz .= 'o.)'; + } else { + $zusatz .= ')'; + } + } + + return $zusatz; + } } diff --git a/application/models/content/Content_model.php b/application/models/content/Content_model.php index 278022b59..48a155cb2 100644 --- a/application/models/content/Content_model.php +++ b/application/models/content/Content_model.php @@ -234,9 +234,9 @@ class Content_model extends DB_Model FROM campus.tbl_content c1 LEFT JOIN - campus.tbl_contentsprache s1 ON c1.content_id=s1.content_id AND s1.sprache=? + campus.tbl_contentsprache s1 ON c1.content_id=s1.content_id AND s1.sprache=? AND sichtbar=true WHERE - sichtbar=true + c1.aktiv = true ) s2 LEFT JOIN campus.tbl_contentsprache s3 USING(content_id, sprache) @@ -277,7 +277,7 @@ class Content_model extends DB_Model JOIN campus.tbl_contentsprache s USING(contentsprache_id) LEFT JOIN - campus.tbl_contentchild k ON(m.content_id=k.content_id) + campus.tbl_contentchild k ON(m.content_id=k.content_id) and c.aktiv = true WHERE EXISTS ( SELECT 1 FROM campus.tbl_contentgruppe diff --git a/application/models/dashboard/Bookmark_model.php b/application/models/dashboard/Bookmark_model.php index 5efacc26b..d9756294e 100644 --- a/application/models/dashboard/Bookmark_model.php +++ b/application/models/dashboard/Bookmark_model.php @@ -11,8 +11,4 @@ class Bookmark_model extends DB_Model $this->dbTable = 'dashboard.tbl_bookmark'; $this->pk = 'bookmark_id'; } - - - - } diff --git a/application/models/dashboard/Dashboard_model.php b/application/models/dashboard/Dashboard_model.php index 88946ed83..78f6b1100 100644 --- a/application/models/dashboard/Dashboard_model.php +++ b/application/models/dashboard/Dashboard_model.php @@ -11,15 +11,4 @@ class Dashboard_model extends DB_Model $this->dbTable = 'dashboard.tbl_dashboard'; $this->pk = 'dashboard_id'; } - - - /** - * Get Dashboard by kurzbz. - * @param string dashboard_kurzbz - * @return array - */ - public function getDashboardByKurzbz($dashboard_kurzbz) - { - return $this->loadWhere(array('dashboard_kurzbz' => $dashboard_kurzbz)); - } } diff --git a/application/models/education/Abschlusspruefung_model.php b/application/models/education/Abschlusspruefung_model.php index 0ba8fd55c..fda196d23 100644 --- a/application/models/education/Abschlusspruefung_model.php +++ b/application/models/education/Abschlusspruefung_model.php @@ -100,12 +100,14 @@ class Abschlusspruefung_model extends DB_Model if (isError($abschlussarbeit)) return $abschlussarbeit; + if (hasData($abschlussarbeit)) { $abschlussarbeit = getData($abschlussarbeit)[0]; $abschlusspruefungdata->projektarbeit_studiengangstyp_name = $abschlussarbeit->projekttyp_kurzbz; $abschlusspruefungdata->abschlussarbeit_titel = $abschlussarbeit->titel; $abschlusspruefungdata->abschlussarbeit_note = $abschlussarbeit->note; + $abschlusspruefungdata->abschlussarbeit_sprache = $abschlussarbeit->sprache_bezeichnung; } } } diff --git a/application/models/education/LePruefung_model.php b/application/models/education/LePruefung_model.php index 6e51f1975..35cedc324 100644 --- a/application/models/education/LePruefung_model.php +++ b/application/models/education/LePruefung_model.php @@ -52,4 +52,53 @@ class LePruefung_model extends DB_Model 'student_uid' => $student_uid ]); } + + public function getPruefungenByLvStudiensemester($lv_id, $sem_kurzbz) { + $qry = "SELECT lehre.tbl_pruefung.*, tbl_lehrveranstaltung.bezeichnung as lehrveranstaltung_bezeichnung, tbl_lehrveranstaltung.lehrveranstaltung_id, + tbl_note.bezeichnung as note_bezeichnung, tbl_pruefungstyp.beschreibung as typ_beschreibung, tbl_lehreinheit.studiensemester_kurzbz as studiensemester_kurzbz + FROM lehre.tbl_pruefung, lehre.tbl_lehreinheit, lehre.tbl_lehrveranstaltung, lehre.tbl_note, lehre.tbl_pruefungstyp + WHERE lehre.tbl_pruefung.lehreinheit_id=tbl_lehreinheit.lehreinheit_id + AND tbl_lehreinheit.lehrveranstaltung_id=tbl_lehrveranstaltung.lehrveranstaltung_id + AND lehre.tbl_pruefung.note = tbl_note.note + AND lehre.tbl_pruefung.pruefungstyp_kurzbz=tbl_pruefungstyp.pruefungstyp_kurzbz + AND tbl_lehrveranstaltung.lehrveranstaltung_id = ? + AND tbl_lehreinheit.studiensemester_kurzbz = ? + ORDER BY datum DESC;"; + + return $this->execReadOnlyQuery($qry, array($lv_id, $sem_kurzbz)); + } + + public function getPruefungenByUidTypLvStudiensemester($uid, $typ = null, $lv_id = null, $sem_kurzbz = null) { + $params = [$uid]; + $qry = "SELECT tbl_pruefung.*, tbl_lehrveranstaltung.bezeichnung as lehrveranstaltung_bezeichnung, tbl_lehrveranstaltung.lehrveranstaltung_id, + tbl_note.bezeichnung as note_bezeichnung, tbl_pruefungstyp.beschreibung as typ_beschreibung, tbl_lehreinheit.studiensemester_kurzbz as studiensemester_kurzbz + FROM lehre.tbl_pruefung, lehre.tbl_lehreinheit, lehre.tbl_lehrveranstaltung, lehre.tbl_note, lehre.tbl_pruefungstyp + WHERE student_uid= ? + AND tbl_pruefung.lehreinheit_id=tbl_lehreinheit.lehreinheit_id + AND tbl_lehreinheit.lehrveranstaltung_id=tbl_lehrveranstaltung.lehrveranstaltung_id + AND tbl_pruefung.note = tbl_note.note + AND tbl_pruefung.pruefungstyp_kurzbz=tbl_pruefungstyp.pruefungstyp_kurzbz"; + if ($typ != null) + { + $qry .= " AND tbl_pruefungstyp.pruefungstyp_kurzbz = ?"; + $params[] = $typ; + } + + if ($lv_id != null) + { + $qry .= " AND tbl_lehrveranstaltung.lehrveranstaltung_id = ?"; + $params[] = $lv_id; + } + + if ($sem_kurzbz != null) + { + $qry .= " AND tbl_lehreinheit.studiensemester_kurzbz = ?"; + $params[] = $sem_kurzbz; + } + + + $qry .= " ORDER BY datum DESC"; + + return $this->execReadOnlyQuery($qry, $params); + } } diff --git a/application/models/education/Lehreinheit_model.php b/application/models/education/Lehreinheit_model.php index 2f955c295..fed5b9d23 100644 --- a/application/models/education/Lehreinheit_model.php +++ b/application/models/education/Lehreinheit_model.php @@ -739,4 +739,26 @@ EOSQL; )"; } + + public function getAllLehreinheitenForLvaAndMaUid($lva_id, $ma_uid, $sem_kurzbz) + { + $query = "SELECT DISTINCT tbl_lehreinheitmitarbeiter.lehreinheit_id, tbl_lehreinheit.lehrveranstaltung_id, tbl_lehreinheit.lehrform_kurzbz, + tbl_lehreinheitmitarbeiter.mitarbeiter_uid, + tbl_lehreinheitgruppe.semester, + tbl_lehreinheitgruppe.verband, + tbl_lehreinheitgruppe.gruppe, + tbl_lehreinheitgruppe.gruppe_kurzbz, + tbl_lehrveranstaltung.kurzbz, + tbl_studiengang.kurzbzlang, + (SELECT COUNT(DISTINCT datum) FROM campus.vw_stundenplan WHERE lehreinheit_id = lehre.tbl_lehreinheit.lehreinheit_id) as termincount, + (SELECT COUNT(*) FROM campus.vw_student_lehrveranstaltung WHERE lehreinheit_id = lehre.tbl_lehreinheit.lehreinheit_id) as studentcount + FROM lehre.tbl_lehreinheit JOIN lehre.tbl_lehreinheitmitarbeiter USING(lehreinheit_id) + JOIN lehre.tbl_lehreinheitgruppe USING(lehreinheit_id) + JOIN lehre.tbl_lehrveranstaltung USING(lehrveranstaltung_id) + JOIN public.tbl_studiengang ON (tbl_lehreinheitgruppe.studiengang_kz = tbl_studiengang.studiengang_kz) + WHERE lehrveranstaltung_id = ? AND studiensemester_kurzbz = ? AND mitarbeiter_uid = ? + ORDER BY tbl_lehreinheitgruppe.gruppe_kurzbz"; + + return $this->execQuery($query, [$lva_id, $sem_kurzbz, $ma_uid]); + } } diff --git a/application/models/education/Lehrveranstaltung_model.php b/application/models/education/Lehrveranstaltung_model.php index 5422c290e..463c7bcfb 100644 --- a/application/models/education/Lehrveranstaltung_model.php +++ b/application/models/education/Lehrveranstaltung_model.php @@ -317,7 +317,7 @@ class Lehrveranstaltung_model extends DB_Model tbl_bisio.bisio_id, tbl_bisio.von, tbl_bisio.bis, tbl_student.studiengang_kz AS stg_kz_student, tbl_zeugnisnote.note, tbl_mitarbeiter.mitarbeiter_uid, tbl_person.matr_nr, tbl_benutzer.uid, UPPER(tbl_studiengang.typ::varchar(1) || tbl_studiengang.kurzbz) as kuerzel, tbl_studiengang.orgform_kurzbz, vw_student_lehrveranstaltung.semester, vw_student_lehrveranstaltung.studiensemester_kurzbz, vw_student_lehrveranstaltung.bezeichnung, - tbl_student.prestudent_id + tbl_student.prestudent_id, campus.vw_student_lehrveranstaltung.lehreinheit_id FROM campus.vw_student_lehrveranstaltung JOIN public.tbl_benutzer USING(uid) @@ -1346,4 +1346,65 @@ class Lehrveranstaltung_model extends DB_Model return $this->execQuery($qry, $params); } + + public function getLvForLektorInSemester($sem_kurzbz, $uid) { + $qry = "SELECT DISTINCT (tbl_lehrveranstaltung.lehrveranstaltung_id), + UPPER(tbl_studiengang.typ::varchar(1) || tbl_studiengang.kurzbz) as stg_kurzbz, + tbl_lehrveranstaltung.semester as lv_semester, + tbl_lehrveranstaltung.bezeichnung as lv_bezeichnung, + (SELECT kurzbz FROM public.tbl_mitarbeiter + WHERE mitarbeiter_uid=tbl_lehreinheitmitarbeiter.mitarbeiter_uid) as lektor + FROM + lehre.tbl_lehreinheit JOIN lehre.tbl_lehreinheitmitarbeiter USING(lehreinheit_id) + JOIN lehre.tbl_lehrveranstaltung USING(lehrveranstaltung_id) + JOIN public.tbl_studiengang USING(studiengang_kz) + JOIN lehre.tbl_lehrveranstaltung as lehrfach ON(tbl_lehreinheit.lehrfach_id=lehrfach.lehrveranstaltung_id) + WHERE + tbl_lehreinheit.studiensemester_kurzbz = ? + AND mitarbeiter_uid = ? + ORDER BY stg_kurzbz,lv_semester,lv_bezeichnung"; + + return $this->execReadOnlyQuery($qry, array($sem_kurzbz, $uid)); + } + + // used for cis4 mylv mitarbeiter + public function getLvsByMitarbeiterInSemester($mitarbeiter_uid, $sem_kurzbz) { + $qry = "SELECT * FROM ( + SELECT DISTINCT ON (lehre.tbl_lehrveranstaltung.lehrveranstaltung_id) + public.tbl_studiengang.studiengang_kz, + lehre.tbl_lehrveranstaltung.semester, + public.tbl_studiengang.bezeichnung as sg_bezeichnung, + public.tbl_studiengang.english as sg_bezeichnung_eng, + UPPER(tbl_studiengang.typ::varchar(1) || tbl_studiengang.kurzbz) as studiengang_kuerzel, + lehre.tbl_lehrveranstaltung.lehrveranstaltung_id, + lehre.tbl_lehrveranstaltung.bezeichnung, + lehre.tbl_lehrveranstaltung.bezeichnung_english as bezeichnung_eng, + lehre.tbl_lehrveranstaltung.farbe, + lehre.tbl_lehrveranstaltung.lvinfo, + lehre.tbl_lehrveranstaltung.benotung, + lehre.tbl_lehrveranstaltung.orgform_kurzbz, + lehre.tbl_lehrveranstaltung.sprache, + lehre.tbl_lehrveranstaltung.ects, + lehre.tbl_lehrveranstaltung.incoming + FROM + lehre.tbl_lehreinheit JOIN lehre.tbl_lehreinheitmitarbeiter USING(lehreinheit_id) + JOIN lehre.tbl_lehrveranstaltung USING(lehrveranstaltung_id) + JOIN public.tbl_studiengang USING(studiengang_kz) + JOIN lehre.tbl_lehrveranstaltung as lehrfach ON(tbl_lehreinheit.lehrfach_id=lehrfach.lehrveranstaltung_id) + WHERE + tbl_lehreinheit.studiensemester_kurzbz = ? + AND mitarbeiter_uid = ?) as distincted_by_lva_id + JOIN ( + SELECT lehrveranstaltung_id, TRUNC(SUM(lehre.tbl_lehreinheitmitarbeiter.semesterstunden)) as semesterstunden + FROM lehre.tbl_lehreinheit + JOIN lehre.tbl_lehreinheitmitarbeiter USING(lehreinheit_id) + JOIN lehre.tbl_lehrveranstaltung USING(lehrveranstaltung_id) + WHERE tbl_lehreinheit.studiensemester_kurzbz = ? + AND mitarbeiter_uid = ? + GROUP BY lehrveranstaltung_id + ) semesterstundenAggregatedSubquery USING(lehrveranstaltung_id) + ORDER BY studiengang_kuerzel, semester, bezeichnung"; + + return $this->execReadOnlyQuery($qry, [$sem_kurzbz, $mitarbeiter_uid, $sem_kurzbz, $mitarbeiter_uid]); + } } diff --git a/application/models/education/Lvgesamtnote_model.php b/application/models/education/Lvgesamtnote_model.php index c30045ff0..44645e75b 100644 --- a/application/models/education/Lvgesamtnote_model.php +++ b/application/models/education/Lvgesamtnote_model.php @@ -14,7 +14,7 @@ class Lvgesamtnote_model extends DB_Model } /** - * Laedt die Noten + * Laedt die Noten - lvgesamtnote (Vorschlag) JOIN tbl.note (zeugnisnote) * * @param integer $lehrveranstaltung_id * @param string $student_uid @@ -46,4 +46,19 @@ class Lvgesamtnote_model extends DB_Model return $this->loadWhere($where); } + + public function getLvGesamtNoteVorschlag($lehrveranstaltung_id, $student_uid, $studiensemester_kurzbz) + { + $qry = "SELECT * FROM campus.tbl_lvgesamtnote + WHERE campus.tbl_lvgesamtnote.student_uid = ? + AND campus.tbl_lvgesamtnote.studiensemester_kurzbz = ?"; + $params = [$student_uid, $studiensemester_kurzbz]; + + if ($lehrveranstaltung_id) { + $qry .= "AND campus.tbl_lvgesamtnote.lehrveranstaltung_id = ?"; + $params[] = $lehrveranstaltung_id; + } + + return $this->execReadOnlyQuery($qry, $params); + } } diff --git a/application/models/education/Note_model.php b/application/models/education/Note_model.php index 87a1501e0..e92bb8452 100644 --- a/application/models/education/Note_model.php +++ b/application/models/education/Note_model.php @@ -11,12 +11,33 @@ class Note_model extends DB_Model $this->dbTable = 'lehre.tbl_note'; $this->pk = 'note'; } - + public function getAllActive() { $qry ="SELECT * FROM lehre.tbl_note WHERE aktiv = true"; + + return $this->execReadOnlyQuery($qry); + } + + // used to determine the primary key of note "entschuldigt" to avoid hardcoded magic numbers + // that might differ in a different installation of fhcomplete + public function getEntschuldigtNote() { + $qry ="SELECT * + FROM lehre.tbl_note + WHERE bezeichnung = 'entschuldigt'"; return $this->execReadOnlyQuery($qry); } + + // used to determine the primary key of note "noch nicht eingetragen" to avoid hardcoded magic numbers + // that might differ in a different installation of fhcomplete + public function getNochNichtEingetragenNote() { + $qry ="SELECT * + FROM lehre.tbl_note + WHERE bezeichnung = 'Noch nicht eingetragen'"; + + return $this->execReadOnlyQuery($qry); + } + } \ No newline at end of file diff --git a/application/models/education/Notenschluesselaufteilung_model.php b/application/models/education/Notenschluesselaufteilung_model.php index d48e16b0b..f9031de47 100644 --- a/application/models/education/Notenschluesselaufteilung_model.php +++ b/application/models/education/Notenschluesselaufteilung_model.php @@ -26,6 +26,9 @@ class Notenschluesselaufteilung_model extends DB_Model $this->load->model('education/Notenschluesselzuordnung_model', 'NotenschluesselzuordnungModel'); $notenschluessel_kurzbz = $this->NotenschluesselzuordnungModel->getKurzbzForLv($lehrveranstaltung_id, $studiensemester_kurzbz); + if($notenschluessel_kurzbz == null) + return success(null); + $this->addSelect("note"); $this->addOrder("punkte", "DESC"); $this->addLimit(1); diff --git a/application/models/education/Paabgabe_model.php b/application/models/education/Paabgabe_model.php index 99b9b75f1..f073e5f73 100644 --- a/application/models/education/Paabgabe_model.php +++ b/application/models/education/Paabgabe_model.php @@ -61,6 +61,174 @@ class Paabgabe_model extends DB_Model return $this->execReadOnlyQuery($qry, array($person_id)); } + /** + * Gets project submissions for search criteria. + * @param array $projekttyp_kurzbz_arr contains all relevant project types (e.g. Bachelor) + * @param int $studiengang_kz study program + * @param string $abgabetyp_kurzbz project submission type (e.g. end upload, intermediate submission) + * @param string $abgabedatum due date for hand-in + * @param string $personSearchString for searching by person, i.e. name, uid, person/prestudent id + * @param int $limit limiting max number of results if search criteria is not precise enough + * @return object + */ + public function getPaAbgaben( + $projekttyp_kurzbz_arr, + $studiengang_kz = null, + $abgabetyp_kurzbz = null, + $abgabedatum = null, + $personSearchString = null, + $limit = 1000 + ) { + $params = []; + + $qry = " + SELECT + stg.bezeichnung AS stgbez, paabg.datum AS termin, + paabg.paabgabe_id, paabg.projektarbeit_id, paabg.paabgabetyp_kurzbz, paabg.abgabedatum, + abgabetyp.bezeichnung AS paabgabetyp_bezeichnung, ben.uid, pers.vorname, pers.nachname, pa.projekttyp_kurzbz, pa.titel, + UPPER(stg.typ || stg.kurzbz) AS studiengang_kuerzel, + ( + /* show all relevant Studiengänge of person and wether it is an employee*/ + SELECT + STRING_AGG(studiengang || ' ' || last_status, ' | ') + || (CASE WHEN EXISTS ( + SELECT 1 FROM public.tbl_mitarbeiter ma + JOIN public.tbl_benutzer ben ON ma.mitarbeiter_uid = ben.uid + WHERE person_id = prestudents.person_id + AND ben.aktiv + ) THEN ' | Mitarbeiter' ELSE '' END) + FROM ( + SELECT + DISTINCT person_id, prestudent_id, UPPER(stg.typ || stg.kurzbz) AS studiengang, + get_rolle_prestudent(ps.prestudent_id, null) AS last_status + FROM + public.tbl_prestudent ps + JOIN public.tbl_studiengang stg USING (studiengang_kz) + WHERE + person_id = pers.person_id + ORDER BY + prestudent_id DESC + ) prestudents + WHERE + last_status IN ('Abgewiesener','Aufgenommener', 'Student', 'Incoming', 'Diplomand', 'Abbrecher', 'Unterbrecher', 'Absolvent') + GROUP BY + person_id + LIMIT 1; + ) AS status + FROM + lehre.tbl_projektarbeit pa + JOIN campus.tbl_paabgabe paabg USING(projektarbeit_id) + JOIN campus.tbl_paabgabetyp abgabetyp USING(paabgabetyp_kurzbz) + LEFT JOIN public.tbl_benutzer ben ON(uid=student_uid) + LEFT JOIN public.tbl_person pers ON(ben.person_id=pers.person_id) + LEFT JOIN lehre.tbl_lehreinheit USING(lehreinheit_id) + LEFT JOIN lehre.tbl_lehrveranstaltung USING(lehrveranstaltung_id) + LEFT JOIN public.tbl_studiengang stg USING(studiengang_kz) + WHERE + TRUE"; + + if (isset($projekttyp_kurzbz_arr) && !isEmptyArray($projekttyp_kurzbz_arr)) + { + $qry .= " AND projekttyp_kurzbz IN ?"; + $params[] = $projekttyp_kurzbz_arr; + } + + if (isset($studiengang_kz) && is_numeric($studiengang_kz)) + { + $qry .= " AND stg.studiengang_kz=?"; + $params[] = $studiengang_kz; + } + + if (isset($abgabetyp_kurzbz)) + { + $qry .= " AND paabg.paabgabetyp_kurzbz=?"; + $params[] = $abgabetyp_kurzbz; + } + + if (isset($abgabedatum)) + { + $qry .= " AND paabg.datum=?"; + $params[] = $abgabedatum; + } + + if (is_numeric($personSearchString)) + { + $personSearchString = (int) $personSearchString; + $params = array_merge($params, [$personSearchString, $personSearchString]); + $qry .= " AND ( + pers.person_id = ? + OR EXISTS (SELECT 1 FROM public.tbl_prestudent WHERE person_id = pers.person_id AND prestudent_id = ?) + )"; + } + elseif (is_string($personSearchString)) + { + // remove empty spaces and lowercase + $personSearchString = strtolower(str_replace(' ', '', $personSearchString)); + $qry .= " AND ( + LOWER(REPLACE(pers.nachname || pers.vorname || pers.nachname, ' ', '')) LIKE ".$this->db->escape('%'.$personSearchString.'%')." + OR ben.uid LIKE ".$this->db->escape('%'.$personSearchString.'%')." + )"; + } + + $qry .= " ORDER BY nachname"; + + if (isset($limit) && is_numeric($limit) && (!isset($studiengang_kz) || !is_numeric($studiengang_kz)) && !isset($abgabedatum)) + { + $qry .= " LIMIT ?"; + $params[] = $limit; + } + + return $this->execReadOnlyQuery($qry, $params); + } + + /** + * Gets due dates for projekt submission search criteria. + * @param array $projekttyp_kurzbz_arr contains all relevant project types (e.g. Bachelor) + * @param int $studiengang_kz study program + * @param string $abgabetyp_kurzbz project submission type (e.g. end upload, intermediate submission) + * @return object + */ + public function getTermine($projekttyp_kurzbz_arr, $studiengang_kz, $abgabetyp_kurzbz) + { + $params = []; + + $qry = " + SELECT + DISTINCT tbl_paabgabe.datum as termin, to_char(tbl_paabgabe.datum, 'DD.MM.YYYY') as termin_anzeige + FROM + lehre.tbl_projektarbeit + JOIN campus.tbl_paabgabe USING(projektarbeit_id) + LEFT JOIN public.tbl_benutzer ON(uid=student_uid) + LEFT JOIN public.tbl_person ON(tbl_benutzer.person_id=tbl_person.person_id) + LEFT JOIN lehre.tbl_lehreinheit USING(lehreinheit_id) + LEFT JOIN lehre.tbl_lehrveranstaltung USING(lehrveranstaltung_id) + LEFT JOIN public.tbl_studiengang USING(studiengang_kz) + WHERE + TRUE"; + + if (isset($projekttyp_kurzbz_arr) && !isEmptyArray($projekttyp_kurzbz_arr)) + { + $qry .= " AND projekttyp_kurzbz IN ?"; + $params[] = $projekttyp_kurzbz_arr; + } + + if (isset($studiengang_kz) && is_numeric($studiengang_kz)) + { + $qry .= " AND public.tbl_studiengang.studiengang_kz=?"; + $params[] = $studiengang_kz; + } + + if (isset($abgabetyp_kurzbz)) + { + $qry .= " AND campus.tbl_paabgabe.paabgabetyp_kurzbz=?"; + $params[] = $abgabetyp_kurzbz; + } + + $qry .= " ORDER BY termin DESC"; + + return $this->execReadOnlyQuery($qry, $params); + } + public function findAbgabenNewOrUpdatedSince($interval, $relevantTypes) { diff --git a/application/models/education/Projektarbeit_model.php b/application/models/education/Projektarbeit_model.php index 3b1ea55e5..31e6301ba 100644 --- a/application/models/education/Projektarbeit_model.php +++ b/application/models/education/Projektarbeit_model.php @@ -23,9 +23,11 @@ class Projektarbeit_model extends DB_Model */ public function getProjektarbeit($student_uid, $studiengang_kz = null, $studiensemester_kurzbz = null, $projekttyp = null, $final = null) { + $sprache_index = "COALESCE((SELECT index FROM public.tbl_sprache WHERE sprache=" . $this->escape(getUserLanguage()) . " LIMIT 1), 1)"; $qry = "SELECT pa.*, tbl_projekttyp.bezeichnung, tbl_lehreinheit.studiensemester_kurzbz, tbl_lehrveranstaltung.lehrveranstaltung_id, + tbl_sprache.bezeichnung[".$sprache_index."] AS sprache_bezeichnung tbl_firma.name AS firma_name, ( SELECT @@ -44,6 +46,7 @@ class Projektarbeit_model extends DB_Model JOIN lehre.tbl_lehreinheit USING (lehreinheit_id) JOIN lehre.tbl_lehrveranstaltung USING (lehrveranstaltung_id) LEFT JOIN public.tbl_firma USING (firma_id) + LEFT JOIN public.tbl_sprache ON tbl_projektarbeit.sprache = tbl_sprache.sprache WHERE pa.student_uid = ?"; diff --git a/application/models/education/Pruefung_model.php b/application/models/education/Pruefung_model.php index 927d83c82..2f7ba8cf8 100644 --- a/application/models/education/Pruefung_model.php +++ b/application/models/education/Pruefung_model.php @@ -306,4 +306,5 @@ class Pruefung_model extends DB_Model return $this->loadWhereCommitteeExamsFailed(); } + } diff --git a/application/models/organisation/Studienplan_model.php b/application/models/organisation/Studienplan_model.php index 4a5f87832..66c23de0a 100644 --- a/application/models/organisation/Studienplan_model.php +++ b/application/models/organisation/Studienplan_model.php @@ -59,6 +59,37 @@ class Studienplan_model extends DB_Model 'tbl_studienplan_lehrveranstaltung.semester' => $semester )); } + + public function getStudienplanByLvaSemKurzbz($lehrveranstaltung_id, $studiensemester_kurzbz) { + $qry= " + SELECT + DISTINCT tbl_studienplan.* + FROM + lehre.tbl_studienplan + JOIN lehre.tbl_studienplan_lehrveranstaltung + USING(studienplan_id) + WHERE + tbl_studienplan_lehrveranstaltung.lehrveranstaltung_id IN ( + SELECT + lv.lehrveranstaltung_id + FROM + lehre.tbl_lehrveranstaltung AS lv + LEFT JOIN lehre.tbl_lehrveranstaltung AS t ON t.lehrveranstaltung_id=lv.lehrveranstaltung_template_id + WHERE + lv.lehrtyp_kurzbz<>'tpl' + AND (lv.lehrveranstaltung_id= ? OR (lv.lehrveranstaltung_template_id= ? AND t.lehrtyp_kurzbz='tpl')) + ) + AND EXISTS ( + SELECT 1 + FROM + lehre.tbl_studienplan_semester + WHERE studienplan_id=tbl_studienplan.studienplan_id + AND studiensemester_kurzbz= ? + AND semester = tbl_studienplan_lehrveranstaltung.semester) + ORDER BY bezeichnung"; + + return $this->execReadOnlyQuery($qry, array($lehrveranstaltung_id, $lehrveranstaltung_id, $studiensemester_kurzbz)); + } public function getStudienplanLehrveranstaltungForPrestudent($studienplan_id, $semester, $prestudent_id) { diff --git a/application/models/organisation/Studiensemester_model.php b/application/models/organisation/Studiensemester_model.php index 5fa6ffb14..bed138b8a 100644 --- a/application/models/organisation/Studiensemester_model.php +++ b/application/models/organisation/Studiensemester_model.php @@ -242,6 +242,30 @@ class Studiensemester_model extends DB_Model return $this->loadWhere(['uid' => $student_uid, 'v.lehre' => true]); } + public function getWhereMitarbeiterHasLvs($uid) { + // first order by year with last 2 letter from right, + // then order by WS/SS inside the years + // query it asc so the ordering magic in cis4 turns it around again + $qry = "WITH unique_semesters AS ( + SELECT DISTINCT ON (studiensemester_kurzbz) + studiensemester_kurzbz, + start, + ende, + bezeichnung, + studienjahr_kurzbz + FROM lehre.tbl_lehreinheit + JOIN lehre.tbl_lehreinheitmitarbeiter USING(lehreinheit_id) + JOIN public.tbl_studiensemester USING(studiensemester_kurzbz) + WHERE mitarbeiter_uid = ? + ) + SELECT * FROM unique_semesters + ORDER BY + RIGHT(studiensemester_kurzbz, 2) ASC, + LEFT(studiensemester_kurzbz, 2) ASC;"; + + return $this->execReadOnlyQuery($qry, [$uid]); + } + public function getAktAndFutureSemester() { $query = 'SELECT studiensemester_kurzbz diff --git a/application/models/ressource/Reservierung_model.php b/application/models/ressource/Reservierung_model.php index 0c391ea20..59de896eb 100644 --- a/application/models/ressource/Reservierung_model.php +++ b/application/models/ressource/Reservierung_model.php @@ -18,10 +18,10 @@ class Reservierung_model extends DB_Model * * @return stdClass */ - public function getReservierungen($start_date, $end_date, $ort_kurzbz = null) + public function getReservierungen($start_date, $end_date, $ort_kurzbz = null, $uid = null) { - - $lvplan_reservierungen_query="SELECT r.* , stund.beginn, stund.ende, + + $lvplan_reservierungen_query = "SELECT r.* , stund.beginn, stund.ende, CASE WHEN r.gruppe_kurzbz IS NOT NULL THEN r.gruppe_kurzbz ELSE CONCAT(UPPER(studg.typ),UPPER(studg.kurzbz),'-',COALESCE(CAST(r.semester AS varchar),'/'),COALESCE(CAST(r.verband AS varchar),'/')) @@ -35,7 +35,7 @@ class Reservierung_model extends DB_Model LEFT JOIN public.tbl_studiensemester ss2 ON slv.studiensemester_kurzbz = ss2.studiensemester_kurzbz AND ss2.start <=r.datum AND ss2.ende >= r.datum WHERE datum >= ? AND datum <= ? AND (ss1.studiensemester_kurzbz IS NOT NULL OR ss2.studiensemester_kurzbz IS NOT NULL)"; - + $raum_reservierungen_query = "SELECT res.*, beginn, ende, CASE WHEN res.gruppe_kurzbz IS NOT NULL THEN res.gruppe_kurzbz @@ -46,28 +46,29 @@ class Reservierung_model extends DB_Model JOIN lehre.tbl_stunde ON lehre.tbl_stunde.stunde = res.stunde WHERE res.ort_kurzbz = ? AND datum >= ? AND datum <= ?"; - $subquery = is_null($ort_kurzbz)? $lvplan_reservierungen_query:$raum_reservierungen_query; - - $query_result= $this->execReadOnlyQuery(" + $subquery = is_null($ort_kurzbz) ? $lvplan_reservierungen_query : $raum_reservierungen_query; + + $query_result = $this->execReadOnlyQuery(" SELECT - 'reservierung' as type, beginn, ende, datum, + DISTINCT(insertvon), + 'reservierung' as type, beginn, ende, datum, array_agg(DISTINCT reservierung_id) AS reservierung_id, COALESCE(titel, beschreibung) as topic, array_agg(DISTINCT mitarbeiter_kurzbz) as lektor, array_agg(DISTINCT (gruppe,verband,semester,studiengang_kz,gruppen_kuerzel)) as gruppe, - + array_agg(DISTINCT(uid)) as uids, ort_kurzbz, 'FFFFFF' as farbe FROM ( - ". $subquery ." + " . $subquery . " ) AS subquery - GROUP BY datum, beginn, ende, ort_kurzbz, titel, beschreibung + GROUP BY datum, beginn, ende, ort_kurzbz, titel, beschreibung, insertvon ORDER BY datum, beginn - ", is_null($ort_kurzbz) ?[getAuthUID(), getAuthUID(),$start_date,$end_date]: [$ort_kurzbz, $start_date, $end_date]); + ", is_null($ort_kurzbz) ? [$uid ?? getAuthUID(), $uid ?? getAuthUID(), $start_date, $end_date] : [$ort_kurzbz, $start_date, $end_date]); + - return $query_result; } @@ -76,7 +77,7 @@ class Reservierung_model extends DB_Model * * @return stdClass */ - public function getReservierungenMitarbeiter($start_date, $end_date) + public function getReservierungenMitarbeiter($start_date, $end_date, $uid = null) { $raum_reservierungen_query = "SELECT res.*, beginn, ende, @@ -91,8 +92,8 @@ class Reservierung_model extends DB_Model $subquery = $raum_reservierungen_query; - - $query_result= $this->execReadOnlyQuery(" + + $query_result = $this->execReadOnlyQuery(" SELECT 'reservierung' as type, beginn, ende, datum, COALESCE(titel, beschreibung) as topic, @@ -103,13 +104,13 @@ class Reservierung_model extends DB_Model FROM ( - ". $subquery ." + " . $subquery . " ) AS subquery GROUP BY datum, beginn, ende, ort_kurzbz, titel, beschreibung ORDER BY datum, beginn - ", [getAuthUID(), $start_date, $end_date]); + ", [$uid ?? getAuthUID(), $start_date, $end_date]); return $query_result; @@ -129,9 +130,9 @@ class Reservierung_model extends DB_Model $this->addJoin('public.tbl_studiensemester ss2', 'slv.studiensemester_kurzbz=ss2.studiensemester_kurzbz AND ss2.start<=r.datum AND ss2.ende>=r.datum', 'LEFT'); $this->db->or_where('ss1.studiensemester_kurzbz IS NOT NULL', null, false); $this->db->or_where('ss2.studiensemester_kurzbz IS NOT NULL', null, false); - + $query = $this->db->get_compiled_select('campus.vw_reservierung r'); - + return $this->execQuery($query, [$uid, $uid]); } diff --git a/application/models/ressource/Stundenplan_model.php b/application/models/ressource/Stundenplan_model.php index d0a97ed9d..997451243 100644 --- a/application/models/ressource/Stundenplan_model.php +++ b/application/models/ressource/Stundenplan_model.php @@ -388,6 +388,84 @@ class Stundenplan_model extends DB_Model ORDER BY datum, beginn", [$start_date, $end_date, $ma_uid]); } + + /** + * queries Stundenplan and filters by studiengang, semester, verband gruppe + * + * @return void + */ + public function getStundenplanStudiengang($start_date, $end_date, $stg_kz, $sem, $verband, $gruppe) { + + $qry_params = [$start_date, $end_date, $stg_kz]; + + $qry = " + SELECT + 'lehreinheit' as type, beginn, ende, datum, + CONCAT(lehrfach,'-',lehrform) as topic, + array_agg(DISTINCT lektor) as lektor, + array_agg(DISTINCT (gruppe,verband,semester,studiengang_kz,gruppen_kuerzel)) as gruppe, + string_agg(DISTINCT ort_kurzbz, '/') as ort_kurzbz, + array_agg(DISTINCT lehreinheit_id) as lehreinheit_id, + titel, lehrfach, lehrform, lehrfach_bez, organisationseinheit, farbe, lehrveranstaltung_id + FROM + ( + SELECT unr,datum,beginn, ende, + CASE + WHEN sp.mitarbeiter_kurzbz IS NOT NULL THEN sp.mitarbeiter_kurzbz + ELSE sp.lektor + END as lektor, + CASE + WHEN gruppe_kurzbz IS NOT NULL THEN gruppe_kurzbz + ELSE CONCAT(UPPER(sp.stg_typ),UPPER(sp.stg_kurzbz),'-',COALESCE(CAST(sp.semester AS varchar),'/'),COALESCE(CAST(sp.verband AS varchar),'/')) + END as gruppen_kuerzel, + (SELECT bezeichnung + FROM public.tbl_organisationseinheit + WHERE oe_kurzbz IN( + SELECT oe_kurzbz + FROM lehre.tbl_lehrveranstaltung + WHERE lehrveranstaltung_id = sp.lehrveranstaltung_id + )) as organisationseinheit, + sp.ort_kurzbz, sp.studiengang_kz, sp.titel,sp.lehreinheit_id,sp.lehrfach_id,sp.anmerkung,fix,lehrveranstaltung_id,stg_kurzbzlang,stg_bezeichnung,stg_typ,fachbereich_kurzbz,lehrfach,lehrfach_bez,farbe,lehrform,anmerkung_lehreinheit,gruppe, verband, semester,stg_kurzbz + FROM ( + SELECT sp.* + FROM lehre.vw_stundenplan sp + WHERE + sp.datum >= ? + AND sp.datum <= ? + ) sp + JOIN lehre.tbl_stunde ON lehre.tbl_stunde.stunde = sp.stunde + WHERE studiengang_kz = ? "; + + if($sem != NULL) + { + $qry_params[] = $sem; + $qry .= " AND (semester = ? OR semester IS NULL)"; + } + if($verband != NULL) + { + $qry_params[] = $verband; + $qry .= " AND (verband = ? OR verband IS NULL OR verband = '0' OR verband = '')"; + } + if($gruppe != NULL) + { + $qry_params[] = $gruppe; + $qry .= " AND (gruppe = ? OR gruppe IS NULL OR gruppe = '0' OR gruppe = '') "; + } + $qry.= " AND ( + gruppe_kurzbz is null OR EXISTS( + SELECT 1 + FROM + public.tbl_gruppe WHERE gruppe_kurzbz = sp.gruppe_kurzbz AND direktinskription = false + ) + )"; + + $qry.= " ) as subquery + + GROUP BY unr, datum, beginn, ende, titel, lehrform, lehrfach, lehrfach_bez, organisationseinheit, farbe, lehrveranstaltung_id + ORDER BY datum, beginn; "; + + return $this->execReadOnlyQuery($qry, $qry_params); + } /** * NO STANDALONE FUNCTION - Generates a SQL query string to fetch 'stundenplan' events for a specific student within the current semester. diff --git a/application/models/ressource/Zeitsperre_model.php b/application/models/ressource/Zeitsperre_model.php index 078d29d8b..c7dc9dfff 100644 --- a/application/models/ressource/Zeitsperre_model.php +++ b/application/models/ressource/Zeitsperre_model.php @@ -12,6 +12,8 @@ class Zeitsperre_model extends DB_Model $this->pk = 'zeitsperre_id'; } + const BLOCKIERENDE_ZEITSPERREN = ['Krank','Urlaub','ZA','DienstV','PflegeU','DienstF','CovidSB','CovidKS']; + /** * Save or update Zeitsperre. * @@ -61,4 +63,128 @@ class Zeitsperre_model extends DB_Model return $this->execQuery($qry); } + + /** + * get Zeitsperren of a user + * + * @param $uid mitarbeiteruid + * @param $bisgrenze @true show only entries of actual business year (1.9.- 31.8.) + * + * @return array + */ + public function getZeitsperrenUser($uid, $bisgrenze = true) + { + $qry = " + SELECT + tbl_zeitsperre.*, tbl_zeitsperretyp.*, tbl_erreichbarkeit.farbe AS erreichbarkeit_farbe, + tbl_erreichbarkeit.beschreibung AS erreichbarkeit_beschreibung, + CONCAT (ps.vorname, ' ', ps.nachname) as vertretung + FROM (campus.tbl_zeitsperre JOIN campus.tbl_zeitsperretyp USING (zeitsperretyp_kurzbz)) + LEFT JOIN campus.tbl_erreichbarkeit USING (erreichbarkeit_kurzbz) + LEFT JOIN public.tbl_benutzer ON campus.tbl_zeitsperre.vertretung_uid = public.tbl_benutzer.uid + LEFT JOIN public.tbl_person ps USING (person_id) + WHERE mitarbeiter_uid= ? + "; + + if($bisgrenze) + { + $qry.=" + AND ( + (date_part('month',vondatum)>=9 AND date_part('year', vondatum)>='".(date('Y')-1)."') + OR + (date_part('month',vondatum)<9 AND date_part('year', vondatum)>='".(date('Y'))."') + )"; + } + + $qry.= " ORDER BY vondatum DESC"; + + return $this->execQuery($qry, array('mitarbeiter_uid' => $uid)); + } + + /** + * check a date for existing zeitsperre + * + * @param $uid mitarbeiteruid + * @param $datum datum to check + * @param $stunde stunde (default = null) + * @param bool $nurblockierend if only hr relevante zeitsperren have to be checked + * + * @return array + */ + public function getSperreByDate($uid, $datum, $stunde = null, $nurblockierend = false) + { + $parametersArray = [$datum, $datum]; + + $qry = " + SELECT + * + FROM + campus.tbl_zeitsperre + WHERE + vondatum <= ? + AND bisdatum>= ?"; + + if($nurblockierend) + { + $qry .= " AND zeitsperretyp_kurzbz IN ('" + . implode("','", self::BLOCKIERENDE_ZEITSPERREN) + . "')"; + } + + if(!is_null($stunde)) + { + $parametersArray = array_merge( + $parametersArray, + [$datum, $stunde, $datum, $datum, $stunde, $datum] + ); + + $qry.=" AND + ((vondatum= ? AND vonstunde<= ? OR vonstunde is null OR vondatum<> ?) AND + (bisdatum= ? AND bisstunde>= ? OR bisstunde is null OR bisdatum<> ?))"; + } + + array_push($parametersArray, $uid); + + $qry .= "AND mitarbeiter_uid= ? "; + + return $this->execQuery($qry, $parametersArray); + } + + /** + * check a date for existing zeitsperre + * + * @param $uid mitarbeiteruid + * @param $vondatum datum in Format IS0 + * @param $bisdatum datum in Format ISO + * + * @return array + */ + public function existsZeitaufzeichnung($uid, $vonDay, $bisDay) + { + try { + $from = new DateTime($vonDay); + $to = new DateTime($bisDay); + } catch (Exception $e) { + throw new Exception("Invalid date format"); + } + + //remove hour stamps + $from->setTime(0, 0, 0); + $to->setTime(0, 0, 0)->modify('+1 day'); + + $fromSql = $from->format('Y-m-d'); + $toSql = $to->format('Y-m-d'); + $params = [$uid, $fromSql, $toSql]; + + $qry = " + SELECT * + FROM campus.tbl_zeitaufzeichnung + WHERE uid = ? + AND start >= ? + AND ende < ? "; + + $result = $this->execQuery($qry, $params); + + return $result; + } } diff --git a/application/views/CisRouterView/CisRouterView.php b/application/views/CisRouterView/CisRouterView.php index 6ff428362..1aa8c398e 100644 --- a/application/views/CisRouterView/CisRouterView.php +++ b/application/views/CisRouterView/CisRouterView.php @@ -11,6 +11,7 @@ $includesArray = array( 'skipID' => '#fhccontent', 'vuedatepicker11' => true, 'customCSSs' => array( + 'vendor/vuejs/vuedatepicker_css/main.css', 'public/css/components/verticalsplit.css', 'public/css/components/searchbar/searchbar.css', 'public/css/Fhc.css', @@ -24,22 +25,30 @@ $includesArray = array( 'public/css/components/abgabetool/abgabe.css', 'public/css/Cis4/Cms.css', 'public/css/Cis4/Studium.css', + 'public/css/Cis4/Benotungstool.css', + 'public/css/Cis4/Zeitsperren.css', ), 'customJSs' => array( 'vendor/npm-asset/primevue/accordion/accordion.min.js', 'vendor/npm-asset/primevue/accordiontab/accordiontab.min.js', 'vendor/npm-asset/primevue/checkbox/checkbox.min.js', + 'vendor/npm-asset/primevue/chips/chips.min.js', + 'vendor/npm-asset/primevue/multiselect/multiselect.min.js', 'vendor/npm-asset/primevue/inputnumber/inputnumber.min.js', 'vendor/npm-asset/primevue/speeddial/speeddial.min.js', 'vendor/npm-asset/primevue/textarea/textarea.min.js', 'vendor/npm-asset/primevue/timeline/timeline.min.js', 'vendor/npm-asset/primevue/inplace/inplace.min.js', 'vendor/npm-asset/primevue/message/message.min.js', + 'vendor/npm-asset/primevue/divider/divider.min.js', + 'vendor/npm-asset/primevue/password/password.js', + 'vendor/npm-asset/primevue/multiselect/multiselect.js', 'vendor/npm-asset/primevue/tieredmenu/tieredmenu.js', 'vendor/moment/luxonjs/luxon.min.js' ), 'customJSModules' => array( - 'public/js/apps/Dashboard/Fhc.js', + 'public/js/apps/Cis/Cis.js', + 'vendor/olifolkerd/tabulator5/src/js/modules/ColumnCalcs/ColumnCalcs.js' ), ); @@ -47,8 +56,6 @@ $includesArray = array( $this->load->view('templates/CISVUE-Header', $includesArray); ?>
> - +
load->view('templates/CISVUE-Footer', $includesArray); ?> diff --git a/application/views/lehre/lehrauftrag/Dashboard.php b/application/views/lehre/lehrauftrag/Dashboard.php index 3da01700c..60faa2649 100644 --- a/application/views/lehre/lehrauftrag/Dashboard.php +++ b/application/views/lehre/lehrauftrag/Dashboard.php @@ -1,7 +1,5 @@ load->view( - 'templates/FHC-Header', - array( +$includesArray = array( 'title' => 'Lehrauftrag bestellen', 'jquery3' => true, 'jqueryui1' => true, @@ -12,8 +10,15 @@ $this->load->view( 'dialoglib' => true, 'navigationwidget' => true, 'addons' => true, - ) ); + + +$this->load->view( + 'templates/FHC-Header', + $includesArray +); + + ?> widgetlib->widget('NavigationWidget'); ?> @@ -33,4 +38,12 @@ $this->load->view( -load->view('templates/FHC-Footer'); ?> +load->view( + 'templates/FHC-Footer', + $includesArray +); + + ?> diff --git a/application/views/lehre/lehrauftrag/LehrendeUebersicht.php b/application/views/lehre/lehrauftrag/LehrendeUebersicht.php index 4f49a2518..a91bac29d 100644 --- a/application/views/lehre/lehrauftrag/LehrendeUebersicht.php +++ b/application/views/lehre/lehrauftrag/LehrendeUebersicht.php @@ -1,7 +1,6 @@ load->view( - 'templates/FHC-Header', - array( + +$includesArray = array( 'title' => 'Lehrauftrag bestellen', 'jquery3' => true, 'bootstrap3' => true, @@ -9,8 +8,13 @@ $this->load->view( 'sbadmintemplate3' => true, 'ajaxlib' => true, 'navigationwidget' => true, - ) ); + +$this->load->view( + 'templates/FHC-Header', + $includesArray +); + ?> widgetlib->widget('NavigationWidget'); ?> @@ -34,4 +38,11 @@ $this->load->view( -load->view('templates/FHC-Footer'); ?> +load->view( + 'templates/FHC-Footer', + $includesArray +); + +?> diff --git a/application/views/lehre/lehrauftrag/acceptLehrauftrag.php b/application/views/lehre/lehrauftrag/acceptLehrauftrag.php index 0e8aa13cb..33d3fe2e0 100644 --- a/application/views/lehre/lehrauftrag/acceptLehrauftrag.php +++ b/application/views/lehre/lehrauftrag/acceptLehrauftrag.php @@ -1,89 +1,101 @@ load->view( - 'templates/FHC-Header', - array( - 'title' => 'Lehrauftrag annehmen', - 'jquery3' => true, - 'jqueryui1' => true, - 'jquerycheckboxes1' => true, - 'bootstrap5' => true, - 'fontawesome6' => true, - 'sbadmintemplate' => false, - 'tabulator5' => true, - 'tabulator5JQuery' => true, - 'cis'=>true, - 'momentjs2' => true, - 'ajaxlib' => true, - 'dialoglib' => true, - 'tablewidget' => true, - 'phrases' => array( - 'global' => array( - 'lehrauftraegeAnnehmen', - 'dokumentePDF', - 'PDFLehrauftraegeFH', - 'PDFLehrauftraegeLehrgaenge' - ), - 'ui' => array( - 'anzeigen', - 'alleAnzeigen', - 'nurBestellteAnzeigen', - 'nurErteilteAnzeigen', - 'nurAngenommeneAnzeigen', - 'nurStornierteAnzeigen', - 'hilfeZuDieserSeite', - 'alleAuswaehlen', - 'alleAbwaehlen', - 'ausgewaehlteZeilen', - 'hilfe', - 'tabelleneinstellungen', - 'keineDatenVorhanden', - 'spaltenEinstellen', - 'bestelltVon', - 'erteiltVon', - 'angenommenVon', - 'storniertVon', - 'lehrauftragInBearbeitung', - 'wartetAufErteilung', - 'wartetAufErneuteErteilung', - 'letzterStatusBestellt', - 'letzterStatusErteilt', - 'letzterStatusAngenommen', - 'vertragWurdeStorniert', - ), - 'password' => array('password'), - 'dms' => array('informationsblattExterneLehrende'), - 'table' => array( - 'spaltenEinAusblenden', - 'spaltenEinAusblendenMitKlickOeffnen', - 'spaltenEinAusblendenAufEinstellungenKlicken', - 'spaltenEinAusblendenMitKlickAktivieren', - 'spaltenEinAusblendenMitKlickSchliessen', - 'spaltenbreiteVeraendern', - 'spaltenbreiteVeraendernText', - 'spaltenbreiteVeraendernInfotext', - 'zeilenAuswaehlen', - 'zeilenAuswaehlenEinzeln', - 'zeilenAuswaehlenBereich', - 'zeilenAuswaehlenAlle' - ), - 'lehre' => array( - 'lehrauftraegeAnnehmen', - 'lehrauftraegeAnnehmenText', - 'lehrauftraegeAnnehmenKlickStatusicon', - 'lehrauftraegeAnnehmenLehrauftraegeWaehlen', - 'lehrauftraegeAnnehmenMitKlickAnnehmen', - 'lehrauftraegeNichtAuswaehlbar', - 'lehrauftraegeNichtAuswaehlbarTextBeiAnnahme', - 'filterAlleBeiAnnahme', - 'filterErteiltBeiAnnahme', - 'filterAngenommen' - ) + +$includesArray = array( + 'title' => 'Lehrauftrag annehmen', + 'jquery3' => true, + 'jqueryui1' => true, + 'jquerycheckboxes1' => true, + 'bootstrap5' => true, + 'fontawesome6' => true, + 'sbadmintemplate' => false, + 'tabulator5' => true, + 'tabulator5JQuery' => true, + 'cis'=>true, + 'momentjs2' => true, + 'ajaxlib' => true, + 'dialoglib' => true, + 'tablewidget' => true, + 'phrases' => array( + 'global' => array( + 'lehrauftraegeAnnehmen', + 'dokumentePDF', + 'PDFLehrauftraegeFH', + 'PDFLehrauftraegeLehrgaenge' ), - 'customJSs' => array( - 'public/js/bootstrapper.js', - 'public/js/lehre/lehrauftrag/acceptLehrauftrag.js') + 'ui' => array( + 'anzeigen', + 'alleAnzeigen', + 'nurBestellteAnzeigen', + 'nurErteilteAnzeigen', + 'nurAngenommeneAnzeigen', + 'nurStornierteAnzeigen', + 'hilfeZuDieserSeite', + 'alleAuswaehlen', + 'alleAbwaehlen', + 'ausgewaehlteZeilen', + 'hilfe', + 'tabelleneinstellungen', + 'keineDatenVorhanden', + 'spaltenEinstellen', + 'bestelltVon', + 'erteiltVon', + 'angenommenVon', + 'storniertVon', + 'lehrauftragInBearbeitung', + 'wartetAufErteilung', + 'wartetAufErneuteErteilung', + 'letzterStatusBestellt', + 'letzterStatusErteilt', + 'letzterStatusAngenommen', + 'vertragWurdeStorniert', + ), + 'password' => array('password'), + 'dms' => array('informationsblattExterneLehrende'), + 'table' => array( + 'spaltenEinAusblenden', + 'spaltenEinAusblendenMitKlickOeffnen', + 'spaltenEinAusblendenAufEinstellungenKlicken', + 'spaltenEinAusblendenMitKlickAktivieren', + 'spaltenEinAusblendenMitKlickSchliessen', + 'spaltenbreiteVeraendern', + 'spaltenbreiteVeraendernText', + 'spaltenbreiteVeraendernInfotext', + 'zeilenAuswaehlen', + 'zeilenAuswaehlenEinzeln', + 'zeilenAuswaehlenBereich', + 'zeilenAuswaehlenAlle' + ), + 'lehre' => array( + 'lehrauftraegeAnnehmen', + 'lehrauftraegeAnnehmenText', + 'lehrauftraegeAnnehmenKlickStatusicon', + 'lehrauftraegeAnnehmenLehrauftraegeWaehlen', + 'lehrauftraegeAnnehmenMitKlickAnnehmen', + 'lehrauftraegeNichtAuswaehlbar', + 'lehrauftraegeNichtAuswaehlbarTextBeiAnnahme', + 'filterAlleBeiAnnahme', + 'filterErteiltBeiAnnahme', + 'filterAngenommen' + ) + ), + 'customJSs' => array( + 'public/js/bootstrapper.js', + 'public/js/lehre/lehrauftrag/acceptLehrauftrag.js' ) ); + +if (defined("CIS4")) { + $this->load->view( + 'templates/CISVUE-Header', + $includesArray + ); +} else { + $this->load->view( + 'templates/FHC-Header', + $includesArray + ); +} + ?> @@ -230,5 +242,17 @@ $this->load->view(
-load->view('templates/FHC-Footer'); ?> +load->view( + 'templates/CISVUE-Footer', + $includesArray + ); +} else { + $this->load->view( + 'templates/FHC-Footer', + $includesArray + ); +} +?> diff --git a/application/views/lehre/lehrauftrag/approveLehrauftrag.php b/application/views/lehre/lehrauftrag/approveLehrauftrag.php index a598be015..822cd396e 100644 --- a/application/views/lehre/lehrauftrag/approveLehrauftrag.php +++ b/application/views/lehre/lehrauftrag/approveLehrauftrag.php @@ -1,98 +1,101 @@ 'Lehrauftrag erteilen', + 'jquery3' => true, + 'jqueryui1' => true, + 'jquerycheckboxes1' => true, + 'bootstrap3' => true, + 'fontawesome6' => true, + 'sbadmintemplate3' => true, + 'tabulator5' => true, + 'tabulator5JQuery' => true, + 'momentjs2' => true, + 'ajaxlib' => true, + 'dialoglib' => true, + 'tablewidget' => true, + 'navigationwidget' => true, + 'phrases' => array( + 'global' => array( + 'lehrauftraegeErteilen', + 'mehrHilfe', + 'weitereInformationenUnter' + ), + 'ui' => array( + 'anzeigen', + 'alleAnzeigen', + 'nurNeueAnzeigen', + 'nurBestellteAnzeigen', + 'nurErteilteAnzeigen', + 'nurAngenommeneAnzeigen', + 'nurGeaenderteAnzeigen', + 'nurDummiesAnzeigen', + 'hilfeZuDieserSeite', + 'alleAuswaehlen', + 'alleAbwaehlen', + 'ausgewaehlteZeilen', + 'hilfe', + 'tabelleneinstellungen', + 'keineDatenVorhanden', + 'spaltenEinstellen', + 'bestelltVon', + 'erteiltVon', + 'angenommenVon', + 'stundenStundensatzGeaendert', + 'neuerLehrauftragOhneLektorVerplant', + 'wartetAufBestellung', + 'wartetAufErneuteBestellung', + 'neuerLehrauftragWartetAufBestellung', + 'letzterStatusBestellt', + 'letzterStatusErteilt', + 'letzterStatusAngenommen', + ), + 'table' => array( + 'spaltenEinAusblenden', + 'spaltenEinAusblendenMitKlickOeffnen', + 'spaltenEinAusblendenAufEinstellungenKlicken', + 'spaltenEinAusblendenMitKlickAktivieren', + 'spaltenEinAusblendenMitKlickSchliessen', + 'spaltenbreiteVeraendern', + 'spaltenbreiteVeraendernText', + 'spaltenbreiteVeraendernInfotext', + 'zeilenAuswaehlen', + 'zeilenAuswaehlenEinzeln', + 'zeilenAuswaehlenBereich', + 'zeilenAuswaehlenAlle' + ), + 'lehre' => array( + 'lehrauftragStandardBestellprozess', + 'lehrauftragStandardBestellprozessBestellen', + 'lehrauftragStandardBestellprozessErteilen', + 'lehrauftragStandardBestellprozessAnnehmen', + 'lehrauftraegeErteilen', + 'lehrauftraegeErteilenText', + 'lehrauftraegeErteilenKlickStatusicon', + 'lehrauftraegeErteilenLehrauftraegeWaehlen', + 'lehrauftraegeErteilenMitKlickErteilen', + 'geaenderteLehrauftraege', + 'geaenderteLehrauftraegeTextBeiErteilung', + 'lehrauftraegeNichtAuswaehlbar', + 'lehrauftraegeNichtAuswaehlbarTextBeiErteilung', + 'filterAlle', + 'filterNeu', + 'filterBestellt', + 'filterErteilt', + 'filterAngenommen', + 'filterGeaendert', + 'filterDummies' + ) + ), + 'customJSs' => array( + 'public/js/bootstrapper.js', + 'public/js/lehre/lehrauftrag/approveLehrauftrag.js' + ) +); + + $this->load->view( - 'templates/FHC-Header', - array( - 'title' => 'Lehrauftrag erteilen', - 'jquery3' => true, - 'jqueryui1' => true, - 'jquerycheckboxes1' => true, - 'bootstrap3' => true, - 'fontawesome6' => true, - 'sbadmintemplate3' => true, - 'tabulator5' => true, - 'tabulator5JQuery' => true, - 'momentjs2' => true, - 'ajaxlib' => true, - 'dialoglib' => true, - 'tablewidget' => true, - 'navigationwidget' => true, - 'phrases' => array( - 'global' => array( - 'lehrauftraegeErteilen', - 'mehrHilfe', - 'weitereInformationenUnter' - ), - 'ui' => array( - 'anzeigen', - 'alleAnzeigen', - 'nurNeueAnzeigen', - 'nurBestellteAnzeigen', - 'nurErteilteAnzeigen', - 'nurAngenommeneAnzeigen', - 'nurGeaenderteAnzeigen', - 'nurDummiesAnzeigen', - 'hilfeZuDieserSeite', - 'alleAuswaehlen', - 'alleAbwaehlen', - 'ausgewaehlteZeilen', - 'hilfe', - 'tabelleneinstellungen', - 'keineDatenVorhanden', - 'spaltenEinstellen', - 'bestelltVon', - 'erteiltVon', - 'angenommenVon', - 'stundenStundensatzGeaendert', - 'neuerLehrauftragOhneLektorVerplant', - 'wartetAufBestellung', - 'wartetAufErneuteBestellung', - 'neuerLehrauftragWartetAufBestellung', - 'letzterStatusBestellt', - 'letzterStatusErteilt', - 'letzterStatusAngenommen', - ), - 'table' => array( - 'spaltenEinAusblenden', - 'spaltenEinAusblendenMitKlickOeffnen', - 'spaltenEinAusblendenAufEinstellungenKlicken', - 'spaltenEinAusblendenMitKlickAktivieren', - 'spaltenEinAusblendenMitKlickSchliessen', - 'spaltenbreiteVeraendern', - 'spaltenbreiteVeraendernText', - 'spaltenbreiteVeraendernInfotext', - 'zeilenAuswaehlen', - 'zeilenAuswaehlenEinzeln', - 'zeilenAuswaehlenBereich', - 'zeilenAuswaehlenAlle' - ), - 'lehre' => array( - 'lehrauftragStandardBestellprozess', - 'lehrauftragStandardBestellprozessBestellen', - 'lehrauftragStandardBestellprozessErteilen', - 'lehrauftragStandardBestellprozessAnnehmen', - 'lehrauftraegeErteilen', - 'lehrauftraegeErteilenText', - 'lehrauftraegeErteilenKlickStatusicon', - 'lehrauftraegeErteilenLehrauftraegeWaehlen', - 'lehrauftraegeErteilenMitKlickErteilen', - 'geaenderteLehrauftraege', - 'geaenderteLehrauftraegeTextBeiErteilung', - 'lehrauftraegeNichtAuswaehlbar', - 'lehrauftraegeNichtAuswaehlbarTextBeiErteilung', - 'filterAlle', - 'filterNeu', - 'filterBestellt', - 'filterErteilt', - 'filterAngenommen', - 'filterGeaendert', - 'filterDummies' - ) - ), - 'customJSs' => array( - 'public/js/bootstrapper.js', - 'public/js/lehre/lehrauftrag/approveLehrauftrag.js' - ) - ) + 'templates/FHC-Header', + $includesArray ); ?> @@ -208,5 +211,13 @@ $this->load->view(
-load->view('templates/FHC-Footer'); ?> +load->view( + 'templates/FHC-Footer', + $includesArray +); + + ?> diff --git a/application/views/lehre/lehrauftrag/orderLehrauftrag.php b/application/views/lehre/lehrauftrag/orderLehrauftrag.php index 6806aa401..107db2871 100644 --- a/application/views/lehre/lehrauftrag/orderLehrauftrag.php +++ b/application/views/lehre/lehrauftrag/orderLehrauftrag.php @@ -1,98 +1,101 @@ 'Lehrauftrag bestellen', + 'jquery3' => true, + 'jqueryui1' => true, + 'jquerycheckboxes1' => true, + 'bootstrap3' => true, + 'fontawesome6' => true, + 'sbadmintemplate3' => true, + 'tabulator5' => true, + 'tabulator5JQuery' => true, + 'momentjs2' => true, + 'ajaxlib' => true, + 'dialoglib' => true, + 'tablewidget' => true, + 'navigationwidget' => true, + 'phrases' => array( + 'global' => array( + 'lehrauftraegeBestellen', + 'mehrHilfe', + 'weitereInformationenUnter' + ), + 'ui' => array( + 'anzeigen', + 'alleAnzeigen', + 'nurNeueAnzeigen', + 'nurBestellteAnzeigen', + 'nurErteilteAnzeigen', + 'nurAngenommeneAnzeigen', + 'nurGeaenderteAnzeigen', + 'nurDummiesAnzeigen', + 'hilfeZuDieserSeite', + 'alleAuswaehlen', + 'alleAbwaehlen', + 'ausgewaehlteZeilen', + 'hilfe', + 'tabelleneinstellungen', + 'keineDatenVorhanden', + 'spaltenEinstellen', + 'bestelltVon', + 'erteiltVon', + 'angenommenVon', + 'neuerLehrauftragOhneLektorVerplant', + 'neuerLehrauftragWartetAufBestellung', + 'letzterStatusBestellt', + 'letzterStatusErteilt', + 'letzterStatusAngenommen', + 'nachAenderungStundensatzStunden', + 'vorAenderungStundensatzStunden' + ), + 'table' => array( + 'spaltenEinAusblenden', + 'spaltenEinAusblendenMitKlickOeffnen', + 'spaltenEinAusblendenAufEinstellungenKlicken', + 'spaltenEinAusblendenMitKlickAktivieren', + 'spaltenEinAusblendenMitKlickSchliessen', + 'spaltenbreiteVeraendern', + 'spaltenbreiteVeraendernText', + 'spaltenbreiteVeraendernInfotext', + 'zeilenAuswaehlen', + 'zeilenAuswaehlenEinzeln', + 'zeilenAuswaehlenBereich', + 'zeilenAuswaehlenAlle' + ), + 'lehre' => array( + 'lehrauftragStandardBestellprozess', + 'lehrauftragStandardBestellprozessBestellen', + 'lehrauftragStandardBestellprozessErteilen', + 'lehrauftragStandardBestellprozessAnnehmen', + 'lehrauftraegeBestellen', + 'lehrauftraegeBestellenText', + 'lehrauftraegeBestellenKlickStatusicon', + 'lehrauftraegeBestellenLehrauftraegeWaehlen', + 'lehrauftraegeBestellenMitKlickBestellen', + 'lehrauftraegeBestellenVertragWirdAngelegt', + 'geaenderteLehrauftraege', + 'geaenderteLehrauftraegeText', + 'lehrauftraegeNichtAuswaehlbar', + 'lehrauftraegeNichtAuswaehlbarText', + 'filterAlle', + 'filterNeu', + 'filterBestellt', + 'filterErteilt', + 'filterAngenommen', + 'filterGeaendert', + 'filterDummies' + ) + ), + 'customJSs' => array( + 'public/js/bootstrapper.js', + 'public/js/lehre/lehrauftrag/orderLehrauftrag.js' + ) +); + + $this->load->view( - 'templates/FHC-Header', - array( - 'title' => 'Lehrauftrag bestellen', - 'jquery3' => true, - 'jqueryui1' => true, - 'jquerycheckboxes1' => true, - 'bootstrap3' => true, - 'fontawesome6' => true, - 'sbadmintemplate3' => true, - 'tabulator5' => true, - 'tabulator5JQuery' => true, - 'momentjs2' => true, - 'ajaxlib' => true, - 'dialoglib' => true, - 'tablewidget' => true, - 'navigationwidget' => true, - 'phrases' => array( - 'global' => array( - 'lehrauftraegeBestellen', - 'mehrHilfe', - 'weitereInformationenUnter' - ), - 'ui' => array( - 'anzeigen', - 'alleAnzeigen', - 'nurNeueAnzeigen', - 'nurBestellteAnzeigen', - 'nurErteilteAnzeigen', - 'nurAngenommeneAnzeigen', - 'nurGeaenderteAnzeigen', - 'nurDummiesAnzeigen', - 'hilfeZuDieserSeite', - 'alleAuswaehlen', - 'alleAbwaehlen', - 'ausgewaehlteZeilen', - 'hilfe', - 'tabelleneinstellungen', - 'keineDatenVorhanden', - 'spaltenEinstellen', - 'bestelltVon', - 'erteiltVon', - 'angenommenVon', - 'neuerLehrauftragOhneLektorVerplant', - 'neuerLehrauftragWartetAufBestellung', - 'letzterStatusBestellt', - 'letzterStatusErteilt', - 'letzterStatusAngenommen', - 'nachAenderungStundensatzStunden', - 'vorAenderungStundensatzStunden' - ), - 'table' => array( - 'spaltenEinAusblenden', - 'spaltenEinAusblendenMitKlickOeffnen', - 'spaltenEinAusblendenAufEinstellungenKlicken', - 'spaltenEinAusblendenMitKlickAktivieren', - 'spaltenEinAusblendenMitKlickSchliessen', - 'spaltenbreiteVeraendern', - 'spaltenbreiteVeraendernText', - 'spaltenbreiteVeraendernInfotext', - 'zeilenAuswaehlen', - 'zeilenAuswaehlenEinzeln', - 'zeilenAuswaehlenBereich', - 'zeilenAuswaehlenAlle' - ), - 'lehre' => array( - 'lehrauftragStandardBestellprozess', - 'lehrauftragStandardBestellprozessBestellen', - 'lehrauftragStandardBestellprozessErteilen', - 'lehrauftragStandardBestellprozessAnnehmen', - 'lehrauftraegeBestellen', - 'lehrauftraegeBestellenText', - 'lehrauftraegeBestellenKlickStatusicon', - 'lehrauftraegeBestellenLehrauftraegeWaehlen', - 'lehrauftraegeBestellenMitKlickBestellen', - 'lehrauftraegeBestellenVertragWirdAngelegt', - 'geaenderteLehrauftraege', - 'geaenderteLehrauftraegeText', - 'lehrauftraegeNichtAuswaehlbar', - 'lehrauftraegeNichtAuswaehlbarText', - 'filterAlle', - 'filterNeu', - 'filterBestellt', - 'filterErteilt', - 'filterAngenommen', - 'filterGeaendert', - 'filterDummies' - ) - ), - 'customJSs' => array( - 'public/js/bootstrapper.js', - 'public/js/lehre/lehrauftrag/orderLehrauftrag.js' - ) - ) + 'templates/FHC-Header', + $includesArray ); ?> @@ -209,5 +212,11 @@ $this->load->view(
-load->view('templates/FHC-Footer'); ?> +load->view( + 'templates/FHC-Footer', + $includesArray +); + ?> diff --git a/application/views/lehre/pruefungsprotokoll.php b/application/views/lehre/pruefungsprotokoll.php index 3ac23da6a..cdc206377 100644 --- a/application/views/lehre/pruefungsprotokoll.php +++ b/application/views/lehre/pruefungsprotokoll.php @@ -9,22 +9,8 @@ $sitesettings = array( 'ajaxlib' => true, 'sbadmintemplate3' => true, 'phrases' => array( - 'abschlusspruefung' => array( - 'freigegebenAm', - 'pruefungGespeichert', - 'pruefungSpeichernFehler', - 'abschlussbeurteilungLeer', - 'beginnzeitLeer', - 'beginnzeitFormatError', - 'endezeitLeer', - 'endezeitFormatError', - 'endezeitBeforeError', - 'verfNotice' - ), - 'ui' => array( - 'stunde', - 'minute' - ) + 'abschlusspruefung', + 'ui' ), 'customCSSs' => array( 'public/css/sbadmin2/admintemplate_contentonly.css', @@ -37,10 +23,18 @@ $sitesettings = array( ) ); -$this->load->view( - 'templates/FHC-Header', - $sitesettings -); +if(defined('CIS4')){ + $this->load->view( + 'templates/CISVUE-Header', + $sitesettings + ); +}else{ + $this->load->view( + 'templates/FHC-Header', + $sitesettings + ); +} + ?>
@@ -161,6 +155,14 @@ $this->load->view( studiengangstyp == 'Bachelor' ? $this->p->t('abschlusspruefung', 'pruefungsgegenstandBachelor') : $this->p->t('abschlusspruefung', 'pruefungsgegenstandMaster')) ?> + + + p->t('abschlusspruefung', 'spracheDerArbeit') ?>  + + + abschlussarbeit_sprache ?? '' ?> + + p->t('global', 'notizen')); ?> @@ -201,7 +203,9 @@ $this->load->view(

freigabedatum); ?> - +

@@ -236,7 +240,14 @@ $this->load->view(
load->view( - 'templates/FHC-Footer', - $sitesettings -); +if (defined('CIS4')) { + $this->load->view( + 'templates/CISVUE-Footer', + $sitesettings + ); +} else { + $this->load->view( + 'templates/FHC-Footer', + $sitesettings + ); +} \ No newline at end of file diff --git a/application/views/templates/CISVUE-Footer.php b/application/views/templates/CISVUE-Footer.php index d7c1de24c..eae2a94ff 100644 --- a/application/views/templates/CISVUE-Footer.php +++ b/application/views/templates/CISVUE-Footer.php @@ -6,7 +6,7 @@ $includesArray = array( 'fontawesome6' => true, 'axios027' => true, 'customJSModules' => array_merge([ - 'public/js/apps/Cis.js' + 'public/js/apps/Cis/Menu.js' ], $customJSModules ?? []), 'customCSSs' => array_merge([ 'public/css/Cis4/Cis.css' diff --git a/application/views/templates/CISVUE-Header.php b/application/views/templates/CISVUE-Header.php index 804a43821..d98cbc9cd 100644 --- a/application/views/templates/CISVUE-Header.php +++ b/application/views/templates/CISVUE-Header.php @@ -8,7 +8,7 @@ $includesArray = array( 'axios027' => true, 'primevue3' => true, 'customJSModules' => array_merge([ - 'public/js/apps/Cis.js' + 'public/js/apps/Cis/Menu.js' ], $customJSModules ?? []), 'customCSSs' => array_merge([ 'public/css/Cis4/Cis.css', diff --git a/application/views/widgets/navigationMenu.php b/application/views/widgets/navigationMenu.php index 42dd901fb..974128343 100644 --- a/application/views/widgets/navigationMenu.php +++ b/application/views/widgets/navigationMenu.php @@ -1,4 +1,4 @@ - ` -} +} \ No newline at end of file diff --git a/public/js/components/Calendar/Base.js b/public/js/components/Calendar/Base.js index 36d9877da..aeb374cde 100644 --- a/public/js/components/Calendar/Base.js +++ b/public/js/components/Calendar/Base.js @@ -44,7 +44,10 @@ export default { return () => true; }), hasDragoverFunc: Vue.computed(() => this.onDragover), - mode: Vue.computed(() => this.mode) + mode: Vue.computed(() => this.mode), + reservierbarMap: Vue.computed(() => this.reservierbarMap), + isReservierbar: Vue.computed(() => this.isReservierbar), + createContext: Vue.computed(() => this.createContext) }; }, props: { @@ -97,7 +100,13 @@ export default { draggableEvents: [Boolean, Array, Function], dropableEvents: [Boolean, Array, Function], onDragover: Function, - onDrop: Function + onDrop: Function, + isReservierbar: Boolean, + createContext: Object, + reservierbarMap: { + type: Object, + default: () => ({}) + }, }, emits: [ "click:next", @@ -105,11 +114,13 @@ export default { "click:mode", "click:event", "click:day", + "click:slot", "click:week", "update:date", "update:mode", "update:range", - "drop" + "drop", + "create-event" ], data() { return { diff --git a/public/js/components/Calendar/Base/DragAndDrop.js b/public/js/components/Calendar/Base/DragAndDrop.js index 631a792a8..fb6cb1848 100644 --- a/public/js/components/Calendar/Base/DragAndDrop.js +++ b/public/js/components/Calendar/Base/DragAndDrop.js @@ -20,7 +20,8 @@ export default { }, inject: { mode: "mode", - dropableEvents: "dropableEvents" + dropableEvents: "dropableEvents", + timezone: "timezone" }, props: { events: Array, diff --git a/public/js/components/Calendar/Base/Grid.js b/public/js/components/Calendar/Base/Grid.js index 3418a9151..1a10533b3 100644 --- a/public/js/components/Calendar/Base/Grid.js +++ b/public/js/components/Calendar/Base/Grid.js @@ -2,6 +2,7 @@ import GridLine from './Grid/Line.js'; import GridLineEvent from './Grid/Line/Event.js'; import CalDnd from '../../../directives/Calendar/DragAndDrop.js'; +import CalClick from '../../../directives/Calendar/Click.js'; export default { name: "CalendarGrid", @@ -10,12 +11,15 @@ export default { GridLineEvent }, directives: { - CalDnd + CalDnd, + CalClick }, inject: { originalEvents: "events", originalBackgrounds: "backgrounds", - dropAllowed: "dropAllowed" + dropAllowed: "dropAllowed", + timezone: "timezone", + reservierbar: "isReservierbar" }, provide() { return { @@ -308,6 +312,20 @@ export default { } else { this.$refs.scroller.scrollTo(0, 0); } + }, + isFreeSlot(date, part, dayEvents) { + const pastEnd = luxon.DateTime.now().setZone(this.timezone); + + const start = date.plus(part.start || part); + const end = date.plus(part.end || part.plus({ hours: 1 })); + + if (start < pastEnd) + return false; + + if (!dayEvents || !dayEvents.length) + return true; + + return !dayEvents.some(ev => ev.start < end && ev.end > start); } }, beforeUnmount() { @@ -400,6 +418,18 @@ export default { :style="'grid-' + axisCol + ':' + (1+index) + ';grid-' + axisRow + ':ps_' + i + '/pe_' + i" > + +
+
+ +
+
+
{ - const rows = [1, -1]; + formattedEvents() { + let formattedEvents = this.events.map((event) => { + event.rows = [1, -1]; if (event.startsHere) { - rows[0] = 't_' + event.start.diff(this.date).toMillis(); + event.rows[0] = + "t_" + event.start.diff(this.date).toMillis(); } if (event.endsHere) { - rows[1] = 't_' + event.end.diff(this.date).toMillis(); + event.rows[1] = "t_" + event.end.diff(this.date).toMillis(); } - events.push({ - ...event, - rows - }); + return event; }); - return events; - } + + if (this.shouldCompactEvents && this.compactibleEventTypes?.length) { + formattedEvents = + this.compactEvents(formattedEvents, this.compactibleEventTypes); + } + + return formattedEvents; + }, }, - template: /* html */` + methods: { + compactEvents(events, compactibleEventTypes) { + let formattedEvents = events + .filter( + (event) => + !compactibleEventTypes.includes(event.type), + ) + .map((event) => { + event.display = "default"; + return event; + }); + let eventsToBeCompacted = events.filter((event) => + compactibleEventTypes.includes(event.type), + ); + let compactedEvents = []; + + eventsToBeCompacted.forEach((event) => { + let existingCompactedEvent = compactedEvents.find( + (compactedEvent) => + event.rows[0] === compactedEvent.rows[0] && + event.rows[1] === compactedEvent.rows[1], + ); + + if (!existingCompactedEvent) { + compactedEvents.push({ + events: [ + { + farbe: event.orig.farbe, + }, + ], + rows: event.rows, + }); + } else { + existingCompactedEvent.events.push({ + farbe: event.orig.farbe, + }); + } + }); + + compactedEvents.forEach((compactedEvent) => { + if (compactedEvent.events.length < 4) { + formattedEvents.push({ + display: "compacted", + ...compactedEvent, + }); + } else { + formattedEvents.push({ + display: "compacted", + events: compactedEvent.events.slice(0, 3), + rows: compactedEvent.rows, + }); + formattedEvents.push({ + display: "compactedExtra", + events: compactedEvent.events.slice(3), + rows: compactedEvent.rows, + }); + } + }); + + return formattedEvents; + }, + }, + template: /* html */ `
- - - +
- ` -} + `, +}; diff --git a/public/js/components/Calendar/LvPlan.js b/public/js/components/Calendar/LvPlan.js index e0e918f01..d7049b23f 100644 --- a/public/js/components/Calendar/LvPlan.js +++ b/public/js/components/Calendar/LvPlan.js @@ -3,24 +3,20 @@ import FhcCalendar from "./Base.js"; import ApiLvPlan from '../../api/factory/lvPlan.js'; import { useEventLoader } from '../../composables/EventLoader.js'; +import { useRenderers } from '../../composables/Renderers.js'; import ModeDay from './Mode/Day.js'; import ModeWeek from './Mode/Week.js'; import ModeMonth from './Mode/Month.js'; +import ModeList from './Mode/List.js'; export default { name: "CalendarLvPlan", components: { FhcCalendar }, - inject: [ - "renderers" - ], + inject: ["isMobile"], props: { - timezone: { - type: String, - required: true - }, date: { type: [Date, String, Number, luxon.DateTime], default: luxon.DateTime.local() @@ -32,20 +28,49 @@ export default { getPromiseFunc: { type: Function, required: true - } + }, + reservierbar: { + type: Boolean, + default: false + }, + createContext: { + type: Object, + default: () => ({}) + }, + }, + provide() { + return { + shouldCompactEvents: Vue.computed( + () => this.$props.mode === "Month" && this.isMobile, + ), + compactibleEventTypes: Vue.computed( + () => this.compactibleEventTypes, + ), + }; }, emits: [ "update:date", "update:mode", - "update:range" + "update:range", + "create-event", + "delete-event", + 'update:reservierbarMap' ], data() { return { - modes: { - day: Vue.markRaw(ModeDay), - week: Vue.markRaw(ModeWeek), - month: Vue.markRaw(ModeMonth) - }, + timezone: FHC_JS_DATA_STORAGE_OBJECT.timezone, + isReservierbar: Vue.computed(() => { + if (!this.reservierbar) + return false; + + if (!this.reservierbarMap) + return false; + + if (typeof this.reservierbarMap === 'object') + return Object.keys(this.reservierbarMap).length > 0; + + return false; + }), modeOptions: { day: { emptyMessage: Vue.computed(() => this.$p.t('lehre/noLvFound')), @@ -53,9 +78,13 @@ export default { }, week: { collapseEmptyDays: false - } + }, + list: { + length: 7, + }, }, - teachingunits: null + teachingunits: null, + compactibleEventTypes: [], }; }, computed: { @@ -77,7 +106,20 @@ export default { label: now.startOf('minute').toISOTime({ suppressSeconds: true, includeOffset: false }) } ]; - } + }, + modes() { + let modes = { + day: Vue.markRaw(ModeDay), + month: Vue.markRaw(ModeMonth), + }; + if (this.isMobile) { + modes.list = Vue.markRaw(ModeList); + } else { + modes.week = Vue.markRaw(ModeWeek); + } + + return modes; + }, }, methods: { eventStyle(event) { @@ -88,33 +130,55 @@ export default { updateRange(rangeInterval) { this.rangeInterval = rangeInterval; this.$emit('update:range', rangeInterval); - } + }, + resetEventLoader() { + this.reset(); + }, + async getStunden() { + let stundenResponse = await this.$api.call(ApiLvPlan.getStunden()); + this.teachingunits = stundenResponse.data.map((el) => ({ + id: el.stunde, + start: el.beginn, + end: el.ende, + })); + }, + async getCompactibleEventTypes() { + let compactibleEventTypesResponse = await this.$api.call( + ApiLvPlan.getCompactibleEventTypes(), + ); + this.compactibleEventTypes = compactibleEventTypesResponse.data; + }, + closeModal() { + this.$refs.calendar.hideEventModal(); + }, }, setup(props, context) { const rangeInterval = Vue.ref(null); - const { events, lv } = useEventLoader(rangeInterval, props.getPromiseFunc); + const { events, lv, reservierbarMap, reset } = useEventLoader(rangeInterval, props.getPromiseFunc); Vue.watch(lv, newValue => { context.emit('update:lv', newValue); }); + const { renderers } = useRenderers(); + + Vue.watch(reservierbarMap, newVal => { + context.emit('update:reservierbarMap', newVal); + }); + return { rangeInterval, events, - lv + lv, + reservierbarMap, + reset, + renderers }; }, - created() { - this.$api - .call(ApiLvPlan.getStunden()) - .then(res => { - return this.teachingunits = res.data.map(el => ({ - id: el.stunde, - start: el.beginn, - end: el.ende - })); - }); + async created() { + await this.getStunden(); + await this.getCompactibleEventTypes(); }, template: /* html */` diff --git a/public/js/components/Calendar/Mode/Day/View.js b/public/js/components/Calendar/Mode/Day/View.js index cd953f33a..18de8207c 100644 --- a/public/js/components/Calendar/Mode/Day/View.js +++ b/public/js/components/Calendar/Mode/Day/View.js @@ -16,7 +16,19 @@ export default { inject: { timeGrid: "timeGrid", originalEvents: "events", - timezone: "timezone" + timezone: "timezone", + reservierbar: { + from: "isReservierbar", + default: false + }, + reservierbarMap: { + type: Object, + default: () => ({}) + }, + createContext: { + from: 'createContext', + default: () => {} + }, }, props: { day: { @@ -103,6 +115,27 @@ export default { }); } } + else if (evt.detail.source == 'slot') + { + if (!this.reservierbar) + return; + + const { date, part } = evt.detail.value || {}; + if (!date) + return; + let reservierbar = this.reservierbarMap?.[date.toISODate()] === true; + if (!reservierbar) + return; + + this.$emit('requestModalOpen', { + event: { + type: this.createContext?.scope ?? 'slot', + start: date.plus(part.start || part), + end: date.plus(part.end || part.plus({ hours: 1 })), + createContext: this.createContext + } + }); + } } }, setup() { diff --git a/public/js/components/Calendar/Mode/List.js b/public/js/components/Calendar/Mode/List.js index 76acfc610..92a42a1e9 100644 --- a/public/js/components/Calendar/Mode/List.js +++ b/public/js/components/Calendar/Mode/List.js @@ -93,7 +93,7 @@ export default { mounted() { this.$emit('update:range', this.range); }, - template: ` + template: /*html*/ `
{} + }, + }, props: { currentDate: { type: luxon.DateTime, @@ -83,14 +91,33 @@ export default { }, 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; + 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; + case 'slot': + { + const { date, part } = evt.detail.value || {}; + if (!date) + return; + let reservierbar = this.reservierbarMap?.[date.toISODate()] === true; + if (!reservierbar) + return; + + this.$emit('requestModalOpen', { + event: { + type: this.createContext?.scope ?? 'slot', + start: date.plus(part.start || part), + end: date.plus(part.end || part.plus({ hours: 1 })), + createContext: this.createContext + } + }); + break; + } } } }, diff --git a/public/js/components/Calendar/Widget.js b/public/js/components/Calendar/Widget.js index f9d641d4e..54109268e 100644 --- a/public/js/components/Calendar/Widget.js +++ b/public/js/components/Calendar/Widget.js @@ -1,6 +1,7 @@ import FhcCalendar from "./Base.js"; import { useEventLoader } from '../../composables/EventLoader.js'; +import { useRenderers } from '../../composables/Renderers.js'; import ModeList from '../Calendar/Mode/List.js'; @@ -9,22 +10,17 @@ export default { components: { FhcCalendar }, - inject: [ - "renderers" - ], props: { - timezone: { - type: String, - required: true - }, getPromiseFunc: { type: Function, required: true } }, data() { + const timezone = FHC_JS_DATA_STORAGE_OBJECT.timezone; return { - now: luxon.DateTime.now().setZone(this.timezone), + timezone, + now: luxon.DateTime.now().setZone(timezone), modes: { list: Vue.markRaw(ModeList) }, @@ -59,10 +55,12 @@ export default { const rangeInterval = Vue.ref(null); const { events } = useEventLoader(rangeInterval, props.getPromiseFunc); + const { renderers } = useRenderers(); return { rangeInterval, - events + events, + renderers }; }, template: /* html */` diff --git a/public/js/components/Cis/Abgabetool/AbgabetoolAssistenz.js b/public/js/components/Cis/Abgabetool/AbgabetoolAssistenz.js index bb5c6a710..d65f7222b 100644 --- a/public/js/components/Cis/Abgabetool/AbgabetoolAssistenz.js +++ b/public/js/components/Cis/Abgabetool/AbgabetoolAssistenz.js @@ -42,14 +42,6 @@ export const AbgabetoolAssistenz = { stg_kz_prop: { default: null }, - viewData: { - type: Object, - required: true, - default: () => ({name: '', uid: ''}), - validator(value) { - return value && value.uid // && value.name -> extensive viewData use only for cis4 onwards - } - } }, data() { return { @@ -530,41 +522,20 @@ export const AbgabetoolAssistenz = { const table = this.$refs.abgabeTable.tabulator this.tableBuiltResolve() - - table.on("columnMoved", () => { - this.saveState(table); - }); - - table.on("columnResized", () => { - this.saveState(table); - }); - - table.on("columnVisibilityChanged", () => { - this.saveState(table); - }); - - table.on("filterChanged", () => { - this.saveState(table); - }); - - table.on("headerFilterChanged", () => { - this.saveState(table); - }); - - table.on("dataSorted", () => { - this.saveState(table); - }); - - table.on("columnSorted", () => { - this.saveState(table); - }); - - table.on("sortersChanged", () => { - this.saveState(table); - }); const saved = this.loadState(); + // setup change eventlisteners + const events = [ + "columnMoved", "columnResized", "columnVisibilityChanged", + "filterChanged", "headerFilterChanged", "dataSorted", + "columnSorted", "sortersChanged" + ]; + + events.forEach(eventName => { + table.on(eventName, () => this.saveState(table)); + }); + table.on("renderComplete", () => { if(!this.stateRestored) { @@ -1007,7 +978,6 @@ export const AbgabetoolAssistenz = { this.tableData = this.mapProjekteToTableData(this.projektarbeiten) await this.tableBuiltPromise - this.$refs.abgabeTable.tabulator.setData(this.tableData); }, loadProjektarbeiten(all = false, callback) { diff --git a/public/js/components/Cis/Abgabetool/AbgabetoolMitarbeiter.js b/public/js/components/Cis/Abgabetool/AbgabetoolMitarbeiter.js index 6791d31aa..0acb46bb6 100644 --- a/public/js/components/Cis/Abgabetool/AbgabetoolMitarbeiter.js +++ b/public/js/components/Cis/Abgabetool/AbgabetoolMitarbeiter.js @@ -31,16 +31,6 @@ export const AbgabetoolMitarbeiter = { old_abgabe_beurteilung_link: Vue.computed(() => this.old_abgabe_beurteilung_link) } }, - props: { - viewData: { - type: Object, - required: true, - default: () => ({name: '', uid: ''}), - validator(value) { - return value && value.uid // && value.name -> extensive viewData use only for cis4 onwards - } - } - }, data() { return { tableData: null, diff --git a/public/js/components/Cis/Abgabetool/AbgabetoolStudent.js b/public/js/components/Cis/Abgabetool/AbgabetoolStudent.js index e8401d309..24b2f7619 100644 --- a/public/js/components/Cis/Abgabetool/AbgabetoolStudent.js +++ b/public/js/components/Cis/Abgabetool/AbgabetoolStudent.js @@ -3,6 +3,7 @@ import ApiAbgabe from '../../../api/factory/abgabe.js' import BsModal from "../../Bootstrap/Modal.js"; import FhcOverlay from "../../Overlay/FhcOverlay.js"; import { getDateStyleClass} from "./getDateStyleClass.js"; +import ApiAuthinfo from "../../../api/factory/authinfo.js"; export const AbgabetoolStudent = { name: "AbgabetoolStudent", @@ -24,14 +25,6 @@ export const AbgabetoolStudent = { student_uid_prop: { default: null }, - viewData: { - type: Object, - required: true, - default: () => ({uid: ''}), - validator(value) { - return value && value.uid - } - } }, data() { return { @@ -44,9 +37,18 @@ export const AbgabetoolStudent = { detail: null, projektarbeiten: null, selectedProjektarbeit: null, - moodle_link: null + moodle_link: null, + uid: null, }; }, + computed: { + isViewMode() { + return this.student_uid !== this.uid + }, + student_uid() { + return this.student_uid_prop || this.uid || null + } + }, methods: { checkQualityGatesStrict(termine) { let qgate1Passed = false @@ -258,18 +260,11 @@ export const AbgabetoolStudent = { }, handleDownloadBeurteilung2(projektarbeit) { window.open(projektarbeit.beurteilung2) - } - }, - watch: { - - }, - computed: { - isViewMode() { - return this.student_uid !== this.viewData.uid }, - student_uid() { - return this.student_uid_prop || this.viewData?.uid || null - } + async fetchAuthUID() { + const authIdResponse = await this.$api.call(ApiAuthinfo.getAuthUID()); + this.uid = authIdResponse.data.uid; + }, }, async created() { this.phrasenPromise = this.$p.loadCategory(['abgabetool', 'global']) @@ -302,6 +297,8 @@ export const AbgabetoolStudent = { }).catch(e => { this.loading = false }) + + await this.fetchAuthUID(); }, mounted() { this.setupMounted() diff --git a/public/js/components/Cis/Abgabetool/DeadlineOverview.js b/public/js/components/Cis/Abgabetool/DeadlineOverview.js index 6d0ddd02d..5f6d1e159 100644 --- a/public/js/components/Cis/Abgabetool/DeadlineOverview.js +++ b/public/js/components/Cis/Abgabetool/DeadlineOverview.js @@ -10,14 +10,6 @@ export const DeadlineOverview = { person_uid_prop: { default: null }, - viewData: { - type: Object, - required: true, - default: () => ({name: '', uid: ''}), - validator(value) { - return value && value.name && value.uid - } - } }, data() { return { diff --git a/public/js/components/Cis/Benotungstool/Benotungstool.js b/public/js/components/Cis/Benotungstool/Benotungstool.js new file mode 100644 index 000000000..3e3374164 --- /dev/null +++ b/public/js/components/Cis/Benotungstool/Benotungstool.js @@ -0,0 +1,2347 @@ +import {CoreFilterCmpt} from "../../filter/Filter.js"; +import ApiLehre from "../../../api/factory/lehre.js"; +import ApiNoten from "../../../api/factory/noten.js"; +import ApiStudiensemester from "../../../api/factory/studiensemester.js"; +import BsModal from '../../Bootstrap/Modal.js'; +import BsOffcanvas from '../../Bootstrap/Offcanvas.js'; +import VueDatePicker from '../../vueDatepicker.js.php'; +import LehreinheitenModule from '../../DropdownModes/LehreinheitenModule'; +import MobilityLegende from '../../Mobility/Legende.js'; +import FhcOverlay from "../../Overlay/FhcOverlay.js"; +import {debounce} from "../../../helpers/debounce.js"; +import {centeredFormatter} from "../../../tabulator/formatter/centered.js"; + +export const Benotungstool = { + name: "Benotungstool", + components: { + BsModal, + BsOffcanvas, + CoreFilterCmpt, + MobilityLegende, + Dropdown: primevue.dropdown, + Divider: primevue.divider, + InputNumber: primevue.inputnumber, + Password: primevue.password, + Textarea: primevue.textarea, + Datepicker: VueDatePicker, + Multiselect: primevue.multiselect, + FhcOverlay + }, + props: { + lv_id: { + default: null, + required: true + }, + sem_kurzbz: { + default: null, + required: true + }, + viewData: { + type: Object, + required: true, + default: () => ({uid: ''}), + validator(value) { + return value && value.uid + } + } + }, + data() { + return { + headerFiltersRestored: false, + filtersRestored: false, + colLayoutRestored: false, + sortRestored: false, + stateRestored: false, + persistenceID: 'notenToolTable2026-02-16', + debouncedFetchPunkteForPruefung: null, + config: null, // cis config + neuesPruefungsdatumModalVisible: false, + loading: false, + selectedUids: [], // shared selection state + selectedLehreinheit: null, + tabulatorCanBeBuilt: false, + selectedPruefungNote: null, + selectedPruefungDate: new Date(), // v-model for pruefung edit datepicker + selectedPruefungPunkte: null, + distinctPruefungsDates: null, + pruefungStudent: null, + pruefung: null, + password: '', + changedNotenCounter: 0, + tabulatorUuid: Vue.ref(0), + domain: '', + importString: '', + teilnoten: null, + lv: null, + studenten: null, + pruefungen: null, + studiensemester: null, + selectedSemester: null, + lehrveranstaltungen: null, + selectedLehrveranstaltung: null, + tableBuiltResolve: null, + notenOptions: null, + notenOptionsLehre: null, + notenOptionsPromise: null, + tableBuiltPromise: null, + notenTableOptions: null, // built later when noten are available + notenTableEventHandlers: [ + { + event: "rowSelectionChanged", + handler: async (data, rows) => { + // avoid an expensive update loop if selection happens in modal + if(this.neuesPruefungsdatumModalVisible) return + + if(data.length == 1 && this.selectedUids.length == 1 && data[0].uid === this.selectedUids[0].uid){ + // special case to work around an internal tabulator selection quirk + this.selectedUids = [] + } else { + this.selectedUids = data.filter(d => d.selectable); + } + + } + }, + { + event: "cellEdited", + handler: async (cell) => { + const field = cell.getField() + + if(field === 'note_vorschlag') { + const rowData = cell.getRow().getData(); + const newValue = cell.getValue(); + const original = rowData._originalNoteVorschlag; + + // If nothing was selected, restore + if (newValue == null || newValue === "" || newValue === original) { + // revert value + cell.setValue(original, true); + } + + delete rowData._originalNoteVorschlag; // Clean up + + const row = cell.getRow() + row.reformat() // trigger reformat of arrow + } + } + }, + { + event: "cellClick", + handler: async (e, cell) => { + const field = cell.getField() + + if(field == "mobility_zusatz") { + this.$refs.drawer.show() + e.stopPropagation() + this.undoSelection(cell) + } else if (field == "punkte" || field == "note_vorschlag" || field == "übernehmen") { + this.undoSelection(cell) + } + } + } + ]}; + }, + methods: { + loadState() { + return JSON.parse(localStorage.getItem(this.persistenceID) || "null"); + }, + saveState(table) { + // Only save if we have finished the initial restoration + // AND the table actually has columns (to avoid saving empty states) + if (!this.stateRestored) return; + + const rawLayout = table.getColumnLayout(); + const filteredLayout = rawLayout.filter(col => { + if(this.notenTableOptions.columns.some(colDef => colDef.field === col.field)) return col + return null + }) + + // TODO: if dynamic cols have sort/filter/headerfilter functionality filter them here before persisting + // into local storage + const rawSorters = table.getSorters() + + const rawFilters = table.getFilters() + + const rawHeaderFilters = table.getHeaderFilters() + const state = { + columns: filteredLayout.map(col => ({ + field: col.field, + visible: col.visible, + width: col.width, + })), + sort: rawSorters.map(s => ({ + field: s.field, + dir: s.dir, + })), + filters: rawFilters, + headerFilters: rawHeaderFilters + }; + + localStorage.setItem(this.persistenceID, JSON.stringify(state)); + }, + handleTableBuilt() { + const table = this.$refs.notenTable.tabulator; + + this.tableBuiltResolve() + + const saved = this.loadState(); + + // setup change eventlisteners + const events = [ + "columnMoved", "columnResized", "columnVisibilityChanged", + "filterChanged", "headerFilterChanged", "dataSorted", + "columnSorted", "sortersChanged" + ]; + + events.forEach(eventName => { + table.on(eventName, () => this.saveState(table)); + }); + + // renderComplete restore state logic + table.on("renderComplete", () => { + if (this.stateRestored) return; + + // layout restore should be happening in setupData() + + if (saved?.filters && !this.filtersRestored) { + this.filtersRestored = true; + table.setFilter(saved.filters); + } + + if (saved?.headerFilters && !this.headerFiltersRestored) { + this.headerFiltersRestored = true; + saved.headerFilters.forEach(hf => { + table.setHeaderFilterValue(hf.field, hf.value); + }); + } + + if (saved?.sort?.length && !this.sortRestored) { + this.sortRestored = true; + setTimeout(() => { + const sortList = saved.sort.map(s => { + const col = table.columnManager.findColumn(s.field); + return col ? { column: col, dir: s.dir } : null; + }).filter(Boolean); + + if (sortList.length) { + table.setSort(sortList); + } + }, 100); + } + + this.stateRestored = true; + }); + + // finalize the promise + if (this.tableResolve) this.tableResolve(); + }, + undoSelection(cell) { + // checks if cells row is selected and unselects -> imitates columns which dont trigger row selection + // but actually just revert it after the fact + + const row = cell.getRow() + if(row.isSelected()) { + row.deselect(); + } + }, + // using this to expose input event of editor element properly, tabulator makes it hard to access on default editor + // implemented after tabulator/src/js/modules/edit/defaults/editors/number.js + liveNumberEditor(cell, onRendered, success, cancel) { + const editor = document.createElement("input"); + editor.setAttribute("type", "number"); + editor.value = cell.getValue(); + + const row = cell.getRow() + const rowData = row.getData() + + rowData._debouncedFetchNoteForPunkte = debounce(this.fetchNoteForPunkte, 500) + editor.addEventListener("input", (e) => { + rowData._debouncedFetchNoteForPunkte(e.target.value, row) + }); + + onRendered(() => { + editor.focus(); + editor.style.height = "100%"; + }); + + editor.addEventListener("change", () => success(editor.value)); + editor.addEventListener("blur", () => success(editor.value)); + editor.addEventListener("keydown", (e) => { + if (e.keyCode === 13) success(editor.value); + if (e.keyCode === 27) cancel(); + }); + + return editor; + }, + fetchNoteForPunkte(valueParam, row) { + const value = valueParam == '' ? null : valueParam + this.$api.call(ApiNoten.getNoteByPunkte(value, this.lv_id, this.sem_kurzbz)).then(res => { + if(res?.meta?.status === 'success' && res.data >= 0) { + row.update({note_vorschlag: res.data}) + row.reformat() + } + }) + }, + fetchNoteForPunktePruefung(event) { + const value = event.value == '' ? null : event.value + this.$api.call(ApiNoten.getNoteByPunkte(value, this.lv_id, this.sem_kurzbz)).then(res => { + if(res?.meta?.status === 'success' && res.data >= 0) { + this.selectedPruefungNote = this.notenOptions.find(n => n.note == res.data) + } + }) + }, + isValidDate_ddmmyyyy(str) { + if (typeof str !== 'string') return false; + + // Check format: dd.mm.yyyy + const regex = /^(\d{2})\.(\d{2})\.(\d{4})$/; + const match = str.match(regex); + if (!match) return false; + + // Extract date parts + const day = parseInt(match[1], 10); + const month = parseInt(match[2], 10); + const year = parseInt(match[3], 10); + + // Check valid ranges + if (month < 1 || month > 12 || day < 1 || day > 31) return false; + + // Handle months with different days and leap years + const date = new Date(year, month - 1, day); + return ( + date.getFullYear() === year && + date.getMonth() === month - 1 && + date.getDate() === day + ); + }, + identifyUid(str) { + if (typeof str !== 'string') return null; + const firstChar = str.charAt(0); + + if (/^[0-9]$/.test(firstChar)) { + return 'matrikelnr'; + } else if (/^[a-zA-Z]$/.test(firstChar)) { + return 'uid'; + } else { + return null; + } + }, + validatePruefungBulk(pruefungen) { + // need to check pruefungen for validity in respect to the students nr of antritte + // pruefungsdatum will be validated aswell so we dont get a termin 3 chronologically before + // a termin 2 which is totally possible in the old tool + const validatedPruefungen = [] + pruefungen.forEach( p => { + const student = this.studenten.find(s => s.uid === p.uid) + + // if a student doesnt have a lv_note entered definitely dont allow to create pruefung + if(!student.lv_note) { + this.$fhcAlert.alertWarning('Student ' + student.uid + ' hat noch keine LV-Note eingetragen! Es wird keine Prüfung angelegt.') + return + } + + if(!student.note) { + this.$fhcAlert.alertWarning('Student ' + student.uid + ' hat noch keine Zeugnis Note eingetragen! Es wird keine Prüfung angelegt.') + return + } + + // check if student antrittCount is too high already + if(student.hoechsterAntritt >= this.maxAntrittCount) { + this.$fhcAlert.alertWarning('Student ' + student.uid + ' hat bereits ' + student.hoechsterAntritt + ' Prüfungsantritte abgelegt. Die Zeile wurde übersprungen.') + return + } + + // get student for pruefung and check if proposed datum does not conflict (no new pruefungen before existing ones) + const youngerPruefung = student.pruefungen.find(pr => { + return pr.dateObj >= p.dateObj + }) + if(youngerPruefung) { + this.$fhcAlert.alertWarning('Student ' + student.uid + ' hat bereits eine Prüfung am '+ youngerPruefung.datum +' eingetragen. Die Zeile wurde übersprungen.') + return + } + + validatedPruefungen.push(p) + }) + + pruefungen.splice(0, pruefungen.length, ...validatedPruefungen); + }, + validateNotenBulk(noten) { + // in case we need to further validate noten, currently parser does all + }, + parseNote(rowParts, notenbulk, rowNum) { + const id = this.identifyUid(rowParts[0]) + const idTrimmed = rowParts[0].trim() + let student = null + + if(id === 'matrikelnr') { // find student by matrnr and use uid later on + student = this.studenten.find(s => s.matrikelnr?.trim() === idTrimmed) + } else if(id === 'uid') { + student = this.studenten.find(s => s.uid?.trim() === idTrimmed) + } + if(!student) { + this.$fhcAlert.alertWarning(this.$p.t('benotungstool/c4importNoStudentFoundForIdInRow', [rowParts[0], rowNum])) + return + } + + let punkte = null + let note = null + if(this.config?.CIS_GESAMTNOTE_PUNKTE) { + punkte = Number.parseFloat(rowParts[1]) + } else { + note = rowParts[1] + + // find notenoption and check if its allowed to use in lehre + const notenOption = this.notenOptions.find(n => n.note == note) + if(!notenOption.lehre) { + this.$fhcAlert.alertWarning(this.$p.t('benotungstool/c4importNoGradeFoundForIdInRow', [rowParts[0], rowNum])) + return + } + } + + notenbulk.push({uid: student.uid, note, punkte}) + }, + parsePruefung(rowParts, pruefungbulk, rowNum) { + const id = this.identifyUid(rowParts[0]) + const idTrimmed = rowParts[0].trim() + let student = null + if(id === 'matrikelnr') { // find student by matrnr and use uid later on + student = this.studenten.find(s => s.matrikelnr?.trim() === idTrimmed) + } else if(id === 'uid') { + student = this.studenten.find(s => s.uid?.trim() === idTrimmed) + } + if(!student) { + this.$fhcAlert.alertWarning(this.$p.t('benotungstool/c4importNoStudentFoundForIdInRow', [rowParts[0], rowNum])) + return + } + + const datum = rowParts[1] // should be in 'dd.MM.yyyy' + if(!this.isValidDate_ddmmyyyy(datum)) { + this.$fhcAlert.alertWarning(this.$p.t('benotungstool/c4importInvalidDateFoundForIdInRow', [rowParts[0], rowNum])) + return + } + const datumParts = datum.split('.') + const day = datumParts[0] + const month = datumParts[1].padStart(2, '0') + const year = datumParts[2].padStart(2, '0') + const dateStr = `${year}-${month}-${day}` + + // build date obj for validation later on + let monthInt = parseInt(month, 10) + monthInt -= 1 + const dateObj = new Date(year, monthInt, day) + + + let punkte = null + let note = null + if(this.config?.CIS_GESAMTNOTE_PUNKTE) { + punkte = Number.parseFloat(rowParts[1]) + } else { + note = rowParts[1] + + // find notenoption and check if its allowed to use in lehre + const notenOption = this.notenOptions.find(n => n.note == note) + if(!notenOption.lehre) { + this.$fhcAlert.alertWarning(this.$p.t('benotungstool/c4importNoGradeFoundForIdInRow', [rowParts[0], rowNum])) + return + } + } + + const typ = this.getPruefungstypForStudentByAntritt(student) + + pruefungbulk.push({uid: student.uid, datum: dateStr, note, punkte, typ, lehreinheit_id: student.lehreinheit_id, dateObj}) + }, + saveNotenBulk(notenbulk) { + this.loading = true + this.$api.call(ApiNoten.saveNotenvorschlagBulk(this.lv_id, this.sem_kurzbz, notenbulk)).then(res => { + if(res.meta.status === 'success') { + this.$fhcAlert.alertDefault( + 'success', + 'Info', + this.$capitalize(this.$p.t('benotungstool/notenImportSuccessAlert')), + true + ) + const lvNoten = res.data + + + lvNoten.forEach(lvn => { + // 1.) get relevant student row by uid + const s = this.studenten.find(s => s.uid === lvn.student_uid) + s.note_vorschlag = lvn.note // TODO: check if note_vorschlag should be changed by import + + s.lv_note = lvn.note + if(this.config?.CIS_GESAMTNOTE_PUNKTE) { + s.punkte = lvn.punkte + } + + this.teilnoten[s.uid].note_lv = lvn.note + // recalculate freigabestatus + s.freigabedatum = this.parseDate(lvn['freigabedatum']) + s.benotungsdatum = this.parseDate(lvn['benotungsdatum']) + + s.freigegeben = this.checkFreigabe(s.freigabedatum, s.benotungsdatum, s.uid); + }) + + } + + this.$refs.notenTable.tabulator.redraw(true) + }).finally(()=>{ + this.loading = false + }) + }, + savePruefungBulk(pruefungenbulk) { + this.loading = true + this.$api.call(ApiNoten.saveStudentPruefungBulk(this.lv_id, this.sem_kurzbz, pruefungenbulk)) + .then((res)=> { + if(res.meta.status === 'success') { + this.$fhcAlert.alertDefault( + 'success', + 'Info', + this.$capitalize(this.$p.t('benotungstool/pruefungImportSuccessAlert')), + true + ) + this.handleAddNewPruefungenResponse(res, pruefungenbulk) + } + }).finally(()=>{this.loading = false}) + }, + handleAddNewPruefungenResponse(res, uids) { + // in case we reload when changing lva_id or stsem to always consider local storage layout + this.colLayoutRestored = false; + + const pruefungen = res.data + uids.forEach(entry => { + const saved = pruefungen[entry.uid].savedPruefung?.[0] + const extra = pruefungen[entry.uid].extraPruefung?.[0] + + const student = this.studenten.find(s => s.uid == entry.uid) + if(!student) return + + // check for extra pruefung (termin1) to add before + if(extra) { + student["Termin1"] = extra + student.pruefungen.push(extra) // push to pruefungen array for antritt evaluation + } + + this.correctOldTerminTypenForStudent(student, saved) + + if(!this.distinctPruefungsDates.includes(saved.datum)) { + this.insertSortedDate(this.distinctPruefungsDates, saved.datum) + } + + // add pruefung to pruefungen array + student.pruefungen.push(saved) + + // add pruefung to student via its datum as a field + student[saved.datum] = saved + + // usually should be in order naturally, just to be save + student.pruefungen.sort((p1, p2) => { + if(p1.datum > p2.datum) { + return 1 + } else if (p1.datum < p2.datum) { + return -1 + } else { + return 0 + } + }) + + // recalculate student antritte + student.hoechsterAntritt = this.getAntrittCountStudent(student) + this.recalculateSelectable(student) + this.reformatStudentRow(student) + }) + + // add col to table + const cols = [...this.notenTableOptions.columns.slice(0, -1)]; + let kommCol = null + if(this.config?.CIS_GESAMTNOTE_PRUEFUNG_KOMMPRUEF) kommCol = this.notenTableOptions.columns[this.notenTableOptions.columns.length - 1]; + + if(this.distinctPruefungsDates.length) { + cols.push({ + title: this.$capitalize(this.$p.t('benotungstool/c4originalZnote')), + field: "Termin1", + formatter: this.pruefungFormatter, + titleFormatter: this.pruefungTitleFormatter, + topCalc: this.terminCalcFunc, + topCalcFormatter: this.terminCalcFormatter, + hozAlign:"center", + widthGrow: 1, + minWidth: 200, + width: 250, + visible: true, + tooltip: false + }) + } + + this.distinctPruefungsDates.forEach((date)=>{ + const dateparts = date.split('-') + const titledate = `${dateparts[2]}.${dateparts[1]}.${dateparts[0]}` + + cols.push({ + title: titledate,//this.$p.t('benotungstool/pruefungNr', [index+1]), + field: date, + formatter: this.pruefungFormatter, + titleFormatter: this.pruefungTitleFormatter, + topCalc: this.terminCalcFunc, + topCalcFormatter: this.terminCalcFormatter, + hozAlign:"center", + widthGrow: 1, + minWidth: 200, + width: 250, + visible: true, + tooltip: false + }) + }) + + if(this.config?.CIS_GESAMTNOTE_PRUEFUNG_KOMMPRUEF) cols.push(kommCol) // keep kommPruef Col as last + // redraw table + + + // preemptively do the persistence part of columns already here so we get the merged definition before + // tabulator internal go to war with vue reactives + let colsUsed = null + const saved = this.loadState(); + if (saved && saved.columns) { + // new columns map for lookup + const colMap = new Map(cols.map(c => [c.field, c])); + + const restoredCols = []; + + // add columns in the SAVED order, applying saved width/visibility + saved.columns.forEach(savedCol => { + const originalDef = colMap.get(savedCol.field); + if (originalDef) { + restoredCols.push({ + ...originalDef, // Keep formatters, calcs, etc. + width: savedCol.width, + visible: savedCol.visible + }); + colMap.delete(savedCol.field); // Remove so we don't double-add + } + }); + + // append any NEW columns aka pruefungs cols + colMap.forEach((def) => { + restoredCols.push(def); + }); + + colsUsed = restoredCols; + this.colLayoutRestored = true; + } + + this.loading = false + + const colsFinal = colsUsed ?? cols + this.$refs.notenTable.tabulator.setColumns(colsFinal) + this.$refs.notenTable.tabulator.setData(this.studenten); + this.$refs.notenTable.tabulator.redraw(true); + }, + reformatStudentRow(student) { + const table = this.$refs.notenTable.tabulator + if(!table) return + + const row = table.rowManager.getRowFromDataObject(student) + + const rowComponent = row.getComponent() + rowComponent.reformat() + }, + correctOldTerminTypenForStudent(student, saved) { + // check if student has a preceding pruefung from same type and remove it since in this case + // the new pruefung will have overwritten the old one + const oldP = student.pruefungen.find(p => p.pruefungstyp_kurzbz == saved.pruefungstyp_kurzbz) + if(oldP) { + delete student[oldP.datum] // delete the variable col value + + const pIndex = student.pruefungen.indexOf(oldP) + if (pIndex > -1) { + student.pruefungen.splice(pIndex, 1) + } + // student.pruefungen.splice(student.pruefungen.indexOf(oldP), 1) // delete from pruefungen array + + // check if on that date any other student has a pruefung, if not delete it from distinctPruefungsDates + const other = this.studenten.find(s => s[oldP.datum] !== undefined) + if(!other) { + const dateIndex = this.distinctPruefungsDates.indexOf(oldP.datum); + if (dateIndex > -1) { + this.distinctPruefungsDates.splice(dateIndex, 1); + } + // this.distinctPruefungsDates.splice(this.distinctPruefungsDates.findIndex(oldP.datum), 1) + } + } + }, + importNoten() { + const rows = this.importString.split('\n') + const bulk = [] + let mode = '' + // read the lines + rows.forEach((r,i) => { + const rowParts = r.split('\t') + if(rowParts.length === 3) { + this.parsePruefung(rowParts, bulk, i) + mode = 'pruefung' // if line parts are not uniform we are in trouble + } else if(rowParts.length === 2) { + this.parseNote(rowParts, bulk, i) + mode = 'note' + } + }) + + // parsers check for notenOption.lehre === true and if student uid/matrikelnr matches + + // pruefungen check for younger pruefungen, so there are no further antritte with + // previous dates from automatic imports + if(mode === 'note') { + this.validateNotenBulk(bulk) + this.saveNotenBulk(bulk) + } + else if (mode === 'pruefung') { + this.validatePruefungBulk(bulk) + this.savePruefungBulk(bulk) + } + + this.$refs.modalContainerNotenImport.hide() + }, + selectionArraysAreEqual(arr1, arr2) { + if(arr1.length !== arr2.length) return false + + const sortFunc = (s1, s2) => { + if(s1.nachname > s2.nachname) { + return 1 + } else if (s1.nachname < s2.nachname) { + return -1 + } else { + return 0 + } + } + const sortedArr1 = arr1.sort(sortFunc) + const sortedArr2 = arr2.sort(sortFunc) + + const arrsREqual = sortedArr1.every((val, index) => val === sortedArr2[index]); + + return arrsREqual + }, + getNotenTableOptions() { + + return { + height: 700, + virtualDom: true, + virtualDomBuffer: 5000, + index: 'uid', + layout: 'fitDataStretch', + placeholder: this.$capitalize(this.$p.t('global/noDataAvailable')), + selectable: true, + selectableRangeMode: "click", // shift+click + selectablePersistence: false, // reset selection on table reload + selectableCheck: this.selectableCheck, + rowFormatter: this.fixTabulatorSelectionFormatter, + columns: this.getColumnsDefinition(), + persistence: false, + } + + }, + selectableCheck(row, e) { + const data = row.getData(); + + if(data['kommPruef']) return false + else if(data.hoechsterAntritt >= this.maxAntrittCount) return false // 2 or 3 pruefungen counted + + return true; // student can be selected to add pruefung + + }, + getColumnsDefinition() { + const columns = [] + + + columns.push({ + formatter: function (cell, formatterParams, onRendered) { + // create the built-in checkbox + let checkbox = document.createElement("input"); + checkbox.type = "checkbox"; + + // Handle select manually + checkbox.addEventListener("click", (e) => { + e.stopPropagation(); + + // call our function + if (formatterParams && formatterParams.handleClick) { + formatterParams.handleClick(e, cell); + } + }); + + return checkbox; + }, + titleFormatter: function (cell, formatterParams, onRendered) { + // create the built-in checkbox + let checkbox = document.createElement("input"); + checkbox.type = "checkbox"; + + // Handle "select all" manually + checkbox.addEventListener("click", (e) => { + e.stopPropagation(); + + // call our function + if (formatterParams && formatterParams.handleClick) { + formatterParams.handleClick(e, cell); + } + }); + + return checkbox; + }, + hozAlign: "center", + headerSort: false, + formatterParams: { + handleClick: this.selectHandler + }, + titleFormatterParams: { + handleClick: this.selectAllHandler + }, + width: 50, + cssClass: 'sticky-col', + field: 'selectCol', + title: '' + }) + columns.push({title: 'UID', field: 'uid', tooltip: false, widthGrow: 1, topCalc: this.sumCalcFunc, formatter: centeredFormatter, cssClass: 'sticky-col'}) + columns.push({title: Vue.computed(() => this.$capitalize(this.$p.t('benotungstool/c4mail'))), field: 'email', formatter: this.mailFormatter, tooltip: false, visible: false, widthGrow: 1, variableHeight: true}) + columns.push({title: Vue.computed(() => this.$capitalize(this.$p.t('benotungstool/c4antrittCountv2'))), field: 'hoechsterAntritt', formatter: centeredFormatter, tooltip: false, widthGrow: 1}) + columns.push({title: Vue.computed(() => this.$capitalize(this.$p.t('benotungstool/c4vorname'))), field: 'vorname', formatter: centeredFormatter, headerFilter: true, tooltip: false, widthGrow: 1}) + columns.push({title: Vue.computed(() => this.$capitalize(this.$p.t('benotungstool/c4nachname'))), field: 'nachname', formatter: centeredFormatter, headerFilter: true, widthGrow: 1}) + columns.push({title: Vue.computed(() => this.$capitalize(this.$p.t('benotungstool/c4anwesenheitsquote'))), field: 'anwquote', widthGrow: 1, formatter: this.percentFormatter}) + columns.push({title: Vue.computed(() => this.$capitalize(this.$p.t('benotungstool/c4mobility'))), field: 'mobility_zusatz', formatter: centeredFormatter, headerFilter: true, widthGrow: 1, visible: false}) + if(this.config?.CIS_GESAMTNOTE_PRUEFUNG_MOODLE_LE_NOTE) { + columns.push({title: Vue.computed(() => this.$capitalize(this.$p.t('benotungstool/c4teilnoten'))), field: 'teilnote', widthGrow: 1, formatter: this.teilnotenFormatter, variableHeight: true}) + } + if(this.config?.CIS_GESAMTNOTE_PUNKTE) { + columns.push({title: Vue.computed(() => this.$capitalize(this.$p.t('benotungstool/c4punkte'))), field: 'punkte', widthGrow: 1, + editor: this.liveNumberEditor, + editable: (cell) => { + const rowData = cell.getRow().getData(); + if(this.pruefungen?.find(p => p.student_uid == rowData.uid)) return false + + return true + }, + variableHeight: true + }) + } + columns.push({title: Vue.computed(() => this.$capitalize(this.$p.t('benotungstool/c4notenvorschlag'))), field: 'note_vorschlag', + editor: 'list', + editorParams: (cell) => { + // write original cell value into row to it can be retrieved if edit is cancelled without selection + const rowData = cell.getRow().getData(); + rowData._originalNoteVorschlag = cell.getValue(); + + return { + values: this.notenOptionsLehre.map(opt => ({ + label: opt.bezeichnung, + value: opt.note + })) + }; + }, + editable: (cell) => { + // punkte features enables mapping but unable to set note directly + if(this.config?.CIS_GESAMTNOTE_PUNKTE) return false + const rowData = cell.getRow().getData(); + const noteOption = this.notenOptions.find(opt => opt.note == rowData.note) + if(!noteOption) return true + + // also if student has any pruefungsnote disable noten selection + if(this.pruefungen?.find(p => p.student_uid == rowData.uid)) return false + + return noteOption.lkt_ueberschreibbar + }, + formatter: (cell) => { + const rowData = cell.getRow().getData(); + const value = cell.getValue() + const match = this.notenOptions?.find(opt => opt.note == value) + const val = match ? match.bezeichnung : value + const p = this.pruefungen?.find(p => p.student_uid == rowData.uid) + let style = '' + + if(val === undefined) return '' + if(p || !match?.lkt_ueberschreibbar) style = 'color: gray;font-style: italic; background-color: #f0f0f0;pointer-events: none;opacity: 0.6;user-select: none;cursor: not-allowed;' + return '
' + val + '
' + }, + widthGrow: 1 + }) + columns.push({title: Vue.computed(() => this.$capitalize(this.$p.t('benotungstool/c4notenvorschlagUebernehmen'))), field: 'übernehmen', width: 150, hozAlign: 'center', formatter: this.arrowFormatter, + cellClick: this.saveNote, + variableHeight: true}) + columns.push({title: Vue.computed(() => this.$capitalize(this.$p.t('benotungstool/c4lvnote'))), field: 'lv_note', + formatter: this.notenFormatter, + headerFilter: 'list', + headerFilterParams: () => { + return { values: ["\u00A0",this.$p.t('benotungstool/c4noteEmpty') ,this.$p.t('benotungstool/c4positiv'), this.$p.t('benotungstool/c4negativ') ,...this.notenOptions.map(opt => opt.bezeichnung)] } + }, + headerFilterFunc: this.notenFilterFunc, + widthGrow: 1 + }) + columns.push({title: Vue.computed(() => this.$capitalize(this.$p.t('benotungstool/c4freigabe'))), field: 'freigegeben', widthGrow: 1, formatter: this.freigabeFormatter, variableHeight: true}) + columns.push({title: Vue.computed(() => this.$capitalize(this.$p.t('benotungstool/c4zeugnisnote'))), + field: 'note', + formatter: this.notenFormatter, + topCalc: this.negativeNotenCalc, + topCalcFormatter: this.negativeNotenCalcFormatter, + headerFilter: 'list', + headerFilterParams: () => { + return { values: ["\u00A0", this.$p.t('benotungstool/c4noteEmpty'),this.$p.t('benotungstool/c4positiv'), this.$p.t('benotungstool/c4negativ') ,...this.notenOptions.map(opt => opt.bezeichnung)] } + }, + headerFilterFunc: this.notenFilterFunc, + widthGrow: 1 + }) + columns.push({title: Vue.computed(() => this.$capitalize(this.$p.t('benotungstool/c4kommPruef'))), + field: 'kommPruef', widthGrow: 1, + formatter: this.pruefungFormatter, + sorter: this.pruefungSorter, + topCalc: this.terminCalcFunc, + topCalcFormatter: this.terminCalcFormatter, + hozAlign:"center", minWidth: 150, visible: false, + tooltip: false + }) + + return columns + }, + pruefungSorter(a, b, aRow, bRow, column, dir, params) { + if (a === null || typeof a === "undefined" || a === '') return -1; + if (b === null || typeof b === "undefined" || b === '') return 1; + + // sort by notenvalue since pruefungen are in same date by column + return a.note - b.note + }, + selectHandler(e, cell) { + const row = cell.getRow(); + + if(row.isSelected()){ + row.deselect(); + } else { + row.select(); + } + + // stop built-in handler + e.stopPropagation(); + return false; + }, + selectAllHandler(e, cell) { + const table = cell.getTable(); + const rows = table.getRows(); + + // custom select all logic + const allowed = rows.filter(r => r.getData().selectable); + const selected = allowed.every(r => r.isSelected()); + + if(selected){ + allowed.forEach(r => r.deselect()); + } else { + allowed.forEach(r => r.select()); + } + + // stop built-in handler + e.stopPropagation(); + return false; + }, + fixTabulatorSelectionFormatter(row) { + // if a row is not selectable, remove the checkbox from the dom + + const data = row.getData() + + const notSelectable = data.pruefungen?.find(p => p.pruefungstyp_kurzbz == 'kommPruef') || data.hoechsterAntritt >= this.maxAntrittCount + if(notSelectable) { + const el = row.getElement() + el.children[0]?.children[0]?.remove() + + el.classList.remove("tabulator-selectable"); + el.classList.add("tabulator-unselectable"); + } else { + const el = row.getElement() + + el.classList.add("tabulator-selectable"); + el.classList.remove("tabulator-unselectable"); + } + }, + terminCalcFunc(entries) { + return entries.reduce((acc, cur) => { + if(cur !== undefined) acc++ + return acc + }, 0) + }, + terminCalcFormatter(cell) { + const cellval = cell.getValue() + return this.$capitalize(this.$p.t('benotungstool/prueflingSelectionv2'))+': ' + cellval + }, + negativeNotenCalcFormatter(cell) { + const cellval = cell.getValue() + return this.$capitalize(this.$p.t('benotungstool/c4negativ'))+': ' + cellval + }, + negativeNotenCalc(entries) { + return entries.reduce((acc, cur) => { + const opt = this.notenOptions.find(opt => opt.note == cur) + if(opt && !opt.positiv) acc++ + return acc + }, 0) + }, + sumCalcFunc(entries) { + return entries.length + }, + notenFilterFunc(filterVal, rowVal) { + // option of the searchterm + const opt = this.notenOptions.find(opt => opt.bezeichnung === filterVal) + // searchterm is not empty fallback and the note finds an option match + if(rowVal !== null && rowVal !== undefined && opt?.note == rowVal) { + return true + } + + // empty searchterm fallback to show all + if(filterVal === "\u00A0" || filterVal === "" || filterVal === null) { + return true + } + + // specific searchterm cases + if(filterVal === this.$capitalize(this.$p.t('benotungstool/c4positiv'))) { + // option of the rowValue + const valOpt = this.notenOptions.find(opt => opt.note == rowVal) + if(!valOpt) return false + return valOpt.positiv + } + if(filterVal === this.$capitalize(this.$p.t('benotungstool/c4negativ'))) { + const valOpt = this.notenOptions.find(opt => opt.note == rowVal) + if(!valOpt) return false + return !valOpt.positiv + } + if(filterVal === this.$capitalize(this.$p.t('benotungstool/c4noteEmpty')) && rowVal === null) { + return true + } + + return false + }, + parseDate(timestamp) { + if(!timestamp) return null + const [datePart, timePart] = timestamp.split(" "); + const [year, month, day] = datePart.split("-").map(Number); + const [hour, minute, second] = timePart.split(":").map(Number); + return new Date(year, month - 1, day, hour, minute, second); + }, + checkFreigabe(freigabedatum, benotungsdatum, uid) { + if(!freigabedatum) { + // check for change -> set freigabe to 'changed' on change + return 'offen' + } else if(benotungsdatum > freigabedatum) { + return 'changed' + } else { + return 'ok' + } + }, + unselectableFormatter(row) { + + }, + notenFormatter(cell) { + const value = cell.getValue() + const field = cell.getField() + let style = 'display: flex; justify-content: center; align-items: center; height: 100%;'; + // Wenn sich die Zeugnisnote von der von Ihnen freigegebenen Note unterscheidet, + // wird erstere rot umrandet markiert. + + + const data = cell.getData() + if(field == 'note' && data.note && data.note != data.lv_note) { + style += 'color:red; border-color:red; border-style:solid; border-width:1px;' + } + + const match = this.notenOptions.find(opt => opt.note == value) + const val = match ? match.bezeichnung : value + if(val) return '
' + val + '
' + else return '' + + }, + freigabeFormatter(cell) { + const value = cell.getValue() + + let style = 'display: flex; justify-content: center; align-items: center; height: 100%;' + + if(value === 'ok') { + return '
' + + '
' + } else if (value === 'offen') { + return '
' + + '
' + } else if (value === 'changed') { + return '
' + + '
' + } + + return value + }, + saveNote(e, cell) { // Notenvorschlag freigeben + const row = cell.getRow() + const data = row.getData() + + if(!data.note_vorschlag) return + + // if vorschlag is the same as lv_note do nothing + if(data.note_vorschlag == data.lv_note) return + + // if the student already has pruefungen disable this part + if(data.pruefungen.length) return + + this.loading = true + this.$api.call(ApiNoten.saveNotenvorschlag(this.lv_id, this.sem_kurzbz, data.uid, data.note_vorschlag, data.punkte)) + .then((res) => { + if (res.meta.status === 'success') { + const s = this.studenten.find(s => s.uid === data.uid) + this.teilnoten[s.uid].note_lv = data.note_vorschlag + s.freigabedatum = this.parseDate(res.data[0]['freigabedatum']) + s.benotungsdatum = this.parseDate(res.data[0]['benotungsdatum']) + + s.freigegeben = this.checkFreigabe(s.freigabedatum, s.benotungsdatum, s.uid); + + row.update({ lv_note: data.note_vorschlag, freigegeben: 'changed' }) + // row.update({ freigegeben: 'changed' }) + row.reformat() // trigger reformat of arrow + this.changedNotenCounter++; + } + }).finally(()=>this.loading = false) + + + }, + teilnotenFormatter(cell) { + const val = cell.getValue() + + let style = 'white-space: pre-line;' + + return '
'+val+'
' + }, + pruefungFormatter(cell) { + const data = cell.getData() + + const noteDef = data.note ? this.notenOptions.find(n => n.note == data.note) : null + if(!data.note || !noteDef?.lkt_ueberschreibbar) { + return '' + } + + const colDef = cell.getColumn().getDefinition() + + // column is just a date, student can have any of his antritte on this date, so we need to get + // student.pruefungen and look for a pruefung with this cols title as date + + const field = cell.getColumn().getField() + let studentPruefung = null + if(field === 'kommPruef') { studentPruefung = data['kommPruef'] } + else if(field === "Termin1") { studentPruefung = data['Termin1'] } + else { studentPruefung = data.pruefungen.find(p => p.datum === field) } + + // is this column/cell allowed to have an add pruefung action + const canAdd = field !== 'kommPruef' && field !== "Termin1" && data.hoechsterAntritt < this.maxAntrittCount + + // TODO: check for some time limit maybe? old pruefungen can be changed/created + + // Create root row div + const rowDiv = document.createElement('div'); + rowDiv.className = 'row flex-nowrap'; + rowDiv.style.display = 'flex'; + rowDiv.style.justifyContent = 'center'; + rowDiv.style.alignItems = 'center'; + rowDiv.style.height = '100%'; + + if(studentPruefung) { + let color = '' + switch(studentPruefung.pruefungstyp_kurzbz) { + case 'Termin1': + color = 'green' + break + case 'Termin2': + color = 'yellow' + break + case 'Termin3': + color = 'orange' + break + case 'kommPruef': + color = 'red' + break + } + + rowDiv.style.borderLeft = `4px solid ${color}`; + rowDiv.style.marginLeft = "6px"; // small indent so text doesn't overlap + rowDiv.style.boxSizing = "border-box"; + } + + function createCol(content, classParam) { + const colDiv = document.createElement('div'); + colDiv.className = classParam ?? 'col-4'; + colDiv.style.justifyContent = 'center'; + colDiv.style.alignItems = 'center'; + colDiv.style.alignContent = 'center'; + colDiv.style.height = '100%'; + + if (typeof content === 'string') { + colDiv.textContent = content; + } else if (content instanceof HTMLElement) { + colDiv.appendChild(content); + } + + return colDiv; + } + + if(data[field]) { + + const noteDefEntry = data.note ? this.notenOptions.find(n => n.note == data[field].note) : null + + // Second column (note_bezeichnung) + rowDiv.appendChild(createCol(noteDefEntry.bezeichnung || '', 'col-auto d-flex justify-content-center align-items-center')); + + // no actions on kommPruef allowed + // no actions on termin1 aka pruefung 0 aka ursprüngliche note erlaubt + if(field === 'kommPruef' || field === "Termin1") { + return rowDiv + } + + if(data[field]?.pruefungstyp_kurzbz !== 'Termin1') { + // Third column (button) + const button = document.createElement('button'); + button.className = 'btn btn-outline-secondary'; + button.textContent = this.$capitalize(this.$p.t('benotungstool/changePruefungButtonText')); + button.addEventListener('click', () => { + this.openPruefungModal(data, data[field], field); + }); + + rowDiv.appendChild(createCol(button, 'col-4 d-flex justify-content-center align-items-center')); + } + + return rowDiv; + + } else if (canAdd) { // return new btn action + // dont render the add button in cells where a younger pruefung exists for the students + const youngerPruefung = data.pruefungen.find(p => p.datum > field) + if(youngerPruefung) return rowDiv + + const button = document.createElement('button'); + button.className = 'btn btn-outline-secondary'; + button.textContent = this.$capitalize(this.$p.t('benotungstool/addPruefungButtonText')); + button.addEventListener('click', () => { + this.openPruefungModal(data, null, field) + }); + + rowDiv.appendChild(createCol(button), 'col-4 d-flex justify-content-center align-content-center'); + + return rowDiv; + } else { + return '' + } + }, + openPruefungModal(student, pruefung = null, field) { + this.pruefungStudent = student + this.pruefung = pruefung + const dateStr = this.pruefung?.datum ?? field + + const pruefungDateParts = dateStr.split('-') + + const newDate = new Date(Number(pruefungDateParts[0]), Number(pruefungDateParts[1]) - 1, Number(pruefungDateParts[2])) + this.selectedPruefungDate = newDate + + + if(this.pruefung?.note) { + this.selectedPruefungNote = this.notenOptions.find(n => n.note == this.pruefung.note) + } else { + this.selectedPruefungNote = null + } + + this.selectedPruefungPunkte = this.pruefung?.punkte ?? null + + this.$refs.modalContainerPruefung.show() + }, + pruefungTitleFormatter(cell) { + const def = cell.getColumn().getDefinition() + return def.title; + }, + arrowFormatter(cell) { + const row = cell.getRow() + const data = row.getData() + + let style = 'display: flex; justify-content: center; align-items: center; height: 100%;' + + if(!data.note_vorschlag || (data.note_vorschlag == data.lv_note) || data.pruefungen.length) { + // arrow to ambiguous in meaning, use str8 forward worded button here instead + // uncolored arrow + // return '
' + + // '
' + + return '' + } + + const button = document.createElement('button'); + button.className = 'btn btn-outline-secondary'; + button.textContent = this.$capitalize(this.$p.t('benotungstool/c4notenvorschlagUebernehmen')); + return button; + + // // can save a notenvorschlag -> colored + // return '
' + + // '
' + }, + mailFormatter(cell) { + const val = cell.getValue() + + let style = 'display: flex; justify-content: center; align-items: center; height: 100%;' + + return '
' + + '
' + }, + percentFormatter(cell) { + const data = cell.getData() + const val = data.anwquote ?? '-' + return '
'+ val + ' %
' + }, + buildMailToLink(student){ + return 'mailto:' + student.uid +'@'+ this.domain + }, + insertSortedDate(arr, dateStr) { + // Binary search to find insertion index + let left = 0, right = arr.length; + while (left < right) { + const mid = (left + right) >> 1; + if (arr[mid] < dateStr) left = mid + 1; + else right = mid; + } + arr.splice(left, 0, dateStr); // insert at index + return arr; + }, + tableResolve(resolve) { + this.tableBuiltResolve = resolve + }, + notenOptionsResolve(resolve) { + this.notenOptionsResolve = resolve + }, + async setupData(data){ + // in case we reload when changing lva_id or stsem to always consider local storage layout + this.colLayoutRestored = false; + + this.studenten = data[0] ?? [] + this.studenten.forEach(s => { + s.pruefungen = [] + s.infoString = `${s.vorname} ${s.nachname}`// (${s.semester}${s.verband}${s.gruppe}) Mat.: ${s.matrikelnr}`// used for multiselect + }) + this.pruefungen = data[1] ?? [] + this.domain = data[2] + + // contains notenvorschläge from moodle, lv_note + this.teilnoten = data[3] ?? [] + + // let pruefungenRegularColCount = 0; + this.distinctPruefungsDates = [] + const cols = [...this.notenTableOptions.columns.slice(0, -1)]; + let kommCol = null + if(this.config?.CIS_GESAMTNOTE_PRUEFUNG_KOMMPRUEF) kommCol = this.notenTableOptions.columns[this.notenTableOptions.columns.length - 1]; + + this.pruefungen?.forEach(p => { + const dateParts = p.datum.split('-') + p.dateObj = new Date(dateParts[0], +(dateParts[1]) - 1, dateParts[2]) + + const student = this.studenten.find(s => s.uid === p.student_uid) + if(!student) return + + if(p.pruefungstyp_kurzbz !== 'kommPruef' + && p.pruefungstyp_kurzbz !== 'Termin1' + && !this.distinctPruefungsDates.includes(p.datum)) { + this.distinctPruefungsDates.push(p.datum) + } + + // seperate kommPruefungen from previous pruefungen counts since the column count variability always ends with this + if(p.pruefungstyp_kurzbz == 'kommPruef') { + student['kommPruef'] = p + } else { + student.pruefungen.push(p) + } + + }) + + this.studenten?.forEach(s => { + // sort students regular pruefungen by datum + s.pruefungen.sort((p1, p2) => { + if(p1.datum > p2.datum) { + return 1 + } else if (p1.datum < p2.datum) { + return -1 + } else { + return 0 + } + }) + // set the sorted pruefungen to their respective column fields + s.pruefungen.forEach((p, i) => { + // Termin1 aka originale Zeugnisnote should note create variable columns + if(p.pruefungstyp_kurzbz == "Termin1") { + s["Termin1"] = p + } else { + s[p.datum] = p + } + }) + + s.hoechsterAntritt = this.getAntrittCountStudent(s) + s.email = this.buildMailToLink(s) + s.lv_note = this.teilnoten[s.uid].note_lv + s.freigabedatum = this.parseDate(this.teilnoten[s.uid]['freigabedatum']) + s.benotungsdatum = this.parseDate(this.teilnoten[s.uid]['benotungsdatum']) + s.freigegeben = this.checkFreigabe(s.freigabedatum, s.benotungsdatum, s.uid); + + s.punkte = this.teilnoten[s.uid].punkte_lv + + const grades = this.teilnoten[s.uid].grades + s.teilnote = '' + s.mobility_zusatz = this.teilnoten[s.uid].mobility_zusatz + grades.forEach(g => { + const notenOption = this.notenOptions.find(n=>n.note == g.grade) + if(notenOption.positiv) s.teilnote += (''+g.text +''+ '
') + else s.teilnote += (''+g.text +''+ '
') + }) + + const vueThis = this + Object.defineProperty(s, 'selectable', { + get() { + const kP = s.pruefungen?.find(p => p.pruefungstyp_kurzbz == 'kommPruef') + const maxAntrittReached = s.hoechsterAntritt >= vueThis.maxAntrittCount + return !(kP || maxAntrittReached) + + }, + set() { + // empty setter so tabulator doesnt scream + }, + enumerable: true, + configurable: true + }) + + }) + + // if we have any pruefungen at all someone must have original note/termin1 pruefung + if(this.distinctPruefungsDates.length) { + cols.push({ + title: this.$capitalize(this.$p.t('benotungstool/c4originalZnote')), + field: "Termin1", + formatter: this.pruefungFormatter, + titleFormatter: this.pruefungTitleFormatter, + sorter: this.pruefungSorter, + topCalc: this.terminCalcFunc, + topCalcFormatter: this.terminCalcFormatter, + hozAlign:"center", + widthGrow: 1, + minWidth: 200, + width: 250, + visible: true, + tooltip: false + }) + } + + this.distinctPruefungsDates.sort((d1, d2) => { + if(d1 > d2) { + return 1 + } else if (d1 < d2) { + return -1 + } else { + return 0 + } + }) + this.distinctPruefungsDates.forEach((date)=>{ + const dateparts = date.split('-') + const titledate = `${dateparts[2]}.${dateparts[1]}.${dateparts[0]}` + + cols.push({ + title: titledate, + field: date, + formatter: this.pruefungFormatter, + titleFormatter: this.pruefungTitleFormatter, + sorter: this.pruefungSorter, + topCalc: this.terminCalcFunc, + topCalcFormatter: this.terminCalcFormatter, + hozAlign:"center", + widthGrow: 1, + minWidth: 200, + width: 250, + visible: true, + tooltip: false + }) + }) + + if(this.config?.CIS_GESAMTNOTE_PRUEFUNG_KOMMPRUEF) cols.push(kommCol) // keep kommPruef Col as last + + + // preemptively do the persistence part of columns already here so we get the merged definition before + // tabulator internal go to war with vue reactives + let colsUsed = null + const saved = this.loadState(); + if (saved && saved.columns) { + // new columns map for lookup + const colMap = new Map(cols.map(c => [c.field, c])); + + const restoredCols = []; + + // add columns in the SAVED order, applying saved width/visibility + saved.columns.forEach(savedCol => { + const originalDef = colMap.get(savedCol.field); + if (originalDef) { + restoredCols.push({ + ...originalDef, // Keep formatters, calcs, etc. + width: savedCol.width, + visible: savedCol.visible + }); + colMap.delete(savedCol.field); // Remove so we don't double-add + } + }); + + // append any NEW columns aka pruefungs cols + colMap.forEach((def) => { + restoredCols.push(def); + }); + + colsUsed = restoredCols; + this.colLayoutRestored = true; + } + + const colsFinal = colsUsed ?? cols + this.loading = false + + this.$refs.notenTable.tabulator.setColumns(colsFinal) + this.$refs.notenTable.tabulator.setData(this.studenten); + this.$refs.notenTable.tabulator.redraw(true); + }, + loadNoten(lv_id, sem_kurzbz) { + this.loading = true + this.$api.call(ApiNoten.getStudentenNoten(lv_id, sem_kurzbz)) + .then(res => { + if(res?.data) this.setupData(res.data) + if(res?.meta?.getExternalGradesError) this.$fhcAlert.alertError(res.meta.getExternalGradesError) + }).finally(()=> { + this.loading = false + }) + }, + handleUuidDefined(uuid) { + this.tabulatorUuid = uuid + }, + calcMaxTableHeight() { + const tableID = this.tabulatorUuid ? ('-' + this.tabulatorUuid) : '' + const tableDataSet = document.getElementById('filterTableDataset' + tableID); + if(!tableDataSet) return + const rect = tableDataSet.getBoundingClientRect(); + + this.notenTableOptions.height = window.visualViewport.height - rect.top - 50 + this.$refs.notenTable.tabulator.setHeight(this.notenTableOptions.height) + }, + async setupCreated() { + this.loading = true + + this.debouncedFetchPunkteForPruefung = debounce(this.fetchNoteForPunktePruefung, 500) + + // fetch cis config regarding gesamtnoteneingabe, needs to be fetched before setup can finish + const configPromise = this.$api.call(ApiNoten.getCisConfig()).then(res => { + this.config = res.data + }) + + // fetch lva dropdown + this.$api.call(ApiLehre.getZugewieseneLv(this.viewData?.uid, this.sem_kurzbz)).then(res => { + this.lehrveranstaltungen = res.data + + // build dropdown option string + this.lehrveranstaltungen.forEach(lva => { + lva.fullString = `${lva.stg_kurzbz} - ${lva.lv_semester}: ${lva.lv_bezeichnung}` + }) + + this.selectedLehrveranstaltung = this.lehrveranstaltungen.find(lva => lva.lehrveranstaltung_id == this.lv_id) + }) + + LehreinheitenModule.setupContext(this.$.appContext.config.globalProperties) + LehreinheitenModule.bindParams(Vue.ref(Vue.computed(() => this.LeDropdownParams))); + + // fetch sem_kurzbz dropdown + this.$api.call(ApiStudiensemester.getStudiensemester()).then(res => { + this.studiensemester = res.data[0] + this.selectedSemester = this.studiensemester.find(sem => sem.studiensemester_kurzbz === this.sem_kurzbz) + }) + + // fetch noten dropdown + this.$api.call(ApiNoten.getNoten()).then(async res => { + this.notenOptions = res.data + this.notenOptionsLehre = res.data.filter(n => n.lehre === true) + + await configPromise + this.notenTableOptions = this.getNotenTableOptions() + this.tabulatorCanBeBuilt = true // because promises would be more work and not much better here + }).catch(e => { + this.loading = false + }) + + }, + async setupMounted() { + this.tableBuiltPromise = new Promise(this.tableResolve) + await this.tableBuiltPromise + + this.loadNoten(this.lv_id, this.sem_kurzbz) + this.calcMaxTableHeight() + + }, + lvChanged(e) { + this.$router.push({ + name: "Benotungstool", + params: { + sem_kurzbz: this.sem_kurzbz, + lv_id: e.value.lehrveranstaltung_id + } + }) + + // reload data + this.loadNoten(e.value.lehrveranstaltung_id, this.sem_kurzbz) + }, + ssChanged(e) { + // change url params & write history + this.$router.push({ + name: "Benotungstool", + params: { + sem_kurzbz: e.value.studiensemester_kurzbz, + lv_id: this.lv_id + } + }) + + this.loading = true + // diff lv_id -> reload zugewiesene lv + this.$api.call(ApiLehre.getZugewieseneLv(this.viewData?.uid, e.value.studiensemester_kurzbz)).then(res => { + this.lehrveranstaltungen = res.data + + // build dropdown option string + this.lehrveranstaltungen.forEach(lva => { + lva.fullString = `${lva.stg_kurzbz} - ${lva.lv_semester}: ${lva.lv_bezeichnung}` + }) + + this.selectedLehrveranstaltung = this.lehrveranstaltungen.find(lva => lva.lehrveranstaltung_id == this.lv_id) + }).then(()=>{ + + // reload data + this.loadNoten(this.lv_id, e.value.studiensemester_kurzbz) + }).finally( () => this.loading = false) + + }, + getOptionLabel(option) { + return option.studiensemester_kurzbz + }, + getOptionLabelLv(option) { + return option.fullString + }, + getOptionLabelLe(option) { + return option.infoString + }, + getPruefungstypForStudentByAntritt(student) { + // when adding new pruefungen, determine the next pruefungstyp by using the antritt counter + switch (student.hoechsterAntritt) { + case 0: + return "Termin2" + break + case 1: + return "Termin2" + break + case 2: + return "Termin3" + break + default: + return "" + } + }, + savePruefungEingabe() { + const year = this.selectedPruefungDate.getFullYear(); + const month = String(this.selectedPruefungDate.getMonth() + 1).padStart(2, '0'); // Months are 0-based + const day = String(this.selectedPruefungDate.getDate()).padStart(2, '0'); + const dateStr = `${year}-${month}-${day}`; + + // first pruefung is always "Termin2" since normal note counts as Termin1 + // const pOffset = this.pruefung === null && this.pruefungStudent.pruefungen.length === 0 ? 2 : 1 + + const typ = this.pruefung ? this.pruefung.pruefungstyp_kurzbz : this.getPruefungstypForStudentByAntritt(this.pruefungStudent) + const note = this.selectedPruefungNote?.note ?? 9 // noch nicht eingetragen + const punkte = this.selectedPruefungPunkte ?? 0 + this.$api.call(ApiNoten.saveStudentPruefung( + this.pruefungStudent.uid, + note, + punkte, + dateStr, + this.lv_id, + this.pruefungStudent.lehreinheit_id, + this.sem_kurzbz, + typ + )).then(res => { + if(res.meta.status === 'success') { //'Prüfung für Student ' + this.pruefungStudent.uid + ' bearbeitet oder angelegt' + this.$fhcAlert.alertDefault( + 'success', + 'Info', + this.$capitalize(this.$p.t('benotungstool/pruefungSaveForUid', [this.pruefungStudent.uid])), + true + ) + const s = this.studenten.find(s => s.uid === res.data[1]?.student_uid) + + s.freigabedatum = this.parseDate(res.data[1]?.['freigabedatum']) + s.benotungsdatum = this.parseDate(res.data[1]?.['benotungsdatum']) + + s.freigegeben = this.checkFreigabe(s.freigabedatum, s.benotungsdatum, s.uid); + + s.lv_note = res.data[1]?.note + const oldScrollLeft = this.$refs.notenTable.tabulator.rowManager.scrollLeft + const oldScrollTop = this.$refs.notenTable.tabulator.rowManager.scrollTop + + // add new pruefung to row + if(!this.pruefung) { + this.handleAddNewTermin(res.data, s) + } else { // update existing + const oldIndex = s.pruefungen.findIndex(p => p.pruefung_id == this.pruefung.pruefung_id) + if(oldIndex !== -1) { + s.pruefungen.splice(oldIndex, 1, res.data[0]) + s[res.data[0].datum] = res.data[0] + } + + // antritte might have changed due to different benotung + s.hoechsterAntritt = this.getAntrittCountStudent(s) + this.recalculateSelectable(s) + this.reformatStudentRow(s) + } + + this.$refs.notenTable.tabulator.redraw(true) + Vue.nextTick(()=> { + const table = this.$refs.notenTable.tabulator.element.querySelector('.tabulator-tableholder') + if(table) { + table.scrollLeft = oldScrollLeft; + table.scrollTop = oldScrollTop; + } + }) + + } + }).finally(()=> { + this.pruefungStudent = null + this.pruefung = null + }) + + this.$refs.modalContainerPruefung.hide() + }, + handleAddNewTermin(data, student){ + this.colLayoutRestored = false; // always keep persistence options in mind when modifying table cols dynamically + + const savedPruefung = data[0] + const extra = data[2] + + // check for extra pruefung (termin1) to add before + if(extra) { + student["Termin1"] = extra + student.pruefungen.push(extra) // push to pruefungen array for antritt evaluation + } + + this.correctOldTerminTypenForStudent(student, savedPruefung) + + if(!this.distinctPruefungsDates.includes(savedPruefung.datum)) { + this.insertSortedDate(this.distinctPruefungsDates, savedPruefung.datum) + } + + // add pruefung to pruefungen array + student.pruefungen.push(savedPruefung) + + // add pruefung to student via its datum as a field + student[savedPruefung.datum] = savedPruefung + + // usually should be in order naturally, just to be save + student.pruefungen.sort((p1, p2) => { + if(p1.datum > p2.datum) { + return 1 + } else if (p1.datum < p2.datum) { + return -1 + } else { + return 0 + } + }) + + // recalculate student antritte + + student.hoechsterAntritt = this.getAntrittCountStudent(student) + this.recalculateSelectable(student) + this.reformatStudentRow(student) + + // add col to table + const cols = [...this.notenTableOptions.columns.slice(0, -1)]; + let kommCol = null + if(this.config?.CIS_GESAMTNOTE_PRUEFUNG_KOMMPRUEF) kommCol = this.notenTableOptions.columns[this.notenTableOptions.columns.length - 1]; + + if(this.distinctPruefungsDates.length) { + cols.push({ + title: this.$capitalize(this.$p.t('benotungstool/c4originalZnote')), + field: "Termin1", + formatter: this.pruefungFormatter, + titleFormatter: this.pruefungTitleFormatter, + sorter: this.pruefungSorter, + topCalc: this.terminCalcFunc, + topCalcFormatter: this.terminCalcFormatter, + hozAlign:"center", + widthGrow: 1, + minWidth: 200, + width: 250, + visible: true, + tooltip: false + }) + } + + this.distinctPruefungsDates.forEach((date)=>{ + const dateparts = date.split('-') + const titledate = `${dateparts[2]}.${dateparts[1]}.${dateparts[0]}` + + cols.push({ + title: titledate,//this.$p.t('benotungstool/pruefungNr', [index+1]), + field: date, + formatter: this.pruefungFormatter, + titleFormatter: this.pruefungTitleFormatter, + sorter: this.pruefungSorter, + topCalc: this.terminCalcFunc, + topCalcFormatter: this.terminCalcFormatter, + hozAlign:"center", + widthGrow: 1, + minWidth: 200, + width: 250, + visible: true, + tooltip: false + }) + }) + + if(this.config?.CIS_GESAMTNOTE_PRUEFUNG_KOMMPRUEF) cols.push(kommCol) // keep kommPruef Col as last + + + // preemptively do the persistence part of columns already here so we get the merged definition before + // tabulator internal go to war with vue reactives + let colsUsed = null + const saved = this.loadState(); + if (saved && saved.columns) { + // new columns map for lookup + const colMap = new Map(cols.map(c => [c.field, c])); + + const restoredCols = []; + + // add columns in the SAVED order, applying saved width/visibility + saved.columns.forEach(savedCol => { + const originalDef = colMap.get(savedCol.field); + if (originalDef) { + restoredCols.push({ + ...originalDef, // Keep formatters, calcs, etc. + width: savedCol.width, + visible: savedCol.visible + }); + colMap.delete(savedCol.field); // Remove so we don't double-add + } + }); + + // append any NEW columns aka pruefungs cols + colMap.forEach((def) => { + restoredCols.push(def); + }); + + colsUsed = restoredCols; + this.colLayoutRestored = true; + } + + + const colsFinal = colsUsed ?? cols + this.$refs.notenTable.tabulator.setColumns(colsFinal) + // this.$refs.notenTable.tabulator.redraw(true) + // redraw table outside this function + }, + recalculateSelectable(student) { + const vueThis = this + Object.defineProperty(student, 'selectable', { + get() { + const kP = student.pruefungen?.find(p => p.pruefungstyp_kurzbz == 'kommPruef') + return !(kP || student.hoechsterAntritt >= vueThis.maxAntrittCount) + }, + set() { + // empty setter so tabulator doesnt scream + }, + enumerable: true, + configurable: true + }) + }, + saveNoteneingabe() { + this.$api.call(ApiNoten.saveStudentenNoten(this.password, this.changedNoten, this.lv_id, this.sem_kurzbz)) + .then((res) => { + if(res.meta.status === 'success') { + this.$fhcAlert.alertDefault( + 'success', + 'Info', + 'Noten gespeichert', + true + ) + } + + res.data.forEach(d => { + const s = this.studenten.find(s => s.uid === d.uid) + s.freigabedatum = this.parseDate(d.freigabedatum) + s.benotungsdatum = this.parseDate(d.benotungsdatum) + s.freigegeben = this.checkFreigabe(s.freigabedatum, s.benotungsdatum, s.uid); + }) + this.changedNotenCounter++; + + this.$refs.notenTable.tabulator.redraw(true) + }) + + this.$refs.modalContainerNotenSpeichern.hide() + }, + openSaveModal() { + this.$refs.modalContainerNotenSpeichern.show() + }, + openNewPruefungsdatumModal() { + this.$refs.modalContainerNeuesPruefungsdatum.show() + }, + openNotenImportModal() { + this.$refs.modalContainerNotenImport.show() + }, + getOptionLabelNotePruefung(option) { + return option.bezeichnung + }, + leChanged(e) { + this.selectedLehreinheit = e.value + }, + addPruefung(){ + + this.$refs.modalContainerNeuesPruefungsdatum.hide() + + // filter students that already have a pruefung on datum + + const year = this.selectedPruefungDate.getFullYear(); + const month = String(this.selectedPruefungDate.getMonth() + 1).padStart(2, '0'); // Months are 0-based + const day = String(this.selectedPruefungDate.getDate()).padStart(2, '0'); + const dateStrDb = `${year}-${month}-${day}`; + const dateStrFront = `${day}.${month}.${year}`; + + const uids = [] + // const uids = this.selectedUids.map(student => { + this.selectedUids.forEach(student => { + + // if a student doesnt have a lv_note entered definitely dont allow to create pruefung + if(!student.lv_note) { + this.$fhcAlert.alertWarning('Student ' + student.uid + ' hat noch keine LV-Note eingetragen! Es wird keine Prüfung angelegt.') + return + } + + if(!student.note) { + this.$fhcAlert.alertWarning('Student ' + student.uid + ' hat noch keine Zeugnis Note eingetragen! Es wird keine Prüfung angelegt.') + return + } + + // check if student antrittCount is too high already + if(student.hoechsterAntritt >= this.maxAntrittCount) { + this.$fhcAlert.alertWarning('Student ' + student.uid + ' hat bereits ' + student.hoechsterAntritt + ' Prüfungsantritte abgelegt. Es wird keine Prüfung angelegt.') + return + } + + // get student for pruefung and check if proposed datum does not conflict (no new pruefungen before existing ones) + const youngerPruefung = student.pruefungen.find(pr => { + return pr.dateObj >= this.selectedPruefungDate + }) + if(youngerPruefung) { + this.$fhcAlert.alertWarning('Student ' + student.uid + ' hat bereits eine Prüfung am '+ youngerPruefung.datum +' eingetragen. Es wird keine Prüfung angelegt.') + return + } + + uids.push({ + uid: student.uid, + lehreinheit_id: student.lehreinheit_id, + typ: this.getPruefungstypForStudentByAntritt(student)//student.hoechsterAntritt + }) + }) + + this.loading = true; + this.$api.call(ApiNoten.createPruefungen( + uids, + dateStrDb, + this.lv_id, + this.sem_kurzbz, + )).then(res => { + if(res.meta.status === "success") { + + // iterate over response data + // -> alert successful pruefungen + // -> alert denied pruefungen + reason + + let uidListSuccess = '' + let uidListError = '' + const successData = [] + Object.keys(res.data).forEach(student_uid => { + const student = res.data[student_uid] + // actual pruefung has been allocated + if(student.savedPruefung || student.extraPruefung) { + uidListSuccess += ' ' + student_uid + + // keep res.data format intact for handleResponse method + successData[student_uid] = student + } else { // there should be an error message why no pruefungen where allocated for this person, many reasons possible + uidListError += student_uid + ' - ' + student +'\n'// student variable is the error message here + } + }) + + if(uidListError != '') { + this.$fhcAlert.alertError( + this.$capitalize(this.$p.t('benotungstool/c4pruefungAnlageError', [dateStrFront])) + ': ' + uidListError + ' ' + ) + } + + if(uidListSuccess != '') { + this.$fhcAlert.alertDefault( + 'success', + 'Info', + this.$capitalize(this.$p.t('benotungstool/pruefungAngelegtAn', [dateStrFront])) + ': ' + uidListSuccess, + true + ) + + this.handleAddNewPruefungenResponse({data: successData}, uids) + } + + } + }).finally(()=> this.loading = false) + }, + getAntrittCountStudent(student) { + // checks for existence of a prüfung with a note that resolves to a + // "angetretene Prüfung" -> anything except "entschuldigt" & "noch nicht eingetragen" + // and returns the next allowed pruefungstyp from the number of taken pruefungen + + // 1 -> reguläre note + // 2 -> erste Nachprüfung / Termin2 + // 3 -> 2te Nachprüfung / Termin3 + // 4 -> kommPruef + if(student['kommPruef']) return 4 + + let pruefungsAntrittCount = 0 + const pLen = student.pruefungen.length + for(let i = 0; i < pLen; i++) { + const p = student.pruefungen[i] + + const isDefinedAsAntrittsloseNote = this.config.NOTEN_OHNE_ANTRITT.find(n_pk => n_pk == p.note) + if(!isDefinedAsAntrittsloseNote) pruefungsAntrittCount++ + } + + // when student never had to take an exam beyond the original benotung + // aka pruefungsantritt (even though it does not have to have pruefungscharacter) + // it still counts as an antritt, except it is coming from a notenOption like "angerechnet" + // which indicates no participation at all + if(pruefungsAntrittCount === 0 && student.note){ + const noteOption = this.notenOptions.find(note => note.note == student.note) + + if(noteOption.lehre) return 1 + else return 0 + } + + return pruefungsAntrittCount + } + }, + watch: { + selectedUids(newVal, oldVal) { + const table = this.$refs.notenTable?.tabulator + + if (!table) return; + + const allRows = table.getRows(); + + allRows.forEach(row => { + const rowData = row.getData(); + const found = newVal.find(stud => stud.uid == rowData.uid) + if (found) { + row.select(); // ensure row is selected + const cb = row.getElement().children[0]?.children[0] + if(cb) cb.checked = true + } else { + row.deselect(); // ensure row is deselected + const cb = row.getElement().children[0]?.children[0] + if(cb) cb.checked = false + } + }); + }, + selectedLehreinheit(newVal) { + if(!this.$refs.notenTable) return + this.$refs.notenTable.tabulator.clearFilter(); + if(newVal) this.$refs.notenTable.tabulator.setFilter("lehreinheit_id", "=", newVal.lehreinheit_id); + }, + getKommPruefCount(newVal) { + if(!this.config.CIS_GESAMTNOTE_PRUEFUNG_KOMMPRUEF) return 0 + if(this.$refs.notenTable?.tabulator && newVal > 0) { + const kommPruefCol = this.$refs.notenTable?.tabulator.getColumn("kommPruef") + kommPruefCol.show() + } else if(this.$refs.notenTable?.tabulator && newVal == 0) { + const kommPruefCol = this.$refs.notenTable?.tabulator.getColumn("kommPruef") + kommPruefCol.hide() + } + } + }, + computed: { + maxAntrittCount() { + let maxAntritte = 1; + + if(this.config?.CIS_GESAMTNOTE_PRUEFUNG_TERMIN2) maxAntritte++ + if(this.config?.CIS_GESAMTNOTE_PRUEFUNG_TERMIN3) maxAntritte++ + + return maxAntritte; + }, + getFreigabeCounter() { + return this.studenten ? this.studenten.reduce((acc, cur) => { + if(cur.freigegeben == 'changed') { + acc++ + } + return acc + }, 0) : 0 + }, + LehreinheitenModule() { + return LehreinheitenModule; + }, + LeDropdownParams() { + return { + lv_id: this.lv_id, + sem_kurzbz: this.sem_kurzbz + } + }, + getStudentenOptions() { + return this.studenten ? this.studenten.filter(s => s.selectable) : [] + }, + getKommPruefCount(){ + let counter = 0 + this.studenten?.forEach(s => {if(s['kommPruef']){counter++}}) + return counter + }, + getSaveBtnClass() { + return this.changedNoten?.length ? "btn btn-primary ml-2" : "btn btn-secondary ml-2" + }, + getNewBtnClass() { + return "btn btn-primary ml-2" + }, + getNotenImportBtnClass() { + return "btn btn-primary ml-2" + }, + changedNoten() { + const v = this.changedNotenCounter // hack to trigger computed + const cs = this.studenten ? this.studenten.reduce((acc, cur) => { + const teilnote = this.teilnoten[cur.uid] + if(teilnote.note_lv && (cur.benotungsdatum > cur.freigabedatum)) { + + // write noteBezeichnung into changed Note so we can send emails in backend easier... + const opt = this.notenOptions.find(opt => opt.note == cur.lv_note) + cur.noteBezeichnung = opt.bezeichnung + + acc.push(cur) + } + return acc + }, []) : [] + return cs + }, + getNotenfreigabeHinweistext() { + return this.$capitalize(this.$p.t('benotungstool/notenfreigabeHinweistextv4')) + }, + getNotenimportHinweistext() { + return this.$capitalize(this.$p.t('benotungstool/notenimportHinweistextv5')) + } + }, + created() { + this.setupCreated() + }, + mounted() { + this.setupMounted() + }, + template: ` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

{{$capitalize($p.t('benotungstool/benotungstoolTitle'))}}

+

{{ lv?.bezeichnung }}

+
+
+
+ + + +
+
+ +
+
+ + + +
+
+ +
+
+ + + +
+
+
+
+ +
+ + + +
+ `, +}; + +export default Benotungstool; \ No newline at end of file diff --git a/public/js/components/Cis/Cms/News.js b/public/js/components/Cis/Cms/News.js index bf17666a2..edf700db5 100644 --- a/public/js/components/Cis/Cms/News.js +++ b/public/js/components/Cis/Cms/News.js @@ -2,115 +2,137 @@ import Pagination from "../../Pagination/Pagination.js"; import StudiengangInformation from "./StudiengangInformation/StudiengangInformation.js"; import BsConfirm from "../../Bootstrap/Confirm.js"; -import ApiCms from '../../../api/factory/cms.js'; +import ApiCms from "../../../api/factory/cms.js"; export default { name: "NewsComponent", - components: { - Pagination, - StudiengangInformation, - }, - data() { - return { - content: null, - maxPageCount: 0, - page_size: 10, - page:1, - }; - }, - watch:{ - '$p.user_language.value':function(sprache){ - this.fetchNews(); - } - }, - computed:{ - sprache: function(){ - return this.$p.user_language.value; + components: { + Pagination, + StudiengangInformation, }, - }, - methods: { - fetchNews() { - return this.$api - .call(ApiCms.getNews(this.page, this.page_size, this.sprache)) - .then(res => res.data) - .then(result => { - this.content = result; + inject: ["isMobile"], + data() { + return { + content: null, + maxPageCount: 0, + page_size: 10, + page: 1, + }; + }, + watch: { + "$p.user_language.value": function (sprache) { + this.fetchNews(); + }, + }, + computed: { + sprache: function () { + return this.$p.user_language.value; + }, + }, + methods: { + async fetchNews() { + let newsResponse = await this.$api.call( + ApiCms.getNews(this.page, this.page_size, this.sprache), + ); + this.content = newsResponse.data; - document.querySelectorAll("#cms [data-confirm]").forEach((el) => { - el.addEventListener("click", (evt) => { - evt.preventDefault(); - BsConfirm.popup(el.dataset.confirm) - .then(() => { - Axios.get(el.href) - .then((res) => { - // TODO(chris): check for success then show message and/or reload - location = location; - }) - .catch((err) => console.error("ERROR:", err)); + document.querySelectorAll("#cms [data-confirm]").forEach((el) => { + el.addEventListener("click", (evt) => { + evt.preventDefault(); + BsConfirm.popup(el.dataset.confirm) + .then(() => { + Axios.get(el.href) + .then((res) => { + // TODO(chris): check for success then show message and/or reload + location = location; }) - .catch(() => { - }); - }); - }); - document.querySelectorAll("#cms [data-href]").forEach((el) => { - el.href = el.dataset.href.replace( - /^ROOT\//, - FHC_JS_DATA_STORAGE_OBJECT.app_root - ); - }); - Vue.nextTick(()=>{ - document.querySelectorAll(".card-header").forEach((el) => { - el.classList.add("fhc-primary"); - }); - document.querySelectorAll(".row").forEach((el) => { - el.classList.add("w-100"); - el.classList.add("align-items-center"); - - }); - document.querySelectorAll(".row h2").forEach((el) => { - el.classList.add("mb-0"); - }); + .catch((err) => console.error("ERROR:", err)); + }) + .catch(() => {}); + }); + }); + document.querySelectorAll("#cms [data-href]").forEach((el) => { + el.href = el.dataset.href.replace( + /^ROOT\//, + FHC_JS_DATA_STORAGE_OBJECT.app_root, + ); + }); - }) + await this.$nextTick(); + this.formatExternalHtml(); + }, + async loadNewPageContent(data) { + let newsResponse = await this.$api.call( + ApiCms.getNews(data.page, data.rows), + ); + this.content = newsResponse.data; + + await this.$nextTick(); + + this.formatExternalHtml(); + }, + formatExternalHtml() { + document + .querySelectorAll(".news-list-item .card-header") + .forEach((el) => { + el.classList.add("fhc-primary"); + }); + document.querySelectorAll(".news-list-item .row").forEach((el) => { + el.classList.add("w-100"); + el.classList.add("align-items-center"); + }); + document + .querySelectorAll(".news-list-item .row h2") + .forEach((el) => { + el.classList.add("mb-0"); }); }, - loadNewPageContent(data) { - this.$api - .call(ApiCms.getNews(data.page, data.rows)) - .then(res => res.data) - .then(result => { - this.content = result; - - }); - } - }, - created() { - this.fetchNews(); + afterPageUpdated(event) { + this.page = event.page; + this.page_size = event.rows; + this.$refs.newsPageHeading.scrollIntoView({block: 'end'}); + this.loadNewPageContent(event); + }, + }, + created() { + this.fetchNews(); this.$api .call(ApiCms.getNewsRowCount()) - .then(res => res.data) - .then(result => { + .then((res) => res.data) + .then((result) => { this.maxPageCount = result; }); - }, - template: /*html*/ ` -

News

-
- - -
-
-
-
-
-
- + }, + template: /*html*/ ` +
+

News

+
+ +
+
+
+
+
+
+ +
+
- - `, }; diff --git a/public/js/components/Cis/LvPlan/Lehrveranstaltung.js b/public/js/components/Cis/LvPlan/Lehrveranstaltung.js index 4364a5df4..87f82eb0e 100644 --- a/public/js/components/Cis/LvPlan/Lehrveranstaltung.js +++ b/public/js/components/Cis/LvPlan/Lehrveranstaltung.js @@ -3,7 +3,8 @@ import FhcCalendar from "../../Calendar/LvPlan.js"; import ApiLvPlan from '../../../api/factory/lvPlan.js'; import ApiAuthinfo from '../../../api/factory/authinfo.js'; -export const DEFAULT_MODE_LVPLAN = 'Week' +export const DEFAULT_MODE_LVPLAN_MOBILE = 'List'; +export const DEFAULT_MODE_LVPLAN_DESKTOP = 'Week'; export default { name: 'LvPlanLehrveranstaltung', @@ -19,15 +20,21 @@ export default { lv: null }; }, + inject: ["isMobile"], computed:{ currentDay() { if (!this.propsViewData?.focus_date || isNaN(new Date(this.propsViewData?.focus_date))) - return luxon.DateTime.now().setZone(this.viewData.timezone).toISODate(); + return luxon.DateTime.now().setZone(FHC_JS_DATA_STORAGE_OBJECT.timezone).toISODate(); return this.propsViewData?.focus_date; }, currentMode() { - if (!this.propsViewData?.mode || !['day', 'week', 'month'].includes(this.propsViewData?.mode.toLowerCase())) - return DEFAULT_MODE_LVPLAN; + let validModes = ['day', 'month']; + validModes.push(this.isMobile ? 'list' : 'week'); + + const defaultMode = this.isMobile ? DEFAULT_MODE_LVPLAN_MOBILE : DEFAULT_MODE_LVPLAN_DESKTOP; + + if (!this.propsViewData?.mode || !validModes.includes(this.propsViewData?.mode.toLowerCase())) + return defaultMode; return this.propsViewData?.mode; }, currentLv() { @@ -95,7 +102,6 @@ export default { { - this.uid = res.data.uid; - this.isMitarbeiter = res.data.isMitarbeiter; - this.isStudent = res.data.isStudent; - }); + async created() { + await this.fetchAuthInfo(); }, template: /*html*/`
@@ -123,8 +137,9 @@ export default {
{ + this.studiensemester_kurzbz = + res.data.studiensemester_kurzbz; + this.studiensemester_start = res.data.start; + this.studiensemester_ende = res.data.ende; + }); + }, + getPromiseFunc(start, end) { + return [ + this.$api.call( + ApiLvPlan.eventsPersonal( + start.toISODate(), + end.toISODate(), + this.propsViewData.otherUid, + ), + ), + this.$api.call( + ApiLvPlan.getLvPlanReservierungen( + start.toISODate(), + end.toISODate(), + this.propsViewData.otherUid, + ), + ), + ]; + }, + async fetchViewData() { + const viewDataResponse = await this.$api.call( + ApiOtherLvPlan.getOtherLvPlanViewData( + this.propsViewData.otherUid, + ), + ); + + const viewData = viewDataResponse.data; + + this.isOtherPersonMitarbeiter = + !!viewData?.user_data?.is_mitarbeiter; + this.isOtherPersonStudent = !!viewData?.user_data?.is_student; + this.otherPersonData.fullName = + viewData?.user_data?.vorname + + " " + + viewData?.user_data?.nachname; + this.otherPersonData.photo = viewData?.user_data?.foto; + }, + async redirectToMyLvPlanIfAuthUid() { + const authInfoResponse = await this.$api.call( + ApiAuthinfo.getAuthInfo(), + ); + const authId = authInfoResponse.data.uid; + if (authId === this.propsViewData.otherUid) { + this.$router.push({ name: "MyLvPlan" }); + } + }, + }, + async created() { + await this.redirectToMyLvPlanIfAuthUid(); + await this.fetchViewData(); + }, + template: ` +
+

+
+ + {{ $p.t('lehre/stundenplan') + (studiensemester_kurzbz ? " " + studiensemester_kurzbz : "") }} + +
+ + {{ otherPersonData.fullName }} + + profile picture +
+
+

+
+ + + +
+ `, +}; diff --git a/public/js/components/Cis/LvPlan/StgOrg.js b/public/js/components/Cis/LvPlan/StgOrg.js new file mode 100644 index 000000000..8a18e273c --- /dev/null +++ b/public/js/components/Cis/LvPlan/StgOrg.js @@ -0,0 +1,413 @@ +import FormForm from "../../Form/Form.js"; +import FormInput from "../../Form/Input.js"; +import FhcCalendar from "../../Calendar/LvPlan.js"; + +import ApiLvPlan from '../.././../api/factory/lvPlan.js'; +import ApiStgOrgLvPlan from '../.././../api/factory/stgOrgLvPlan.js'; +import ApiAuthinfo from '../../../api/factory/authinfo.js'; + +export const DEFAULT_MODE_LVPLAN_DESKTOP = "Week"; +export const DEFAULT_MODE_LVPLAN_MOBILE = "List"; + +export default { + name: "LvPlanStgOrg", + components: { + FormForm, + FormInput, + FhcCalendar, + }, + props: { + propsViewData: Object + }, + data() { + return { + localProps: {}, + studiensemester_kurzbz: null, + studiensemester_start: null, + studiensemester_ende: null, + uid: null, + isMitarbeiter: false, + isStudent: false, + currentStgBezeichnung: null, + formData: { + stgkz: null, + sem: null, + verband: null, + gruppe: null, + }, + listStg: [], + listSem: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + listVerband: [], + listGroup: [], + rangeIntervalFirst: null, + timezone: FHC_JS_DATA_STORAGE_OBJECT.timezone, + }; + }, + inject: ["isMobile"], + computed: { + maxSemester() { + const currentStg = this.listStg.find( + (item) => item.studiengang_kz === this.formData.stgkz, + ); + return currentStg?.max_semester; + }, + currentDay() { + if (!this.propsViewData?.focus_date || isNaN(new Date(this.propsViewData?.focus_date))) + return luxon.DateTime.now().setZone(this.timezone).toISODate(); + return this.propsViewData?.focus_date; + }, + currentMode() { + let validModes = ["day", "month"]; + validModes.push(this.isMobile ? "list" : "week"); + + const defaultMode = this.isMobile + ? DEFAULT_MODE_LVPLAN_MOBILE + : DEFAULT_MODE_LVPLAN_DESKTOP; + + if ( + !this.propsViewData?.mode || + !validModes.includes(this.propsViewData?.mode.toLowerCase()) + ) + return defaultMode; + return this.propsViewData?.mode; + }, + downloadLinks() { + if ( + !this.studiensemester_start || + !this.studiensemester_ende || + !this.uid + ) + return false; + + let type = false; + type = this.isStudent ? "student" : type; + type = this.isMitarbeiter ? "lektor" : type; + if (false === type) { + return; + } + + const opts = { zone: this.timezone }; + const start = luxon.DateTime + .fromISO(this.studiensemester_start, opts) + .toUnixInteger(); + const ende = luxon.DateTime + .fromISO(this.studiensemester_ende, opts) + .toUnixInteger(); + + const download_link = + FHC_JS_DATA_STORAGE_OBJECT.app_root + + "cis/private/lvplan/stpl_kalender.php" + + "?type=" + + type + + "&pers_uid=" + + this.uid + + "&begin=" + + start + + "&ende=" + + ende; + + return [ + { + title: "excel", + icon: "fa-solid fa-file-excel", + link: download_link + "&format=excel", + }, + { + title: "csv", + icon: "fa-solid fa-file-csv", + link: download_link + "&format=csv", + }, + { + title: "ical1", + icon: "fa-regular fa-calendar", + link: download_link + "&format=ical&version=1&target=ical", + }, + { + title: "ical2", + icon: "fa-regular fa-calendar", + link: download_link + "&format=ical&version=2&target=ical", + }, + ]; + }, + }, + methods: { + loadLvPlan() { + if (!this.formData.stgkz) { + this.$fhcAlert.alertError(this.$p.t("LvPlan", "chooseStg")); + return; + } + + if ( + !this.formData.sem && + (this.formData.verband || this.formData.gruppe) + ) { + this.$fhcAlert.alertError( + this.$p.t("LvPlan", "error_SemMissing"), + ); + return; + } + + if (!this.formData.verband && this.formData.gruppe) { + this.$fhcAlert.alertError( + this.$p.t("LvPlan", "error_VerbandMissing"), + ); + return; + } + + const params = { + mode: this.currentMode, + focus_date: this.currentDay, + stgkz: this.formData.stgkz, + sem: this.formData.sem, + verband: this.formData.verband, + gruppe: this.formData.gruppe, + }; + + //ensure logic: no value after a null value in route + if (params.sem == null) { + params.verband = null; + params.gruppe = null; + } + if (params.verband == null) { + params.gruppe = null; + } + + //delete all null values to avoid null in router + Object.keys(params).forEach( + (key) => params[key] == null && delete params[key], + ); + + this.$router.push({ + name: "StgOrgLvPlan", + params, + }); + + this.$refs['calendar']?.resetEventLoader(); + }, + loadListSem(){ + if(!this.listSem) + this.listSem = [...Array(this.maxSemester).keys()].map(i => i + 1); + }, + loadListVerband() { + this.$api + .call(ApiLvPlan.getLehrverband(this.formData.stgkz, this.formData.sem, this.formData.verband)) + .then(result => { + const data = result.data; + const mappedData = data.map((item) => item.verband); + this.listVerband = [ + ...new Set( + mappedData.filter( + (v) => + v !== null && + v !== undefined && + String(v).trim() !== "", + ), + ), + ].sort(); + }) + .catch(this.$fhcAlert.handleSystemError); + }, + loadListGroup() { + this.$api + .call(ApiLvPlan.getGruppe(this.formData.stgkz, this.formData.sem, this.formData.verband)) + .then(result => { + const data = result.data; + const mappedData = data.map((item) => item.gruppe); + this.listGroup = [ + ...new Set( + mappedData.filter( + (v) => + v !== null && + v !== undefined && + String(v).trim() !== "", + ), + ), + ].sort(); + }) + .catch(this.$fhcAlert.handleSystemError); + }, + handleChangeDate(day, newMode) { + return this.handleChangeMode(newMode, day); + }, + handleChangeMode(newMode, day) { + const mode = newMode[0].toUpperCase() + newMode.slice(1); + const focus_date = day.toISODate(); + + this.$router.push({ + name: "StgOrgLvPlan", + params: { + mode, + focus_date, + stgkz: this.formData.stgkz, + sem: this.formData.sem, + verband: this.formData.verband, + gruppe: this.formData.gruppe, + }, + }); + }, + updateRange(rangeInterval) { + this.$api + .call( + ApiLvPlan.studiensemesterDateInterval( + rangeInterval.end.startOf("week").toISODate(), + ), + ) + .then((res) => { + this.studiensemester_kurzbz = + res.data.studiensemester_kurzbz; + this.studiensemester_start = res.data.start; + this.studiensemester_ende = res.data.ende; + }); + }, + getPromiseFunc(start, end) { + return [ + this.$api.call( + ApiLvPlan.eventsStgOrg( + start, + end, + this.formData.stgkz, + this.formData.sem, + this.formData.verband, + this.formData.gruppe, + ), + ), + ]; + }, + async fetchAuthInfo() { + const authInfoResponse = await this.$api.call(ApiAuthinfo.getAuthInfo()); + + const authInfo = authInfoResponse.data; + this.uid = authInfo.uid; + this.isMitarbeiter = authInfo.isMitarbeiter; + this.isStudent = authInfo.isStudent; + }, + async fetchViewData() { + const viewDataResponse = await this.$api.call(ApiStgOrgLvPlan.getStgOrgLvPlanViewData()); + + const viewData = viewDataResponse.data; + this.listStg = viewData.studiengaenge; + }, + }, + async created(){ + await this.fetchAuthInfo(); + await this.fetchViewData(); + + if (this.propsViewData) { + this.formData.stgkz = this.propsViewData.stgkz ? this.propsViewData.stgkz: null; + this.formData.sem = this.propsViewData.sem ? this.propsViewData.sem: null; + this.formData.verband = this.propsViewData.verband ? this.propsViewData.verband: null; + this.formData.gruppe = this.propsViewData.gruppe ? this.propsViewData.gruppe: null; + + //ensure loading dropdown arrays for version propsView + if(!this.listVerband.length && this.formData.sem) + { + this.loadListVerband(); + } + if(!this.listGroup.length && this.formData.verband) + { + this.loadListGroup(); + } + } + }, + template: ` +
+ +
+ + + + + + + + + + + + + + + + + + + + +
+ + +
+ `, +}; diff --git a/public/js/components/Cis/LvPlan/StgOrg_DEPR.js b/public/js/components/Cis/LvPlan/StgOrg_DEPR.js new file mode 100644 index 000000000..02606a1db --- /dev/null +++ b/public/js/components/Cis/LvPlan/StgOrg_DEPR.js @@ -0,0 +1,187 @@ +import FhcCalendar from "../../Calendar/LvPlan.js"; + +import ApiLvPlan from '../.././../api/factory/lvPlan.js'; +import ApiAuthinfo from '../../../api/factory/authinfo.js'; + +export const DEFAULT_MODE_LVPLAN = 'Week'; + +export default { + name: 'LvPlanStgOrg', + inject: { + cisRoot: { + from: 'cisRoot' + }, + }, + components: { + FhcCalendar, + }, + props: { + viewData: Object, + propsViewData: Object + }, + data() { + return { + studiensemester_kurzbz: null, + studiensemester_start: null, + studiensemester_ende: null, + uid: null, + isMitarbeiter: false, + isStudent: false, + listStg: [], + currentStgBezeichnung: null + }; + }, + computed:{ + currentDay() { + if (!this.propsViewData?.focus_date || isNaN(new Date(this.propsViewData?.focus_date))) + return luxon.DateTime.now().setZone(this.viewData.timezone).toISODate(); + return this.propsViewData?.focus_date; + }, + currentMode() { + if (!this.propsViewData?.mode || !['day', 'week', 'month'].includes(this.propsViewData?.mode.toLowerCase())) + return DEFAULT_MODE_LVPLAN; + return this.propsViewData?.mode; + }, + downloadLinks() { + if (!this.studiensemester_start || !this.studiensemester_ende || !this.uid) + return false; + + let type = false; + type = this.isStudent ? 'student' : type; + type = this.isMitarbeiter ? 'lektor' : type; + if (false === type) + { + return; + } + + const opts = { zone: this.viewData.timezone }; + const start = luxon.DateTime + .fromISO(this.studiensemester_start, opts) + .toUnixInteger(); + const ende = luxon.DateTime + .fromISO(this.studiensemester_ende, opts) + .toUnixInteger(); + + const download_link = FHC_JS_DATA_STORAGE_OBJECT.app_root + + 'cis/private/lvplan/stpl_kalender.php' + + '?type=' + type + + '&pers_uid=' + this.uid + + '&begin=' + start + + '&ende=' + ende; + + return [ + { title: "excel", icon: 'fa-solid fa-file-excel', link: download_link + '&format=excel' }, + { title: "csv", icon: 'fa-solid fa-file-csv', link: download_link + '&format=csv' }, + { title: "ical1", icon: 'fa-regular fa-calendar', link: download_link + '&format=ical&version=1&target=ical' }, + { title: "ical2", icon: 'fa-regular fa-calendar', link: download_link + '&format=ical&version=2&target=ical' } + ]; + } + }, + methods: { + handleChangeDate(day, newMode) { + return this.handleChangeMode(newMode, day); + }, + handleChangeMode(newMode, day) { + const mode = newMode[0].toUpperCase() + newMode.slice(1) + const focus_date = day.toISODate(); + + this.$router.push({ + name: "StgOrgLvPlan", + params: { + mode, + focus_date, + stgkz: this.propsViewData.stgkz, + sem: this.propsViewData.sem, + verband: this.propsViewData.verband, + gruppe: this.propsViewData.gruppe, + } + }); + }, + updateRange(rangeInterval) { + this.$api + .call(ApiLvPlan.studiensemesterDateInterval( + rangeInterval.end.startOf('week').toISODate() + )) + .then(res => { + this.studiensemester_kurzbz = res.data.studiensemester_kurzbz; + this.studiensemester_start = res.data.start; + this.studiensemester_ende = res.data.ende; + }); + }, + getPromiseFunc(start, end) { + return [ + this.$api.call(ApiLvPlan.eventsStgOrg(start.toISODate(), end.toISODate(), this.propsViewData.stgkz, this.propsViewData.sem, this.propsViewData.verband, this.propsViewData.gruppe)), + //local for test + /* this.$api.call(ApiLvPlan.eventsStgOrg(start.toISODate(), end.toISODate(), this.stgkz, this.sem, this.verband, this.gruppe))*/ + ]; + }, + backToDropdown(){ + this.$router.push({ + name: "OverviewLvPlan", + }); + } + }, + created(){ + this.$api + .call(ApiAuthinfo.getAuthInfo()) + .then(res => { + this.uid = res.data.uid; + this.isMitarbeiter = res.data.isMitarbeiter; + this.isStudent = res.data.isStudent; + }); + + if(this.propsViewData.stgkz) { + this.$api + .call(ApiLvPlan.getStudiengaenge()) + .then(result => { + const currentStg = result.data.find( + item => item.studiengang_kz == this.propsViewData.stgkz + ); + this.currentStgBezeichnung = currentStg.kurzbzlang + " - " + currentStg.bezeichnung; + }) + .catch(this.$fhcAlert.handleSystemError); + } + }, + template: ` +
+

{{ $p.t('LvPlan/headerLvPlanLvVerband') }}

+

{{currentStgBezeichnung}} + Semester: {{propsViewData.sem}} + Verband: {{propsViewData.verband}} + Gruppe: {{propsViewData.gruppe}} + +

+

{{ $p.t('LvPlan/noStgProvided') }}

+ + + + +
+ `, +}; \ No newline at end of file diff --git a/public/js/components/Cis/Menu.js b/public/js/components/Cis/Menu.js index f073d5a38..573e569ec 100644 --- a/public/js/components/Cis/Menu.js +++ b/public/js/components/Cis/Menu.js @@ -30,6 +30,7 @@ export default { menuOpen:true, }; }, + inject: ["isMobile"], provide(){ return{ setActiveEntry: this.setActiveEntry, @@ -58,7 +59,7 @@ export default { }, site_url(){ return FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router; - } + }, }, methods: { fetchMenu() { @@ -112,10 +113,26 @@ export default { }); }, template: /*html*/` - - +
+
+ + + + +
+
+ -