diff --git a/application/config/Events.php b/application/config/Events.php
index f9f8d5b3d..eb4baf114 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..81f467a13
--- /dev/null
+++ b/application/config/noten.php
@@ -0,0 +1,64 @@
+ 1]; // über alle Antritte hinweg nur ein entschuldigt
+
+// tbl_note pk of the 'entschuldigt' note. An entschuldigt Termin is preserved as its own dated
+// entry when a new pruefung of the same type is created (instead of being overwritten).
+$config['NOTE_ENTSCHULDIGT'] = 17;
+
+// The maximum number of attempts that count, the first attempt and the kommissionelle attempt
+// included. null derives the number from the old flags:
+// 1 (the original assessment) + TERMIN2 + TERMIN3 + KOMMPRUEF
+// The examination rules permit three attempts. With TERMIN2 and KOMMPRUEF the formula gives 3.
+// An installation with TERMIN3 gets 4, which is one more attempt in the same chain.
+$config['CIS_GESAMTNOTE_MAX_ANTRITTE'] = null;
+
+// The exam types that take place before a commission.
+$config['PRUEFUNG_KOMMISSIONELL_TYPEN'] = ['kommPruef', 'zusKommPruef'];
+
+// The type that the tool writes for the last attempt. The last attempt is always kommissionell.
+$config['PRUEFUNG_TYP_KOMMISSIONELL'] = 'kommPruef';
+
+// Exam types that never use an attempt. A zusKommPruef repeats a kommissionelle Prüfung that had
+// a procedural fault. It stands outside the attempt chain, and the student administration enters
+// it. The examination rules do not describe it.
+$config['PRUEFUNG_TYPEN_OHNE_ANTRITT'] = ['zusKommPruef'];
+
+// Spaltenaufteilung der Prüfungen in der Notentabelle:
+// 'antritt' - eine Spalte je Antrittsnummer, Datum steht in der Zelle (robust bei Einzelterminen)
+// 'datum' - eine Spalte je Prüfungsdatum (kompakt, wenn ganze Jahrgänge am selben Tag antreten)
+// Nur die Vorgabe; im Tool umschaltbar und pro Benutzer gespeichert.
+$config['CIS_GESAMTNOTE_PRUEFUNGSSPALTEN'] = 'antritt';
+
+// Bei der Notenübernahme durch die Assistenz den ersten Antritt als eigene Prüfung anlegen.
+$config['CIS_GESAMTNOTE_ERSTANTRITT_BEI_UEBERNAHME'] = true;
+
+// availability of the two Benotungstool import flows. When both are true they are shown as
+// separate buttons/dialogs.
+$config['CIS_GESAMTNOTE_PRUEFUNGSIMPORT'] = true; // dated import that creates a pruefung per row
+$config['CIS_GESAMTNOTE_NOTENIMPORT'] = false; // classic note-only import (uid + note, no date)
+
+// Noteneintragungsfrist (Prüfungsordnung §1): grade/pruefung entry is only permitted up to this
+// deadline. The month/day below is applied to the studiensemester's year:
+// Sommersemester (SSyyyy) -> deadline in the SAME calendar year (default 15th November)
+// Wintersemester (WSyyyy) -> deadline in the FOLLOWING calendar year (default 15th May)
+$config['CIS_GESAMTNOTE_NOTENEINTRAGUNGSFRIST'] = false; // switch to use the window enforcement
+$config['NOTENEINTRAGUNGSFRIST_SS'] = ['month' => 11, 'day' => 15]; // Sommersemester deadline (same year)
+$config['NOTENEINTRAGUNGSFRIST_WS'] = ['month' => 5, 'day' => 15]; // Wintersemester deadline (following year)
\ No newline at end of file
diff --git a/application/config/routes.php b/application/config/routes.php
index 79e15edd9..b94935f4f 100644
--- a/application/config/routes.php
+++ b/application/config/routes.php
@@ -61,12 +61,29 @@ $route['api/v1/organisation/[O|o]rganisationseinheit/(:any)'] = 'api/v1/organisa
$route['api/v1/ressource/[B|b]etriebsmittelperson/(:any)'] = 'api/v1/ressource/betriebsmittelperson2/$1';
$route['api/v1/system/[S|s]prache/(:any)'] = 'api/v1/system/sprache2/$1';
+// Legacy CMS news routes
+$route['api/frontend/v1/Cms/getNewsItem/(:num)'] = 'api/frontend/v1/NewsAdministrationAPI/getNewsItem/$1';
+$route['api/frontend/v1/Cms/storeNewsItem'] = 'api/frontend/v1/NewsAdministrationAPI/storeNewsItem';
+$route['api/frontend/v1/Cms/updateNewsItem/(:num)'] = 'api/frontend/v1/NewsAdministrationAPI/updateNewsItem/$1';
+$route['api/frontend/v1/Cms/deleteNewsItem/(:num)'] = 'api/frontend/v1/NewsAdministrationAPI/deleteNewsItem/$1';
+
+// Keep legacy uppercase URLs refreshable while using the canonical controller method casing
+$route['CisVue/Cms/NewsAdministration'] = 'CisVue/Cms/newsAdministration';
+
$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['Cis/MyReservation'] = 'Cis/MyReservation/index/$1';
+
+//Route for Zeitsperrenma
+$route['Cis/Zeitsperrenma/.*'] = 'Cis/Zeitsperrenma/index/$1';
+//Route für Zeitsperren
+$route['Cis/Zeitsperren/.*'] = 'Cis/Zeitsperren/index/$1';
$route['Abgabetool/Assistenz'] = 'Cis/Abgabetool/Assistenz';
$route['Abgabetool/Assistenz/(:any)'] = 'Cis/Abgabetool/Assistenz/$1';
diff --git a/application/controllers/Cis/Benotungstool.php b/application/controllers/Cis/Benotungstool.php
new file mode 100644
index 000000000..fe01f3595
--- /dev/null
+++ b/application/controllers/Cis/Benotungstool.php
@@ -0,0 +1,37 @@
+ array('lehre/benotungstool:rw', 'lehre/benotungstool_assistenz:rw')
+ ]);
+
+ $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/MyReservation.php b/application/controllers/Cis/MyReservation.php
new file mode 100644
index 000000000..e4045cf90
--- /dev/null
+++ b/application/controllers/Cis/MyReservation.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' => 'MyReservation']);
+ }
+}
diff --git a/application/controllers/Cis/ProjektabgabeUebersicht.php b/application/controllers/Cis/ProjektabgabeUebersicht.php
new file mode 100644
index 000000000..e7ce692f0
--- /dev/null
+++ b/application/controllers/Cis/ProjektabgabeUebersicht.php
@@ -0,0 +1,30 @@
+ ['basis/cis:r']
+ ]);
+ }
+
+ // -----------------------------------------------------------------------------------------------------------------
+ // Public methods
+
+ /**
+ * @return void
+ */
+ public function index()
+ {
+ $this->load->view('CisRouterView/CisRouterView.php', ['route' => 'ProjektabgabeUebersicht']);
+ }
+}
diff --git a/application/controllers/Cis/Zeitsperren.php b/application/controllers/Cis/Zeitsperren.php
new file mode 100644
index 000000000..caee13c48
--- /dev/null
+++ b/application/controllers/Cis/Zeitsperren.php
@@ -0,0 +1,30 @@
+ ['cis/eigene_zeitsperren: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/Cis/Zeitsperrenma.php b/application/controllers/Cis/Zeitsperrenma.php
new file mode 100644
index 000000000..851291622
--- /dev/null
+++ b/application/controllers/Cis/Zeitsperrenma.php
@@ -0,0 +1,31 @@
+ ['cis/andere_zeitsperren:r']
+ ]);
+
+ }
+
+ // -----------------------------------------------------------------------------------------------------------------
+ // Public methods
+
+ /**
+ * @return void
+ */
+ public function index()
+ {
+ $this->load->view('CisRouterView/CisRouterView.php', ['route' => 'Zeitsperrenma']);
+ }
+}
diff --git a/application/controllers/CisVue/Cms.php b/application/controllers/CisVue/Cms.php
index aa07b919f..8f70e91c8 100644
--- a/application/controllers/CisVue/Cms.php
+++ b/application/controllers/CisVue/Cms.php
@@ -17,14 +17,15 @@ class Cms extends Auth_Controller
array(
'content' => 'basis/cis:r',
'getNews' => 'basis/cis:r',
- 'getNewsRowCount' => 'basis/cis:r',
'getRoomInformation' => 'basis/cis:r',
- 'news' => 'basis/cis:r'
+ 'news' => 'basis/cis:r',
+ 'newsAdministration' => ['basis/news:r'],
)
);
// Loads Libraries
$this->load->library('CmsLib');
+ $this->load->library('PermissionLib');
// Loads phrases system
$this->loadPhrases([
@@ -83,10 +84,18 @@ class Cms extends Auth_Controller
*/
public function news($infoscreen = false, $studiengang_kz = null, $semester = null, $mischen = true, $titel = '', $edit = false, $sichtbar = true)
{
- $viewData = array();
- $this->load->view('CisRouterView/CisRouterView.php', ['viewData'=>$viewData, 'route' => 'News']);
+ $this->load->view('CisRouterView/CisRouterView.php', [
+ 'route' => 'News',
+ ]);
}
+ public function newsAdministration()
+ {
+ $this->load->view('CisRouterView/CisRouterView.php', [
+ 'route' => 'NewsAdministration',
+ ]);
+ }
+
public function getRoomInformation($ort_kurzbz)
{
// Load Config
diff --git a/application/controllers/api/frontend/v1/Abgabe.php b/application/controllers/api/frontend/v1/Abgabe.php
index 759c32d58..62d29f029 100644
--- a/application/controllers/api/frontend/v1/Abgabe.php
+++ b/application/controllers/api/frontend/v1/Abgabe.php
@@ -1916,7 +1916,7 @@ class Abgabe extends FHCAPI_Controller
if (!$mailres)
{
- $this->terminateWithError($this->p->t('abgabetool', 'c4fehlerMailBegutachterv2'), 'general');
+ $this->terminateWithError($this->p->t('abgabetool', 'c4fehlerMailZweitBegutachterv2'), 'general');
}
}
diff --git a/application/controllers/api/frontend/v1/Benutzer.php b/application/controllers/api/frontend/v1/Benutzer.php
new file mode 100644
index 000000000..f5dea1c7f
--- /dev/null
+++ b/application/controllers/api/frontend/v1/Benutzer.php
@@ -0,0 +1,48 @@
+.
+ */
+
+if (!defined('BASEPATH')) exit('No direct script access allowed');
+
+class Benutzer extends FHCAPI_Controller
+{
+
+ /**
+ * Object initialization
+ */
+ public function __construct()
+ {
+ parent::__construct([
+ 'getUserData' => self::PERM_LOGGED,
+ ]);
+
+ $this->load->model('person/Benutzer_model', 'BenutzerModel');
+ }
+
+ //------------------------------------------------------------------------------------------------------------------
+ // Public methods
+
+ public function getUserData()
+ {
+ $uid = $this->input->get("uid") ?? getAuthUID();
+ $this->BenutzerModel->addSelect("uid, person_id, insertamum");
+ $userResult = $this->BenutzerModel->load(["uid" => $uid]);
+ $userData = $this->getDataOrTerminateWithError($userResult);
+ $this->terminateWithSuccess($userData);
+ }
+}
+
diff --git a/application/controllers/api/frontend/v1/Cms.php b/application/controllers/api/frontend/v1/Cms.php
index b8937a1f4..06b88c454 100644
--- a/application/controllers/api/frontend/v1/Cms.php
+++ b/application/controllers/api/frontend/v1/Cms.php
@@ -36,14 +36,15 @@ class Cms extends FHCAPI_Controller
'ContentID' => self::PERM_LOGGED,
'getOrtKurzbzContent' => self::PERM_LOGGED,
'content' => self::PERM_LOGGED,
- 'news' => self::PERM_LOGGED,
- 'getNewsRowCount' => self::PERM_LOGGED,
+ 'newsForWidget' => self::PERM_LOGGED,
'getNews' => self::PERM_LOGGED,
]);
$this->load->model('content/News_model', 'NewsModel');
-
+ $this->load->model('crm/Student_model', 'StudentModel');
+ $this->load->model('crm/Prestudent_model', 'PrestudentModel');
+
// setting up the papgination_size
$this->page_size = 10;
@@ -59,7 +60,6 @@ class Cms extends FHCAPI_Controller
//------------------------------------------------------------------------------------------------------------------
// Private methods
-
//------------------------------------------------------------------------------------------------------------------
// Public methods
@@ -106,7 +106,7 @@ class Cms extends FHCAPI_Controller
$this->terminateWithSuccess($content_id);
}
- public function news()
+ public function newsForWidget()
{
// form validation
@@ -118,7 +118,7 @@ class Cms extends FHCAPI_Controller
$this->load->model('content/news_model', 'NewsModel');
$limit = $this->input->get('limit',TRUE);
-
+
//query the news
$news = $this->NewsModel->getAll($limit);
@@ -157,23 +157,7 @@ class Cms extends FHCAPI_Controller
}
- public function getNewsRowCount($infoscreen = false, $studiengang_kz = null, $semester = null, $mischen = true, $titel = '', $fachbereich_kurzbz = null, $maxalter = 0, $edit = false, $sichtbar = true, $page = 1, $page_size = 10)
- {
- list($studiengang_kz, $semester) = $this->cmslib->getStgAndSem($studiengang_kz, $semester);
- $all = $edit;
-
- $this->load->model('content/News_model','NewsModel');
-
- $num_rows = $this->NewsModel->countNewsWithContent(getSprache(), $studiengang_kz, $semester, $fachbereich_kurzbz, $sichtbar, $maxalter, $page, $page_size, $all, $mischen);
-
- $num_rows = $this->getDataOrTerminateWithError($num_rows);
-
- $this->terminateWithSuccess($num_rows);
-
- }
-
-
- public function getNews($infoscreen = false, $studiengang_kz = null, $semester = null, $mischen = true, $titel = '', $edit = false, $sichtbar = true)
+ public function getNews($infoscreen = false, $studiengang_kz = null, $semester = null, $mischen = false, $titel = '', $edit = false, $sichtbar = true)
{
//form validation
$this->load->library('form_validation');
@@ -193,15 +177,45 @@ class Cms extends FHCAPI_Controller
// default value for the page_size is 10
$page_size = $page_size ?? 10;
+
+ $passedSichtbar = $this->input->get('published', true);
+ if($passedSichtbar !== null)
+ {
+ $sichtbar = $passedSichtbar;
+ }
- $news = $this->cmslib->getNews($infoscreen, $studiengang_kz, $semester, $mischen, $titel, $edit, $sichtbar, $page, $page_size, $sprache);
+ $filterForDegreePrograms = false;
+ $entitledDegreePrograms = [];
+ $student = $this->StudentModel->loadWhere(['student_uid' => getAuthUID()]);
+ if(isError($student))
+ {
+ $this->terminateWithError(getError($student));
+ }
+ if(hasData($student))
+ {
+ $student = current(getData($student));
+
+ $degreeProgramSemesterDataResult = $this->PrestudentModel->getStudiengaengAndAusbildungssemesterByStudentUid(getAuthUID());
+ $degreeProgramSemesterData = $this->getDataOrTerminateWithError($degreeProgramSemesterDataResult)[0];
+ if ($degreeProgramSemesterData) {
+ $studiengang_kz = isset($degreeProgramSemesterData->studiengang_kz) ? $degreeProgramSemesterData->studiengang_kz : null;
+ $semester = isset($degreeProgramSemesterData->ausbildungssemester) ? $degreeProgramSemesterData->ausbildungssemester : null;
+ }
+
+ $mischen = true;
+ } else {
+ $filterForDegreePrograms = true;
+ $entitledDegreePrograms = $this->permissionlib->getSTG_isEntitledFor("basis/news");
+ array_push($entitledDegreePrograms, 0);
+ }
+
+ $news = $this->cmslib->getNews($infoscreen, $studiengang_kz, $semester, $mischen, $titel, $edit, $sichtbar, $page, $page_size, $sprache, $filterForDegreePrograms, $entitledDegreePrograms, true);
$news = $this->getDataOrTerminateWithError($news);
- $this->addMeta('phrases', json_decode($this->p->getJson()));
- $this->terminateWithSuccess($news);
+ $this->addMeta('row_count', $news["row_count"] ?? 0);
+ $this->terminateWithSuccess($news["content"]);
}
}
-
diff --git a/application/controllers/api/frontend/v1/Lehre.php b/application/controllers/api/frontend/v1/Lehre.php
index c2ebeb10b..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
*/
@@ -40,38 +31,56 @@ class Lehre extends FHCAPI_Controller
'LV' => 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)
@@ -81,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
*/
@@ -145,5 +154,46 @@ class Lehre extends FHCAPI_Controller
$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..311edb1d9 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){
@@ -372,7 +379,9 @@ class LvMenu extends FHCAPI_Controller
'name'=>$this->p->t('lehre', 'gesamtnote'),
'phrase' => 'lehre/gesamtnote',
'c4_icon'=> base_url('skin/images/button_endnote.png'),
- 'c4_link'=> base_url('cis/private/lehre/benotungstool/lvgesamtnoteverwalten.php?lvid='.urlencode($lvid).'&stsem='.urlencode($angezeigtes_stsem))
+ 'c4_link'=> base_url('cis.php/Cis/Compat/legacy/cis/private/lehre/benotungstool/lvgesamtnoteverwalten.php?lvid='.urlencode($lvid).'&stsem='.urlencode($angezeigtes_stsem))
+ //'c4_link'=> site_url('Cis/Benotungstool/'.urlencode($lvid).'/'.urlencode($angezeigtes_stsem))
+ //'c4_link'=> base_url('cis/private/lehre/benotungstool/lvgesamtnoteverwalten.php?lvid='.urlencode($lvid).'&stsem='.urlencode($angezeigtes_stsem))
//'c4_link'=> base_url('benotungstool/lvgesamtnoteverwalten.php?lvid='.urlencode($lvid).'&stsem='.urlencode($angezeigtes_stsem))
);
}
@@ -541,4 +550,4 @@ class LvMenu extends FHCAPI_Controller
);
}
}
-}
\ No newline at end of file
+}
diff --git a/application/controllers/api/frontend/v1/MaZeitsperren.php b/application/controllers/api/frontend/v1/MaZeitsperren.php
new file mode 100644
index 000000000..0045b15e8
--- /dev/null
+++ b/application/controllers/api/frontend/v1/MaZeitsperren.php
@@ -0,0 +1,167 @@
+ ['cis/andere_zeitsperren:r', 'admin:r', 'assistenz:r'],
+ 'getAllZeitsperrenFixeMa' => ['cis/andere_zeitsperren:r', 'admin:r', 'assistenz:r'],
+ 'getAllZeitsperrenLector' => ['cis/andere_zeitsperren:r', 'admin:r', 'assistenz:r'],
+ 'getAllZeitsperrenOes' => ['cis/andere_zeitsperren:r', 'admin:r', 'assistenz:r'],
+ 'getZeitsperrenAss' => ['cis/andere_zeitsperren:r', 'admin:r', 'assistenz:r'],
+ 'getStgLectors' => self::PERM_LOGGED,
+ 'loadZeitsperrenLectorStg' => ['cis/andere_zeitsperren:r', 'admin:r', 'assistenz:r'],
+ 'loadZeitsperrenMa' => ['cis/andere_zeitsperren:r', 'admin:r', 'assistenz:r'],
+ 'getDetailsMa' => 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');
+ }
+
+ //only user with zeitsperren
+ public function getAllActiveZeitsperren($days)
+ {
+ $result = $this->ZeitsperreModel->getZeitsperrenForNextDays($days);
+ $data = $this->getDataOrTerminateWithError($result);
+
+ $this->terminateWithSuccess($data);
+ }
+
+ //TODO: check query: more results after join with hr-table
+ //all fixe Ma
+ public function getAllZeitsperrenFixeMa($days)
+ {
+ $von = date('Y-m-d');
+ $bis = date('Y-m-d', strtotime("+{$days} days"));
+ $result = $this->ZeitsperreModel->getMitarbeiterWithZeitsperren($von, $bis, true, false, null, false);
+ $data = $this->getDataOrTerminateWithError($result);
+
+ $this->terminateWithSuccess($data);
+ }
+
+ public function getAllZeitsperrenLector($days)
+ {
+ $von = date('Y-m-d');
+ $bis = date('Y-m-d', strtotime("+{$days} days"));
+ $result = $this->ZeitsperreModel->getMitarbeiterWithZeitsperren($von, $bis, false, true, null, false);
+
+ $data = $this->getDataOrTerminateWithError($result);
+
+ $this->terminateWithSuccess($data);
+ }
+
+ public function getZeitsperrenAss($days)
+ {
+ $von = date('Y-m-d');
+ $bis = date('Y-m-d', strtotime("+{$days} days"));
+ $result = $this->ZeitsperreModel->getMitarbeiterWithZeitsperren($von, $bis, false, false, null, true);
+
+ $data = $this->getDataOrTerminateWithError($result);
+
+ $this->terminateWithSuccess($data);
+ }
+
+ public function getAllZeitsperrenOes($days, $oe)
+ {
+ if(!$oe)
+ {
+ return $this->terminateWithError($this->p->t('ui', 'error_missingId', ['id'=> 'OE Kurzbezeichnung']), self::ERROR_TYPE_GENERAL);
+ }
+ $von = date('Y-m-d');
+ $bis = date('Y-m-d', strtotime("+{$days} days"));
+ $result = $this->ZeitsperreModel->getMitarbeiterWithZeitsperren($von, $bis, false, false, $oe, false);
+
+ $data = $this->getDataOrTerminateWithError($result);
+
+ $this->terminateWithSuccess($data);
+ }
+
+ public function loadZeitsperrenLectorStg($days, $stg)
+ {
+ if(!$stg)
+ {
+ return $this->terminateWithError($this->p->t('ui', 'error_missingId', ['id'=> 'Studiengangskennzahl']), self::ERROR_TYPE_GENERAL);
+ }
+ if(!is_numeric($stg))
+ {
+ return $this->terminateWithError($this->p->t('ui', 'error_paramNoNumber', ['param'=> 'Studiengangskennzahl']), self::ERROR_TYPE_GENERAL);
+ }
+
+ $von = date('Y-m-d');
+ $bis = date('Y-m-d', strtotime("+{$days} days"));
+ $result = $this->ZeitsperreModel->getMitarbeiterWithZeitsperren($von, $bis, false, true, false, false, $stg);
+
+ $data = $this->getDataOrTerminateWithError($result);
+
+ $this->terminateWithSuccess($data);
+ }
+
+ public function getStgLectors()
+ {
+ $sql = "
+ select
+ studiengang_kz,
+ kurzbz,
+ kurzbzlang,
+ typ,
+ bezeichnung,
+ kurzbzlang || ' (' || bezeichnung || ')' AS label,
+ aktiv
+ from
+ public.tbl_studiengang
+ where
+ typ in ('b','m')
+ order by typ asc, kurzbz Asc
+ ";
+
+ $this->load->model('organisation/Studiengang_model', 'StudiengangModel');
+ $result = $this->StudiengangModel->execReadOnlyQuery($sql);
+ $data = $this->getDataOrTerminateWithError($result);
+
+ $this->terminateWithSuccess($data);
+ }
+
+ public function loadZeitsperrenMa($days, $uid)
+ {
+ if ($uid === null || $uid === '')
+ {
+ return $this->terminateWithError($this->p->t('ui', 'error_missingId', ['id'=> 'Mitarbeiter UID']), self::ERROR_TYPE_GENERAL);
+ }
+ $result = $this->ZeitsperreModel->getZeitsperrenForNextDays($days, $uid);
+
+ $data = $this->getDataOrTerminateWithError($result);
+
+ $this->terminateWithSuccess($data);
+ }
+
+ public function getDetailsMa($uid)
+ {
+ if ($uid === null || $uid === '')
+ {
+ $this->terminateWithError($this->p->t('ui', 'error_missingId', ['id'=> 'Mitarbeiter UID']), self::ERROR_TYPE_GENERAL);
+ }
+ $this->load->model('person/Person_model', 'PersonModel');
+ $result = $this->PersonModel->getFullName($uid);
+
+ $data = $this->getDataOrTerminateWithError($result);
+
+ $this->terminateWithSuccess($data);
+ }
+
+}
+
diff --git a/application/controllers/api/frontend/v1/NewsAdministrationAPI.php b/application/controllers/api/frontend/v1/NewsAdministrationAPI.php
new file mode 100644
index 000000000..e15e403cd
--- /dev/null
+++ b/application/controllers/api/frontend/v1/NewsAdministrationAPI.php
@@ -0,0 +1,948 @@
+.
+ */
+
+if (!defined('BASEPATH')) exit('No direct script access allowed');
+
+class NewsAdministrationAPI extends FHCAPI_Controller
+{
+ /**
+ * News API constructor.
+ */
+ public function __construct()
+ {
+ parent::__construct([
+ 'getNews' => 'basis/news:r',
+ 'getNewsItem' => 'basis/news:r',
+ 'storeNewsItem' => 'basis/news:w',
+ 'updateNewsItem' => 'basis/news:w',
+ 'deleteNewsItem' => 'basis/news:w'
+ ]);
+
+ $this->load->model('content/Content_model', 'ContentModel');
+ $this->load->model('content/Contentsprache_model', 'ContentspracheModel');
+ $this->load->model('content/News_model', 'NewsModel');
+ $this->load->model('organisation/Studiengang_model', 'StudiengangModel');
+ $this->load->model('ressource/Mitarbeiter_model', 'MitarbeiterModel');
+ $this->load->model('system/Sprache_model', 'SpracheModel');
+ $this->load->model('person/Benutzerfunktion_model', 'BenutzerfunktionModel');
+
+ $this->loadPhrases([
+ 'global'
+ ]);
+
+ $this->load->library('CmsLib');
+
+ $this->load->helper('hlp_sancho_helper');
+ }
+
+ //------------------------------------------------------------------------------------------------------------------
+ // Public methods
+
+ public function getNews($infoscreen = false, $studiengang_kz = null, $semester = null, $titel = '', $active = true, $sichtbar = true)
+ {
+ $this->load->library('form_validation');
+ $this->form_validation->set_data($_GET);
+ $this->form_validation->set_rules('page','Page','required|is_natural');
+ $this->form_validation->set_rules('page_size', 'PageSize', 'is_natural');
+ if($this->form_validation->run() == FALSE) $this->terminateWithValidationErrors($this->form_validation->error_array());
+
+ $page = intval($this->input->get('page', true));
+ $page_size = intval($this->input->get('page_size', true));
+ $sprache = $this->input->get('sprache', true);
+ if(!$sprache)
+ {
+ $sprache = getUserLanguage();
+ }
+
+ // default value for the page_size is 10
+ $page_size = $page_size ?? 10;
+
+ $passedSichtbar = $this->input->get('published', true);
+ if($passedSichtbar !== null)
+ {
+ $sichtbar = $passedSichtbar;
+ }
+
+ $passedIsActive = $this->input->get('isActive', true);
+ if ($passedIsActive !== null)
+ {
+ $isActive = filter_var(
+ $passedIsActive,
+ FILTER_VALIDATE_BOOLEAN,
+ FILTER_NULL_ON_FAILURE
+ );
+ if ($isActive !== null)
+ {
+ $active = $isActive;
+ }
+ }
+
+ $degreeProgramShortCode = $this->input->get('degreeProgramShortCode', true);
+ if ($degreeProgramShortCode !== null && $degreeProgramShortCode !== '')
+ {
+ if (!ctype_digit((string)$degreeProgramShortCode))
+ {
+ $this->terminateWithValidationErrors([
+ 'degreeProgramShortCode' => 'A valid degree program is required'
+ ]);
+ }
+ $studiengang_kz = $degreeProgramShortCode;
+ }
+
+ $passedSemester = $this->input->get('semester', true);
+ if ($passedSemester !== null && $passedSemester !== '')
+ {
+ if (
+ !ctype_digit((string)$passedSemester)
+ || (int)$passedSemester < 1
+ || (int)$passedSemester > 8
+ )
+ {
+ $this->terminateWithValidationErrors([
+ 'semester' => 'A valid semester is required'
+ ]);
+ }
+ $semester = filter_var($passedSemester, FILTER_VALIDATE_INT);
+ }
+
+ $entitledDegreePrograms = $this->permissionlib->getSTG_isEntitledFor("basis/news");
+
+ $news = $this->cmslib->getNews($infoscreen, $studiengang_kz, $semester, false, $titel, true, $sichtbar, $page, $page_size, $sprache, true, $entitledDegreePrograms, $active);
+ $news = $this->getDataOrTerminateWithError($news);
+
+ $this->addMeta('row_count', $news["row_count"] ?? 0);
+ $this->terminateWithSuccess($news["content"]);
+ }
+
+ /**
+ * Returns one news entry with all available language versions for editing.
+ *
+ * @return void
+ */
+ public function getNewsItem($newsId)
+ {
+ if (!is_numeric($newsId) || (int)$newsId < 1)
+ {
+ $this->terminateWithValidationErrors([
+ 'newsId' => 'A valid news ID is required'
+ ]);
+ }
+
+ $newsId = (int)$newsId;
+ $newsResult = $this->NewsModel->load($newsId);
+
+ if (isError($newsResult))
+ {
+ $this->terminateWithError(getError($newsResult), self::ERROR_TYPE_DB);
+ }
+
+ if (!hasData($newsResult))
+ {
+ $this->terminateWithValidationErrors([
+ 'newsId' => 'The requested news entry does not exist'
+ ]);
+ }
+
+ $news = current(getData($newsResult));
+ $entitledDegreePrograms = $this->permissionlib->getSTG_isEntitledFor("basis/news");
+
+ if (!in_array($news->studiengang_kz, $entitledDegreePrograms)) {
+ $this->terminateWithError('You are not entitled to access this news entry', self::ERROR_TYPE_PERMISSION);
+ }
+ $translationsResult = $this->ContentspracheModel->loadWhere([
+ 'content_id' => $news->content_id
+ ]);
+
+ if (isError($translationsResult))
+ {
+ $this->terminateWithError(getError($translationsResult), self::ERROR_TYPE_DB);
+ }
+
+ $languages = [];
+
+ foreach ((array)getData($translationsResult) as $translation)
+ {
+ $languages[$translation->sprache] = true;
+ }
+
+ $translations = [];
+
+ foreach (array_keys($languages) as $language)
+ {
+ $contentResult = $this->ContentModel->getContent(
+ $news->content_id,
+ $language,
+ null,
+ null,
+ false
+ );
+
+ if (isError($contentResult))
+ {
+ $this->terminateWithError(getError($contentResult), self::ERROR_TYPE_DB);
+ }
+
+ $content = getData($contentResult);
+ $document = new DOMDocument('1.0', 'UTF-8');
+
+ if ($content->content !== '' && $document->loadXML($content->content) === false)
+ {
+ $this->terminateWithError(
+ 'The stored news content is invalid',
+ self::ERROR_TYPE_GENERAL
+ );
+ }
+
+ $author = $document->getElementsByTagName('verfasser')->item(0);
+ $title = $document->getElementsByTagName('betreff')->item(0);
+ $text = $document->getElementsByTagName('text')->item(0);
+
+ $translations[] = [
+ 'language' => $language,
+ 'author' => $author ? $author->nodeValue : '',
+ 'title' => $title ? $title->nodeValue : '',
+ 'text' => $text ? str_replace('dms.php', APP_ROOT . 'cms/dms.php', $text->nodeValue) : '',
+ 'isPublished' => (bool)$content->sichtbar
+ ];
+ }
+
+ $this->terminateWithSuccess([
+ 'newsId' => (int)$news->news_id,
+ 'visibleFrom' => $news->datum,
+ 'visibleTo' => $news->datum_bis,
+ 'degreeProgramShortCode' => $news->studiengang_kz,
+ 'semester' => $news->semester,
+ 'translations' => $translations
+ ]);
+ }
+
+ /**
+ * Creates a news entry and all supplied translations.
+ *
+ * @return void
+ */
+ public function storeNewsItem()
+ {
+ $this->load->library('form_validation');
+ $postData = $this->input->post(null, true);
+ $this->form_validation->set_data(is_array($postData) ? $postData : []);
+ $this->form_validation->set_rules('visibleFrom', 'Visible from', 'required|is_valid_date');
+ $this->form_validation->set_rules('visibleTo', 'Visible to', 'is_valid_date');
+ $this->form_validation->set_rules(
+ 'degreeProgramShortCode',
+ 'Degree program',
+ 'required|is_natural'
+ );
+ $this->form_validation->set_rules(
+ 'semester',
+ 'Semester',
+ 'is_natural_no_zero|less_than_equal_to[8]'
+ );
+
+ $errors = $this->form_validation->run() === false
+ ? $this->form_validation->error_array()
+ : [];
+ $visibleFrom = $this->input->post('visibleFrom', true);
+ $visibleTo = $this->input->post('visibleTo', true);
+ $translations = $this->input->post('translations', true);
+
+ if (
+ is_string($visibleFrom)
+ && is_string($visibleTo)
+ && $visibleTo !== ''
+ && is_valid_date($visibleFrom)
+ && is_valid_date($visibleTo)
+ && new DateTime($visibleTo) < new DateTime($visibleFrom)
+ )
+ {
+ $errors['visibleTo'] = 'Visible to must not be before Visible from';
+ }
+
+ if (!is_array($translations) || count($translations) === 0)
+ {
+ $errors['translations'] = 'At least one translation is required';
+ }
+ else
+ {
+ foreach ($translations as $index => $translation)
+ {
+ if (!is_array($translation))
+ {
+ $errors['translations.' . $index] = 'Each translation must be an object';
+ }
+ }
+ }
+
+ if (count($errors) > 0)
+ {
+ $this->terminateWithValidationErrors($errors);
+ }
+
+
+ $this->_ensureUserIsEntitledForDegreeProgram($this->input->post('degreeProgramShortCode', true));
+
+ $degreeProgramShortCode = $this->input->post('degreeProgramShortCode', true);
+ $semester = $this->input->post('semester', true);
+ $semester = $semester === '' ? null : $semester;
+ $visibleTo = $visibleTo === '' ? null : $visibleTo;
+
+ $studiengang = $this->_getStudiengang($degreeProgramShortCode);
+
+ $data = [
+ 'studiengang_kz' => $degreeProgramShortCode,
+ 'semester' => $semester,
+ 'datum' => $visibleFrom,
+ 'datum_bis' => $visibleTo,
+ 'translations' => []
+ ];
+
+ foreach ($translations as $index => $translation)
+ {
+ $data['translations'][$index] = [
+ 'sprache' => $translation['language'] ?? '',
+ 'verfasser' => $translation['author'] ?? '',
+ 'betreff' => $translation['title'] ?? '',
+ 'text' => $translation['text'] ?? '',
+ 'sichtbar' => isset($translation['isPublished']) ? (bool)$translation['isPublished'] : false
+ ];
+ }
+
+ $this->_validateLanguages($data['translations']);
+
+ foreach ($data['translations'] as $index => $translation)
+ {
+ $data['translations'][$index]['content'] = $this->_buildNewsXml($translation);
+ }
+
+ $uid = getAuthUID();
+ $now = date('Y-m-d H:i:s');
+
+ if ($this->db->trans_begin() === false)
+ {
+ $this->terminateWithError($this->db->error(), self::ERROR_TYPE_DB);
+ }
+
+ $contentResult = $this->ContentModel->insert([
+ 'template_kurzbz' => 'news',
+ 'oe_kurzbz' => $studiengang->oe_kurzbz,
+ 'aktiv' => true,
+ 'menu_open' => false,
+ 'insertamum' => $now,
+ 'insertvon' => $uid
+ ]);
+ $contentId = (int)$this->_getInsertedIdOrRollback($contentResult);
+
+ $newsResult = $this->NewsModel->insert([
+ 'uid' => $uid,
+ 'studiengang_kz' => $data['studiengang_kz'],
+ 'semester' => $data['semester'],
+ 'datum' => $data['datum'],
+ 'datum_bis' => $data['datum_bis'],
+ 'content_id' => $contentId,
+ 'insertamum' => $now,
+ 'insertvon' => $uid,
+ 'updateamum' => $now,
+ 'updatevon' => $uid
+ ]);
+ $newsId = (int)$this->_getInsertedIdOrRollback($newsResult);
+
+ foreach ($data['translations'] as $translation)
+ {
+ $translation['content'] = $this->formatContentURLsToRelative($translation['content']);
+
+ $translationResult = $this->ContentspracheModel->insert([
+ 'sprache' => $translation['sprache'],
+ 'content_id' => $contentId,
+ 'version' => 1,
+ 'sichtbar' => $translation['sichtbar'],
+ 'content' => $translation['content'],
+ 'titel' => $translation['betreff'],
+ 'insertamum' => $now,
+ 'insertvon' => $uid,
+ 'updateamum' => $now,
+ 'updatevon' => $uid
+ ]);
+
+ $this->_getInsertedIdOrRollback($translationResult);
+ }
+
+ $newsItemResult = $this->NewsModel->load($newsId);
+ $newsItem = $this->getDataOrTerminateWithError($newsItemResult);
+ $newsItem = $newsItem[0];
+
+ $this->_sendNewsItemTranslationRequiredMail(
+ $newsItem
+ );
+
+ if ($this->db->trans_status() === false)
+ {
+ $error = $this->db->error();
+ $this->db->trans_rollback();
+ $this->terminateWithError($error, self::ERROR_TYPE_DB);
+ }
+
+ if ($this->db->trans_commit() === false)
+ {
+ $error = $this->db->error();
+ $this->db->trans_rollback();
+ $this->terminateWithError($error, self::ERROR_TYPE_DB);
+ }
+
+ $this->terminateWithSuccess($newsId);
+ }
+
+ /**
+ * Updates a news entry and its supplied translations.
+ *
+ * @return void
+ */
+ public function updateNewsItem($newsId)
+ {
+ if (!is_numeric($newsId) || (int)$newsId < 1)
+ {
+ $this->terminateWithValidationErrors([
+ 'newsId' => 'A valid news ID is required'
+ ]);
+ }
+
+ $newsId = (int)$newsId;
+ $existingNewsResult = $this->NewsModel->load($newsId);
+
+ if (isError($existingNewsResult))
+ {
+ $this->terminateWithError(getError($existingNewsResult), self::ERROR_TYPE_DB);
+ }
+
+ if (!hasData($existingNewsResult))
+ {
+ $this->terminateWithValidationErrors([
+ 'newsId' => 'The requested news entry does not exist'
+ ]);
+ }
+
+ $existingNews = current(getData($existingNewsResult));
+
+ $this->_ensureUserIsEntitledForDegreeProgram($existingNews->studiengang_kz);
+
+ $this->load->library('form_validation');
+ $postData = $this->input->post(null, true);
+ $this->form_validation->set_data(is_array($postData) ? $postData : []);
+ $this->form_validation->set_rules('visibleFrom', 'Visible from', 'required|is_valid_date');
+ $this->form_validation->set_rules('visibleTo', 'Visible to', 'is_valid_date');
+ $this->form_validation->set_rules(
+ 'degreeProgramShortCode',
+ 'Degree program',
+ 'required|is_natural'
+ );
+ $this->form_validation->set_rules(
+ 'semester',
+ 'Semester',
+ 'is_natural_no_zero|less_than_equal_to[8]'
+ );
+
+ $errors = $this->form_validation->run() === false
+ ? $this->form_validation->error_array()
+ : [];
+ $visibleFrom = $this->input->post('visibleFrom', true);
+ $visibleTo = $this->input->post('visibleTo', true);
+ $translations = $this->input->post('translations', true);
+
+ if (
+ is_string($visibleFrom)
+ && is_string($visibleTo)
+ && $visibleTo !== ''
+ && is_valid_date($visibleFrom)
+ && is_valid_date($visibleTo)
+ && new DateTime($visibleTo) < new DateTime($visibleFrom)
+ )
+ {
+ $errors['visibleTo'] = 'Visible to must not be before Visible from';
+ }
+
+ if (!is_array($translations) || count($translations) === 0)
+ {
+ $errors['translations'] = 'At least one translation is required';
+ }
+ else
+ {
+ foreach ($translations as $index => $translation)
+ {
+ if (!is_array($translation))
+ {
+ $errors['translations.' . $index] = 'Each translation must be an object';
+ }
+ }
+ }
+
+ if (count($errors) > 0)
+ {
+ $this->terminateWithValidationErrors($errors);
+ }
+
+ $degreeProgramShortCode = $this->input->post('degreeProgramShortCode', true);
+ $semester = $this->input->post('semester', true);
+ $semester = $semester === '' ? null : $semester;
+ $visibleTo = $visibleTo === '' ? null : $visibleTo;
+
+ $studiengang = $this->_getStudiengang($degreeProgramShortCode);
+
+ $data = [
+ 'studiengang_kz' => $degreeProgramShortCode,
+ 'semester' => $semester,
+ 'datum' => $visibleFrom,
+ 'datum_bis' => $visibleTo,
+ 'translations' => []
+ ];
+
+ foreach ($translations as $index => $translation)
+ {
+ $data['translations'][$index] = [
+ 'sprache' => $translation['language'] ?? '',
+ 'verfasser' => $translation['author'] ?? '',
+ 'betreff' => $translation['title'] ?? '',
+ 'text' => $translation['text'] ?? '',
+ 'sichtbar' => isset($translation['isPublished']) ? (bool)$translation['isPublished'] : false
+ ];
+ }
+
+ $this->_validateLanguages($data['translations']);
+
+ foreach ($data['translations'] as $index => $translation)
+ {
+ $data['translations'][$index]['content'] = $this->_buildNewsXml($translation);
+ }
+
+ $uid = getAuthUID();
+ $now = date('Y-m-d H:i:s');
+
+ if ($this->db->trans_begin() === false)
+ {
+ $this->terminateWithError($this->db->error(), self::ERROR_TYPE_DB);
+ }
+
+ $contentResult = $this->ContentModel->update($existingNews->content_id, [
+ 'oe_kurzbz' => $studiengang->oe_kurzbz,
+ 'updateamum' => $now,
+ 'updatevon' => $uid
+ ]);
+
+ if (isError($contentResult))
+ {
+ $this->db->trans_rollback();
+ $this->terminateWithError(getError($contentResult), self::ERROR_TYPE_DB);
+ }
+
+ $newsResult = $this->NewsModel->update($newsId, [
+ 'studiengang_kz' => $data['studiengang_kz'],
+ 'semester' => $data['semester'],
+ 'datum' => $data['datum'],
+ 'datum_bis' => $data['datum_bis'],
+ 'updateamum' => $now,
+ 'updatevon' => $uid
+ ]);
+
+ if (isError($newsResult))
+ {
+ $this->db->trans_rollback();
+ $this->terminateWithError(getError($newsResult), self::ERROR_TYPE_DB);
+ }
+
+ $existingTranslationsResult = $this->ContentspracheModel->loadWhere([
+ 'content_id' => $existingNews->content_id
+ ]);
+
+ if (isError($existingTranslationsResult))
+ {
+ $this->db->trans_rollback();
+ $this->terminateWithError(getError($existingTranslationsResult), self::ERROR_TYPE_DB);
+ }
+
+ $existingTranslations = [];
+
+ foreach ((array)getData($existingTranslationsResult) as $existingTranslation)
+ {
+ $language = $existingTranslation->sprache;
+
+ if (
+ !isset($existingTranslations[$language])
+ || (int)$existingTranslation->version > (int)$existingTranslations[$language]->version
+ )
+ {
+ $existingTranslations[$language] = $existingTranslation;
+ }
+ }
+
+ $submittedLanguages = array_column($data['translations'], 'sprache');
+ $this->db->where('content_id', $existingNews->content_id);
+ $this->db->where_not_in('sprache', $submittedLanguages);
+
+ if ($this->db->delete('campus.tbl_contentsprache') === false)
+ {
+ $error = $this->db->error();
+ $this->db->trans_rollback();
+ $this->terminateWithError($error, self::ERROR_TYPE_DB);
+ }
+
+ foreach ($data['translations'] as $translation)
+ {
+ $translation['content'] = $this->formatContentURLsToRelative($translation['content']);
+ $translationData = [
+ 'sichtbar' => $translation['sichtbar'],
+ 'content' => $translation['content'],
+ 'titel' => $translation['betreff'],
+ 'updateamum' => $now,
+ 'updatevon' => $uid
+ ];
+
+ $existingTranslation = $existingTranslations[$translation['sprache']] ?? null;
+
+ if ($existingTranslation)
+ {
+ $translationResult = $this->ContentspracheModel->update(
+ $existingTranslation->contentsprache_id,
+ $translationData
+ );
+ }
+ else
+ {
+ $translationResult = $this->ContentspracheModel->insert([
+ 'sprache' => $translation['sprache'],
+ 'content_id' => $existingNews->content_id,
+ 'version' => 1,
+ 'insertamum' => $now,
+ 'insertvon' => $uid
+ ] + $translationData);
+ }
+
+ $this->_getInsertedIdOrRollback($translationResult);
+ }
+
+ if ($this->db->trans_status() === false)
+ {
+ $error = $this->db->error();
+ $this->db->trans_rollback();
+ $this->terminateWithError($error, self::ERROR_TYPE_DB);
+ }
+
+ if ($this->db->trans_commit() === false)
+ {
+ $error = $this->db->error();
+ $this->db->trans_rollback();
+ $this->terminateWithError($error, self::ERROR_TYPE_DB);
+ }
+
+ $this->terminateWithSuccess($newsId);
+ }
+
+ /**
+ * Deletes one news entry and its linked content.
+ *
+ * @return void
+ */
+ public function deleteNewsItem($newsId)
+ {
+ if (!is_numeric($newsId) || (int)$newsId < 1)
+ {
+ $this->terminateWithValidationErrors([
+ 'newsId' => 'A valid news ID is required'
+ ]);
+ }
+
+ $newsId = (int)$newsId;
+ $newsResult = $this->NewsModel->load($newsId);
+
+ if (isError($newsResult))
+ {
+ $this->terminateWithError(getError($newsResult), self::ERROR_TYPE_DB);
+ }
+
+ if (!hasData($newsResult))
+ {
+ $this->terminateWithValidationErrors([
+ 'newsId' => 'The requested news entry does not exist'
+ ]);
+ }
+
+ $news = current(getData($newsResult));
+
+
+ $this->_ensureUserIsEntitledForDegreeProgram($news->studiengang_kz);
+
+ if ($this->db->trans_begin() === false)
+ {
+ $this->terminateWithError($this->db->error(), self::ERROR_TYPE_DB);
+ }
+
+ $newsDeleteResult = $this->NewsModel->delete($newsId);
+
+ if (isError($newsDeleteResult))
+ {
+ $this->db->trans_rollback();
+ $this->terminateWithError(getError($newsDeleteResult), self::ERROR_TYPE_DB);
+ }
+
+ $this->db->where('content_id', $news->content_id);
+
+ if ($this->db->delete('campus.tbl_contentsprache') === false)
+ {
+ $error = $this->db->error();
+ $this->db->trans_rollback();
+ $this->terminateWithError($error, self::ERROR_TYPE_DB);
+ }
+
+ $contentDeleteResult = $this->ContentModel->delete($news->content_id);
+
+ if (isError($contentDeleteResult))
+ {
+ $this->db->trans_rollback();
+ $this->terminateWithError(getError($contentDeleteResult), self::ERROR_TYPE_DB);
+ }
+
+ if ($this->db->trans_status() === false)
+ {
+ $error = $this->db->error();
+ $this->db->trans_rollback();
+ $this->terminateWithError($error, self::ERROR_TYPE_DB);
+ }
+
+ if ($this->db->trans_commit() === false)
+ {
+ $error = $this->db->error();
+ $this->db->trans_rollback();
+ $this->terminateWithError($error, self::ERROR_TYPE_DB);
+ }
+
+ $this->terminateWithSuccess($newsId);
+ }
+
+ //------------------------------------------------------------------------------------------------------------------
+ // Private methods
+
+ /**
+ * Loads the requested Studiengang or reports a validation error.
+ *
+ * @param integer $studiengangKz
+ * @return stdClass
+ */
+ private function _getStudiengang($studiengangKz)
+ {
+ $result = $this->StudiengangModel->load($studiengangKz);
+
+ if (isError($result))
+ {
+ $this->terminateWithError(getError($result), self::ERROR_TYPE_DB);
+ }
+
+ if (!hasData($result))
+ {
+ $this->terminateWithValidationErrors([
+ 'studiengang_kz' => 'The requested Studiengang does not exist'
+ ]);
+ }
+
+ $studiengaenge = getData($result);
+
+ return current($studiengaenge);
+ }
+
+ /**
+ * Ensures that all supplied languages exist in tbl_sprache.
+ *
+ * @param array $translations
+ * @return void
+ */
+ private function _validateLanguages($translations)
+ {
+ $languages = [];
+
+ foreach ($translations as $translation)
+ {
+ $languages[] = $translation['sprache'];
+ }
+
+ $result = $this->SpracheModel->loadMultiple($languages);
+
+ if (isError($result))
+ {
+ $this->terminateWithError(getError($result), self::ERROR_TYPE_DB);
+ }
+
+ $existingLanguages = [];
+
+ foreach ((array)getData($result) as $language)
+ {
+ $existingLanguages[$language->sprache] = true;
+ }
+
+ $errors = [];
+
+ foreach ($translations as $index => $translation)
+ {
+ if (!isset($existingLanguages[$translation['sprache']]))
+ {
+ $errors['translations.' . $index . '.sprache'] = 'The requested language does not exist';
+ }
+ }
+
+ if (count($errors) > 0)
+ {
+ $this->terminateWithValidationErrors($errors);
+ }
+ }
+
+ private function _ensureUserIsEntitledForDegreeProgram($degreeProgramShortCode)
+ {
+ $entitledDegreePrograms = $this->permissionlib->getSTG_isEntitledFor("basis/news:w");
+ if (!is_array($entitledDegreePrograms))
+ {
+ $this->terminateWithError('Failed to retrieve entitled degree programs for the user', self::ERROR_TYPE_GENERAL);
+ }
+
+ if (!in_array($degreeProgramShortCode, $entitledDegreePrograms)) {
+ $this->terminateWithError("User is not entitled for the specified degree program");
+ }
+ }
+ /**
+ * Returns an inserted identifier or rolls the transaction back on error.
+ *
+ * @param stdClass $result
+ * @return mixed
+ */
+ private function _getInsertedIdOrRollback($result)
+ {
+ if (isError($result))
+ {
+ $this->db->trans_rollback();
+ $this->terminateWithError(getError($result), self::ERROR_TYPE_DB);
+ }
+
+ return getData($result);
+ }
+
+ /**
+ * Builds the XML format consumed by existing news readers.
+ *
+ * @param array $translation
+ * @return string
+ */
+ private function _buildNewsXml($translation)
+ {
+ $document = new DOMDocument('1.0', 'UTF-8');
+ $news = $document->createElement('news');
+ $document->appendChild($news);
+
+ $this->_appendCdataElement($document, $news, 'verfasser', $translation['verfasser']);
+ $this->_appendCdataElement($document, $news, 'betreff', $translation['betreff']);
+ $this->_appendCdataElement($document, $news, 'text', $translation['text']);
+
+ return $document->saveXML($news);
+ }
+
+ /**
+ * Appends a CDATA element and safely preserves CDATA terminators.
+ *
+ * @param DOMDocument $document
+ * @param DOMElement $parent
+ * @param string $name
+ * @param string $value
+ * @return void
+ */
+ private function _appendCdataElement($document, $parent, $name, $value)
+ {
+ $element = $document->createElement($name);
+ $parts = explode(']]>', $value);
+ $lastIndex = count($parts) - 1;
+
+ foreach ($parts as $index => $part)
+ {
+ if ($index > 0)
+ {
+ $part = '>' . $part;
+ }
+
+ if ($index < $lastIndex)
+ {
+ $part .= ']]';
+ }
+
+ $element->appendChild($document->createCDATASection($part));
+ }
+
+ $parent->appendChild($element);
+ }
+
+ private function _sendNewsItemTranslationRequiredMail($newsItem)
+ {
+ $translatorsResult = $this->BenutzerfunktionModel->getBenutzerFunktionen("translate", $newsItem->studiengang_kz);
+ $translators = $this->getDataOrTerminateWithError($translatorsResult);
+
+ $newsItemContentResult = $this->ContentspracheModel->loadWhere([
+ 'content_id' => $newsItem->content_id,
+ 'sprache' => 'German'
+ ]);
+ $newsItemContentEntry = getData($newsItemContentResult)[0] ?? null;
+ if ($newsItemContentEntry) {
+ $newsItem->sichtbar = $newsItemContentEntry->sichtbar ?? false;
+ $newsItem->content = $newsItemContentEntry->content;
+ }
+ $content = getData(
+ $this->cmslib->createNewsContent(
+ $this->cmslib->createNewsWrapper($newsItem, true),
+ false,
+ ""
+ )
+ );
+
+ foreach ($translators as $translator) {
+ sendSanchoMail(
+ "Sancho_Mail_News_Translation_Req",
+ [
+ "newsItemUrl" => APP_ROOT . "cis.php/CisVue/Cms/newsAdministration?newsId=" . $newsItem->news_id,
+ "newsItemAuthor" => $newsItem->verfasser,
+ "newsItemSubject" => $newsItem->betreff,
+ "newsItemContent" => $content
+ ],
+ $translator->uid . "@" . DOMAIN,
+ "Übersetzung erforderlich / Translation required"
+ );
+ }
+ }
+
+ private function formatContentURLsToRelative($content)
+ {
+ $content = preg_replace(
+ '#https?://[^"\'>\s]*/(dms\.php(?:\?[^"\'>\s]*)?)#i',
+ '$1',
+ $content
+ );
+
+ $content = preg_replace(
+ '#(?:\.\./)+[^"\'>\s]*/(dms\.php(?:\?[^"\'>\s]*)?)#i',
+ '$1',
+ $content
+ );
+
+ $content = preg_replace(
+ '#/[^"\'>\s]*/(dms\.php(?:\?[^"\'>\s]*)?)#i',
+ '$1',
+ $content
+ );
+
+ return $content;
+ }
+}
diff --git a/application/controllers/api/frontend/v1/Noten.php b/application/controllers/api/frontend/v1/Noten.php
new file mode 100644
index 000000000..6a7d0f640
--- /dev/null
+++ b/application/controllers/api/frontend/v1/Noten.php
@@ -0,0 +1,1782 @@
+.
+ */
+
+if (! defined('BASEPATH')) exit('No direct script access allowed');
+
+use CI3_Events as Events;
+
+class Noten extends FHCAPI_Controller
+{
+ /** tbl_note by PK, read once per request. @see aktiveNoten() */
+ private $aktiveNotenCache = null;
+
+ /**
+ * Object initialization
+ */
+ public function __construct()
+ {
+ $permissions = array('lehre/benotungstool:rw', 'lehre/benotungstool_assistenz:rw');
+ parent::__construct([
+ 'getStudentenNoten' => $permissions,
+ 'getNoten' => $permissions,
+ 'saveStudentenNoten' => $permissions,
+ 'getNotenvorschlagStudent' => $permissions,
+ 'saveNotenvorschlag' => $permissions,
+ 'saveStudentPruefung' => $permissions,
+ 'createPruefungen' => $permissions,
+ 'saveNotenvorschlagBulk' => $permissions,
+ 'savePruefungenBulk' => $permissions,
+ 'getCisConfig' => $permissions,
+ 'getNoteByPunkte' => $permissions,
+ 'getBenotungstoolContext' => $permissions,
+ 'getLehrendeFuerLehreinheit' => $permissions,
+ 'getLvForStudiengang' => $permissions
+ ]);
+
+ $this->load->library('AuthLib', null, 'AuthLib');
+ $this->load->library('PhrasesLib');
+ $this->load->library('PruefungsverlaufLib', null, 'VerlaufLib');
+
+ // 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',
+ 'password'
+ ]);
+
+ $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('education/Note_model', 'NoteModel');
+ $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() {
+ // resolved from tbl_note (Bezeichnung) with config fallback -> single source of truth
+ $special = $this->VerlaufLib->getSpecialNotes();
+ $NOTEN_OHNE_ANTRITT = $special['ohneAntritt'];
+ $NOTEN_OCCURANCE_LIMIT_MAP = $special['limitMap'];
+ $NOTE_ENTSCHULDIGT = $special['entschuldigt'];
+
+ $this->terminateWithSuccess(
+ array(
+ // show the points during the grade entry
+ '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,
+
+ // The maximum number of attempts that count in this tool. The server derives it from the
+ // configuration or from the old TERMIN2/TERMIN3 flags. The client does not calculate it.
+ 'CIS_GESAMTNOTE_MAX_ANTRITTE' => $this->VerlaufLib->getMaxAntritte(),
+
+ // the default column layout ('antritt' or 'datum'); the user can change it in the tool
+ 'CIS_GESAMTNOTE_PRUEFUNGSSPALTEN' => $this->config->item('CIS_GESAMTNOTE_PRUEFUNGSSPALTEN'),
+
+ // 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,
+
+ // availability of the two import flows (application/config/noten.php); when both are
+ // true they are shown separately
+ 'CIS_GESAMTNOTE_PRUEFUNGSIMPORT' => $this->config->item('CIS_GESAMTNOTE_PRUEFUNGSIMPORT'),
+ 'CIS_GESAMTNOTE_NOTENIMPORT' => $this->config->item('CIS_GESAMTNOTE_NOTENIMPORT'),
+
+ // send a mail when approving grades
+ 'CIS_GESAMTNOTE_FREIGABEMAIL_NOTE' => CIS_GESAMTNOTE_FREIGABEMAIL_NOTE,
+
+ 'NOTEN_OHNE_ANTRITT' => $NOTEN_OHNE_ANTRITT,
+
+ 'NOTEN_OCCURANCE_LIMIT_MAP' => $NOTEN_OCCURANCE_LIMIT_MAP,
+
+ // pk of the 'entschuldigt' note; used to preserve excused Termine on new pruefung creation
+ 'NOTE_ENTSCHULDIGT' => $NOTE_ENTSCHULDIGT,
+
+ // Noteneintragungsfrist window (enforced server-side; also surfaced so the UI can hint at it)
+ 'CIS_GESAMTNOTE_NOTENEINTRAGUNGSFRIST' => $this->config->item('CIS_GESAMTNOTE_NOTENEINTRAGUNGSFRIST')
+ )
+ );
+ }
+
+ /**
+ * GET METHOD
+ * expects (optional) 'sem_kurzbz'
+ * Single role-determining entry point for the tool: the server decides which flow the current user
+ * gets and returns exactly the initial dropdown data for it.
+ * A teacher (holds lehre/benotungstool) gets their assigned
+ * Lehrveranstaltungen directly. Assistenz (only lehre/benotungstool_assistenz) gets the
+ * degree programmes they are entitled for, and picks one before a course is loaded.
+ */
+ public function getBenotungstoolContext() {
+ $sem_kurzbz = $this->input->get("sem_kurzbz", TRUE);
+ $lv_id = $this->input->get("lv_id", TRUE); // optional: deep-link target, used to preselect
+
+ $this->load->library('PermissionLib');
+
+ // teachers keep the classic assigned-LV flow; the Studiengang flow is only for Assistenz.
+ // Role determination mirrors assertLvAccess, which scopes each role's actual data
+ // access. A teacher sees the own courses, an assistant sees the entitled degree programmes.
+ $isLektor = $this->permissionlib->isBerechtigt('lehre/benotungstool');
+ $entitledStgs = $this->permissionlib->getSTG_isEntitledFor('lehre/benotungstool_assistenz');
+ $isAssistenz = !$isLektor && is_array($entitledStgs) && count($entitledStgs) > 0;
+
+ $studiengaenge = array();
+ $lehrveranstaltungen = array();
+ $preselectStudiengang_kz = null;
+
+ if (isset($sem_kurzbz) && !isEmptyString($sem_kurzbz)) {
+ if ($isAssistenz) {
+ $this->load->model('organisation/Studiengang_model', 'StudiengangModel');
+ $result = $this->StudiengangModel->getByStgs($entitledStgs, $sem_kurzbz);
+ if (!isError($result)) $studiengaenge = getData($result) ?? array();
+
+ // deep-link: resolve the Studiengang of the requested LV so the frontend can preselect
+ // the Studiengang dropdown (and then its LV) - only if the Assistenz is entitled for it
+ if (isset($lv_id) && !isEmptyString($lv_id)) {
+ $res = $this->LehrveranstaltungModel->load($lv_id);
+ if (!isError($res) && hasData($res)) {
+ $stg = getData($res)[0]->studiengang_kz;
+ if (in_array($stg, $entitledStgs)) $preselectStudiengang_kz = $stg;
+ }
+ }
+ } else {
+ $result = $this->LehrveranstaltungModel->getLvForLektorInSemester($sem_kurzbz, getAuthUID());
+ if (!isError($result)) $lehrveranstaltungen = getData($result) ?? array();
+ }
+ }
+
+ $this->terminateWithSuccess(array(
+ 'isAssistenz' => $isAssistenz,
+ 'studiengaenge' => $studiengaenge,
+ 'lehrveranstaltungen' => $lehrveranstaltungen,
+ 'preselectStudiengang_kz' => $preselectStudiengang_kz
+ ));
+ }
+
+ /**
+ * GET METHOD
+ * expects 'studiengang_kz', 'sem_kurzbz'
+ * Returns the Lehrveranstaltungen of a Studiengang in a Studiensemester for the Assistenz flow.
+ * The caller may load only the degree programmes of lehre/benotungstool_assistenz.
+ */
+ public function getLvForStudiengang() {
+ $studiengang_kz = $this->input->get("studiengang_kz", TRUE);
+ $sem_kurzbz = $this->input->get("sem_kurzbz", TRUE);
+
+ if (!isset($studiengang_kz) || isEmptyString($studiengang_kz)
+ || !isset($sem_kurzbz) || isEmptyString($sem_kurzbz)) {
+ $this->terminateWithError($this->p->t('global', 'wrongParameters'), 'general');
+ }
+
+ $this->load->library('PermissionLib');
+ $entitledStgs = $this->permissionlib->getSTG_isEntitledFor('lehre/benotungstool_assistenz');
+ $isAdmin = $this->permissionlib->isBerechtigt('admin');
+
+ if (!$isAdmin && (!is_array($entitledStgs) || !in_array($studiengang_kz, $entitledStgs))) {
+ $this->terminateWithError($this->p->t('ui', 'keineBerechtigung'), 'general');
+ }
+
+ $result = $this->LehrveranstaltungModel->getLvForStudiengangInSemester($sem_kurzbz, $studiengang_kz);
+ $data = $this->getDataOrTerminateWithError($result);
+ $this->terminateWithSuccess($data);
+ }
+
+ /**
+ * Scopes data access to the Lehrveranstaltungen a user may act on, so a shared/guessed URL cannot be
+ * used to read or edit foreign grades. A teacher (lehre/benotungstool) may
+ * only touch LVs they are assigned to teach in the given Studiensemester. Assistenz may
+ * touch LVs of a Studiengang they are entitled for.
+ */
+ private function assertLvAccess($lv_id, $sem_kurzbz = null)
+ {
+ $this->load->library('PermissionLib');
+
+ // admins keep full access
+ if ($this->permissionlib->isBerechtigt('admin')) return;
+
+ // teachers: only their own LVs (assigned as lehreinheitmitarbeiter in this semester)
+ if ($this->permissionlib->isBerechtigt('lehre/benotungstool')) {
+ $res = $this->LehrveranstaltungModel->getLektorIsTeachingLva($lv_id, getAuthUID(), $sem_kurzbz);
+ $rows = getData($res);
+ if (!isError($res) && !empty($rows) && $rows[0]->teaches > 0) return;
+ // not a teacher of this LV -> fall through (a both-role user may still be entitled as Assistenz)
+ }
+
+ // (pure or additional) Assistenz: only LVs of an entitled Studiengang
+ $entitledStgs = $this->permissionlib->getSTG_isEntitledFor('lehre/benotungstool_assistenz');
+ $lv = null;
+ if (is_array($entitledStgs) && count($entitledStgs) > 0) {
+ $res = $this->LehrveranstaltungModel->load($lv_id);
+ if (!isError($res) && hasData($res)) {
+ $lv = getData($res)[0];
+ if (in_array($lv->studiengang_kz, $entitledStgs)) return;
+ }
+ }
+
+ if ($lv === null) {
+ $res = $this->LehrveranstaltungModel->load($lv_id);
+ if (!isError($res) && hasData($res)) $lv = getData($res)[0];
+ }
+ $bezeichnung = $lv !== null ? $lv->bezeichnung : $lv_id;
+
+ $this->terminateWithError(
+ $this->p->t('benotungstool', 'keineBerechtigungNoten', [$bezeichnung, $sem_kurzbz]),
+ 'general'
+ );
+ }
+
+ /**
+ * 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.
+ * At the end it also fetches all exams of every student for that course 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');
+
+ $this->assertLvAccess($lv_id, $sem_kurzbz);
+
+ // 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($this->p->t('benotungstool', 'c4keineStudentenGefunden'));
+ }
+
+ $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'] = [];
+
+ // Read without the filter. getLvGesamtNoten() gives released grades only. With that getter
+ // the course grade and the release state are empty after a reload.
+ $lvgesamtnote = $this->getLvGesamtnoteRow($lv_id, $uid, $sem_kurzbz);
+ if($lvgesamtnote !== null) {
+ $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('throwable', $t->getTrace());
+ $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 the grade proposals from the partial grades
+ foreach($studentenData as $student) {
+
+ // null when the Anwesenheiten addon is absent - the column stays empty in the UI
+ $student->anwquote = $anwresult[$student->prestudent_id] ?? null;
+
+ $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;
+ }
+ }
+
+ // Without a partial grade that counts there is no average. Without this guard the
+ // division uses zero. PHP 7 gives INF, and INF gives the best grade. PHP 8 stops with
+ // a fatal error and the full grade table stays empty.
+ $gewichtet = defined('CIS_GESAMTNOTE_GEWICHTUNG') && CIS_GESAMTNOTE_GEWICHTUNG;
+ $divisor = $gewichtet ? $gewichtsumme : $anzahlnoten;
+
+ if ($divisor > 0) {
+ if (CIS_GESAMTNOTE_PUNKTE) {
+ $punkte_vorschlag = round(($gewichtet ? $punktesumme_gewichtet : $punktesumme) / $divisor, 2);
+ $note_vorschlag_result = $this->NotenschluesselaufteilungModel->getNote($punkte_vorschlag, $lv_id, $sem_kurzbz);
+ $note_vorschlag = $this->getDataOrTerminateWithError($note_vorschlag_result);
+ } else {
+ $note_vorschlag = round(($gewichtet ? $notensumme_gewichtet : $notensumme) / $divisor);
+ }
+
+ $student->note_vorschlag = $note_vorschlag;
+ }
+ }
+ }
+
+ // get all exams with grades of that semester and that course
+ $pruefungen = $this->LePruefungModel->getPruefungenByLvStudiensemester($lv_id, $sem_kurzbz);
+ $pruefungenData = getData($pruefungen);
+
+ // the server derives the history for each student; the client only reads it
+ $proStudent = [];
+ foreach($pruefungenData ?: [] as $p) {
+ $proStudent[$p->student_uid][] = $p;
+ }
+
+ // the transcript grades come with the student list (tbl_zeugnisnote.note)
+ $zeugnisnoten = [];
+ foreach($studentenData as $s) $zeugnisnoten[$s->uid] = $s->note;
+
+ $pruefungenAbgeleitet = [];
+ foreach(array_unique(array_merge($student_uids, array_keys($proStudent))) as $uid) {
+ $lvNote = isset($grades[$uid]) ? ($grades[$uid]['note_lv'] ?? null) : null;
+ $verlauf = $this->VerlaufLib->buildVerlauf($proStudent[$uid] ?? [], $lvNote, $zeugnisnoten[$uid] ?? null);
+
+ foreach($verlauf->pruefungen as $p) $pruefungenAbgeleitet[] = $p;
+
+ if(isset($grades[$uid])) {
+ $grades[$uid]['verlauf'] = $this->verlaufSummary(
+ $verlauf, false, $this->getLvGesamtnoteRow($lv_id, $uid, $sem_kurzbz) !== null
+ );
+ }
+ }
+
+ $this->terminateWithSuccess(array($studentenData, $pruefungenAbgeleitet, 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('password', 'wrongPassword'), 'general');
+ }
+
+ $lv_id = $result->lv_id;
+ $sem_kurzbz = $result->sem_kurzbz;
+
+ $this->assertLvAccess($lv_id, $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 .= "" . $this->p->t('person','personenkennzeichen') . " \n
+ " . $this->p->t('lehre','studiengang') . " \n
+ " . $this->p->t('benotungstool','c4nachname') . " \n
+ " . $this->p->t('benotungstool','c4vorname') . " \n";
+ if(defined('CIS_GESAMTNOTE_PUNKTE') && CIS_GESAMTNOTE_PUNKTE) {
+ $studlist .= "" . $this->p->t('benotungstool','c4punkte') . " \n";
+ }
+ $studlist .= "" . $this->p->t('benotungstool','c4grade') . " \n";
+ $studlist .= "" . $this->p->t('ui','bearbeitetVon') . " \n";
+ } else {
+ $studlist .= "" . $this->p->t('person','uid') . " \n";
+ }
+
+ foreach($result->noten as $note) {
+
+ $resultLVGes = $this->LvgesamtnoteModel->getLvGesamtNoteVorschlag($lv_id, $note->uid, $sem_kurzbz);
+ 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];
+
+ // The release makes the grade binding, therefore the first exam starts
+ // here. A new exam never creates a second exam in addition.
+ $this->upsertErstantritt(
+ $lv_id, $lvgesamtnote->student_uid, $sem_kurzbz,
+ $lvgesamtnote->note, $lvgesamtnote->punkte, $lvgesamtnote->benotungsdatum
+ );
+
+ // The verlauf goes back with the answer, so the table shows the new exam
+ // at once. Without it the row updates only after a reload.
+ $ret[] = array(
+ 'uid' => $note->uid,
+ 'freigabedatum' => $lvgesamtnote->freigabedatum,
+ 'benotungsdatum' => $lvgesamtnote->benotungsdatum,
+ 'verlauf' => $this->buildVerlaufSummary($note->uid, $lv_id, $sem_kurzbz)
+ );
+ }
+ }
+
+ if (defined('CIS_GESAMTNOTE_FREIGABEMAIL_NOTE') && CIS_GESAMTNOTE_FREIGABEMAIL_NOTE)
+ {
+ $studlist .= "" . trim($note->matrikelnr) . " ";
+ $studlist .= "" . trim($note->kuerzel) . " ";
+ $studlist .= "" . trim($note->nachname) . " ";
+ $studlist .= "" . trim($note->vorname) . " ";
+
+ if(defined('CIS_GESAMTNOTE_PUNKTE') && CIS_GESAMTNOTE_PUNKTE) {
+ $studlist .= "" . trim($lvgesamtnote->punkte) . " ";
+ }
+ $studlist .= "" .$note->noteBezeichnung. " ";
+
+ $studlist .= "" . $lvgesamtnote->mitarbeiter_uid;
+ if ($lvgesamtnote->updatevon != '')
+ $studlist .= " (" . $lvgesamtnote->updatevon . ")";
+ $studlist .= " ";
+ } else {
+ $studlist .= "" . trim($note->uid) . " \n";
+ }
+ }
+ }
+ }
+ $studlist .= "
";
+
+ $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');
+ }
+
+ $this->assertLvAccess($lv_id, $sem_kurzbz);
+
+
+ $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
+ * exam, and sets the grade of that exam.
+ * Updates the course grade of the student. It never writes the transcript grade, the student
+ * administration does that.
+ */
+ 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;
+ // pruefung_id identifies the record being edited; null when a new pruefung is added
+ $pruefung_id = property_exists($result, 'pruefung_id') ? $result->pruefung_id : null;
+
+ $stsem = $result->sem_kurzbz;
+
+ $this->assertLvAccess($lva_id, $stsem);
+
+ // examination rules: no entry and no change after the grade entry deadline
+ $this->enforceNoteneintragungsfrist($stsem);
+
+ $jetzt = date("Y-m-d H:i:s");
+
+ if(CIS_GESAMTNOTE_PUNKTE && isset($punkte) && $punkte >= 0) {
+ // with a points entry the server checks the grade again and corrects it
+ $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;
+ }
+
+ // the dialog sends the teacher when the Lehreinheit has more than one
+ $mitarbeiter_uid = property_exists($result, 'mitarbeiter_uid') ? $result->mitarbeiter_uid : null;
+
+ $result = $this->savePruefungFuerStudent($pruefung_id, $student_uid, $lva_id, $stsem, $lehreinheit_id, $note, $punkte, $datum, $mitarbeiter_uid);
+
+ // validation errors and write errors come back as a translated message
+ if(is_string($result)) $this->terminateWithError($result, 'general');
+
+ $savedPruefung = $result['savedPruefung'] ?? [];
+ $savedPruefungData = count($savedPruefung) > 0 ? $savedPruefung[0] : null;
+ $lvgesamtnote = $result['lvgesamtnote'] ?? null;
+
+ $this->terminateWithSuccess(array($savedPruefungData, $lvgesamtnote, $result['verlauf'] ?? null));
+ }
+
+ /**
+ * The core of one exam entry for ONE student: validate, write the course grade, write the exam.
+ * The dialog and the bulk endpoints both use this method. The transcript grade stays unchanged.
+ *
+ * @return array|string ['savedPruefung', 'lvgesamtnote', 'verlauf'] or an error message
+ */
+ private function savePruefungFuerStudent($pruefung_id, $student_uid, $lva_id, $stsem, $lehreinheit_id, $note, $punkte, $datum, $mitarbeiter_uid = null)
+ {
+ // §7 and §11: every exam must take place before the grade entry deadline. The kommissionelle
+ // Prüfung is the important case, because it decides if the student loses a semester.
+ $fristError = $this->pruefungsdatumNachFrist($stsem, $datum, $student_uid);
+ if($fristError !== null) return $fristError;
+
+ // validate before any write
+ $editError = $this->validatePruefungEdit($student_uid, $lva_id, $stsem, $note, $datum, $pruefung_id);
+ if($editError !== null) return $editError;
+
+ if($pruefung_id === null || $pruefung_id === '') {
+ $addError = $this->validatePruefungAdd($student_uid, $lva_id, $stsem, $note, $datum);
+ if($addError !== null) return $addError;
+ }
+
+ // the client does not always send it; without it the insert fails on the NOT NULL column
+ if(!$lehreinheit_id) {
+ $resLe = $this->LehrveranstaltungModel->getLeByStudent($student_uid, $stsem, $lva_id);
+ if(!isError($resLe) && hasData($resLe)) $lehreinheit_id = current(getData($resLe))->lehreinheit_id;
+ }
+
+ $jetzt = date("Y-m-d H:i:s");
+
+ // Read without the filter. A grade that is not released still exists. You must update it.
+ // A new insert breaks the primary key.
+ $bestehendeLvNote = $this->getLvGesamtnoteRow($lva_id, $student_uid, $stsem);
+
+ // this decides if the course grade is the implicit first attempt; read it before the update
+ $origLvNote = $bestehendeLvNote ? $bestehendeLvNote->note : null;
+ $lvgesamtnote = $bestehendeLvNote;
+
+ if($bestehendeLvNote === null) {
+ $lvgesamtnote = $this->createLvGesamtnote($lva_id, $student_uid, $stsem, $note, $punkte, $lehreinheit_id, $mitarbeiter_uid);
+
+ $this->logLib->logInfoDB(array('pruefung: lvnote angelegt', $student_uid, $lva_id, $stsem,
+ $note, $punkte, getAuthUID(), getAuthPersonId()));
+ } else {
+ $id = $this->LvgesamtnoteModel->update(
+ [$bestehendeLvNote->student_uid, $bestehendeLvNote->studiensemester_kurzbz, $bestehendeLvNote->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('pruefung: lvnote aktualisiert', $student_uid, $lva_id, $stsem,
+ $note, $punkte, getAuthUID(), getAuthPersonId()));
+ }
+
+ // save pruefung after updating lvnote, since pruefungspunkte get loaded by lv punkte
+ $pruefungenChanged = $this->savePruefungstermin($pruefung_id, $student_uid, $lva_id, $stsem, $lehreinheit_id, $note, $punkte, $datum, $origLvNote, $mitarbeiter_uid);
+ if(is_string($pruefungenChanged)) return $pruefungenChanged;
+
+ $pruefungenChanged['lvgesamtnote'] = $lvgesamtnote;
+
+ return $pruefungenChanged;
+ }
+
+ /**
+ * Creates an exam or updates an exam. The exam type has no meaning here: the position comes
+ * from the history. The code writes pruefungstyp_kurzbz, but it never reads it.
+ *
+ * @param int $pruefung_id gesetzt = diesen Datensatz bearbeiten, null = neuer Antritt
+ * @return array|string the changed exams or a translated error message
+ */
+ private function savePruefungstermin($pruefung_id, $student_uid, $lva_id, $stsem, $lehreinheit_id, $note, $punkte, $datum, $origLvNote = null, $mitarbeiter_uid = null)
+ {
+ // no exam without a course grade (a grade that is not released also counts)
+ if($this->getLvGesamtnoteRow($lva_id, $student_uid, $stsem) === null) {
+ return $this->p->t('benotungstool', 'c4keineLvNoteEingetragen');
+ }
+
+ $pruefungen = $this->VerlaufLib->getPruefungen($student_uid, $lva_id, $stsem);
+
+ $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) {
+ $entschuldigtNote = $this->VerlaufLib->getSpecialNotes()['entschuldigt'];
+
+ // the limit was checked on the grade BEFORE the override, therefore check it again
+ if($entschuldigtNote !== null
+ && !$this->VerlaufLib->ueberschreitetNotenLimit($pruefungen, $entschuldigtNote, $pruefung_id)) {
+ $note = $entschuldigtNote;
+ }
+ }
+
+ $jetzt = date("Y-m-d H:i:s");
+
+ $pruefungenChanged = [];
+
+ // edit: only the addressed record, no type change and no new record
+ if($pruefung_id !== null && $pruefung_id !== '') {
+ $id = $this->LePruefungModel->update(
+ $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('pruefung updated', $res, getAuthUID(), getAuthPersonId()));
+
+ if(!isset($pruefungenChanged['savedPruefung'])) {
+ return $this->p->t('benotungstool', 'c4pruefungNichtGespeichert', [$student_uid]);
+ }
+
+ $pruefungenChanged['verlauf'] = $this->buildVerlaufSummary($student_uid, $lva_id, $stsem);
+ return $pruefungenChanged;
+ }
+
+ $verlauf = $this->VerlaufLib->buildVerlauf($pruefungen, $origLvNote);
+ $rolle = $verlauf->naechsteRolle;
+
+ // one action makes one exam; the release creates the first attempt (upsertErstantritt)
+ $typ = ($rolle === PruefungsverlaufLib::ROLLE_ERSTANTRITT)
+ ? $this->VerlaufLib->legacyTypFuerAntritt(1)
+ : $this->VerlaufLib->legacyTypFuerWiederholung($verlauf);
+
+ $id = $this->LePruefungModel->insert(
+ array(
+ 'lehreinheit_id' => $lehreinheit_id,
+ 'student_uid' => $student_uid,
+ 'mitarbeiter_uid' => $this->benotenderMitarbeiter($lehreinheit_id, $mitarbeiter_uid),
+ '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('pruefung inserted ('.$rolle.')', $res, getAuthUID(), getAuthPersonId()));
+
+ // savedPruefung is the proof of success. Without this guard a failed insert (for example
+ // a missing lehreinheit_id) tells the client that the write was successful.
+ if(!isset($pruefungenChanged['savedPruefung'])) {
+ return $this->p->t('benotungstool', 'c4pruefungNichtGespeichert', [$student_uid]);
+ }
+
+ $pruefungenChanged['verlauf'] = $this->buildVerlaufSummary($student_uid, $lva_id, $stsem);
+ return $pruefungenChanged;
+ }
+
+ /** The history for the client. Each write answer contains it, so the client calculates nothing. */
+ private function buildVerlaufSummary($student_uid, $lva_id, $stsem)
+ {
+ $lvNote = $this->getLvGesamtnoteRow($lva_id, $student_uid, $stsem);
+ $verlauf = $this->VerlaufLib->getVerlauf(
+ $student_uid, $lva_id, $stsem,
+ $lvNote ? $lvNote->note : null,
+ $this->getZeugnisnote($lva_id, $student_uid, $stsem)
+ );
+
+ return $this->verlaufSummary($verlauf, true, $lvNote !== null);
+ }
+
+ /**
+ * @param bool $withPruefungen add the exams (for the write paths) or send the counters only
+ * @return array
+ */
+ private function verlaufSummary($verlauf, $withPruefungen = false, $hatLvNote = null)
+ {
+ $summary = array(
+ 'antrittCount' => $verlauf->antrittCount,
+ 'maxAntritte' => $verlauf->maxAntritte,
+ 'canAdd' => $verlauf->canAdd,
+ 'terminal' => $verlauf->terminal,
+ 'erstantrittMoeglich' => $verlauf->erstantrittMoeglich,
+ 'naechsteRolle' => $verlauf->naechsteRolle,
+ // credited: the row is visible, but you cannot select it and it gets no exams
+ 'angerechnet' => $verlauf->angerechnet,
+ 'hatLvNote' => $hatLvNote // ungefiltert, also inklusive noch nicht freigegebener
+ );
+
+ if($withPruefungen) $summary['pruefungen'] = $verlauf->pruefungen;
+
+ return $summary;
+ }
+
+ /**
+ * Guards an EDIT ($pruefung_id is set; if not, validatePruefungAdd applies). The date must stay
+ * between the two adjacent exams. An exam with a later date locks the grade.
+ *
+ * @return string|null lokalisierte Fehlermeldung oder null
+ */
+ private function validatePruefungEdit($student_uid, $lva_id, $stsem, $newNote, $newDatum, $pruefung_id)
+ {
+ if($pruefung_id === null || $pruefung_id === '') return null; // add, not an edit
+
+ $zeugnisNote = $this->getZeugnisnote($lva_id, $student_uid, $stsem);
+ if($this->VerlaufLib->istAnrechnungsnote($zeugnisNote)) {
+ return $this->p->t('benotungstool', 'c4angerechnetKeinePruefung', [$student_uid]);
+ }
+
+ $pruefungen = $this->VerlaufLib->getPruefungen($student_uid, $lva_id, $stsem);
+ if(count($pruefungen) === 0) return null;
+
+ $verlauf = $this->VerlaufLib->buildVerlauf($pruefungen, null, $zeugnisNote);
+
+ // the record being edited
+ $current = null;
+ foreach($verlauf->pruefungen as $p) {
+ if($p->pruefung_id == $pruefung_id) { $current = $p; break; }
+ }
+ if($current === null) return null;
+
+ // The student administration owns the exams that use no attempt (zusKommPruef), so this tool
+ // does not validate them. The kommissionelle Prüfung itself is a normal attempt now.
+ if(in_array($current->pruefungstyp_kurzbz, $this->VerlaufLib->getTypenOhneAntritt())) return null;
+
+ $currentDate = substr((string)$current->datum, 0, 10);
+ $new = substr((string)$newDatum, 0, 10);
+
+ // the limits come from the two exams before and after this one
+ $lower = null; $upper = null;
+ foreach($verlauf->pruefungen as $p) {
+ if($p->pruefung_id == $current->pruefung_id) continue;
+
+ $d = substr((string)$p->datum, 0, 10);
+ if($d === '') continue;
+
+ if($d < $currentDate) { if($lower === null || $d > $lower) $lower = $d; }
+ elseif($d > $currentDate) { if($upper === null || $d < $upper) $upper = $d; }
+ }
+
+ // grade is locked once a later attempt exists
+ if($this->VerlaufLib->hatSpaeterenTermin($verlauf, $current->pruefung_id) && $newNote != $current->note) {
+ return $this->p->t('benotungstool', 'pruefungNoteLocked', [$student_uid]);
+ }
+
+ // datum must stay strictly between the neighbouring exam dates
+ if(($lower !== null && $new <= $lower) || ($upper !== null && $new >= $upper)) {
+ return $this->p->t('benotungstool', 'pruefungDatumOutOfRange', [$student_uid]);
+ }
+
+ // an edit may also change the note (e.g. to 'entschuldigt') -> keep the occurrence limit
+ if($this->VerlaufLib->ueberschreitetNotenLimit($verlauf->pruefungen, $newNote, $current->pruefung_id)) {
+ return $this->p->t('benotungstool', 'noteOccuranceLimitReached', [$student_uid]);
+ }
+
+ return null;
+ }
+
+ /**
+ * Guards a NEW attempt. Rule A is the attempt limit, and nothing follows a final exam. Rule B
+ * is the chronological order. Rule C is the occurrence limit of a grade.
+ *
+ * @return string|null lokalisierte Fehlermeldung oder null
+ */
+ private function validatePruefungAdd($student_uid, $lva_id, $stsem, $note, $datum)
+ {
+ $zeugnisNote = $this->getZeugnisnote($lva_id, $student_uid, $stsem);
+ if($this->VerlaufLib->istAnrechnungsnote($zeugnisNote)) {
+ return $this->p->t('benotungstool', 'c4angerechnetKeinePruefung', [$student_uid]);
+ }
+
+ $pruefungen = $this->VerlaufLib->getPruefungen($student_uid, $lva_id, $stsem);
+
+ // the implicit first attempt; read it before the caller changes it
+ $lvRow = $this->getLvGesamtnoteRow($lva_id, $student_uid, $stsem);
+ $lvNote = $lvRow ? $lvRow->note : null;
+
+ $verlauf = $this->VerlaufLib->buildVerlauf($pruefungen, $lvNote, $zeugnisNote);
+
+ // A: the first attempt only materialises the course grade, therefore it adds no attempt
+ if($verlauf->naechsteRolle !== PruefungsverlaufLib::ROLLE_ERSTANTRITT && !$verlauf->canAdd) {
+ return $this->p->t('benotungstool', 'maxAntritteReached', [$student_uid, $verlauf->maxAntritte]);
+ }
+
+ // B: no attempt on the same day as an existing exam, and no attempt before it
+ $newDate = substr((string)$datum, 0, 10);
+ foreach($pruefungen as $p) {
+ $d = substr((string)$p->datum, 0, 10);
+ if($d !== '' && $d >= $newDate) {
+ return $this->p->t('benotungstool', 'pruefungDatumBeforeExisting', [$student_uid]);
+ }
+ }
+
+ // C: the occurrence limit, for example one 'entschuldigt' only
+ if($this->VerlaufLib->ueberschreitetNotenLimit($pruefungen, $note, null)) {
+ return $this->p->t('benotungstool', 'noteOccuranceLimitReached', [$student_uid]);
+ }
+
+ return null;
+ }
+
+ /**
+ * The active grades, keyed by their PK. getNoten() sends the client the same set, so both sides
+ * judge a grade by the same flags.
+ *
+ * @return array note (string) => zeile aus lehre.tbl_note
+ */
+ private function aktiveNoten()
+ {
+ if($this->aktiveNotenCache !== null) return $this->aktiveNotenCache;
+
+ $this->aktiveNotenCache = array();
+
+ $result = $this->NoteModel->getAllActive();
+ if(!isError($result) && hasData($result)) {
+ foreach(getData($result) as $n) $this->aktiveNotenCache[(string)$n->note] = $n;
+ }
+
+ return $this->aktiveNotenCache;
+ }
+
+ /**
+ * Guards a DIRECT api write of the course grade (saveNotenvorschlag and its bulk variant). All
+ * three rules are also in the cell editor, but the API and the CSV import reach the server
+ * without it. The order follows the editor: grade, exam, transcript.
+ *
+ * @return string|null lokalisierte Fehlermeldung oder null
+ */
+ private function validateNotenvorschlag($lva_id, $student_uid, $stsem, $note)
+ {
+ $noten = $this->aktiveNoten();
+ $wert = trim((string)$note);
+
+ // the editor offers the lehre grades only; an administrative grade belongs to the transcript
+ if(!isset($noten[$wert]) || !$noten[$wert]->lehre) {
+ return $this->p->t('benotungstool', 'c4noteNichtInLehre', [$student_uid]);
+ }
+
+ // as soon as an exam exists the grade belongs to the attempt history and follows its rules
+ if(count($this->VerlaufLib->getPruefungen($student_uid, $lva_id, $stsem)) > 0) {
+ return $this->p->t('benotungstool', 'c4notenvorschlagGesperrt', [$student_uid]);
+ }
+
+ // a transcript grade can forbid the teacher to overwrite it, for example 'intern angerechnet'.
+ // An unknown or inactive grade locks nothing, which is what the editor does as well.
+ $zeugnisnote = trim((string)$this->getZeugnisnote($lva_id, $student_uid, $stsem));
+ if(isset($noten[$zeugnisnote]) && !$noten[$zeugnisnote]->lkt_ueberschreibbar) {
+ return $this->p->t('benotungstool', 'c4zeugnisnoteGesperrt', [$student_uid]);
+ }
+
+ return null;
+ }
+
+ /**
+ * Checks the DATE of the exam against the deadline. enforceNoteneintragungsfrist compares the
+ * current time, which is a different question: a user can enter an exam today and give it a
+ * date after the deadline.
+ *
+ * @return string|null a translated error message, or null if the date is permitted
+ */
+ private function pruefungsdatumNachFrist($sem_kurzbz, $datum, $student_uid)
+ {
+ if(!$this->config->item('CIS_GESAMTNOTE_NOTENEINTRAGUNGSFRIST')) return null;
+
+ $deadline = $this->computeNoteneintragungsfrist($sem_kurzbz);
+ if($deadline === null) return null;
+
+ $tag = substr((string) $datum, 0, 10);
+ if($tag === '' || $tag <= $deadline->format('Y-m-d')) return null;
+
+ return $this->p->t('benotungstool', 'pruefungsdatumNachFrist', [$student_uid, $deadline->format('d.m.Y')]);
+ }
+
+ /** Stops the request if the grade entry deadline of the semester has passed. */
+ private function enforceNoteneintragungsfrist($sem_kurzbz)
+ {
+ if(!$this->config->item('CIS_GESAMTNOTE_NOTENEINTRAGUNGSFRIST')) return;
+
+ $deadline = $this->computeNoteneintragungsfrist($sem_kurzbz);
+ if($deadline === null) return;
+
+ if(new DateTime() > $deadline) {
+ $this->terminateWithError(
+ $this->p->t('benotungstool', 'noteneintragungsfristVorbei', [$deadline->format('d.m.Y')]),
+ 'general'
+ );
+ }
+ }
+
+ /** The deadline from '{SS|WS}yyyy': SS in the same year, WS in the next year. @return DateTime|null */
+ private function computeNoteneintragungsfrist($sem_kurzbz)
+ {
+ if(!is_string($sem_kurzbz) || strlen($sem_kurzbz) < 6) return null;
+
+ $type = strtoupper(substr($sem_kurzbz, 0, 2));
+ $year = (int) substr($sem_kurzbz, 2, 4);
+ if($year <= 0) return null;
+
+ if($type === 'SS') {
+ $cfg = $this->config->item('NOTENEINTRAGUNGSFRIST_SS');
+ $deadlineYear = $year;
+ } elseif($type === 'WS') {
+ $cfg = $this->config->item('NOTENEINTRAGUNGSFRIST_WS');
+ $deadlineYear = $year + 1;
+ } else {
+ return null;
+ }
+
+ $month = (is_array($cfg) && isset($cfg['month'])) ? (int)$cfg['month'] : ($type === 'SS' ? 11 : 5);
+ $day = (is_array($cfg) && isset($cfg['day'])) ? (int)$cfg['day'] : 15;
+
+ $deadline = new DateTime();
+ $deadline->setDate($deadlineYear, $month, $day);
+ $deadline->setTime(23, 59, 59);
+ return $deadline;
+ }
+
+ /**
+ * 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;
+
+ $this->assertLvAccess($lv_id, $sem_kurzbz);
+
+ // examination rules: no entry and no change after the grade entry deadline
+ $this->enforceNoteneintragungsfrist($sem_kurzbz);
+
+ // In the points mode the grading scale decides, not the grade from the client. If not, you
+ // get a course grade that contradicts its own points. Without points there is nothing to
+ // derive (for example a proposal from Moodle partial grades), then the given grade applies.
+ if(CIS_GESAMTNOTE_PUNKTE && $punkte !== null && $punkte !== '') {
+ $abgeleitet = $this->noteAusPunkten($punkte, $lv_id, $sem_kurzbz, $student_uid);
+ if(is_string($abgeleitet)) $this->terminateWithError($abgeleitet, 'general');
+ $note = $abgeleitet;
+ }
+
+ $fehler = $this->validateNotenvorschlag($lv_id, $student_uid, $sem_kurzbz, $note);
+ if($fehler !== null) $this->terminateWithError($fehler, 'general');
+
+ $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' => $this->benotenderMitarbeiterFuerStudent($lv_id, $student_uid, $sem_kurzbz),
+ '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.
+ * The answer uses the uid as the key. Each row holds the course grade or an error message.
+ */
+ 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;
+
+ $this->assertLvAccess($lv_id, $sem_kurzbz);
+
+ // examination rules: no entry and no change after the grade entry deadline
+ $this->enforceNoteneintragungsfrist($sem_kurzbz);
+
+ $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) {
+ $abgeleitet = $this->noteAusPunkten($note->punkte, $lv_id, $sem_kurzbz, $note->uid);
+ // no grade can be derived: skip the row, but do not stop the full request
+ if(is_string($abgeleitet)) { $retLvNoten[$note->uid] = $abgeleitet; continue; }
+ $note->note = $abgeleitet;
+ }
+
+ // one bad row must not stop the import, so the message goes into this row
+ $fehler = $this->validateNotenvorschlag($lv_id, $note->uid, $sem_kurzbz, $note->note);
+ if($fehler !== null) { $retLvNoten[$note->uid] = $fehler; continue; }
+
+ 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' => $this->benotenderMitarbeiterFuerStudent($lv_id, $note->uid, $sem_kurzbz),
+ '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[$note->uid] = $lvgesamtnote;
+ }
+
+ $this->terminateWithSuccess($retLvNoten);
+ }
+
+ /**
+ * POST METHOD
+ * expects 'uids', 'datum', optional 'note'/'punkte'
+ * The bulk variant of saveStudentPruefung. It creates one exam for several students.
+ * Without a selected grade it writes "noch nicht eingetragen".
+ */
+ public function createPruefungen() {
+ $payload = $this->getPostJSON();
+
+ if(!property_exists($payload, 'uids') || !property_exists($payload, 'datum')) {
+ $this->terminateWithError($this->p->t('global', 'missingParameters'), 'general');
+ }
+
+ $uids = $payload->uids;
+ $datum = $payload->datum;
+ $lva_id = $payload->lva_id;
+
+ $stsem = $payload->sem_kurzbz;
+
+ $this->assertLvAccess($lva_id, $stsem);
+
+ // examination rules: no entry after the grade entry deadline
+ $this->enforceNoteneintragungsfrist($stsem);
+
+ $ret = [];
+
+ $note = property_exists($payload, 'note') ? $payload->note : null;
+ $punkte = property_exists($payload, 'punkte') ? $payload->punkte : null;
+
+ // the points win: the grade then comes from the grading scale
+ if(CIS_GESAMTNOTE_PUNKTE && $punkte !== null && $punkte !== '' && $punkte >= 0) {
+ $resNote = $this->NotenschluesselaufteilungModel->getNote($punkte, $lva_id, $stsem);
+ $note = $this->getDataOrTerminateWithError($resNote);
+ }
+
+ // without a selection the exam has no grade
+ if($note === null || $note === '') {
+ $resNote = $this->NoteModel->getNochNichtEingetragenNote();
+ $note = getData($resNote)[0]->note;
+ $punkte = null;
+ }
+
+ // the dialog sends the teacher when all selected students share one Lehreinheit
+ $mitarbeiter_uid = property_exists($payload, 'mitarbeiter_uid') ? $payload->mitarbeiter_uid : null;
+
+ // the same core as the dialog; each row gets its own error message
+ foreach ($uids as $student) {
+ $ret[$student->uid] = $this->savePruefungFuerStudent(
+ null, $student->uid, $lva_id, $stsem, $student->lehreinheit_id, $note, $punkte, $datum,
+ $mitarbeiter_uid
+ );
+ }
+
+ $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;
+
+ $this->assertLvAccess($lv_id, $sem_kurzbz);
+
+ // examination rules: no entry after the grade entry deadline
+ $this->enforceNoteneintragungsfrist($sem_kurzbz);
+
+ $ret = [];
+
+ foreach ($pruefungen as $pruefung) {
+
+ if(CIS_GESAMTNOTE_PUNKTE) {
+ $note = $this->noteAusPunkten($pruefung->punkte, $lv_id, $sem_kurzbz, $pruefung->uid);
+ // no grade can be derived: skip the row, but do not stop the full request
+ if(is_string($note)) { $ret[$pruefung->uid] = $note; continue; }
+ $pruefung->note = $note;
+ }
+
+ // the same as the dialog in the table, but for each import row
+ $ret[$pruefung->uid] = $this->savePruefungFuerStudent(
+ null, $pruefung->uid, $lv_id, $sem_kurzbz, $pruefung->lehreinheit_id,
+ $pruefung->note, $pruefung->punkte, $pruefung->datum,
+ property_exists($pruefung, 'mitarbeiter_uid') ? $pruefung->mitarbeiter_uid : null
+ );
+ }
+
+ $this->logLib->logInfoDB(array('savePruefungenBulk',$ret, getAuthUID(), getAuthPersonId()));
+
+ $this->terminateWithSuccess($ret);
+ }
+
+ /**
+ * Creates or updates the first attempt. The release calls it.
+ *
+ * The date is the benotungsdatum of the course grade, which is the moment the teacher recorded
+ * the assessment. The examination rules ask for the date of the last performance (§8), but the
+ * system does not hold that date, because many courses have no exam date at all. The old tool
+ * used the benotungsdatum for the same row, so the value stays comparable with the old data.
+ *
+ * It applies only while one exam or no exam exists. After that the course grade holds the last
+ * grade, and it must not overwrite attempt 1. A credited grade changes nothing.
+ */
+ /**
+ * The grade from the points for ONE bulk row. It gives the grade, or a translated error message
+ * if no grade can be derived. The caller then skips the row and continues with the other rows.
+ *
+ * @return mixed|string
+ */
+ private function noteAusPunkten($punkte, $lv_id, $sem_kurzbz, $uid)
+ {
+ $result = $this->NotenschluesselaufteilungModel->getNote($punkte, $lv_id, $sem_kurzbz);
+ if(isError($result)) return getError($result);
+
+ $note = getData($result);
+ if($note === null || $note === '') {
+ return $this->p->t('benotungstool', 'c4punkteKeineNoteErmittelt', [$uid]);
+ }
+
+ return $note;
+ }
+
+ private function upsertErstantritt($lva_id, $student_uid, $stsem, $note, $punkte, $datum)
+ {
+ if($this->VerlaufLib->istAnrechnungsnote($this->getZeugnisnote($lva_id, $student_uid, $stsem))) return;
+
+ $pruefungen = $this->VerlaufLib->getPruefungen($student_uid, $lva_id, $stsem);
+ if(count($pruefungen) > 1) return;
+
+ $jetzt = date("Y-m-d H:i:s");
+
+ if(count($pruefungen) === 1) {
+ $this->LePruefungModel->update(
+ $pruefungen[0]->pruefung_id,
+ array(
+ 'note' => $note,
+ 'punkte' => $punkte,
+ 'datum' => $datum,
+ 'updateamum' => $jetzt,
+ 'updatevon' => getAuthUID()
+ )
+ );
+
+ $this->logLib->logInfoDB(array('erstantritt aktualisiert (freigabe)', $student_uid, getAuthUID(), getAuthPersonId()));
+ return;
+ }
+
+ // the server finds the lehreinheit_id; it does not use the value from the client
+ $resLe = $this->LehrveranstaltungModel->getLeByStudent($student_uid, $stsem, $lva_id);
+ if(isError($resLe) || !hasData($resLe)) return;
+ $le = current(getData($resLe));
+
+ $this->LePruefungModel->insert(
+ array(
+ 'lehreinheit_id' => $le->lehreinheit_id,
+ 'student_uid' => $student_uid,
+ 'mitarbeiter_uid' => $this->benotenderMitarbeiter($le->lehreinheit_id),
+ 'note' => $note,
+ 'punkte' => $punkte,
+ 'pruefungstyp_kurzbz' => $this->VerlaufLib->legacyTypFuerAntritt(1),
+ 'datum' => $datum,
+ 'anmerkung' => "",
+ 'insertamum' => $jetzt,
+ 'insertvon' => getAuthUID(),
+ 'updateamum' => null,
+ 'updatevon' => null,
+ 'ext_id' => null
+ )
+ );
+
+ $this->logLib->logInfoDB(array('erstantritt angelegt (freigabe)', $student_uid, getAuthUID(), getAuthPersonId()));
+ }
+
+ /**
+ * The teachers of one Lehreinheit, from lehre.tbl_lehreinheitmitarbeiter.
+ *
+ * @return array
+ */
+ private function lehrendeDerLehreinheit($lehreinheit_id)
+ {
+ if(!$lehreinheit_id) return array();
+
+ $this->load->model('education/Lehreinheitmitarbeiter_model', 'LehreinheitmitarbeiterModel');
+ $result = $this->LehreinheitmitarbeiterModel->getLektorenByLe($lehreinheit_id);
+ if(isError($result) || !hasData($result)) return array();
+
+ $lehrende = getData($result);
+ usort($lehrende, function($a, $b) {
+ return strcmp($a->mitarbeiter_uid, $b->mitarbeiter_uid);
+ });
+
+ return $lehrende;
+ }
+
+ /**
+ * The uid of the person who gave the grade. It goes into lehre.tbl_pruefung and into
+ * campus.tbl_lvgesamtnote.
+ *
+ * Historically only teachers used this tool, so the caller was always right. An assistant may
+ * do the data entry too, but the grade still comes from the teacher. The row must therefore
+ * carry the teacher of the Lehreinheit, not the person who types. insertvon and updatevon keep
+ * the caller, because they answer a different question.
+ *
+ * Order: a valid selection wins, then the caller if the caller teaches this Lehreinheit, then
+ * the only teacher. With several teachers and no selection the first one wins, because an
+ * assistant is never the correct answer. Without any teacher the caller stays.
+ *
+ * @return string
+ */
+ private function benotenderMitarbeiter($lehreinheit_id, $gewaehlt = null)
+ {
+ $uids = array();
+ foreach($this->lehrendeDerLehreinheit($lehreinheit_id) as $lehrend) $uids[] = $lehrend->mitarbeiter_uid;
+
+ if(count($uids) === 0) return getAuthUID();
+ if($gewaehlt !== null && $gewaehlt !== '' && in_array($gewaehlt, $uids)) return $gewaehlt;
+ if(in_array(getAuthUID(), $uids)) return getAuthUID();
+
+ return $uids[0];
+ }
+
+ /**
+ * The same answer for a course grade, which has no Lehreinheit of its own. The Lehreinheit of
+ * the student decides.
+ *
+ * @return string
+ */
+ private function benotenderMitarbeiterFuerStudent($lva_id, $student_uid, $stsem, $gewaehlt = null)
+ {
+ $lehreinheit_id = null;
+
+ $resLe = $this->LehrveranstaltungModel->getLeByStudent($student_uid, $stsem, $lva_id);
+ if(!isError($resLe) && hasData($resLe)) $lehreinheit_id = current(getData($resLe))->lehreinheit_id;
+
+ return $this->benotenderMitarbeiter($lehreinheit_id, $gewaehlt);
+ }
+
+ /** The transcript grade or null. Credited grades are there, not in the course grade. @return mixed|null */
+ private function getZeugnisnote($lva_id, $student_uid, $stsem)
+ {
+ $this->load->model('education/Zeugnisnote_model', 'ZeugnisnoteModel');
+
+ $res = $this->ZeugnisnoteModel->load([
+ 'studiensemester_kurzbz' => $stsem,
+ 'student_uid' => $student_uid,
+ 'lehrveranstaltung_id' => $lva_id
+ ]);
+
+ return (!isError($res) && hasData($res)) ? getData($res)[0]->note : null;
+ }
+
+ /**
+ * Reads the course grade WITHOUT the filter. getLvGesamtNoten() uses 'freigabedatum < NOW()'
+ * and therefore hides a new grade. In this tool this wrapper is always the correct one.
+ */
+ private function getLvGesamtnoteRow($lva_id, $student_uid, $stsem)
+ {
+ $res = $this->LvgesamtnoteModel->getLvGesamtNoteVorschlag($lva_id, $student_uid, $stsem);
+ return (!isError($res) && hasData($res)) ? getData($res)[0] : null;
+ }
+
+ /** Creates a course grade. The student administration writes it to the transcript. @return stdClass|null */
+ private function createLvGesamtnote($lva_id, $student_uid, $stsem, $note, $punkte, $lehreinheit_id = null, $mitarbeiter_uid = null)
+ {
+ $jetzt = date("Y-m-d H:i:s");
+
+ // the caller knows the Lehreinheit here, so no second lookup is needed
+ $benotender = $lehreinheit_id
+ ? $this->benotenderMitarbeiter($lehreinheit_id, $mitarbeiter_uid)
+ : $this->benotenderMitarbeiterFuerStudent($lva_id, $student_uid, $stsem, $mitarbeiter_uid);
+
+ $id = $this->LvgesamtnoteModel->insert(
+ array(
+ 'student_uid' => $student_uid,
+ 'lehrveranstaltung_id' => $lva_id,
+ 'studiensemester_kurzbz' => $stsem,
+ 'note' => $note,
+ 'punkte' => $punkte,
+ 'mitarbeiter_uid' => $benotender,
+ 'benotungsdatum' => $jetzt,
+ 'freigabedatum' => null,
+ 'freigabevon_uid' => null,
+ 'bemerkung' => null,
+ 'updateamum' => null,
+ 'updatevon' => null,
+ 'insertamum' => $jetzt,
+ 'insertvon' => getAuthUID()
+ )
+ );
+ if(!$id) return null;
+
+ $res = $this->LvgesamtnoteModel->load($id->retval);
+ return hasData($res) ? getData($res)[0] : null;
+ }
+
+ 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;
+
+ }
+
+ /**
+ * GET METHOD
+ * expects 'lehreinheit_id', 'lv_id', 'sem_kurzbz'
+ * The teachers of one Lehreinheit. The dialog needs them when the Lehreinheit has more than one,
+ * because the exam must carry the teacher and not the person who types.
+ */
+ public function getLehrendeFuerLehreinheit() {
+ $lehreinheit_id = $this->input->get('lehreinheit_id');
+ $lv_id = $this->input->get('lv_id');
+ $sem_kurzbz = $this->input->get('sem_kurzbz');
+
+ if(!$lehreinheit_id || !$lv_id || !$sem_kurzbz) {
+ $this->terminateWithError($this->p->t('global', 'missingParameters'), 'general');
+ }
+
+ $this->assertLvAccess($lv_id, $sem_kurzbz);
+
+ $lehrende = array();
+ foreach($this->lehrendeDerLehreinheit($lehreinheit_id) as $l) {
+ $lehrende[] = array(
+ 'mitarbeiter_uid' => $l->mitarbeiter_uid,
+ 'vorname' => $l->vorname,
+ 'nachname' => $l->nachname
+ );
+ }
+
+ $this->terminateWithSuccess($lehrende);
+ }
+
+ 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/Ort.php b/application/controllers/api/frontend/v1/Ort.php
index 0d8074fa9..c921c0c4f 100644
--- a/application/controllers/api/frontend/v1/Ort.php
+++ b/application/controllers/api/frontend/v1/Ort.php
@@ -25,7 +25,19 @@ if (! defined('BASEPATH')) exit('No direct script access allowed');
*/
class Ort extends FHCAPI_Controller
{
-
+ // advanced filters of getRooms: tbl_ort column => filter type, the same list as in Raumsuche.js.
+ // text: contains, case-insensitive; select: equals; range: _min, _max; flag: true; exists: not null
+ const ADVANCED_FILTERS = [
+ 'ort_kurzbz' => 'text',
+ 'bezeichnung' => 'text',
+ 'planbezeichnung' => 'text',
+ 'stockwerk' => 'range',
+ 'gebteil' => 'select',
+ 'm2' => 'range',
+ 'lehre' => 'flag',
+ 'content_id' => 'exists'
+ ];
+
/**
* Object initialization
*/
@@ -36,7 +48,10 @@ class Ort extends FHCAPI_Controller
'ContentID' => self::PERM_LOGGED,
'getOrtKurzbzContent' => self::PERM_LOGGED,
'getRooms' => self::PERM_LOGGED,
- 'getTypes' => self::PERM_LOGGED
+ 'getTypes' => self::PERM_LOGGED,
+ 'getRoomInfo' => self::PERM_LOGGED,
+ 'getStandorte' => self::PERM_LOGGED,
+ 'getAdvancedFilterOptions' => self::PERM_LOGGED
]);
$this->load->model('ressource/Ort_model', 'OrtModel');
@@ -56,6 +71,17 @@ class Ort extends FHCAPI_Controller
$this->form_validation->set_rules('datum','Datum','required');
$this->form_validation->set_rules('von','Uhrzeit Von','required|regex_match[/^[0-9]{2}:[0-9]{2}$/]');
$this->form_validation->set_rules('bis','Uhrzeit Bis','required|regex_match[/^[0-9]{2}:[0-9]{2}$/]');
+ $this->form_validation->set_rules('standort_id','Standort','regex_match[/^([0-9]+|none)$/]');
+ foreach (self::ADVANCED_FILTERS as $column => $type) {
+ if ($type == 'range') {
+ $this->form_validation->set_rules($column.'_min', $column, 'numeric');
+ $this->form_validation->set_rules($column.'_max', $column, 'numeric');
+ } elseif ($type == 'flag' || $type == 'exists') {
+ $this->form_validation->set_rules($column, $column, 'in_list[true]');
+ } else {
+ $this->form_validation->set_rules($column, $column, 'max_length[255]');
+ }
+ }
if($this->form_validation->run() == FALSE) {
$this->terminateWithValidationErrors($this->form_validation->error_array());
}
@@ -65,6 +91,7 @@ class Ort extends FHCAPI_Controller
$bis = $this->input->get('bis', TRUE);
$typ = $this->input->get('typ', TRUE);
$personenanzahl = $this->input->get('personenanzahl', TRUE);
+ $standort_id = $this->input->get('standort_id', TRUE);
$this->load->model('ressource/Mitarbeiter_model', 'MitarbeiterModel');
@@ -74,22 +101,36 @@ class Ort extends FHCAPI_Controller
$vonStunde = getData($this->StundeModel->getStundeForTime($von))[0]->stunde;
$bisStunde = getData($this->StundeModel->getStundeForTime($bis))[0]->stunde;
+ $this->load->model('ressource/Ort_model', 'OrtModel');
+
$params = array();
- $qry = "SELECT DISTINCT tbl_ort.*
+ $qry = "SELECT DISTINCT tbl_ort.*, tbl_standort.bezeichnung AS standort
FROM public.tbl_ort JOIN public.tbl_ortraumtyp USING(ort_kurzbz)
- WHERE aktiv AND lehre AND ort_kurzbz NOT LIKE '\\\\_%'";
+ LEFT JOIN public.tbl_standort USING(standort_id)
+ WHERE aktiv AND reservieren";
if($typ) {
$params[] = $typ;
- $qry.= "AND raumtyp_kurzbz = ?";
+ $qry.= " AND raumtyp_kurzbz = ?";
}
+ if($standort_id === 'none') { // rooms without a Standort
+ $qry.= " AND standort_id IS NULL";
+ } elseif($standort_id) {
+ $params[] = $standort_id;
+ $qry.= " AND standort_id = ?";
+ } else {
+ $qry.= " AND standort_id IS NOT NULL";
+ }
+
+ $qry.= $this->advancedFilterSql($params);
+
if(!$isMitarbeiter) { // students are only allowed to get a subset defined by config
$qry.= ' AND raumtyp_kurzbz IN ?';
$params[] = $this->config->item('roomtypes_student');
$this->addMeta('config', $this->config->item('roomtypes_student'));
}
- $qry.= "AND (max_person>= ? OR max_person is null)";
+ $qry.= " AND (max_person>= ? OR max_person is null)";
$params[] = $personenanzahl;
$qry.=" AND ort_kurzbz NOT IN
@@ -127,6 +168,41 @@ class Ort extends FHCAPI_Controller
$this->terminateWithSuccess(getData($result));
}
+ /**
+ * Standorte of the rooms that pass the base filter of getRooms
+ */
+ public function getStandorte()
+ {
+ $qry = "SELECT DISTINCT standort_id, tbl_standort.bezeichnung
+ FROM public.tbl_ort JOIN public.tbl_standort USING(standort_id)
+ WHERE aktiv AND reservieren
+ ORDER BY tbl_standort.bezeichnung";
+
+ $result = $this->OrtModel->execReadOnlyQuery($qry);
+
+ $this->terminateWithSuccess($this->getDataOrTerminateWithError($result));
+ }
+
+ /**
+ * Values of the select filters: column => distinct values of the rooms that pass the base filter of getRooms
+ */
+ public function getAdvancedFilterOptions()
+ {
+ $options = [];
+ foreach (self::ADVANCED_FILTERS as $column => $type) {
+ if ($type != 'select')
+ continue;
+
+ $result = $this->OrtModel->execReadOnlyQuery("SELECT DISTINCT ".$column." AS value
+ FROM public.tbl_ort
+ WHERE aktiv AND reservieren AND ".$column." <> ''
+ ORDER BY ".$column);
+ $options[$column] = array_column($this->getDataOrTerminateWithError($result), 'value');
+ }
+
+ $this->terminateWithSuccess($options);
+ }
+
/**
* Gets a JSON body via HTTP POST and provides the parameters
*/
@@ -174,5 +250,60 @@ class Ort extends FHCAPI_Controller
$this->terminateWithSuccess($content);
}
+
+ public function getRoomInfo()
+ {
+ $ortKurzbz = $this->input->get("ortKurzbz");
+
+ $roomResult = $this->OrtModel->getRoomInfo($ortKurzbz);
+ $roomData = $this->getDataOrTerminateWithError($roomResult);
+ $room = count($roomData) ? $roomData[0] : null;
+
+ $this->terminateWithSuccess($room);
+ }
+
+ //------------------------------------------------------------------------------------------------------------------
+ // Private methods
+
+ /**
+ * SQL conditions of the advanced filters in the GET parameters, adds their values to $params
+ */
+ private function advancedFilterSql(&$params)
+ {
+ // no xss_clean: the values are bound, and it could change a select value so that it no longer equals the column
+ $sql = '';
+ foreach (self::ADVANCED_FILTERS as $column => $type) {
+ if ($type == 'range') {
+ $min = $this->input->get($column.'_min');
+ $max = $this->input->get($column.'_max');
+ if ($min !== null && $min !== '') {
+ $sql.= " AND tbl_ort.".$column." >= ?::numeric";
+ $params[] = $min;
+ }
+ if ($max !== null && $max !== '') {
+ $sql.= " AND tbl_ort.".$column." <= ?::numeric";
+ $params[] = $max;
+ }
+ continue;
+ }
+
+ $value = $this->input->get($column);
+ if ($value === null || $value === '')
+ continue;
+
+ if ($type == 'flag') {
+ $sql.= " AND tbl_ort.".$column;
+ } elseif ($type == 'exists') {
+ $sql.= " AND tbl_ort.".$column." IS NOT NULL";
+ } elseif ($type == 'select') {
+ $sql.= " AND tbl_ort.".$column." = ?";
+ $params[] = $value;
+ } else {
+ $sql.= " AND strpos(lower(tbl_ort.".$column."), lower(?)) > 0";
+ $params[] = $value;
+ }
+ }
+ return $sql;
+ }
}
diff --git a/application/controllers/api/frontend/v1/OtherLvPlan.php b/application/controllers/api/frontend/v1/OtherLvPlan.php
index 1807f0a4a..e6616cc99 100644
--- a/application/controllers/api/frontend/v1/OtherLvPlan.php
+++ b/application/controllers/api/frontend/v1/OtherLvPlan.php
@@ -1,6 +1,6 @@
.
+ */
+
+if (!defined('BASEPATH'))
+ exit('No direct script access allowed');
+
+class Permission extends FHCAPI_Controller
+{
+
+ /**
+ * Object initialization
+ */
+ public function __construct()
+ {
+ parent::__construct([
+ 'getPermissions' => self::PERM_LOGGED,
+ ]);
+ }
+
+ //------------------------------------------------------------------------------------------------------------------
+ // Public methods
+
+ /**
+ * retrieves permissions for auth user
+ * @access public
+ */
+ public function getPermissions()
+ {
+ $permissionsParam = $this->input->post("permissions");
+ if (!$permissionsParam || !is_array($permissionsParam)) {
+ $this->terminateWithError("Invalid parameter!");
+ }
+
+ $permissions = [];
+ foreach ($permissionsParam as $permission) {
+ $permissions[$permission] = $this->permissionlib->isBerechtigt($permission);
+ }
+
+ $this->terminateWithSuccess($permissions);
+ }
+
+ // -----------------------------------------------------------------------------------------------------------------
+ // Private methods
+
+}
+
diff --git a/application/controllers/api/frontend/v1/Reservierung.php b/application/controllers/api/frontend/v1/Reservierung.php
new file mode 100644
index 000000000..6ee9b293e
--- /dev/null
+++ b/application/controllers/api/frontend/v1/Reservierung.php
@@ -0,0 +1,67 @@
+.
+ */
+
+if (! defined('BASEPATH')) exit('No direct script access allowed');
+
+class Reservierung extends FHCAPI_Controller
+{
+
+ /**
+ * Object initialization
+ */
+ public function __construct()
+ {
+ parent::__construct([
+ 'getMyReservation' => 'basis/cis:r',
+ 'deleteReservation' => 'lehre/reservierung:rw'
+ ]);
+
+ $this->load->model('ressource/Reservierung_model', 'ReservierungModel');
+
+ }
+
+ /**
+ * Retrieves all reservations from lehre.vw_reservation of the logged in user
+ */
+ public function getMyReservation()
+ {
+ $result = $this->ReservierungModel->getMyReservation();
+ $data = $this->getDataOrTerminateWithError($result);
+
+ $this->terminateWithSuccess($data);
+ }
+
+ /**
+ * deletes a reservation by ID
+ */
+ public function deleteReservation()
+ {
+ $reservierung_id = $this->input->post('reservierung_id');
+
+
+ if ($reservierung_id === NULL || trim((string)$reservierung_id) === '') {
+ $this->terminateWithError($this->p->t('global', 'wrongParameters'), 'general');
+ }
+
+ $result = $this->ReservierungModel->deleteReservation($reservierung_id);
+ $data = $this->getDataOrTerminateWithError($result);
+
+ $this->terminateWithSuccess($data);
+ }
+}
+
diff --git a/application/controllers/api/frontend/v1/Studgang.php b/application/controllers/api/frontend/v1/Studgang.php
index 82aebb666..001706c92 100644
--- a/application/controllers/api/frontend/v1/Studgang.php
+++ b/application/controllers/api/frontend/v1/Studgang.php
@@ -27,7 +27,7 @@ class Studgang extends FHCAPI_Controller
{
parent::__construct([
'getStudiengangInfo'=> self::PERM_LOGGED,
-
+ 'getDegreePrograms'=> self::PERM_LOGGED,
]);
$this->load->model('organisation/Studiengang_model', 'StudiengangModel');
@@ -43,15 +43,46 @@ class Studgang extends FHCAPI_Controller
//------------------------------------------------------------------------------------------------------------------
// Public methods
- public function getStudiengangInfo(){
- $isMitarbeiter = $this->MitarbeiterModel->isMitarbeiter(getAuthUID());
- $isMitarbeiter = $this->getDataOrTerminateWithError($isMitarbeiter);
- if($isMitarbeiter) {
- $this->terminateWithSuccess(null);
+ public function getDegreePrograms() {
+ $entitledDegreePrograms = $this->permissionlib->getSTG_isEntitledFor("basis/news");
+ if (!$entitledDegreePrograms) {
+ $this->terminateWithError("User is not entitled for any degree program");
}
-
+
+ $activeDegreePrograms = $this->StudiengangModel->getDegreePrograms();
+ $activeDegreePrograms= $this->getDataOrTerminateWithError($activeDegreePrograms);
+
+ $activeDegreePrograms = array_values(array_filter($activeDegreePrograms, function($degreeProgram) use ($entitledDegreePrograms) {
+ return in_array($degreeProgram->studiengang_kz, $entitledDegreePrograms);
+ }));
+
+ $this->terminateWithSuccess($activeDegreePrograms);
+ }
+
+ public function getStudiengangInfo(){
+ $studiengang_kz = $this->input->get('studiengang_kz', true);
+ $semester = $this->input->get('semester', true);
+
+ // without a Studiengang the info of the logged in Student is returned
+ if (!is_numeric($studiengang_kz))
+ {
+ $isMitarbeiter = $this->MitarbeiterModel->isMitarbeiter(getAuthUID());
+ $isMitarbeiter = $this->getDataOrTerminateWithError($isMitarbeiter);
+ if($isMitarbeiter) {
+ $this->terminateWithSuccess(null);
+ }
+
+ $studiengang_kz = null;
+ $semester = null;
+ }
+ else
+ {
+ $studiengang_kz = (int)$studiengang_kz;
+ $semester = is_numeric($semester) ? (int)$semester : null;
+ }
+
// fetches the Studiengang Information which is used next to the news
- $studiengangInfo = $this->StudiengangModel->getStudiengangInfoForNews();
+ $studiengangInfo = $this->StudiengangModel->getStudiengangInfoForNews($studiengang_kz, $semester);
$studiengangInfo= $this->getDataOrTerminateWithError($studiengangInfo);
$this->terminateWithSuccess($studiengangInfo);
}
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/StudstatusAntraege.php b/application/controllers/api/frontend/v1/StudstatusAntraege.php
new file mode 100644
index 000000000..44dc4134d
--- /dev/null
+++ b/application/controllers/api/frontend/v1/StudstatusAntraege.php
@@ -0,0 +1,145 @@
+.
+ */
+
+if (! defined('BASEPATH')) exit('No direct script access allowed');
+
+class StudstatusAntraege extends FHCAPI_Controller
+{
+
+ /**
+ * Object initialization
+ */
+ public function __construct()
+ {
+ parent::__construct([
+ 'getTodos' => self::PERM_LOGGED
+ ]);
+
+ //load models
+ $this->load->model('education/Studierendenantrag_model', 'StudierendenantragModel');
+ $this->load->model('person/Benutzerfunktion_model', 'BenutzerfunktionModel');
+
+ }
+
+ //------------------------------------------------------------------------------------------------------------------
+ // Public methods
+
+ /**
+ * gets all todos of antraegen of the user with stg leitungsfunction
+ * or list of open antraege for user with assistance function
+ * @access public
+ * @return array || []
+ */
+ public function getTodos()
+ {
+ $uid = getAuthUID();
+
+ //at first get studiengang with leitungsfunktion
+ $result = $this->BenutzerfunktionModel->getSTGLByUID($uid);
+
+ if(hasData($result))
+ {
+ $funktionen = getData($result);
+
+ $studiengaenge = [];
+
+ foreach ($funktionen as $funktion) {
+ $studiengaenge[] = $funktion->studiengang_kz;
+ }
+
+ $dataAntrage = [];
+
+ $result = $this->StudierendenantragModel->getOpenAntraegeForStgl($studiengaenge);
+
+ if (isError($result)) {
+ $this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL);
+ }
+ $dataAntrage[] = getData($result) ?: [];
+ $statusRole = 1;
+
+ $this->terminateWithSuccess([count($dataAntrage) > 0 ? $dataAntrage[0] : $dataAntrage, $statusRole]);
+
+ }
+
+ //TODO delete after check if needed or not
+ //get studiengaenge of assistance (regarding benutzerfunktion)
+ //$result = $this->BenutzerfunktionModel->getSTGAssByUID($uid);
+ /* if(hasData($result)) {
+ $statusRole = 2;
+ $funktionen = getData($result);
+
+ $studiengaenge = [];
+
+ foreach ($funktionen as $funktion) {
+ $studiengaenge[] = $funktion->studiengang_kz;
+ }
+
+ $ci->addMeta('stgAss', $studiengaenge);
+ $dataAntrage = [];
+
+ foreach ($studiengaenge as $studiengang)
+ {
+ $result = $this->StudierendenantragModel->getOpenAntraegeForAss($studiengang);
+
+ if (isError($result)) {
+ $this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL);
+ }
+ if(hasData($result)) {
+ $dataAntrage[] = getData($result);
+ $statusRole = 2;
+ }
+ }
+ }
+ else
+ {
+ $statusRole = 0;
+ $dataAntrage = [];
+ //$this->terminateWithSuccess([[], $statusRole]);
+ }*/
+
+ //get studiengaenge of assistance (regarding rights)
+ $stgAss = $this->permissionlib->getSTG_isEntitledFor('student/studierendenantrag');
+ if(!is_array($stgAss))
+ {
+ $statusRole = 0;
+ $this->terminateWithSuccess([[], $statusRole]);
+ }
+ else {
+ $statusRole = 2;
+ $studiengaenge = [];
+
+ foreach ($stgAss as $stg) {
+ $studiengaenge[] = (int) $stg;
+ }
+
+ $dataAntrage = [];
+ $result = $this->StudierendenantragModel->getOpenAntraegeForAss($studiengaenge);
+
+ if (isError($result)) {
+ $this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL);
+ }
+ if(hasData($result)) {
+ $dataAntrage[] = getData($result);
+ $statusRole = 2;
+ }
+
+ $this->terminateWithSuccess([count($dataAntrage) > 0 ? $dataAntrage[0] : $dataAntrage, $statusRole]);
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/application/controllers/api/frontend/v1/Zeitsperren.php b/application/controllers/api/frontend/v1/Zeitsperren.php
new file mode 100644
index 000000000..aadc13958
--- /dev/null
+++ b/application/controllers/api/frontend/v1/Zeitsperren.php
@@ -0,0 +1,469 @@
+ ['cis/eigene_zeitsperren:r', 'admin:r', 'assistenz:r'],
+ 'getTypenZeitsperren' => self::PERM_LOGGED,
+ 'getTypenErreichbarkeit' => self::PERM_LOGGED,
+ 'getStunden' => self::PERM_LOGGED,
+ 'loadZeitsperre' => ['cis/eigene_zeitsperren:r', 'admin:r', 'assistenz:r'],
+ 'add' => ['cis/eigene_zeitsperren:rw', 'admin:rw', 'assistenz:rw'],
+ 'update' => ['cis/eigene_zeitsperren:rw', 'admin:rw', 'assistenz:rw'],
+ 'delete' => ['cis/eigene_zeitsperren:rw', 'admin:rw', 'assistenz:rw'],
+ ]);
+
+ // 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()
+ {
+ $excludedTypes = array('ZVerfueg');
+ $result = $this->ZeitsperretypModel->getTypenZeitsperren($excludedTypes);
+ 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);
+
+ if($zeitsperretyp_kurzbz == 'ZA')
+ {
+ $this->sendSanchoMailToVorgesetzten(
+ 'Sancho_Content_Zeitausgleich',
+ array(
+ 'beschreibung' =>$zeitsperretyp_kurzbz,
+ 'vonDatum' => (new DateTime($vondatum))->format('d.m.Y'),
+ 'bisDatum' => (new DateTime($bisdatum))->format('d.m.Y'),
+ 'Saldo'=> ''
+ ),
+ $this->p->t('zeitsperren', 'zeitausgleichNeu'),
+ $mitarbeiter_uid
+ );
+ }
+
+ $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 = (array) $this->getDataOrTerminateWithError($result);
+
+ $data = array_filter($data, function($zeitsperre) use ($zeitsperre_id) {
+ return $zeitsperre->zeitsperre_id != $zeitsperre_id;
+ });
+
+ if(count($data) > 0)
+ {
+ $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);
+
+ if($zeitsperretyp_kurzbz == 'ZA')
+ {
+ $this->sendSanchoMailToVorgesetzten(
+ 'Sancho_Content_ZA_Aendern',
+ array(
+ 'beschreibung' =>$zeitsperretyp_kurzbz,
+ 'vonDatum' => (new DateTime($vondatum))->format('d.m.Y'),
+ 'bisDatum' => (new DateTime($bisdatum))->format('d.m.Y'),
+ 'Saldo'=> ''
+ ),
+ $this->p->t('zeitsperren', 'zeitausgleichGeaendert'),
+ $mitarbeiter_uid
+ );
+ }
+
+ $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);
+ $zeitsperre = (is_array($data) && count($data) > 0) ? current($data) : null;
+ $uid = (!is_null($zeitsperre)) ? $zeitsperre->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);
+ }
+
+ if($zeitsperre->zeitsperretyp_kurzbz == 'ZA')
+ {
+ $res = $this->sendSanchoMailToVorgesetzten(
+ 'Sancho_Mail_ZA_loeschen',
+ array(
+ 'beschreibung' =>$zeitsperre->zeitsperretyp_kurzbz,
+ 'vonDatum' => (new DateTime($zeitsperre->vondatum))->format('d.m.Y'),
+ 'bisDatum' => (new DateTime($zeitsperre->bisdatum))->format('d.m.Y')
+ ),
+ $this->p->t('zeitsperren', 'zeitausgleichGeloescht'),
+ $zeitsperre->mitarbeiter_uid
+ );
+ }
+
+ $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;
+ }
+
+ protected function sendSanchoMailToVorgesetzten($sancho_vorlage_kurzbz,
+ $sancho_vorlage_data, $subject, $mauid)
+ {
+ $this->load->helper('hlp_sancho_helper');
+
+ $this->load->model('ressource/Mitarbeiter_model', 'MitarbeiterModel');
+ $this->load->model('person/Person_model', 'PersonModel');
+
+ $fullname = getData($this->PersonModel->getFullName($mauid));
+ $vorgesetzte = (array) getData($this->MitarbeiterModel->getVorgesetzte($mauid));
+
+ $to = array();
+ $vorgesetzte_namen = array();
+ $mailsent = false;
+ $msg = $this->p->t('zeitsperren', 'konnteKeinFreigabemailVersendetWerden');
+
+ if(count($vorgesetzte) > 0)
+ {
+ foreach($vorgesetzte as $vg)
+ {
+ $to[] = $vg->vorgesetzter .'@' . DOMAIN;
+ $vorgesetzter_fullname = getData($this->PersonModel->getFullName($vg->vorgesetzter));
+ $vorgesetzte_namen[] = $vorgesetzter_fullname;
+ }
+
+ $sancho_vorlage_data['vorgesetzter'] = implode(', ', $vorgesetzte_namen);
+ $sancho_vorlage_data['nameMitarbeiter'] = $fullname;
+
+ $mailsent = sendSanchoMail(
+ $sancho_vorlage_kurzbz,
+ $sancho_vorlage_data,
+ implode(',', $to),
+ $subject
+ );
+
+ if($mailsent)
+ {
+ $msg = $this->p->t('zeitsperren', 'VorgesetzteInformiert',
+ array('vorgesetzte' => $sancho_vorlage_data['vorgesetzter']));
+ }
+ else
+ {
+ $msg = $this->p->t('zeitsperren', 'fehlerBeimSendenAufgetreten',
+ array('vorgesetzte' => $sancho_vorlage_data['vorgesetzter']));
+ }
+ }
+
+ $res = (object) array(
+ 'mailsent' => $mailsent,
+ 'msg' => $msg
+ );
+ $this->addMeta('mailstatus', $res);
+ }
+}
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..522478f35
--- /dev/null
+++ b/application/controllers/api/frontend/v1/calendar/RoomPlan.php
@@ -0,0 +1,276 @@
+.
+ */
+
+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,
+ 'getGroupOptions' => 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) : []));
+ }
+
+ public function getGroupOptions()
+ {
+ $studiengangKz = $this->input->get('studiengangKz');
+
+ $this->load->model('organisation/Lehrverband_model', 'LehrverbandModel');
+ $this->LehrverbandModel->addSelect('semester, verband, gruppe');
+ $this->LehrverbandModel->db->where('aktiv', true);
+ $this->LehrverbandModel->db->where('studiengang_kz', $studiengangKz);
+ $lehrverbandResult = $this->LehrverbandModel->load();
+
+ if (isError($lehrverbandResult))
+ $this->terminateWithError($lehrverbandResult);
+
+ $lehrverbanden = hasData($lehrverbandResult) ? getData($lehrverbandResult) : [];
+
+ $grouppedValues = [];
+ foreach ($lehrverbanden as $lehrverband) {
+ if (isset($grouppedValues[$lehrverband->semester]) && $lehrverband->verband !== ' ') {
+ $grouppedValues[$lehrverband->semester][] = $lehrverband;
+ } else if (!isset($grouppedValues[$lehrverband->semester]) && $lehrverband->verband !== ' ') {
+ $grouppedValues[$lehrverband->semester] = [$lehrverband];
+ } else if (!isset($grouppedValues[$lehrverband->semester])) {
+ $grouppedValues[$lehrverband->semester] = [];
+ }
+ }
+ $grouppedValues = array_map(
+ function ($semester) {
+ $verbandGruppen = [];
+ foreach ($semester as $lehrverband) {
+ if (isset($verbandGruppen[$lehrverband->verband]) && $lehrverband->gruppe !== ' ') {
+ $verbandGruppen[$lehrverband->verband][] = $lehrverband->gruppe;
+ } else if (!isset($verbandGruppen[$lehrverband->verband]) && $lehrverband->gruppe !== ' ') {
+ $verbandGruppen[$lehrverband->verband] = [$lehrverband->gruppe];
+ } else if (!isset($verbandGruppen[$lehrverband->verband])) {
+ $verbandGruppen[$lehrverband->verband] = [];
+ }
+ }
+ return $verbandGruppen;
+ },
+ $grouppedValues
+ );
+
+ $this->terminateWithSuccess($grouppedValues);
+ }
+
+}
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..f4efe2942
--- /dev/null
+++ b/application/controllers/api/frontend/v1/education/PaabgabeUebersicht.php
@@ -0,0 +1,218 @@
+ self::PERM_LOGGED,
+ 'getPaAbgaben' => 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'
+ ]);
+ }
+
+ public function viewData()
+ {
+ $viewData = [
+ "uid" => getAuthUID(),
+ // TODO create permission
+ "showEdit" => true,
+ ];
+
+ $this->terminateWithSuccess($viewData);
+ }
+
+ /**
+ * 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/api/frontend/v1/funktionen/Funktionen.php b/application/controllers/api/frontend/v1/funktionen/Funktionen.php
index a3efcdb51..735aa6990 100644
--- a/application/controllers/api/frontend/v1/funktionen/Funktionen.php
+++ b/application/controllers/api/frontend/v1/funktionen/Funktionen.php
@@ -12,9 +12,9 @@ class Funktionen extends FHCAPI_Controller
parent::__construct(array(
'getAllFunctions' => ['admin:r', 'assistenz:r'],
'getAllUserFunctions' => ['admin:r', 'assistenz:r'],
- 'getOrgHeads' => ['admin:r', 'assistenz:r'],
- 'getOrgetsForCompany' => ['admin:r', 'assistenz:r'],
- 'getAllOrgUnits' => ['admin:r', 'assistenz:r'],
+ 'getOrgHeads' => self::PERM_LOGGED,
+ 'getOrgetsForCompany' => self::PERM_LOGGED,
+ 'getAllOrgUnits' => self::PERM_LOGGED,
'loadFunction' => ['admin:r', 'assistenz:r'],
'insertFunction' => ['admin:rw', 'assistenz:rw'],
'updateFunction' => ['admin:rw', 'assistenz:rw'],
@@ -32,7 +32,6 @@ class Funktionen extends FHCAPI_Controller
]);
// Load models
- $this->load->model('extensions/FHC-Core-Personalverwaltung/Api_model', 'ApiModel');
$this->load->model('ressource/Funktion_model', 'FunktionModel');
$this->load->model('person/Benutzerfunktion_model', 'BenutzerfunktionModel');
diff --git a/application/controllers/api/frontend/v1/lv/Betreuungen.php b/application/controllers/api/frontend/v1/lv/Betreuungen.php
new file mode 100644
index 000000000..2ca822649
--- /dev/null
+++ b/application/controllers/api/frontend/v1/lv/Betreuungen.php
@@ -0,0 +1,81 @@
+.
+ */
+
+if (! defined('BASEPATH')) exit('No direct script access allowed');
+
+class Betreuungen extends FHCAPI_Controller
+{
+
+ /**
+ * Object initialization
+ */
+ public function __construct()
+ {
+ parent::__construct([
+ 'getBetreuungen' => self::PERM_LOGGED,
+ ]);
+
+ }
+
+ //------------------------------------------------------------------------------------------------------------------
+ // Public methods
+
+ public function getBetreuungen()
+ {
+ $semester = $this->input->get("semester");
+ if (!$semester) $this->terminateWithError("Missing parameters!");
+
+ $personId = getAuthPersonId();
+
+ $betreuungenQuery = "SELECT
+ tbl_lehrveranstaltung.bezeichnung,
+ tbl_projektarbeit.titel,
+ (SELECT nachname || ' ' || (CASE WHEN wahlname IS NOT NULL THEN wahlname ELSE vorname END)
+ FROM public.tbl_benutzer
+ JOIN public.tbl_person USING(person_id)
+ WHERE uid=student_uid)
+ as name,
+ (SELECT uid
+ FROM public.tbl_benutzer
+ WHERE uid=student_uid)
+ as uid,
+ tbl_lehrveranstaltung.studiengang_kz,
+ tbl_lehrveranstaltung.semester,
+ tbl_studiengang.kurzbzlang as studiengang,
+ tbl_studiengang.email,
+ tbl_betreuerart.beschreibung AS beutreuerart_beschreibung,
+ tbl_projektbetreuer.stunden
+ FROM
+ lehre.tbl_lehrveranstaltung, lehre.tbl_projektarbeit, lehre.tbl_projektbetreuer, public.tbl_studiengang, lehre.tbl_betreuerart
+ WHERE
+ tbl_lehrveranstaltung.lehrveranstaltung_id=tbl_projektarbeit.lehrveranstaltung_id AND
+ tbl_projektarbeit.studiensemester_kurzbz = '$semester' AND
+ tbl_projektarbeit.projektarbeit_id = tbl_projektbetreuer.projektarbeit_id AND
+ tbl_lehrveranstaltung.studiengang_kz = tbl_studiengang.studiengang_kz AND
+ tbl_projektbetreuer.betreuerart_kurzbz = tbl_betreuerart.betreuerart_kurzbz AND
+ tbl_projektbetreuer.person_id = '$personId'";
+ $this->addMeta("query", $betreuungenQuery);
+
+ $this->load->model('education/Lehrveranstaltung_model', 'LehrveranstaltungModel');
+ $result = $this->LehrveranstaltungModel->execReadOnlyQuery($betreuungenQuery);
+ $result = $this->getDataOrTerminateWithError($result);
+
+ $this->terminateWithSuccess($result);
+ }
+
+}
diff --git a/application/controllers/api/frontend/v1/lv/Lehreinheit.php b/application/controllers/api/frontend/v1/lv/Lehreinheit.php
index 6329d30ac..71ef771e7 100644
--- a/application/controllers/api/frontend/v1/lv/Lehreinheit.php
+++ b/application/controllers/api/frontend/v1/lv/Lehreinheit.php
@@ -19,7 +19,8 @@ class Lehreinheit extends FHCAPI_Controller
'getLehrfach' => ['admin:r', 'assistenz:r'],
'getSprache' => ['admin:r', 'assistenz:r'],
'getRaumtyp' => ['admin:r', 'assistenz:r'],
- 'getLehrform' => ['admin:r', 'assistenz:r']
+ 'getLehrform' => ['admin:r', 'assistenz:r'],
+ 'getLehreinheitenBySemester' => self::PERM_LOGGED,
]);
$this->_ci = &get_instance();
@@ -438,6 +439,27 @@ class Lehreinheit extends FHCAPI_Controller
$this->terminateWithSuccess($this->p->t('global', 'gespeichert'));
}
+ public function getLehreinheitenBySemester()
+ {
+ $semester = $this->input->get('semester');
+ $lehreinheiten = $this->LehreinheitModel->getLehrenheitenBySemester(getAuthUID(), $semester);
+ $lehreinheiten = $this->getDataOrTerminateWithError($lehreinheiten);
+
+ foreach ($lehreinheiten as $lehreinheit) {
+ $groups = $this->LehreinheitModel->getGroupsByLehreinheit($lehreinheit->lehreinheit_id);
+ $groups = $this->getDataOrTerminateWithError($groups);
+ $stgKurzbz = $lehreinheit->stg_kurzbz;
+ $lehreinheit->groups = array_map(
+ function ($group) use ($stgKurzbz) {
+ return $group->gruppe_kurzbz ?? $stgKurzbz . "-" . $group->semester . $group->verband . $group->gruppe;
+ },
+ $groups
+ );
+ }
+
+ $this->terminateWithSuccess($lehreinheiten);
+ }
+
private function checkPermission($lehreinheit_id)
{
diff --git a/application/controllers/api/frontend/v1/stv/Grades.php b/application/controllers/api/frontend/v1/stv/Grades.php
index 128316d2b..abc417bca 100644
--- a/application/controllers/api/frontend/v1/stv/Grades.php
+++ b/application/controllers/api/frontend/v1/stv/Grades.php
@@ -41,6 +41,9 @@ class Grades extends FHCAPI_Controller
// Load Libraries
$this->load->library('VariableLib', ['uid' => getAuthUID()]);
+ $this->load->library('PruefungsverlaufLib', null, 'VerlaufLib');
+
+ $this->load->config('noten');
// Load Phrases
$this->loadPhrases([
@@ -355,23 +358,24 @@ class Grades extends FHCAPI_Controller
$data['lehrveranstaltung_id'] = $lehrveranstaltung_id;
$data['student_uid'] = $student_uid;
$data['studiensemester_kurzbz'] = $studiensemester_kurzbz;
-
- $this->ZeugnisnoteModel->insert($data);
- if (defined('FAS_PRUEFUNG_BEI_NOTENEINGABE_ANLEGEN')
- && FAS_PRUEFUNG_BEI_NOTENEINGABE_ANLEGEN) {
- $result = $this->addTestsForGrade(
- $studiensemester_kurzbz,
- $student_uid,
- $lehrveranstaltung_id,
- $teacherGrade->note,
- $teacherGrade->punkte
- );
- $this->getDataOrTerminateWithError($result);
- }
+ $this->ZeugnisnoteModel->insert($data);
+ }
+
+ // Der erste Antritt wird bereits bei der Übernahme als eigene Prüfung angelegt, damit der
+ // Prüfungsverlauf von Anfang an vollständig ist und das Benotungstool ihn nicht erst beim
+ // Anlegen einer Wiederholung nachtragen muss. Läuft bewusst in BEIDEN Zweigen (auch beim
+ // Überschreiben einer bestehenden Zeugnisnote) und ist idempotent.
+ if ($this->config->item('CIS_GESAMTNOTE_ERSTANTRITT_BEI_UEBERNAHME')) {
+ $result = $this->addErstantrittForGrade(
+ $studiensemester_kurzbz,
+ $student_uid,
+ $lehrveranstaltung_id,
+ $teacherGrade
+ );
+ $this->getDataOrTerminateWithError($result);
}
-
$this->terminateWithSuccess(true);
}
@@ -491,20 +495,32 @@ class Grades extends FHCAPI_Controller
}
/**
- * Helper function that adds tests for a student
- * (Entries in lehre.tbl_pruefung)
+ * Legt bei der Notenübernahme den ersten Prüfungsantritt als eigenen Datensatz an
+ * (Eintrag in lehre.tbl_pruefung).
*
- * @param string $studiensemester_kurzbz
- * @param string $student_uid
- * @param integer $lehrveranstaltung_id
- * @param integer $note
- * @param numeric $punkte
+ * Idempotent: existiert für den Studenten in dieser LV bereits ein Termin, passiert nichts -
+ * die Übernahme darf beliebig oft laufen, ohne den Verlauf zu verfälschen.
+ *
+ * Datiert auf das Freigabedatum der LV-Note: das ist der Zeitpunkt, zu dem die
+ * Leistungsfeststellung verbindlich wurde. Das Benotungsdatum ändert sich bei jeder Korrektur,
+ * der Übernahmezeitpunkt ist reine Verwaltungsarbeit - beide taugen nicht als Termindatum.
+ *
+ * @param string $studiensemester_kurzbz
+ * @param string $student_uid
+ * @param integer $lehrveranstaltung_id
+ * @param stdClass $teacherGrade die übernommene LV-Gesamtnote
*
* @return stdClass
*/
- protected function addTestsForGrade($studiensemester_kurzbz, $student_uid, $lehrveranstaltung_id, $note, $punkte)
+ protected function addErstantrittForGrade($studiensemester_kurzbz, $student_uid, $lehrveranstaltung_id, $teacherGrade)
{
$this->load->model('education/Lehrveranstaltung_model', 'LehrveranstaltungModel');
+ $this->load->model('education/LePruefung_model', 'LePruefungModel');
+
+ // bereits ein Termin vorhanden -> der Verlauf beginnt schon, nichts nachzutragen
+ $vorhanden = $this->VerlaufLib->getPruefungen($student_uid, $lehrveranstaltung_id, $studiensemester_kurzbz);
+ if (count($vorhanden) > 0)
+ return success(null);
// Get Lehreinheit
$result = $this->LehrveranstaltungModel->getLeByStudent($student_uid, $studiensemester_kurzbz, $lehrveranstaltung_id);
@@ -515,19 +531,24 @@ class Grades extends FHCAPI_Controller
return error($this->p->t("stv", "grades_error_lehreinheit_id"));
$le = current(getData($result));
+ $datum = $teacherGrade->freigabedatum ?: ($teacherGrade->benotungsdatum ?: date('Y-m-d'));
+ $now = date('Y-m-d H:i:s');
+
// Prepare
- $this->load->model('education/LePruefung_model', 'LePruefungModel');
$data = [
"student_uid" => $student_uid,
"lehreinheit_id" => $le->lehreinheit_id,
- "datum" => date('Y-m-d'),
- "pruefungstyp_kurzbz" => "Termin1",
- "note" => $note
+ "mitarbeiter_uid" => $teacherGrade->mitarbeiter_uid,
+ "datum" => $datum,
+ "pruefungstyp_kurzbz" => $this->VerlaufLib->legacyTypFuerAntritt(1),
+ "note" => $teacherGrade->note,
+ "insertamum" => $now,
+ "insertvon" => getAuthUID()
];
if (defined('CIS_GESAMTNOTE_PUNKTE') && CIS_GESAMTNOTE_PUNKTE)
- $data["punkte"] = $punkte;
-
+ $data["punkte"] = $teacherGrade->punkte;
+
// Get Anwesenheit
$this->load->model('education/Anwesenheit_model', 'AnwesenheitModel');
$result = $this->AnwesenheitModel->loadAnwesenheitStudiensemester($studiensemester_kurzbz, $student_uid, $lehrveranstaltung_id);
@@ -535,7 +556,8 @@ class Grades extends FHCAPI_Controller
return $result;
$anwesenheit = getData($result);
- if ($anwesenheit && (float)current($anwesenheit)->prozent < FAS_ANWESENHEIT_ROT) {
+ if (defined('FAS_PRUEFUNG_BEI_NOTENEINGABE_ANLEGEN') && FAS_PRUEFUNG_BEI_NOTENEINGABE_ANLEGEN
+ && $anwesenheit && (float)current($anwesenheit)->prozent < FAS_ANWESENHEIT_ROT) {
// Get Anwesenheitsbefreiung
$this->load->model('person/Benutzerfunktion_model', 'BenutzerfunktionModel');
$result = $this->BenutzerfunktionModel->getBenutzerFunktionByUidInStdsem($student_uid, $studiensemester_kurzbz, 'awbefreit');
@@ -545,7 +567,10 @@ class Grades extends FHCAPI_Controller
$anwesenheitsbefreit = hasData($result);
- // Wenn nicht Anwesenheitsbefreit und Anwesenheit unter einem bestimmten Prozentsatz fällt dann wird ein Pruefungsantritt abgezogen
+ // Bei zu geringer Anwesenheit ohne Befreiung wird ein zusätzlicher Termin
+ // 'Nicht beurteilt' vor dem eigentlichen Antritt festgehalten. ACHTUNG: diese Note
+ // zählt nicht mehr als Antritt (NOTEN_OHNE_ANTRITT_BEZEICHNUNGEN) - der frühere
+ // "Antritt abziehen"-Effekt entfällt damit, der Eintrag ist reine Dokumentation.
if (!$anwesenheitsbefreit) {
$data2 = $data;
$data2["note"] = 7;
@@ -556,8 +581,6 @@ class Grades extends FHCAPI_Controller
if (isError($result))
return $result;
-
- $data["pruefungstyp_kurzbz"] = "Termin2";
}
}
diff --git a/application/controllers/api/frontend/v1/stv/Kontakt.php b/application/controllers/api/frontend/v1/stv/Kontakt.php
index d246a04d9..cd35768f3 100644
--- a/application/controllers/api/frontend/v1/stv/Kontakt.php
+++ b/application/controllers/api/frontend/v1/stv/Kontakt.php
@@ -574,6 +574,19 @@ class Kontakt extends FHCAPI_Controller
$person_id = $this->input->post('person_id');
$standort_id = $this->input->post('standort_id');
+ //check person_id: allow editing only of persons who are no mitarbeiter
+ if($person_id)
+ {
+ $this->load->model('person/Benutzer_model', 'BenutzerModel');
+ $this->BenutzerModel->addJoin('public.tbl_mitarbeiter ma', 'ON (ma.mitarbeiter_uid = public.tbl_benutzer.uid)');
+
+ $result = $this->BenutzerModel->loadWhere([
+ 'person_id' => $person_id
+ ]);
+ if(hasData($result))
+ return $this->terminateWithError($this->p->t('ui', 'editingMitarbeiterPVonly'), self::ERROR_TYPE_GENERAL);
+ }
+
$result = $this->KontaktModel->update(
[
'kontakt_id' => $kontakt_id
diff --git a/application/controllers/components/Cis/Mylv.php b/application/controllers/components/Cis/Mylv.php
index 30544b647..c632baa39 100644
--- a/application/controllers/components/Cis/Mylv.php
+++ b/application/controllers/components/Cis/Mylv.php
@@ -13,6 +13,7 @@ class Mylv extends Auth_Controller
*/
public function __construct()
{
+
parent::__construct([
'Student' => self::PERM_LOGGED,
'Studiensemester' => self::PERM_LOGGED,
@@ -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/helpers/hlp_header_helper.php b/application/helpers/hlp_header_helper.php
index eb2cd9f85..41f361bf1 100644
--- a/application/helpers/hlp_header_helper.php
+++ b/application/helpers/hlp_header_helper.php
@@ -82,6 +82,21 @@ function generateCSSsInclude($CSSs)
}
}
+/**
+ * Generates tags for the style sheets you want to include, the parameter could by a string or an array of strings
+ */
+function generatePrintCSS($path)
+{
+ $cssLink = ' ';
+
+ $ci =& get_instance();
+ $cachetoken = '?'.$ci->config->item('fhcomplete_build_version');
+
+ if (isset($path)) {
+ echo sprintf($cssLink, base_url($path).$cachetoken) . PHP_EOL;
+ }
+}
+
/**
* Generates global JS-Object to pass parms to other javascripts
*/
diff --git a/application/libraries/CmsLib.php b/application/libraries/CmsLib.php
index 355cf7c56..89f06c412 100644
--- a/application/libraries/CmsLib.php
+++ b/application/libraries/CmsLib.php
@@ -4,6 +4,7 @@ if (!defined('BASEPATH'))
exit('No direct script access allowed');
use \DateTime as DateTime;
+use \DateInterval as DateInterval;
use \DOMDocument as DOMDocument;
use \XSLTProcessor as XSLTProcessor;
@@ -141,63 +142,6 @@ class CmsLib
]);
}
- /**
- * @param stdClass $stg_obj
- *
- * @return stdClass
- */
- protected function getNewsExtras($stg_obj, $semester)
- {
- $this->ci->load->model('person/Benutzerfunktion_model', 'BenutzerfunktionModel');
-
- $stg_ltg = $this->ci->StudiengangModel->getLeitungDetailed($stg_obj->studiengang_kz);
- if (isError($stg_ltg))
- return $stg_ltg;
- $stg_ltg = getData($stg_ltg) ?: [];
-
- $gf_ltg = $this->ci->BenutzerfunktionModel->getBenutzerFunktionenDetailed('gLtg', $stg_obj->oe_kurzbz);
- if (isError($gf_ltg))
- return $gf_ltg;
- $gf_ltg = getData($gf_ltg) ?: [];
-
- $stv_ltg = $this->ci->BenutzerfunktionModel->getBenutzerFunktionenDetailed('stvLtg', $stg_obj->oe_kurzbz);
- if (isError($stv_ltg))
- return $stv_ltg;
- $stv_ltg = getData($stv_ltg) ?: [];
-
- $ass = $this->ci->BenutzerfunktionModel->getBenutzerFunktionenDetailed('ass', $stg_obj->oe_kurzbz);
- if (isError($ass))
- return $ass;
- $ass = getData($ass) ?: [];
-
- $hochschulvertr = $this->ci->BenutzerfunktionModel->getBenutzerFunktionenDetailed('hsv');
- if (isError($hochschulvertr))
- return $hochschulvertr;
- $hochschulvertr = getData($hochschulvertr) ?: [];
-
- $stdv = $this->ci->BenutzerfunktionModel->getBenutzerFunktionenDetailed('stdv', $stg_obj->oe_kurzbz);
- if (isError($stdv))
- return $stdv;
- $stdv = getData($stdv) ?: [];
-
- $jahrgangsvertr = $this->ci->BenutzerfunktionModel->getBenutzerFunktionenDetailed('jgv', $stg_obj->oe_kurzbz, $semester);
- if (isError($jahrgangsvertr))
- return $jahrgangsvertr;
- $jahrgangsvertr = getData($jahrgangsvertr) ?: [];
-
- return success($this->ci->load->view('Cis/Cms/News/Xml/NewsExtras', [
- 'studiengang' => $stg_obj,
- 'semester' => $semester,
- 'stg_ltg' => $stg_ltg,
- 'gf_ltg' => $gf_ltg,
- 'stv_ltg' => $stv_ltg,
- 'ass' => $ass,
- 'hochschulvertr' => $hochschulvertr,
- 'stdv' => $stdv,
- 'jahrgangsvertr' => $jahrgangsvertr
- ], true));
- }
-
/**
* @param string $studiengang_kz
* @param string $semester
@@ -233,64 +177,102 @@ class CmsLib
*
* @return void
*/
- public function getNews($infoscreen = false, $studiengang_kz = null, $semester = null, $mischen = true, $titel = '', $edit = false, $sichtbar = true, $page = 1, $page_size = 10, $sprache)
+ public function getNews($infoscreen = false, $studiengang_kz = null, $semester = null, $mischen = true, $titel = '', $edit = false, $sichtbar = true, $page = 1, $page_size = 10, $sprache, $filterForDegreePrograms = false, $allowedDegreePrograms = [], $active = true)
+
{
$this->ci->load->model('organisation/Studiengang_model', 'StudiengangModel');
list($studiengang_kz, $semester) = $this->getStgAndSem($studiengang_kz, $semester);
- $all = $edit;
-
- $xml = '';
$this->ci->load->model('content/News_model', 'NewsModel');
- $news = $this->ci->NewsModel->getNewsWithContent($sprache, $studiengang_kz, $semester, null, $sichtbar, 0, $page, $page_size, $all, $mischen);
-
- if (isError($news))
- return $news;
-
- $news = getData($news);
+
+ $news = $this->ci->NewsModel->getNewsWithContent($sprache, $studiengang_kz, $semester, null, $sichtbar, $page, $page_size, $active, $mischen, $filterForDegreePrograms, $allowedDegreePrograms);
+ $news = getData($news) ?? [];
+ $newsWrappers = [];
+
foreach ($news as $newsobj) {
if ($studiengang_kz && $edit && !$newsobj->studiengang_kz)
continue;
- $date = new DateTime($newsobj->datum);
- $datum = 'format('d.m.Y') . ']]> ';
- $datum .= 'format('Y-m-d H:i') . ']]> ';
- $id = $edit ? 'news_id . ']]> ' : '';
- $xml .= "" . $newsobj->content . $datum . $id . " ";
+
+ $newsWrappers[] = $this->createNewsWrapper($newsobj, $edit);
}
- /* if ($studiengang_kz != 0) {
- $stg_obj = $this->ci->StudiengangModel->load($studiengang_kz);
- if (isError($stg_obj))
- return $stg_obj;
- $stg_obj = current(getData($stg_obj) ?: []);
+ $content = $this->createNewsContent($newsWrappers, $infoscreen, $titel);
+ if (isError($content))
+ return $content;
- if ($stg_obj) {
- if (!$edit && !$infoscreen) {
- $extras = $this->getNewsExtras($stg_obj, $semester);
- if (isError($extras))
- return $extras;
- $xml .= getData($extras);
- }
- $xml .= 'bezeichnung . ']]> ';
- }
- } */
+ return success([
+ "content" => getData($content),
+ "row_count" => $news[0]->row_count ?? 0
+ ]);
+ }
+
+ /**
+ * Creates a newswrapper XML element from a news item.
+ *
+ * @param stdClass $newsobj
+ * @param boolean $edit
+ *
+ * @return DOMElement
+ */
+ public function createNewsWrapper($newsobj, $edit)
+ {
+ $id = $edit ? 'news_id . ']]> ' : '';
+ $isPublished = $edit ? 'sichtbar ? 'true' : 'false') . ']]> ' : '';
+
+ $date = new DateTime($newsobj->datum);
+ $datum = 'format('d.m.Y') . ']]> ';
+ $datum .= 'format('Y-m-d H:i') . ']]> ';
+
+ $hasVisibleToDate = isset($newsobj->datum_bis) && $newsobj->datum_bis !== '';
+ $datumTo = $hasVisibleToDate
+ ? new DateTime($newsobj->datum_bis)
+ : (clone $date)->add(new DateInterval('P' . MAXNEWSALTER . 'D'));
+ if ($hasVisibleToDate)
+ $datumTo->setTime(23, 59, 59);
+
+ $now = new DateTime();
+ $isActive = '= $now ? 'true' : 'false') . ']]> ';
+
+ $newsWrapper = new DOMDocument();
+ $newsWrapper->loadXML('' . $newsobj->content . $datum . $id . $isPublished . $isActive . ' ');
+
+ return $newsWrapper->documentElement;
+ }
+
+ /**
+ * Creates rendered news content from one or more newswrapper XML elements.
+ *
+ * @param DOMElement|array $newsWrappers
+ * @param boolean $infoscreen
+ * @param string $titel
+ *
+ * @return stdClass
+ */
+ public function createNewsContent($newsWrappers, $infoscreen, $titel)
+ {
+ if (!is_array($newsWrappers))
+ $newsWrappers = [$newsWrappers];
+
+ $XML = new DOMDocument('1.0', 'UTF-8');
+ $contentElement = $XML->createElement('content');
+ $XML->appendChild($contentElement);
+
+ foreach ($newsWrappers as $newsWrapper)
+ $contentElement->appendChild($XML->importNode($newsWrapper, true));
if ($titel != '') {
- $xml .= '' . $titel . ' ';
+ $titleElement = $XML->createElement('news_titel');
+ $titleElement->appendChild($XML->createTextNode($titel));
+ $contentElement->appendChild($titleElement);
}
- $xml .= ' ';
-
//XSLT Vorlage laden
$template = $this->ci->TemplateModel->load($infoscreen ? 'news_infoscreen' : 'news');
if (isError($template))
return $template;
$template = current(getData($template));
- $XML = new DOMDocument();
- $XML->loadXML($xml);
-
$xsltemplate = new DOMDocument();
$xsltemplate->loadXML($template->xslt_xhtml_c4);
diff --git a/application/libraries/PruefungsverlaufLib.php b/application/libraries/PruefungsverlaufLib.php
new file mode 100644
index 000000000..e0087e527
--- /dev/null
+++ b/application/libraries/PruefungsverlaufLib.php
@@ -0,0 +1,410 @@
+.
+ */
+
+if (! defined('BASEPATH')) exit('No direct script access allowed');
+
+/**
+ * The exam history of one student in one course: the order of the attempts, the attempt count
+ * and the limits. This class is the only source of the examination rules. The controller and the
+ * client only read the result.
+ */
+class PruefungsverlaufLib
+{
+ /** The first attempt: the original assessment, which is the transcript grade itself. */
+ const ROLLE_ERSTANTRITT = 'erstantritt';
+
+ /** Each attempt after the first one. */
+ const ROLLE_PRUEFUNG = 'pruefung';
+
+ /** The last attempt. The examination rules require a commission for it. */
+ const ROLLE_KOMMISSIONELL = 'kommissionell';
+
+ /** Maps an attempt number to the old exam type. Kept for backward compatibility. */
+ private static $_legacyTypen = [1 => 'Termin1', 2 => 'Termin2', 3 => 'Termin3'];
+
+ private $_ci;
+ private $_noteCache = [];
+ private $_specialNotes = null;
+ private $_typen = null;
+
+ public function __construct()
+ {
+ $this->_ci =& get_instance();
+ $this->_ci->load->model('education/LePruefung_model', 'LePruefungModel');
+ $this->_ci->load->model('education/Note_model', 'NoteModel');
+ $this->_ci->load->model('education/Pruefungstyp_model', 'PruefungstypModel');
+ $this->_ci->load->config('noten');
+ }
+
+ /**
+ * @param mixed $lvNote counts as the implicit first attempt while no exam counts
+ * @param mixed $zeugnisNote a credited grade makes all exams impossible
+ * @return stdClass
+ */
+ public function getVerlauf($student_uid, $lv_id, $sem_kurzbz, $lvNote = null, $zeugnisNote = null)
+ {
+ return $this->buildVerlauf($this->getPruefungen($student_uid, $lv_id, $sem_kurzbz), $lvNote, $zeugnisNote);
+ }
+
+ /** All exams of one student in one course, in chronological order. @return array */
+ public function getPruefungen($student_uid, $lv_id, $sem_kurzbz)
+ {
+ $result = $this->_ci->LePruefungModel->getPruefungenByUidTypLvStudiensemester($student_uid, null, $lv_id, $sem_kurzbz);
+ if (isError($result) || !hasData($result)) return [];
+
+ return $this->sortPruefungen(getData($result));
+ }
+
+ /** Sort keys: datum, then tbl_pruefungstyp.sort (old rows without a date), then pruefung_id. */
+ public function sortPruefungen($pruefungen)
+ {
+ $pruefungen = array_values($pruefungen);
+ $typen = $this->getTypen();
+
+ usort($pruefungen, function ($a, $b) use ($typen) {
+ $da = substr((string) $a->datum, 0, 10);
+ $db = substr((string) $b->datum, 0, 10);
+
+ if ($da !== $db) {
+ if ($da === '') return -1;
+ if ($db === '') return 1;
+ return ($da < $db) ? -1 : 1;
+ }
+
+ $sa = isset($typen[$a->pruefungstyp_kurzbz]) ? (int) $typen[$a->pruefungstyp_kurzbz]->sort : 0;
+ $sb = isset($typen[$b->pruefungstyp_kurzbz]) ? (int) $typen[$b->pruefungstyp_kurzbz]->sort : 0;
+ if ($sa !== $sb) return ($sa < $sb) ? -1 : 1;
+
+ return ((int) $a->pruefung_id < (int) $b->pruefung_id) ? -1 : 1;
+ });
+
+ return $pruefungen;
+ }
+
+ /**
+ * Builds the history from a list of exams. Each exam gets: position, zaehlt, antritt_nr (null
+ * if the exam does not count) and terminal.
+ *
+ * @return stdClass
+ */
+ public function buildVerlauf($pruefungen, $lvNote = null, $zeugnisNote = null)
+ {
+ $pruefungen = $this->sortPruefungen($pruefungen);
+
+ $antrittCount = 0;
+ $terminal = false;
+ $eintraege = [];
+
+ foreach ($pruefungen as $i => $p) {
+ $zaehlt = $this->zaehltAlsAntritt($p->note, $p->pruefungstyp_kurzbz);
+ if ($zaehlt) $antrittCount++;
+
+ // A kommissionelle exam closes the chain, also if its grade uses no attempt. A student
+ // with a kommissionelle Prüfung gets no further exam from this tool.
+ $kommissionell = $this->istKommissionell($p->pruefungstyp_kurzbz);
+ if ($kommissionell) $terminal = true;
+
+ $eintrag = clone $p;
+ $eintrag->position = $i + 1;
+ $eintrag->zaehlt = $zaehlt;
+ $eintrag->antritt_nr = $zaehlt ? $antrittCount : null;
+ $eintrag->kommissionell = $kommissionell;
+ $eintraege[] = $eintrag;
+ }
+
+ // if no exam counts, the course grade itself is the first attempt
+ $implizit = ($antrittCount === 0 && $lvNote !== null && $this->zaehltAlsAntritt($lvNote));
+ if ($implizit) $antrittCount = 1;
+
+ $maxAntritte = $this->getMaxAntritte();
+
+ // credited: the row stays visible, but you cannot enter anything
+ $angerechnet = $this->istAnrechnungsnote($zeugnisNote);
+
+ $verlauf = new stdClass();
+ $verlauf->pruefungen = $eintraege;
+ $verlauf->antrittCount = $antrittCount;
+ $verlauf->maxAntritte = $maxAntritte;
+ $verlauf->terminal = $terminal;
+ $verlauf->angerechnet = $angerechnet;
+ $verlauf->impliziterErstantritt = $implizit;
+ $verlauf->canAdd = !$terminal && !$angerechnet && $antrittCount < $maxAntritte;
+
+ // after the first exam each new entry is a repeat attempt
+ $verlauf->erstantrittMoeglich = !$angerechnet && (count($eintraege) === 0);
+
+ // The last attempt of the chain is kommissionell (§17 Abs 1). The role therefore follows
+ // from the position, and the user interface offers no type to select.
+ if (count($eintraege) === 0 && !$implizit) {
+ $verlauf->naechsteRolle = self::ROLLE_ERSTANTRITT;
+ } elseif (($antrittCount + 1) >= $maxAntritte) {
+ $verlauf->naechsteRolle = self::ROLLE_KOMMISSIONELL;
+ } else {
+ $verlauf->naechsteRolle = self::ROLLE_PRUEFUNG;
+ }
+
+ return $verlauf;
+ }
+
+ /** 'angerechnet' or 'intern angerechnet'. The TRANSCRIPT grade decides. @return bool */
+ public function istAnrechnungsnote($note)
+ {
+ if ($note === null || $note === '') return false;
+ return in_array($note, $this->getSpecialNotes()['anrechnung']);
+ }
+
+ /**
+ * All attempts that count, the kommissionelle attempt included. The explicit configuration
+ * wins. Without it the number comes from the old flags: the original assessment, one attempt
+ * for each active repeat, and the kommissionelle attempt.
+ *
+ * @return int
+ */
+ public function getMaxAntritte()
+ {
+ $max = $this->_ci->config->item('CIS_GESAMTNOTE_MAX_ANTRITTE');
+ if (is_numeric($max) && (int) $max > 0) return (int) $max;
+
+ $max = 1;
+ if (defined('CIS_GESAMTNOTE_PRUEFUNG_TERMIN2') && CIS_GESAMTNOTE_PRUEFUNG_TERMIN2) $max++;
+ if (defined('CIS_GESAMTNOTE_PRUEFUNG_TERMIN3') && CIS_GESAMTNOTE_PRUEFUNG_TERMIN3) $max++;
+ if (defined('CIS_GESAMTNOTE_PRUEFUNG_KOMMPRUEF') && CIS_GESAMTNOTE_PRUEFUNG_KOMMPRUEF) $max++;
+ return $max;
+ }
+
+ /**
+ * The NOTE decides, therefore the same type can occur more than one time. The type decides in
+ * one case only: a type in PRUEFUNG_TYPEN_OHNE_ANTRITT never uses an attempt.
+ *
+ * @return bool
+ */
+ public function zaehltAlsAntritt($note, $typ = null)
+ {
+ if ($typ !== null && in_array($typ, $this->getTypenOhneAntritt())) return false;
+ if ($note === null || $note === '') return false;
+ if (in_array($note, $this->getSpecialNotes()['ohneAntritt'])) return false;
+
+ // a credited grade is not an assessment, therefore it uses no attempt
+ if ($this->istAnrechnungsnote($note)) return false;
+
+ $noteRow = $this->getNote($note);
+ return $noteRow ? (bool) $noteRow->lehre : false;
+ }
+
+ /** Tells you if an exam takes place before a commission. @return bool */
+ public function istKommissionell($typ)
+ {
+ $typen = $this->_ci->config->item('PRUEFUNG_KOMMISSIONELL_TYPEN');
+ if (!is_array($typen)) $typen = ['kommPruef', 'zusKommPruef'];
+ return in_array($typ, $typen);
+ }
+
+ /** The exam types that never use an attempt. @return array */
+ public function getTypenOhneAntritt()
+ {
+ $typen = $this->_ci->config->item('PRUEFUNG_TYPEN_OHNE_ANTRITT');
+ return is_array($typen) ? $typen : ['zusKommPruef'];
+ }
+
+ /**
+ * The type for an attempt number. The student administration still reads this column, so the
+ * value must be correct. The last attempt gets the kommissionelle type, the attempts before it
+ * get the old Termin types.
+ *
+ * @return string
+ */
+ public function legacyTypFuerAntritt($antrittNr)
+ {
+ $antrittNr = max(1, (int) $antrittNr);
+ $typen = $this->getTypen();
+
+ $maxAntritte = $this->getMaxAntritte();
+ $kommissionell = $this->_ci->config->item('PRUEFUNG_TYP_KOMMISSIONELL');
+ if (!is_string($kommissionell) || $kommissionell === '') $kommissionell = 'kommPruef';
+
+ if ($maxAntritte > 1 && $antrittNr >= $maxAntritte && isset($typen[$kommissionell])) {
+ return $kommissionell;
+ }
+
+ for ($n = $antrittNr; $n >= 1; $n--) {
+ if (isset(self::$_legacyTypen[$n]) && isset($typen[self::$_legacyTypen[$n]])) {
+ return self::$_legacyTypen[$n];
+ }
+ }
+
+ return self::$_legacyTypen[1];
+ }
+
+ /**
+ * The old type for a new repeat attempt. It is never lower than Termin2, also if the first
+ * exam does not use an attempt.
+ *
+ * @return string
+ */
+ public function legacyTypFuerWiederholung($verlauf)
+ {
+ return $this->legacyTypFuerAntritt(max(2, $verlauf->antrittCount + 1));
+ }
+
+ /** @return array{entschuldigt: mixed, ohneAntritt: array, anrechnung: array, limitMap: array} */
+ public function getSpecialNotes()
+ {
+ if ($this->_specialNotes !== null) return $this->_specialNotes;
+
+ $cfgEntschuldigt = $this->_ci->config->item('NOTE_ENTSCHULDIGT');
+ $cfgOhneAntritt = $this->_ci->config->item('NOTEN_OHNE_ANTRITT');
+ $cfgLimitMap = $this->_ci->config->item('NOTEN_OCCURANCE_LIMIT_MAP');
+ $bezeichnungen = $this->_ci->config->item('NOTEN_OHNE_ANTRITT_BEZEICHNUNGEN');
+ if (!is_array($cfgOhneAntritt)) $cfgOhneAntritt = [];
+ if (!is_array($cfgLimitMap)) $cfgLimitMap = [];
+ if (!is_array($bezeichnungen)) $bezeichnungen = [];
+
+ $resEnt = $this->_ci->NoteModel->getEntschuldigtNote();
+ $entschuldigt = (!isError($resEnt) && hasData($resEnt)) ? getData($resEnt)[0]->note : $cfgEntschuldigt;
+
+ // The name wins against the configured key. Do NOT merge the two sets: the same key has a
+ // different meaning in each installation (17 is 'entschuldigt' here, but 'nicht zugelassen'
+ // in the standard data).
+ $ohneAntritt = [];
+ foreach ($bezeichnungen as $bezeichnung) {
+ $note = $this->getNoteByBezeichnung($bezeichnung);
+ if ($note !== null && !in_array($note, $ohneAntritt)) $ohneAntritt[] = $note;
+ }
+ if (count($ohneAntritt) === 0) $ohneAntritt = $cfgOhneAntritt;
+ if ($entschuldigt !== null && !in_array($entschuldigt, $ohneAntritt)) $ohneAntritt[] = $entschuldigt;
+
+ // move the configured limit for 'entschuldigt' to the key that was found
+ $limitMap = [];
+ foreach ($cfgLimitMap as $k => $v) {
+ $limitMap[($k == $cfgEntschuldigt) ? $entschuldigt : $k] = $v;
+ }
+
+ $this->_specialNotes = [
+ 'entschuldigt' => $entschuldigt,
+ 'ohneAntritt' => array_values($ohneAntritt),
+ 'anrechnung' => $this->resolveNoten('NOTEN_ANRECHNUNG_BEZEICHNUNGEN', 'NOTEN_ANRECHNUNG'),
+ 'limitMap' => $limitMap
+ ];
+ return $this->_specialNotes;
+ }
+
+ /**
+ * The occurrence limit from NOTEN_OCCURANCE_LIMIT_MAP. $excludePruefungId skips the record
+ * that the user edits at this moment.
+ *
+ * @return bool
+ */
+ public function ueberschreitetNotenLimit($pruefungen, $note, $excludePruefungId = null)
+ {
+ $limitMap = $this->getSpecialNotes()['limitMap'];
+
+ $limit = null;
+ foreach ($limitMap as $limitNote => $limitVal) {
+ if ($limitNote == $note) { $limit = $limitVal; break; }
+ }
+ if ($limit === null) return false;
+
+ $count = 0;
+ foreach ($pruefungen as $p) {
+ if ($excludePruefungId !== null && $p->pruefung_id == $excludePruefungId) continue;
+ if ($p->note == $note) $count++;
+ }
+
+ return ($count + 1) > $limit;
+ }
+
+ /** An exam with a later date locks the grade. You can still correct the date. @return bool */
+ public function hatSpaeterenTermin($verlauf, $pruefung_id)
+ {
+ $current = null;
+ foreach ($verlauf->pruefungen as $p) {
+ if ($p->pruefung_id == $pruefung_id) { $current = $p; break; }
+ }
+ if ($current === null) return false;
+
+ foreach ($verlauf->pruefungen as $p) {
+ if ($p->pruefung_id == $current->pruefung_id) continue;
+ if ($p->position > $current->position) return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * tbl_pruefungstyp as a map. It sorts old rows, and it limits the legacy type to the types
+ * that exist. A type that does not exist breaks the foreign key. Termin3 is absent from the
+ * standard data.
+ *
+ * @return array
+ */
+ private function getTypen()
+ {
+ if ($this->_typen !== null) return $this->_typen;
+
+ $this->_typen = [];
+ $result = $this->_ci->PruefungstypModel->load();
+ if (!isError($result) && hasData($result)) {
+ foreach (getData($result) as $typ) {
+ $this->_typen[$typ->pruefungstyp_kurzbz] = $typ;
+ }
+ }
+
+ return $this->_typen;
+ }
+
+ /** Finds the grades by their name. The configured keys are the fallback only. @return array */
+ private function resolveNoten($bezeichnungKey, $pkKey)
+ {
+ $bezeichnungen = $this->_ci->config->item($bezeichnungKey);
+ if (!is_array($bezeichnungen)) $bezeichnungen = [];
+
+ $noten = [];
+ foreach ($bezeichnungen as $bezeichnung) {
+ $note = $this->getNoteByBezeichnung($bezeichnung);
+ if ($note !== null && !in_array($note, $noten)) $noten[] = $note;
+ }
+
+ if (count($noten) === 0) {
+ $fallback = $this->_ci->config->item($pkKey);
+ if (is_array($fallback)) $noten = $fallback;
+ }
+
+ return array_values($noten);
+ }
+
+ /**
+ * @return stdClass|null
+ */
+ private function getNote($note)
+ {
+ if (array_key_exists($note, $this->_noteCache)) return $this->_noteCache[$note];
+
+ $result = $this->_ci->NoteModel->load($note);
+ $this->_noteCache[$note] = (!isError($result) && hasData($result)) ? getData($result)[0] : null;
+ return $this->_noteCache[$note];
+ }
+
+ /**
+ * @return mixed|null
+ */
+ private function getNoteByBezeichnung($bezeichnung)
+ {
+ $result = $this->_ci->NoteModel->loadWhere(['bezeichnung' => $bezeichnung]);
+ return (!isError($result) && hasData($result)) ? getData($result)[0]->note : null;
+ }
+}
diff --git a/application/libraries/StundenplanLib.php b/application/libraries/StundenplanLib.php
index 2c13a29d9..4cfefc634 100644
--- a/application/libraries/StundenplanLib.php
+++ b/application/libraries/StundenplanLib.php
@@ -116,6 +116,7 @@ class StundenplanLib
return $stundenplan_data;
$stundenplan_data = getData($stundenplan_data) ?? [];
+ $this->_ci->addMeta("stundenplanData", $stundenplan_data);
$function_error = $this->expandObjectInformation($stundenplan_data);
if ($function_error)
return $function_error;
@@ -391,6 +392,39 @@ 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 &&
+ (
+ (isset($item->insertvon) && $item->insertvon == getAuthUID()) ||
+ (isset($item->uids) && 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;
+
}
}
@@ -486,6 +520,212 @@ class StundenplanLib
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);
+
+ $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/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/News_model.php b/application/models/content/News_model.php
index ad8ecab99..ee00bf327 100644
--- a/application/models/content/News_model.php
+++ b/application/models/content/News_model.php
@@ -20,14 +20,16 @@ class News_model extends DB_Model
public function getAll($limit = null)
{
$this->addJoin("campus.tbl_content","content_id");
+
return $this->execReadOnlyQuery('
SELECT *, TO_CHAR(campus.tbl_news.datum, ?) as datumformatted
FROM campus.tbl_news
JOIN campus.tbl_content content ON content.content_id = campus.tbl_news.content_id
- WHERE
+ WHERE
--text IS NOT NULL AND
- datum <= NOW() AND (datum_bis IS NULL OR datum_bis >= now()::date)
- ORDER BY datum DESC
+ datum <= NOW() AND (datum_bis >= NOW()::date OR datum_bis IS NULL) AND
+ (NOW()-datum) < interval \'' . MAXNEWSALTER . ' days\'
+ ORDER BY datum DESC, campus.tbl_news.updateamum DESC
LIMIT ' . $this->escape($limit)
, ['DD/MM/YYYY']);
}
@@ -48,31 +50,28 @@ class News_model extends DB_Model
* @param integer | null $semester
* @param string $fachbereich_kurzbz
* @param boolean $sichtbar
- * @param integer $maxalter
* @param integer $page
* @param integer $page_size
- * @param boolean $all
+ * @param boolean $active
* @param boolean $mischen
*
* TODO(chris): this is not a good function -> the params are all over the place
*
*/
- protected function prepareNewsWithContent($sprache, $studiengang_kz, $semester, $fachbereich_kurzbz = null, $sichtbar = true, $maxalter = 0, $page = 1, $page_size = 10, $all = false, $mischen = true)
+ protected function prepareNewsWithContent($sprache, $studiengang_kz, $semester, $fachbereich_kurzbz = null, $sichtbar = true, $page = 1, $page_size = 10, $active = true, $mischen = true, $filterForDegreePrograms = false, $allowedDegreePrograms = [])
{
+ $this->addSelect(["*", "COUNT(*) OVER() AS row_count"]);
$this->addOrder('datum', 'DESC');
+ $this->addOrder('tbl_news.updateamum', 'DESC');
$studiengang_kz = trim($studiengang_kz);
$fachbereich_kurzbz = trim($fachbereich_kurzbz);
$where = [];
- if (trim($maxalter) != '0') {
- $where[] = "(now()-datum) < interval " . $this->db->escape($maxalter) . " days";
-
- }
- if (!$all) {
- $where[] = "datum <= now()";
- $where[] = "(datum_bis >= now()::date OR datum_bis IS NULL)";
+
+ if ($active) {
+ $where[] = "(datum <= NOW()::date AND (datum_bis >= NOW()::date OR (datum_bis IS NULL AND (NOW()-datum) < interval '" . MAXNEWSALTER . " days')))";
}
if ($fachbereich_kurzbz != '*') {
if ($fachbereich_kurzbz == '') {
@@ -82,27 +81,54 @@ class News_model extends DB_Model
}
}
- if ($studiengang_kz == '0') {
- $where[] = "studiengang_kz = " . $this->db->escape($studiengang_kz);
- if ($semester === NULL)
- $where[] = "semester IS NULL";
- elseif ($semester === 0)
- $where[] = "semester = 0";
- } elseif ($studiengang_kz != '') {
- $add = $mischen === true ? " OR (studiengang_kz = 0 AND semester IS NULL)" : "";
- $where[] = "((studiengang_kz = " . $this->db->escape($studiengang_kz) . " AND semester = " . $this->db->escape($semester) . ") OR (studiengang_kz = " . $this->db->escape($studiengang_kz) . " AND semester = 0) OR (studiengang_kz = 0 AND semester = " . $this->db->escape($semester) . ")" . $add . ")";
+ $sgAndSemesterCondition = [];
+ if ($studiengang_kz !== '') {
+ $sgAndSemesterCondition[] = 'studiengang_kz = ' . $this->db->escape($studiengang_kz);
}
+ if ($semester !== null && $semester !== 0) {
+ $sgAndSemesterCondition[] = 'semester = ' . $this->db->escape($semester);
+ }
+
+ $targetedSgAndSemesterCondition = [];
+
+ if (!empty($sgAndSemesterCondition)) {
+ $targetedSgAndSemesterCondition[] = '(' . implode(' AND ', $sgAndSemesterCondition) . ')';
+ }
+ if ($mischen === true) {
+ $targetedSgAndSemesterCondition[] = '(studiengang_kz = 0 AND (semester = 0 OR semester IS NULL))';
+
+ if ($semester !== null && $semester !== 0) {
+ $targetedSgAndSemesterCondition[] = '(studiengang_kz = 0 AND semester = ' . $this->db->escape($semester) . ')';
+ }
+ }
+
+ $finalSgAndSemesterCondition = implode(' OR ', $targetedSgAndSemesterCondition);
+
+ if ($finalSgAndSemesterCondition !== '') {
+ $where[] = "(" . $finalSgAndSemesterCondition . ")";
+ }
+
$this->addJoin('campus.tbl_contentsprache cs', 'content_id');
- $where[] = "cs.sichtbar = " . ($sichtbar ? "true" : "false");
+ if (is_bool($sichtbar) || $sichtbar === 'true' || $sichtbar === 'false') {
+ $isSichtbar = filter_var($sichtbar, FILTER_VALIDATE_BOOLEAN);
+ $where[] = "cs.sichtbar = " . ($isSichtbar ? "true" : "false");
+ }
$where[] = "cs.sprache = (CASE WHEN EXISTS(SELECT 1 FROM campus.tbl_contentsprache cs2 WHERE cs2.content_id=" . $this->dbTable . ".content_id AND sprache=" . $this->db->escape($sprache) . ") THEN " . $this->db->escape($sprache) . " ELSE " . $this->db->escape(DEFAULT_LANGUAGE) . " END)";
$where[] = "cs.version = (SELECT MAX(version) FROM campus.tbl_contentsprache cs3 WHERE cs3.content_id=" . $this->dbTable . ".content_id AND cs3.sprache = (CASE WHEN EXISTS(SELECT 1 FROM campus.tbl_contentsprache cs2 WHERE cs2.content_id=" . $this->dbTable . ".content_id AND sprache=" . $this->db->escape($sprache) . ") THEN " . $this->db->escape($sprache) . " ELSE " . $this->db->escape(DEFAULT_LANGUAGE) . " END))";
+ if ($filterForDegreePrograms) {
+ if (empty($allowedDegreePrograms)) {
+ $where[] = "1=0";
+ } else {
+ $where[] = "studiengang_kz IN (" . implode(',', array_map([$this->db, 'escape'], $allowedDegreePrograms)) . ")";
+ }
+ }
$where = implode(" AND ", $where);
@@ -110,25 +136,16 @@ class News_model extends DB_Model
}
- public function getNewsWithContent($sprache, $studiengang_kz, $semester, $fachbereich_kurzbz = null, $sichtbar = true, $maxalter = 0, $page = 1, $page_size = 10, $all = false, $mischen = true)
+ public function getNewsWithContent($sprache, $studiengang_kz, $semester, $fachbereich_kurzbz = null, $sichtbar = true, $page = 1, $page_size = 10, $active = true, $mischen = true, $filterForDegreePrograms = false, $allowedDegreePrograms = [])
{
- $this->prepareNewsWithContent($sprache, $studiengang_kz, $semester, $fachbereich_kurzbz, $sichtbar, $maxalter, $page, $page_size, $all, $mischen);
-
+ $this->prepareNewsWithContent($sprache, $studiengang_kz, $semester, $fachbereich_kurzbz, $sichtbar, $page, $page_size, $active, $mischen, $filterForDegreePrograms, $allowedDegreePrograms);
// getting the number of rows of the query and adding pagination to the query result
$num_rows = $this->getNumRows(true);
$this->addPagination($page, $page_size, $num_rows);
// preparing the query again because every call to get_compiled_select or cour_all_results will add the from clause to the query
- $this->prepareNewsWithContent($sprache, $studiengang_kz, $semester, $fachbereich_kurzbz, $sichtbar, $maxalter, $page, $page_size, $all, $mischen);
+ $this->prepareNewsWithContent($sprache, $studiengang_kz, $semester, $fachbereich_kurzbz, $sichtbar, $page, $page_size, $active, $mischen, $filterForDegreePrograms, $allowedDegreePrograms);
return $this->load();
}
-
- public function countNewsWithContent($sprache, $studiengang_kz, $semester, $fachbereich_kurzbz = null, $sichtbar = true, $maxalter = 0, $page = 1, $page_size = 10, $all = false, $mischen = true)
- {
- $this->prepareNewsWithContent($sprache, $studiengang_kz, $semester, $fachbereich_kurzbz, $sichtbar, $maxalter, $page, $page_size, $all, $mischen);
- return $this->getNumRows();
- }
-
-
}
diff --git a/application/models/crm/Prestudent_model.php b/application/models/crm/Prestudent_model.php
index ad5c3e141..9b1778c4e 100644
--- a/application/models/crm/Prestudent_model.php
+++ b/application/models/crm/Prestudent_model.php
@@ -88,6 +88,30 @@ class Prestudent_model extends DB_Model
return $this->execQuery($query, $parametersArray);
}
+ /**
+ * Gets the Studiengang and current Ausbildungssemester for every
+ * Prestudent belonging to the same person as the student.
+ *
+ * @param string $student_uid
+ * @return stdClass
+ */
+ public function getStudiengaengAndAusbildungssemesterByStudentUid($student_uid)
+ {
+ $query = 'SELECT p.studiengang_kz,
+ (
+ SELECT ps.ausbildungssemester
+ FROM public.tbl_prestudentstatus ps
+ WHERE ps.prestudent_id = p.prestudent_id
+ ORDER BY ps.datum DESC, ps.insertamum DESC, ps.ext_id DESC
+ LIMIT 1
+ ) AS ausbildungssemester
+ FROM public.tbl_student s
+ JOIN public.tbl_prestudent p USING (prestudent_id)
+ WHERE s.student_uid = ?';
+
+ return $this->execQuery($query, array($student_uid));
+ }
+
/**
* updateAufnahmegruppe
*/
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 8cc701da2..f446f7c84 100644
--- a/application/models/education/Lehreinheit_model.php
+++ b/application/models/education/Lehreinheit_model.php
@@ -840,4 +840,69 @@ 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]);
+ }
+
+ public function getLehrenheitenBySemester($uid, $semester)
+ {
+ $query = "SELECT
+ tbl_lehreinheit.lehreinheit_id,
+ lehrfach.kurzbz as lehrfach,
+ tbl_lehreinheit.lehrform_kurzbz as lehrform,
+ tbl_lehrveranstaltung.bezeichnung as lv_bezeichnung,
+ (SELECT kurzbz FROM public.tbl_mitarbeiter
+ WHERE mitarbeiter_uid=tbl_lehreinheitmitarbeiter.mitarbeiter_uid) as lektor,
+ UPPER(tbl_studiengang.typ::varchar(1) || tbl_studiengang.kurzbz) as stg_kurzbz,
+ tbl_lehrveranstaltung.semester as lv_semester,
+ tbl_lehreinheit.raumtyp,
+ tbl_lehreinheit.raumtypalternativ,
+ tbl_lehreinheit.stundenblockung,
+ tbl_lehreinheit.wochenrythmus,
+ tbl_lehreinheitmitarbeiter.semesterstunden as semesterstunden,
+ tbl_lehreinheit.start_kw,
+ lvangebot.anmeldefenster_start,
+ lvangebot.anmeldefenster_ende,
+ tbl_lehrveranstaltung.lehrveranstaltung_id
+ 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)
+ LEFT JOIN lehre.tbl_lvangebot as lvangebot ON(tbl_lehrveranstaltung.lehrveranstaltung_id=lvangebot.lehrveranstaltung_id)
+ WHERE
+ tbl_lehreinheit.studiensemester_kurzbz = '$semester'
+ AND mitarbeiter_uid = '$uid'
+ ORDER BY stg_kurzbz,lv_semester,lv_bezeichnung";
+
+ return $this->execReadOnlyQuery($query);
+ }
+
+ public function getGroupsByLehreinheit($lehreinheitId)
+ {
+ $query = "SELECT *
+ FROM lehre.tbl_lehreinheitgruppe
+ WHERE lehreinheit_id='$lehreinheitId'";
+
+ return $this->execReadOnlyQuery($query);
+ }
}
diff --git a/application/models/education/Lehrveranstaltung_model.php b/application/models/education/Lehrveranstaltung_model.php
index ffa652cbc..1cf50ee4d 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)
@@ -1379,4 +1379,114 @@ 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_studiengang.studiengang_kz,
+ 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,
+ lehre.tbl_lehrveranstaltung.orgform_kurzbz as orgform
+
+ 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]);
+ }
+
+ /**
+ * All Lehrveranstaltungen of a Studiengang offered in a Studiensemester.
+ */
+ public function getLvForStudiengangInSemester($sem_kurzbz, $studiengang_kz) {
+ $qry = "SELECT DISTINCT (tbl_lehrveranstaltung.lehrveranstaltung_id),
+ UPPER(tbl_studiengang.typ::varchar(1) || tbl_studiengang.kurzbz) as stg_kurzbz,
+ tbl_studiengang.studiengang_kz,
+ tbl_lehrveranstaltung.semester as lv_semester,
+ tbl_lehrveranstaltung.bezeichnung as lv_bezeichnung,
+ tbl_lehrveranstaltung.orgform_kurzbz as orgform
+ FROM
+ lehre.tbl_lehrveranstaltung JOIN public.tbl_studiengang USING(studiengang_kz)
+ WHERE
+ tbl_lehrveranstaltung.studiengang_kz = ?
+ AND tbl_lehrveranstaltung.aktiv = TRUE
+ AND EXISTS (
+ SELECT 1 FROM lehre.tbl_lehreinheit le
+ WHERE le.lehrveranstaltung_id = tbl_lehrveranstaltung.lehrveranstaltung_id
+ AND le.studiensemester_kurzbz = ?
+ )
+ ORDER BY stg_kurzbz,lv_semester,lv_bezeichnung";
+
+ return $this->execReadOnlyQuery($qry, array($studiengang_kz, $sem_kurzbz));
+ }
+
+ /**
+ * Number of Lehreinheit assignments the given mitarbeiter has for a Lehrveranstaltung (i.e. whether
+ * they teach it), optionally restricted to a Studiensemester. Returns a single row with a `teaches`
+ * count - check `teaches > 0`. Used to scope teacher access in the Benotungstool to their own LVs.
+ */
+ public function getLektorIsTeachingLva($lva_id, $ma_uid, $studiensemester_kurzbz = null)
+ {
+ $params = array($lva_id, $ma_uid);
+ $query = "SELECT COUNT(*) AS teaches
+ FROM lehre.tbl_lehreinheitmitarbeiter
+ JOIN lehre.tbl_lehreinheit USING (lehreinheit_id)
+ JOIN lehre.tbl_lehrveranstaltung USING (lehrveranstaltung_id)
+ WHERE lehrveranstaltung_id = ? AND mitarbeiter_uid = ?";
+ if ($studiensemester_kurzbz !== null && $studiensemester_kurzbz !== '') {
+ $query .= " AND tbl_lehreinheit.studiensemester_kurzbz = ?";
+ $params[] = $studiensemester_kurzbz;
+ }
+
+ return $this->execReadOnlyQuery($query, $params);
+ }
}
diff --git a/application/models/education/Lvgesamtnote_model.php b/application/models/education/Lvgesamtnote_model.php
index 0a7638869..00112d39c 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
@@ -50,4 +50,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..33c8de26c 100644
--- a/application/models/education/Notenschluesselaufteilung_model.php
+++ b/application/models/education/Notenschluesselaufteilung_model.php
@@ -13,7 +13,7 @@ class Notenschluesselaufteilung_model extends DB_Model
}
/**
- * Liefert die Note zu Punkten einer Lehrveranstaltung
+ * Gives the grade for the points of a course.
*
* @param number $points
* @param integer $lehrveranstaltung_id
@@ -23,9 +23,18 @@ class Notenschluesselaufteilung_model extends DB_Model
*/
public function getNote($points, $lehrveranstaltung_id, $studiensemester_kurzbz)
{
+ // Without a valid points value you cannot derive a grade. Without this guard the query
+ // builder makes a bad statement from "punkte <=" => null ('punkte' < 'IS' 'NULL'). The
+ // request then fails with HTTP 500 instead of an answer that says "no grade".
+ if (!is_numeric($points))
+ return success(null);
+
$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 064a2507a..3f6159bd6 100644
--- a/application/models/education/Paabgabe_model.php
+++ b/application/models/education/Paabgabe_model.php
@@ -60,6 +60,172 @@ 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_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_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 43d33e4e8..c002349b0 100644
--- a/application/models/education/Projektarbeit_model.php
+++ b/application/models/education/Projektarbeit_model.php
@@ -23,8 +23,10 @@ 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,
+ pa.*, tbl_projekttyp.bezeichnung, tbl_lehrveranstaltung.lehrveranstaltung_id,
+ tbl_sprache.bezeichnung[".$sprache_index."] AS sprache_bezeichnung,
pa.studiensemester_kurzbz, pa.lehrveranstaltung_id,
tbl_firma.name AS firma_name,
(
@@ -43,6 +45,7 @@ class Projektarbeit_model extends DB_Model
JOIN lehre.tbl_projekttyp USING (projekttyp_kurzbz)
JOIN lehre.tbl_lehrveranstaltung USING (lehrveranstaltung_id)
LEFT JOIN public.tbl_firma USING (firma_id)
+ LEFT JOIN public.tbl_sprache ON pa.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/education/Studierendenantrag_model.php b/application/models/education/Studierendenantrag_model.php
index c18d18053..1fa5b89ef 100644
--- a/application/models/education/Studierendenantrag_model.php
+++ b/application/models/education/Studierendenantrag_model.php
@@ -515,4 +515,75 @@ class Studierendenantrag_model extends DB_Model
return hasData($this->load());
}
+
+ /**
+ * Loads all todos for a certain studiengangsleiter
+ *
+ * @param integer $stg_kz
+ *
+ * @return stdClass
+ */
+ public function getOpenAntraegeForStgl($stg_kz)
+ {
+ $qry = "
+ SELECT
+ studierendenantrag_id,
+ prestudent_id,
+ typ,
+ person.nachname,
+ person.vorname,
+ ps.studiengang_kz
+ FROM campus.tbl_studierendenantrag a
+ JOIN public.tbl_prestudent ps USING (prestudent_id)
+ JOIN public.tbl_person person USING (person_id)
+ WHERE studiengang_kz in ?
+ AND (
+ (typ IN ('Abmeldung', 'AbmeldungStgl', 'Unterbrechung')
+ AND campus.get_status_studierendenantrag(a.studierendenantrag_id) = 'Erstellt')
+ OR
+ (typ = 'Wiederholung'
+ AND campus.get_status_studierendenantrag(a.studierendenantrag_id) = 'Lvszugewiesen')
+ )
+ ORDER BY typ;
+ ";
+
+ $params = array($stg_kz);
+ return $this->execQuery($qry, $params);
+ }
+
+ /**
+ * Lists all open antraege for a certain stg
+ *
+ * @param Array $stg_kz
+ *
+ * @return stdClass
+ */
+ public function getOpenAntraegeForAss($stg_kz)
+ {
+ $qry = "
+ SELECT
+ UPPER(stg.typ) || UPPER(stg.kurzbz) || ' ' || stg.bezeichnung AS studiengang,
+ COUNT(*) AS anzahl
+ FROM campus.tbl_studierendenantrag
+ JOIN public.tbl_prestudent p USING (prestudent_id)
+ JOIN public.tbl_studiengang stg ON p.studiengang_kz=stg.studiengang_kz
+ JOIN campus.tbl_studierendenantrag_status as s ON campus.get_status_id_studierendenantrag(campus.tbl_studierendenantrag.studierendenantrag_id) = studierendenantrag_status_id
+ JOIN campus.tbl_studierendenantrag_statustyp as st USING (studierendenantrag_statustyp_kurzbz)
+ WHERE (
+ s.studierendenantrag_statustyp_kurzbz NOT IN('Zurueckgezogen', 'Genehmigt', 'Abgelehnt', 'EinspruchAbgelehnt', 'Abgemeldet', 'Pause', 'EmailVersandt')
+ OR (
+ s.studierendenantrag_statustyp_kurzbz = 'Genehmigt'
+ AND tbl_studierendenantrag.typ = 'AbmeldungStgl'
+ )
+ )
+ AND p.studiengang_kz IN ?
+ GROUP BY studiengang
+ ORDER BY studiengang
+ "
+ ;
+
+ $params = array($stg_kz);
+ return $this->execQuery($qry, $params);
+ }
+
}
diff --git a/application/models/organisation/Studiengang_model.php b/application/models/organisation/Studiengang_model.php
index db0996319..9a0a992b0 100644
--- a/application/models/organisation/Studiengang_model.php
+++ b/application/models/organisation/Studiengang_model.php
@@ -653,31 +653,42 @@ class Studiengang_model extends DB_Model
}
/**
+ * Gets the Studiengang information. Without parameters it uses the Studiengang
+ * of the logged in Student.
+ *
+ * @param int|null $studiengang_kz
+ * @param int|null $semester used for the Jahrgangsvertretung
* @return stdClass
*/
- public function getStudiengangInfoForNews()
+ public function getStudiengangInfoForNews($studiengang_kz = null, $semester = null)
{
$this->load->model('person/Benutzerfunktion_model', 'BenutzerfunktionModel');
$this->load->model('person/Person_model', 'PersonModel');
$this->load->model('crm/Student_model', 'StudentModel');
-
- $student = $this->StudentModel->loadWhere(['student_uid' => getAuthUID()]);
- if (isError($student))
- return error($student);
- if (getData($student)) {
- $student = current(getData($student));
- $studiengang_kz = $student->studiengang_kz;
- $semester = $student->semester;
+
+ if (is_null($studiengang_kz))
+ {
+ $student = $this->StudentModel->loadWhere(['student_uid' => getAuthUID()]);
+ if (isError($student))
+ return error($student);
+ if (getData($student)) {
+ $student = current(getData($student));
+ $studiengang_kz = $student->studiengang_kz;
+ $semester = $student->semester;
+ }
}
-
+
+ // users without a Student record get no information instead of an error
+ if (is_null($studiengang_kz))
+ return success(null);
+
$stg_obj = $this->load($studiengang_kz);
if(isError($stg_obj))
return error($stg_obj);
- if(getData($stg_obj))
- {
- $stg_obj = current(getData($stg_obj));
- }
+ if(!hasData($stg_obj))
+ return error('Studiengang nicht gefunden');
+ $stg_obj = current(getData($stg_obj));
$stg_ltg = $this->getLeitungDetailed($stg_obj->studiengang_kz);
if (isError($stg_ltg))
@@ -886,6 +897,14 @@ class Studiengang_model extends DB_Model
return success($ass);
}
+ public function getDegreePrograms() {
+ $query = "SELECT *
+ FROM public.tbl_studiengang
+ ORDER BY public.tbl_studiengang.kurzbzlang";
+
+ return $this->execReadOnlyQuery($query);
+ }
+
private function addEmailProperty(&$benutzerfunktionen) {
if(count($benutzerfunktionen) && defined('DOMAIN'))
{
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 e53850560..67e2a9e99 100644
--- a/application/models/organisation/Studiensemester_model.php
+++ b/application/models/organisation/Studiensemester_model.php
@@ -375,6 +375,30 @@ EOSQL;
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/person/Benutzerfunktion_model.php b/application/models/person/Benutzerfunktion_model.php
index 52236ba7c..83d106210 100644
--- a/application/models/person/Benutzerfunktion_model.php
+++ b/application/models/person/Benutzerfunktion_model.php
@@ -319,6 +319,40 @@ class Benutzerfunktion_model extends DB_Model
return $record;
}
+ /**
+ * Get active Assistenz(en) of the user by UID.
+ * @param $uid
+ */
+ public function getSTGAssByUID($uid)
+ {
+ $query = '
+ SELECT
+ uid,
+ oe_kurzbz,
+ studiengang_kz,
+ typ,
+ tbl_studiengang.bezeichnung
+ FROM
+ public.tbl_benutzerfunktion
+ JOIN public.tbl_studiengang USING (oe_kurzbz)
+ WHERE
+ funktion_kurzbz = \'ass\'
+ AND (datum_von IS NULL OR datum_von <= now())
+ AND (datum_bis IS NULL OR datum_bis >= now())
+ AND uid = ?
+ ORDER BY
+ oe_kurzbz
+ ';
+
+ $parameters_array = array();
+ if (is_string($uid))
+ {
+ $parameters_array[] = $uid;
+ }
+
+ return $this->execQuery($query, $parameters_array);
+ }
+
function updateBenutzerfunktion($funktionJson)
{
$funktionJson['updatevon'] = getAuthUID();
diff --git a/application/models/ressource/Ort_model.php b/application/models/ressource/Ort_model.php
index de0c8e331..9e2f286d0 100644
--- a/application/models/ressource/Ort_model.php
+++ b/application/models/ressource/Ort_model.php
@@ -12,6 +12,12 @@ class Ort_model extends DB_Model
$this->pk = "ort_kurzbz";
}
+ public function getRoomInfo($ortKurzbz)
+ {
+ $this->addSelect("ort_kurzbz, ausstattung");
+ return $this->OrtModel->loadWhere(array("ort_kurzbz" => $ortKurzbz));
+ }
+
public function getAll($raumtyp_kurzbz)
{
$this->addOrder("ort_kurzbz");
diff --git a/application/models/ressource/Reservierung_model.php b/application/models/ressource/Reservierung_model.php
index d96fe2f6f..88fa0d2dd 100644
--- a/application/models/ressource/Reservierung_model.php
+++ b/application/models/ressource/Reservierung_model.php
@@ -24,7 +24,7 @@ class Reservierung_model extends DB_Model
$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),'/'))
+ ELSE CONCAT(UPPER(studg.typ),UPPER(studg.kurzbz),'-',COALESCE(CAST(r.semester AS varchar),'/'),COALESCE(CAST(r.verband AS varchar),'/'),COALESCE(CAST(r.gruppe AS varchar),'/'))
END as gruppen_kuerzel
FROM campus.vw_reservierung r
JOIN public.tbl_studiengang studg ON studg.studiengang_kz=r.studiengang_kz
@@ -39,7 +39,7 @@ class Reservierung_model extends DB_Model
$raum_reservierungen_query = "SELECT res.*, beginn, ende,
CASE
WHEN res.gruppe_kurzbz IS NOT NULL THEN res.gruppe_kurzbz
- ELSE CONCAT(UPPER(studg.typ),UPPER(studg.kurzbz),'-',COALESCE(CAST(res.semester AS varchar),'/'),COALESCE(CAST(res.verband AS varchar),'/'))
+ ELSE CONCAT(UPPER(studg.typ),UPPER(studg.kurzbz),'-',COALESCE(CAST(res.semester AS varchar),'/'),COALESCE(CAST(res.verband AS varchar),'/'),COALESCE(CAST(res.gruppe AS varchar),'/'))
END as gruppen_kuerzel
FROM campus.vw_reservierung res
JOIN public.tbl_studiengang studg ON studg.studiengang_kz=res.studiengang_kz
@@ -50,11 +50,12 @@ class Reservierung_model extends DB_Model
$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
@@ -62,7 +63,7 @@ class Reservierung_model extends DB_Model
" . $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) ? [$uid ?? getAuthUID(), $uid ?? getAuthUID(), $start_date, $end_date] : [$ort_kurzbz, $start_date, $end_date]);
@@ -82,7 +83,7 @@ class Reservierung_model extends DB_Model
$raum_reservierungen_query = "SELECT res.*, beginn, ende,
CASE
WHEN res.gruppe_kurzbz IS NOT NULL THEN res.gruppe_kurzbz
- ELSE CONCAT(UPPER(studg.typ),UPPER(studg.kurzbz),'-',COALESCE(CAST(res.semester AS varchar),'/'),COALESCE(CAST(res.verband AS varchar),'/'))
+ ELSE CONCAT(UPPER(studg.typ),UPPER(studg.kurzbz),'-',COALESCE(CAST(res.semester AS varchar),'/'),COALESCE(CAST(res.verband AS varchar),'/'),COALESCE(CAST(res.gruppe AS varchar),'/'))
END as gruppen_kuerzel
FROM campus.vw_reservierung res
JOIN public.tbl_studiengang studg ON studg.studiengang_kz=res.studiengang_kz
@@ -94,11 +95,12 @@ class Reservierung_model extends DB_Model
$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
@@ -106,7 +108,7 @@ class Reservierung_model extends DB_Model
" . $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
", [$uid ?? getAuthUID(), $start_date, $end_date]);
@@ -146,4 +148,40 @@ class Reservierung_model extends DB_Model
return $this->execReadOnlyQuery($qry, [$uid, $datum, $stunde]);
}
+ public function deleteReservation($reservierung_id) {
+ $uid = getAuthUID();
+
+ $result = $this->load($reservierung_id);
+
+ if (isError($result))
+ return $result;
+
+ if (!hasData($result))
+ return error('Reservierung nicht gefunden');
+
+ $row = $result->retval[0];
+
+ if ($row->uid !== $uid && $row->insertvon !== $uid)
+ return error('Keine Berechtigung');
+
+ return $this->delete($reservierung_id);
+ }
+
+ public function getMyReservation() {
+ $uid = getAuthUID();
+ $date = date("Y-m-d",time());
+
+ $qry = "SELECT vw_reservierung.*,
+ trim(COALESCE(vw_mitarbeiter.titelpre,'')||' '||COALESCE(vw_mitarbeiter.vorname,'')||' '||COALESCE(vw_mitarbeiter.nachname,'')||' '||COALESCE(vw_mitarbeiter.titelpost,'')) as reserviert_fuer,
+ trim(COALESCE(reserviert_von.titelpre,'')||' '||COALESCE(reserviert_von.vorname,'')||' '||COALESCE(reserviert_von.nachname,'')||' '||COALESCE(reserviert_von.titelpost,'')) as reserviert_von
+ FROM campus.vw_reservierung
+ JOIN campus.vw_mitarbeiter ON vw_reservierung.uid=vw_mitarbeiter.uid
+ LEFT JOIN campus.vw_mitarbeiter reserviert_von ON vw_reservierung.insertvon=reserviert_von.uid
+ WHERE datum >= ?
+ AND (vw_reservierung.uid= ? OR vw_reservierung.insertvon= ? )
+ ORDER BY datum, titel, ort_kurzbz, stunde";
+
+ return $this->execReadOnlyQuery($qry, [$date, $uid, $uid]);
+ }
+
}
diff --git a/application/models/ressource/Zeitsperre_model.php b/application/models/ressource/Zeitsperre_model.php
index 50ba4cb1d..55fdb5b9d 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.
*
@@ -62,6 +64,270 @@ 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;
+ }
+
+ /**
+ * getZeitsperren of Mitarbeiter for the next days
+ * @ days: count of the intervall of the next days
+ * @return array
+ */
+ public function getZeitsperrenForNextDays($days, $uid=null)
+ {
+ $von = date('Y-m-d');
+ $bis = date('Y-m-d', strtotime("+{$days} days"));
+
+ $paramsArray = [$von, $bis, $bis, $von];
+
+ if($uid)
+ $paramsArray[] = $uid;
+
+ $qry = "select
+ nachname,
+ vorname,
+ uid,
+ vondatum,
+ bisdatum,
+ vertretung_uid,
+ erreichbarkeit_kurzbz,
+ mv.kurzbz
+ from
+ campus.vw_mitarbeiter
+ join campus.tbl_zeitsperre on
+ (uid = mitarbeiter_uid)
+ left join public.tbl_mitarbeiter mv on campus.tbl_zeitsperre.vertretung_uid = mv.mitarbeiter_uid
+ where
+ ((? <= bisdatum
+ and ? >= bisdatum)
+ or (?>= vondatum
+ and ? <= vondatum))";
+
+ if($uid)
+ $qry .= " and uid = ? ";
+
+ $qry .= "
+ order by nachname
+ ";
+
+ $result = $this->execQuery($qry, $paramsArray);
+
+ return $result;
+ }
+
+ public function getMitarbeiterWithZeitsperren($von, $bis, $fix=false, $lector=false, $oe=null, $ass=false, $stg=false)
+ {
+ $paramsArray = [$von, $bis, $bis, $von];
+
+ if($oe)
+ {
+ $paramsArray[] = $oe;
+ }
+
+ if($stg)
+ {
+ $paramsArray[] = $stg;
+ }
+
+ $qry = "
+ SELECT
+ m.nachname,
+ m.vorname,
+ m.uid,
+ z.vondatum,
+ z.bisdatum,
+ z.vertretung_uid,
+ z.erreichbarkeit_kurzbz,
+ m.lektor,
+ m.fixangestellt,
+ mv.kurzbz
+ FROM campus.vw_mitarbeiter m
+ join public.tbl_benutzer bn on bn.uid = m.uid";
+
+ if($oe || $ass || $stg)
+ {
+ $qry .= " JOIN public.tbl_benutzerfunktion bf on bf.uid = bn.uid";
+ }
+
+ if($stg)
+ {
+ $qry .= " JOIN public.tbl_studiengang stg using (oe_kurzbz)";
+ }
+
+ $qry.= "
+ LEFT JOIN campus.tbl_zeitsperre z
+ ON m.uid = z.mitarbeiter_uid
+ AND (
+ (? <= z.bisdatum AND ? >= z.bisdatum)
+ OR
+ (? >= z.vondatum AND ? <= z.vondatum)
+ )
+ left join public.tbl_mitarbeiter mv on z.vertretung_uid = mv.mitarbeiter_uid
+ where bn.aktiv = true
+ ";
+
+
+ if($fix) {
+ $qry.= " AND m.fixangestellt = true";
+ }
+ if($lector) {
+ $qry.= " AND m.lektor = true";
+ }
+
+ if ($oe)
+ {
+ $qry.= "
+ AND bf.funktion_kurzbz ='oezuordnung'
+ AND bf.oe_kurzbz= ?
+ AND (bf.datum_von is null or bf.datum_von <=now())
+ AND (bf.datum_bis is null or bf.datum_bis >= now())
+ ";
+ }
+
+ if ($ass)
+ {
+ $qry.= "
+ AND bf.funktion_kurzbz ='ass'
+ AND (bf.datum_von is null or bf.datum_von <=now())
+ AND (bf.datum_bis is null or bf.datum_bis >= now())
+ ";
+ }
+
+ if ($stg)
+ {
+ $qry.= "
+ AND stg.studiengang_kz = ?
+ ";
+ }
+
+ $qry .= " order by nachname, vorname, uid";
+
+
+ $result = $this->execQuery($qry, $paramsArray);
+
+ return $result;
+ }
+
public function checkIfZeitsperreExists($uid, $datum, $stunde)
{
$this->db->select("*");
@@ -71,30 +337,29 @@ class Zeitsperre_model extends DB_Model
$this->db->group_start();
- $this->db->where('vondatum <', $datum);
- $this->db->or_group_start();
- $this->db->where('vondatum', $datum);
- $this->db->group_start();
- $this->db->where('vonstunde <=', $stunde);
- $this->db->or_where('vonstunde IS NULL', null, false);
- $this->db->group_end();
- $this->db->group_end();
+ $this->db->where('vondatum <', $datum);
+ $this->db->or_group_start();
+ $this->db->where('vondatum', $datum);
+ $this->db->group_start();
+ $this->db->where('vonstunde <=', $stunde);
+ $this->db->or_where('vonstunde IS NULL', null, false);
+ $this->db->group_end();
+ $this->db->group_end();
$this->db->group_end();
$this->db->group_start();
- $this->db->where('bisdatum >', $datum);
- $this->db->or_group_start();
- $this->db->where('bisdatum', $datum);
- $this->db->group_start();
- $this->db->where('bisstunde >=', $stunde);
- $this->db->or_where('bisstunde IS NULL', null, false);
- $this->db->group_end();
+ $this->db->where('bisdatum >', $datum);
+ $this->db->or_group_start();
+ $this->db->where('bisdatum', $datum);
+ $this->db->group_start();
+ $this->db->where('bisstunde >=', $stunde);
+ $this->db->or_where('bisstunde IS NULL', null, false);
+ $this->db->group_end();
- $this->db->group_end();
+ $this->db->group_end();
$this->db->group_end();
return $this->load();
-
}
}
diff --git a/application/models/ressource/Zeitsperretyp_model.php b/application/models/ressource/Zeitsperretyp_model.php
index ef54571e8..b693d189b 100644
--- a/application/models/ressource/Zeitsperretyp_model.php
+++ b/application/models/ressource/Zeitsperretyp_model.php
@@ -11,4 +11,20 @@ class Zeitsperretyp_model extends DB_Model
$this->dbTable = 'campus.tbl_zeitsperretyp';
$this->pk = 'zeitsperretyp_kurzbz';
}
+
+ public function getTypenZeitsperren($excludedTypes = [])
+ {
+ $whereNotClause = "";
+ if (count($excludedTypes)) {
+ $excludedTypes = array_map(
+ function ($type) {
+ return "'$type'";
+ },
+ $excludedTypes
+ );
+ $excludedTypes = implode(", ", $excludedTypes);
+ $whereNotClause = "WHERE NOT zeitsperretyp_kurzbz IN ($excludedTypes)";
+ }
+ return $this->execQuery("SELECT * FROM $this->dbTable $whereNotClause ORDER BY beschreibung ASC");
+ }
}
diff --git a/application/views/CisRouterView/CisRouterView.php b/application/views/CisRouterView/CisRouterView.php
index c51ca40c3..529dc1f08 100644
--- a/application/views/CisRouterView/CisRouterView.php
+++ b/application/views/CisRouterView/CisRouterView.php
@@ -10,6 +10,7 @@ $includesArray = array(
'primevue3' => true,
'skipID' => '#fhccontent',
'vuedatepicker11' => true,
+ 'tinymce5' => true,
'customCSSs' => array(
'public/css/components/verticalsplit.css',
'public/css/components/searchbar/searchbar.css',
@@ -21,9 +22,14 @@ $includesArray = array(
'public/css/components/FilterComponent.css',
'public/css/components/Profil.css',
'public/css/components/FormUnderline.css',
+ 'public/css/components/inViewTooltip.css',
'public/css/components/abgabetool/abgabe.css',
'public/css/Cis4/Cms.css',
- 'public/css/Cis4/Studium.css'
+ 'public/css/Cis4/Studium.css',
+ 'public/css/components/vue-datepicker.css',
+ 'public/css/Cis4/Benotungstool.css',
+ 'public/css/components/Zeitsperrenma.css', //70747
+ 'public/css/Cis4/Zeitsperren.css', //77941
),
'customJSs' => array(
'vendor/npm-asset/primevue/accordion/accordion.min.js',
@@ -37,11 +43,15 @@ $includesArray = array(
'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/Cis/Cis.js',
+ 'vendor/olifolkerd/tabulator5/src/js/modules/ColumnCalcs/ColumnCalcs.js'
),
);
@@ -49,6 +59,8 @@ $includesArray = array(
$this->load->view('templates/CISVUE-Header', $includesArray);
?>
>
-
+
load->view('templates/CISVUE-Footer', $includesArray); ?>
diff --git a/application/views/Vertragsverwaltung.php b/application/views/Vertragsverwaltung.php
index 8fa6dff27..390798cd8 100644
--- a/application/views/Vertragsverwaltung.php
+++ b/application/views/Vertragsverwaltung.php
@@ -12,13 +12,13 @@ $includesArray = array(
'tinymce5' => true,
'phrases' => array(
'global',
- 'ui',
+ 'ui'
),
'customCSSs' => [
'public/css/components/vue-datepicker.css',
'public/css/components/primevue.css',
'public/css/Vertragsverwaltung.css',
- 'public/css/components/Detailheader.css'
+ 'public/css/components/Detailheader.css',
],
'customJSs' => [
#'vendor/npm-asset/primevue/tree/tree.min.js',
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); ?>
- >p->t('ui', 'speichern') ?>
+ >
+ p->t('ui', 'speichern') ?>
+
@@ -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/lehre/pruefungsprotokollUebersicht.php b/application/views/lehre/pruefungsprotokollUebersicht.php
index e75e3c914..e9e61e548 100644
--- a/application/views/lehre/pruefungsprotokollUebersicht.php
+++ b/application/views/lehre/pruefungsprotokollUebersicht.php
@@ -1,33 +1,45 @@
load->view(
- 'templates/FHC-Header',
- array(
- 'title' => 'Prüfungsprotokoll',
- 'jquery3' => true,
- 'jqueryui1' => true,
- 'jquerycheckboxes1' => true,
- 'bootstrap3' => true,
- 'fontawesome4' => true,
- 'tablesorter2' => true,
- 'tabulator5' => true,
- 'tabulator5JQuery' => true,
- 'cis'=>true,
- 'ajaxlib' => true,
- 'dialoglib' => true,
- 'tablewidget' => true,
- 'phrases' => array(
- 'ui' => array(
- 'keineDatenVorhanden',
- 'heute',
- 'letzteWoche',
- 'alle',
- 'zeitraum'
- )
- ),
- 'customCSSs' => array('public/css/sbadmin2/tablesort_bootstrap.css'),
- 'customJSs' => array('public/js/bootstrapper.js')
- )
+ $includesArray = array(
+ 'title' => 'Prüfungsprotokoll',
+ 'jquery3' => true,
+ 'jqueryui1' => true,
+ 'jquerycheckboxes1' => true,
+ 'bootstrap3' => true,
+ 'fontawesome4' => true,
+ 'tablesorter2' => true,
+ 'tabulator5' => true,
+ 'tabulator5JQuery' => true,
+ 'cis'=>true,
+ 'ajaxlib' => true,
+ 'dialoglib' => true,
+ 'tablewidget' => true,
+ 'phrases' => array(
+ 'ui' => array(
+ 'keineDatenVorhanden',
+ 'heute',
+ 'letzteWoche',
+ 'alle',
+ 'zeitraum'
+ )
+ ),
+ 'customCSSs' => array('public/css/sbadmin2/tablesort_bootstrap.css'),
+ 'customJSs' => array('public/js/bootstrapper.js')
);
+
+ if(defined("CIS4") && CIS4)
+ {
+ $this->load->view(
+ 'templates/CISVUE-Header',
+ $includesArray
+ );
+ }
+ else
+ {
+ $this->load->view(
+ 'templates/FHC-Header',
+ $includesArray
+ );
+ }
?>
@@ -66,4 +78,15 @@
-load->view('templates/FHC-Footer'); ?>
+load->view(
+ 'templates/CISVUE-Footer',
+ $includesArray
+ );
+ } else {
+ $this->load->view(
+ 'templates/FHC-Footer',
+ $includesArray
+ );
+ }
diff --git a/application/views/templates/CISVUE-Footer.php b/application/views/templates/CISVUE-Footer.php
index eae2a94ff..0523a21d3 100644
--- a/application/views/templates/CISVUE-Footer.php
+++ b/application/views/templates/CISVUE-Footer.php
@@ -5,6 +5,11 @@ $includesArray = array(
'bootstrap5' => true,
'fontawesome6' => true,
'axios027' => true,
+ 'primevue3' => true,
+ 'vuedatepicker11' => true,
+ 'customJSs' => array_merge([
+ 'vendor/moment/luxonjs/luxon.min.js'
+ ], $customJSs ?? []),
'customJSModules' => array_merge([
'public/js/apps/Cis/Menu.js'
], $customJSModules ?? []),
diff --git a/application/views/templates/CISVUE-Header.php b/application/views/templates/CISVUE-Header.php
index f28012dd5..1d5b79dce 100644
--- a/application/views/templates/CISVUE-Header.php
+++ b/application/views/templates/CISVUE-Header.php
@@ -7,6 +7,11 @@ $includesArray = array(
'fontawesome6' => true,
'axios027' => true,
'primevue3' => true,
+ 'vuedatepicker11' => true,
+ 'printCssPath' => 'public/css/Cis4/print.css',
+ 'customJSs' => array_merge([
+ 'vendor/moment/luxonjs/luxon.min.js'
+ ], $customJSs ?? []),
'customJSModules' => array_merge([
'public/js/apps/Cis/Menu.js'
], $customJSModules ?? []),
@@ -26,15 +31,19 @@ $this->load->view('templates/FHC-Header', $includesArray);
document.body.classList.add("in-frame");
-
+
+ {{
+ $p.t('lehre','gruppe')?
+ $p.t('lehre','gruppe')+':'
+ :''
+ }}
+ {{event.gruppe[0].kuerzel.split("/")[0]}}
+
{{
$p.t('lehre','lektor')?
diff --git a/public/js/components/Cis/Renderer/Slot/roomModalContent.js b/public/js/components/Cis/Renderer/Slot/roomModalContent.js
new file mode 100644
index 000000000..87c457f4b
--- /dev/null
+++ b/public/js/components/Cis/Renderer/Slot/roomModalContent.js
@@ -0,0 +1,323 @@
+import FormInput from "../../../Form/Input.js";
+import ApiRoomPlan from "../../../../api/factory/calendar/roomPlan.js";
+
+export default {
+ emits: ["create-event"],
+ components: {
+ FormInput,
+ },
+ inject: {
+ timeGrid: "timeGrid",
+ },
+ props: {
+ event: { type: Object, required: true },
+ },
+ data() {
+ return {
+ timezone: FHC_JS_DATA_STORAGE_OBJECT.timezone,
+ data: {},
+ filteredGroups: [],
+ filteredLektoren: [],
+ selectedStart: null,
+ selectedEnd: null,
+ title: null,
+ beschreibung: null,
+ studiengang: null,
+ semester: null,
+ verband: null,
+ gruppe: null,
+ selectedGruppe: null,
+ selectedLektoren: [],
+ nestedGroupOptions: {},
+ };
+ },
+ computed: {
+ semesterOptions() {
+ if (!this.studiengang) return [];
+
+ return Object.keys(this.nestedGroupOptions)
+ .map((val) => parseInt(val))
+ .sort((a, b) => a - b);
+ },
+ verbandOptions() {
+ if (!this.semester) return [];
+
+ return Object.keys(this.nestedGroupOptions[this.semester]).sort();
+ },
+ gruppeOptions() {
+ if (!this.verband) return [];
+
+ return this.nestedGroupOptions[this.semester][this.verband]
+ .map((val) => parseInt(val))
+ .sort((a, b) => a - b);
+ },
+ },
+ watch: {
+ event: {
+ handler(newEvent) {
+ this.syncFromEvent(newEvent);
+ },
+ deep: true,
+ },
+ studiengang() {
+ this.semester = null;
+ this.verband = null;
+ this.gruppe = null;
+ this.nestedGroupOptions = {};
+ this.fetchGroupOptions();
+ },
+ semester() {
+ this.verband = null;
+ },
+ verband() {
+ this.gruppe = null;
+ },
+ },
+ methods: {
+ syncFromEvent(newEvent) {
+ if (!newEvent) return;
+
+ const startTime = newEvent.start
+ ?.setZone?.(this.timezone)
+ ?.toFormat?.("HH:mm:ss");
+ const endTime = newEvent.end
+ ?.setZone?.(this.timezone)
+ ?.toFormat?.("HH:mm:ss");
+
+ this.selectedStart =
+ this.timeGrid.find((t) => t.start === startTime)?.start ||
+ this.timeGrid[0]?.start;
+ this.selectedEnd =
+ this.timeGrid.find((t) => t.end === endTime)?.end ||
+ this.timeGrid.at(-1)?.end;
+ },
+ saveEvent() {
+ const [startHour, startMinute] = this.selectedStart
+ .split(":")
+ .map(Number);
+ const [endHour, endMinute] = this.selectedEnd
+ .split(":")
+ .map(Number);
+ const selectedStart = this.event.start
+ .startOf("day")
+ .set({ hour: startHour, minute: startMinute });
+ const selectedEnd = this.event.start
+ .startOf("day")
+ .set({ hour: endHour, minute: endMinute });
+
+ const lektoren_uid = this.selectedLektoren.map((m) => m.uid);
+
+ const spezialgruppe = this.selectedGruppe?.gruppe_kurzbz;
+
+ const event = {
+ selectedStart: selectedStart,
+ selectedEnd: selectedEnd,
+ title: this.title,
+ beschreibung: this.beschreibung,
+ studiengang: this.studiengang,
+ semester: this.semester,
+ verband: this.verband,
+ gruppe: this.gruppe,
+ spezialgruppe: spezialgruppe,
+ lektoren: lektoren_uid,
+ };
+
+ this.$emit("create-event", event);
+ },
+ async searchGroup(event) {
+ this.filteredGroups =
+ await this.event.createContext.room_create_information.searchGroup(
+ event,
+ );
+ },
+ async searchLektor(event) {
+ this.filteredLektoren =
+ await this.event.createContext.room_create_information.searchLektor(
+ event,
+ );
+ },
+ capitalize(text) {
+ if (!text) return "";
+ return text.charAt(0).toUpperCase() + text.slice(1);
+ },
+ async fetchGroupOptions() {
+ if (!this.studiengang) return;
+
+ const groupOptionsResponse = await this.$api.call(
+ ApiRoomPlan.getGroupOptions(this.studiengang),
+ );
+
+ if (groupOptionsResponse.meta.status === "success") {
+ this.nestedGroupOptions = groupOptionsResponse.data;
+ }
+ },
+ afterTitleInputLosesFocus() {
+ if (this.title?.length && !this.beschreibung?.length) {
+ this.beschreibung = this.title;
+ this.$refs.beschreibungInput.$el.querySelector("input").focus();
+ }
+ },
+ },
+ mounted() {
+ this.syncFromEvent(this.event);
+ },
+
+ template: `
+
+
Neue Reservierung
+
+
+
+ {{ slot.start }}
+
+
+
+
+
+ {{ slot.end }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ studiengang.kuerzel }} ({{ studiengang.kurzbzlang }})
+
+
+
+
+
+ {{ semester }}
+
+
+
+
+
+ {{ verband }}
+
+
+
+
+
+ {{ gruppe }}
+
+
+
+
+
+
+
+
+
Speichern
+
+ `,
+};
diff --git a/public/js/components/Cis/Renderer/Slot/roomModalTitle.js b/public/js/components/Cis/Renderer/Slot/roomModalTitle.js
new file mode 100644
index 000000000..1f477a997
--- /dev/null
+++ b/public/js/components/Cis/Renderer/Slot/roomModalTitle.js
@@ -0,0 +1,3 @@
+export default {
+ template:`{{ $p.t('lehre','new_reservierung') }}
`
+}
\ No newline at end of file
diff --git a/public/js/components/Cis/Studium/Studium.js b/public/js/components/Cis/Studium/Studium.js
index 77459c913..3bf831165 100644
--- a/public/js/components/Cis/Studium/Studium.js
+++ b/public/js/components/Cis/Studium/Studium.js
@@ -1,8 +1,10 @@
import LvUebersicht from "../Mylv/LvUebersicht.js";
+import StudiengangInformation from "../Cms/StudiengangInformation/StudiengangInformation.js";
import ApiCisStudium from '../../../api/factory/cis/studium.js';
export default {
+ name: 'Studium',
data(){
return {
studienSemester :[],
@@ -16,6 +18,7 @@ export default {
lehrveranstaltungen: [],
selectedLehrveranstaltung: null,
menu:null,
+ showStudiengangInfo: false,
}
},
provide(){
@@ -28,8 +31,10 @@ export default {
}
},
name: "OverviewStudiengaenge",
+ inject: ["isMobile"],
components: {
LvUebersicht,
+ StudiengangInformation,
},
watch:{
selectedStudiensemester: function(newVal, oldVal){
@@ -48,13 +53,18 @@ export default {
}
}
},
- selectedStudiengang: function (newVal, oldVal) {
+ selectedStudiengang: async function (newVal, oldVal) {
if (newVal && newVal != oldVal) {
const studiengang = this.getDataFromLocalStorage("studiengang");
if (!studiengang || (studiengang && studiengang != newVal)) {
this.storeDataToLocalStorage("studiengang", JSON.stringify(newVal));
}
}
+
+ if (newVal && this.showStudiengangInfo) {
+ await this.$nextTick();
+ document.querySelector("#studiengang-info-body").classList.add("show");
+ }
},
selectedStudienordnung: function (newVal, oldVal) {
if (newVal && newVal != oldVal) {
@@ -119,6 +129,23 @@ export default {
const value = localStorage.getItem(key);
return value;
},
+ displayStudiengangInfo(){
+ this.showStudiengangInfo = true;
+ this.storeDataToLocalStorage("studiengangInfo", this.showStudiengangInfo);
+ setTimeout(() => {
+ this.toggleStudiengangInfoCollapse();
+ }, this.isMobile ? 0 : 500);
+ },
+ hideStudiengangInfo(){
+ this.showStudiengangInfo = false;
+ this.storeDataToLocalStorage("studiengangInfo", this.showStudiengangInfo);
+ },
+ toggleStudiengangInfoCollapse() {
+ new bootstrap.Collapse(
+ "#studiengang-info-body",
+ { toggle: true },
+ );
+ },
changeSelectedStudienSemester(studiensemester_kurzbz) {
return this.$api
.call(ApiCisStudium.getAllStudienSemester(studiensemester_kurzbz, this.selectedStudiengang, this.selectedSemester, this.selectedStudienordnung))
@@ -247,6 +274,11 @@ export default {
},
computed:{
+ studiengangInfoToggleTitel(){
+ return this.showStudiengangInfo
+ ? this.$p.t('studiengangInformation', 'studiengangsinformationen_ausblenden')
+ : this.$p.t('studiengangInformation', 'studiengangsinformationen_anzeigen');
+ },
isGermanLanguage(){
return this.$p.user_language.value == "German"
},
@@ -287,6 +319,10 @@ export default {
created(){
+ // the last toggle wins, otherwise the desktop view shows the information and the mobile view hides it
+ const studiengangInfo = this.getDataFromLocalStorage("studiengangInfo");
+ this.showStudiengangInfo = studiengangInfo === null ? !this.isMobile : studiengangInfo === "true";
+
const studiensemester = this.getDataFromLocalStorage("sudiensemester") ?? undefined;
const studiengang = JSON.parse(this.getDataFromLocalStorage("studiengang")) ?? undefined;
const semester = this.getDataFromLocalStorage("semester") ?? undefined;
@@ -304,112 +340,147 @@ export default {
},
template: /*html*/`
-
{{$p.t('studium','studium')}}
-
-
-
-
-
{{$p.t('studium','lektoren')}}:
-
{{lektor.name}}
+
{{$p.t('studium','studium')}}
+
+
+
+
+ Menu:
+
+
+
+
+
{{$p.t('studium','studiensemester')}}:
+
+
+
+
+
+ {{studiensemesterTitel(semester.studiensemester_kurzbz) }}
+
+
+
+
+
+
+
+
+
{{$p.t('lehre','studiengang')}}:
+
+
+
+
+
+ {{studiengangTitel(studiengang)}}
+
+
+
+
+
+
+
+
+
{{$p.t('lehre','semester')}}:
+
+
+
+
+
+ {{sem}}. Semester
+
+
+
+
+
+
+
+
+
{{$p.t('studium','studienordnung')}}:
+
+
+
+
+
+ {{ordnung.bezeichnung}}
+
+
+
+
+
-
Menu:
-
-
-
-
-
{{$p.t('studium','studiensemester')}}:
-
-
-
-
-
- {{studiensemesterTitel(semester.studiensemester_kurzbz) }}
-
-
-
-
-
-
-
{{$p.t('lehre','studiengang')}}:
-
-
-
-
-
- {{studiengangTitel(studiengang)}}
-
-
-
-
-
-
+
-
-
{{$p.t('lehre','semester')}}:
-
-
-
-
-
- {{sem}}. Semester
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+ {{$p.t('global','ansprechpartner')}} {{$p.t('lehre','studiengang')}}
+
+
+
+
+
+
-
-
{{$p.t('studium','studienordnung')}}:
-
-
-
-
-
- {{ordnung.bezeichnung}}
-
-
-
-
-
+
-
-
-
-
-
-
-
-
`
};
\ No newline at end of file
diff --git a/public/js/components/Cis/Zeitsperren/Zeitsperren.js b/public/js/components/Cis/Zeitsperren/Zeitsperren.js
new file mode 100644
index 000000000..78822d75b
--- /dev/null
+++ b/public/js/components/Cis/Zeitsperren/Zeitsperren.js
@@ -0,0 +1,697 @@
+import {CoreFilterCmpt} from "../../filter/Filter.js";
+import FormForm from '../../Form/Form.js';
+import FormInput from '../../Form/Input.js';
+import PvAutoComplete from '../../../../../index.ci.php/public/js/components/primevue/autocomplete/autocomplete.esm.min.js';
+
+import ApiAuthinfo from '../../../api/factory/authinfo.js';
+import ApiTimelocks from "../../../api/factory/cis/zeitsperren.js";
+import ApiStvAbschlusspruefung from "../../../api/factory/stv/abschlusspruefung.js";
+
+export default {
+ name: 'ZeitsperrenComponent',
+ components: {
+ CoreFilterCmpt,
+ FormForm,
+ FormInput,
+ PvAutoComplete
+ },
+ data(){
+ return {
+ uid: null,
+ statusNew: true,
+ timeRecordingLockedUntil: '2015-08-31', //TODO(Manu) check if needed
+ typesTimeLocks: ["Urlaub", "PflegeU", "ZA", "Krank", "DienstF", "DienstV", "CovidSB", "CovidKS"],
+ typesHideStunden: ["Urlaub", "ZA", "Krank", "DienstF", "DienstV", "CovidSB", "CovidKS"],
+ listTypenZeitsperren: [],
+ listTypenErreichbarkeit: [],
+ listStunden: [],
+ tabulatorOptions: null,
+ tabulatorEvents: [],
+ originalData: {},
+ zeitsperreData: {
+ vondatum : new Date(),
+ bisdatum: new Date(),
+ vonISO : "00:00:00", //later
+ bisISO: "23:59:59", //later
+ erreichbarkeit_kurzbz: 'n',
+ zeitsperretyp_kurzbz: 'Arzt',
+ vonstunde: null,
+ bisstunde: null
+ },
+ changedData: {},
+ selectedVertretung: null,
+ filteredMitarbeiter: [],
+ abortController: {
+ mitarbeiter: null
+ },
+ };
+ },
+ computed: {
+ dienstverhinderungen() {
+ return {
+ "Eheschließung": "a) " + this.$p.t('zeitsperren', 'eheschliessung'),
+ "Geburt eigenes Kind": "b) " + this.$p.t('zeitsperren', 'geburt'),
+ "Heirat Kind/Geschwister": "c) " + this.$p.t('zeitsperren', 'heirat'),
+ "Eigene Sponsion/Promotion": "d) " + this.$p.t('zeitsperren', 'sponsion'),
+ "Lebensbedr. Erkrankung P/K/E": "e) " + this.$p.t('zeitsperren', 'erkrankung_lebensbedr'),
+ "Ableben P/K/E": "f) " + this.$p.t('zeitsperren', 'ableben'),
+ "Bestattung G/S/G": "g) " + this.$p.t('zeitsperren', 'bestattung'),
+ "Wohnungswechsel": "h) " + this.$p.t('zeitsperren', 'umzug'),
+ "Bundesheer": "i) " + this.$p.t('zeitsperren', 'bundesheer'),
+ "Volksschultag": "j) " + this.$p.t('zeitsperren', 'volksschultag')};
+ },
+ showInfo(){
+ return this.zeitsperreData.zeitsperretyp_kurzbz === 'DienstF';
+ },
+ },
+ methods: {
+ actionEditZeitsperre(zeitsperre_id){
+ this.statusNew = false;
+ return this.$api
+ .call(ApiTimelocks.loadZeitsperre(zeitsperre_id))
+ .then(response => {
+ this.originalData = structuredClone(response.data);
+ this.zeitsperreData = structuredClone(response.data);
+
+ this.selectedVertretung = {
+ label: this.getPersonLabel(this.zeitsperreData.ma_titelpre, this.zeitsperreData.ma_nachname, this.zeitsperreData.ma_vorname, this.zeitsperreData.ma_titelpost, this.zeitsperreData.vertretung_uid),
+ person_id: this.zeitsperreData.ma_person_id,
+ mitarbeiter_uid: this.zeitsperreData.vertretung_uid
+ };
+ })
+ .catch(this.$fhcAlert.handleSystemError);
+ },
+ actionDeleteZeitsperre(zeitsperre_id){
+ this.$fhcAlert
+ .confirmDelete()
+ .then(result => result
+ ? zeitsperre_id
+ : Promise.reject({ handled: true })
+ )
+ .then(() => this.deleteZeitsperre(zeitsperre_id))
+ .catch(this.$fhcAlert.handleSystemError);
+ },
+ alertMailstatus(response) {
+ if(response?.meta?.mailstatus) {
+ if(response.meta.mailstatus.mailsent) {
+ this.$fhcAlert.alertInfo(response.meta.mailstatus.msg);
+ } else {
+ this.$fhcAlert.alertWarning(response.meta.mailstatus.msg);
+ }
+ }
+ },
+ saveZeitsperre(zeitsperreId = null) {
+ const isNew = !zeitsperreId;
+
+ let payload = isNew
+ ? { ...this.zeitsperreData } // add
+ : this.getChangedFields(this.originalData, // edit
+ this.zeitsperreData);
+
+ if (!isNew && Object.keys(payload).length === 0) {
+ return Promise.resolve();
+ }
+
+ const request = isNew
+ ? ApiTimelocks.addZeitsperre(this.uid, payload)
+ : ApiTimelocks.editZeitsperre(zeitsperreId, payload);
+
+ return this.$refs.dataZeitsperre
+ .call(request)
+ .then((response) => {
+ this.$fhcAlert.alertSuccess(this.$p.t('ui', 'successSave'));
+ this.alertMailstatus(response);
+
+ this.reset();
+ this.reload();
+ })
+ .catch(this.$fhcAlert.handleSystemError);
+ },
+ deleteZeitsperre(zeitsperre_id){
+ return this.$api
+ .call(ApiTimelocks.deleteZeitsperre(zeitsperre_id))
+ .then(response => {
+ this.$fhcAlert.alertSuccess(this.$p.t('ui', 'successDelete'));
+ this.alertMailstatus(response);
+ })
+ .catch(this.$fhcAlert.handleSystemError)
+ .finally(()=> {
+ this.reload();
+ });
+ },
+ searchMitarbeiter(event) {
+ if (this.abortController.mitarbeiter) {
+ this.abortController.mitarbeiter.abort();
+ }
+
+ this.abortController.mitarbeiter = new AbortController();
+
+ return this.$api
+ .call(ApiStvAbschlusspruefung.getMitarbeiter(event.query))
+ .then(result => {
+ this.filteredMitarbeiter = [];
+ for (let mitarbeiter of result.data.retval) {
+ this.filteredMitarbeiter.push(
+ {
+ label: this.getPersonLabel(
+ mitarbeiter.titelpre,
+ mitarbeiter.nachname,
+ mitarbeiter.vorname,
+ mitarbeiter.titelpost,
+ mitarbeiter.mitarbeiter_uid
+ ),
+ person_id: mitarbeiter.person_id,
+ mitarbeiter_uid: mitarbeiter.mitarbeiter_uid
+ }
+ );
+ }
+ });
+ },
+ getPersonLabel(titelpre, nachname, vorname, titelpost, uid) {
+ if(!uid)
+ return '';
+ return nachname + ' ' + vorname + (titelpre ? ' ' + titelpre : '') + (titelpost ? ' ' + titelpost : '') + (uid ? ' (' + uid + ')' : '');
+ },
+ reload() {
+ if (this.$refs.table)
+ this.$refs.table.reloadTable();
+ },
+ reset(){
+ this.statusNew = true;
+ this.selectedVertretung = null;
+ this.zeitsperreData = {
+ vondatum : new Date(),
+ bisdatum: new Date(),
+ vonISO : "00:00:00", //later
+ bisISO: "23:59:59", //later
+ erreichbarkeit_kurzbz: 'n',
+ zeitsperretyp_kurzbz: 'Arzt',
+ vonstunde: null,
+ bisstunde: null
+ };
+ this.originalData = {};
+ },
+ handleChangeVonStunde(){
+ let stunde = this.zeitsperreData.vonstunde;
+ const result = this.listStunden.find(item => item.stunde === stunde);
+ if (!result) {
+ this.zeitsperreData.vonISO = '00:00:00';
+ return;
+ }
+ this.zeitsperreData.vonISO = result.beginn;
+ },
+ handleChangeBisStunde(){
+ let stunde = this.zeitsperreData.bisstunde;
+ const result = this.listStunden.find(item => item.stunde === stunde);
+ if (!result) {
+ this.zeitsperreData.bisISO = '23:59:59';
+ return;
+ }
+ this.zeitsperreData.bisISO = result.ende;
+ },
+ handleStunden(){
+ if (this.typesHideStunden.includes(this.zeitsperreData.zeitsperretyp_kurzbz)){
+ this.zeitsperreData.vonstunde = null;
+ this.zeitsperreData.bisstunde = null;
+ this.zeitsperreData.vonISO = '00:00:00';
+ this.zeitsperreData.bisISO = '23:59:59';
+ }
+ },
+ copyDateForBis(){
+ this.zeitsperreData.bisdatum = this.zeitsperreData.vondatum;
+ },
+ getChangedFields(original, current) {
+ const diff = {};
+
+ Object.keys(current).forEach((key) => {
+ if (current[key] !== original[key]) {
+ diff[key] = current[key];
+ }
+ });
+ return diff;
+ },
+ async updateTableTranslations() {
+ await this.$p.loadCategory(['global', 'person', 'zeitsperren', 'ui', 'abschlusspruefung']);
+
+ let cm = this.$refs.table.tabulator.columnManager;
+
+ cm.getColumnByField('bezeichnung').component.updateDefinition({
+ title: this.$capitalize(this.$p.t('person', 'grund'))
+ });
+ cm.getColumnByField('beschreibung').component.updateDefinition({
+ title: this.$capitalize(this.$p.t('global', 'bezeichnung'))
+ });
+ cm.getColumnByField('vondatum').component.updateDefinition({
+ title: this.$capitalize(this.$p.t('ui', 'von'))
+ });
+ cm.getColumnByField('bisdatum').component.updateDefinition({
+ title: this.$capitalize(this.$p.t('global', 'bis'))
+ });
+ cm.getColumnByField('vonstunde').component.updateDefinition({
+ title: this.$capitalize(this.$p.t('zeitsperren', 'stunde_von'))
+ });
+ cm.getColumnByField('bisstunde').component.updateDefinition({
+ title: this.$capitalize(this.$p.t('zeitsperren', 'stunde_bis'))
+ });
+ cm.getColumnByField('vertretung').component.updateDefinition({
+ title: this.$capitalize(this.$p.t('person', 'vertretung'))
+ });
+ cm.getColumnByField('erreichbarkeit_beschreibung').component.updateDefinition({
+ title: this.$capitalize(this.$p.t('person', 'erreichbarkeit'))
+ });
+ cm.getColumnByField('freigabeamum').component.updateDefinition({
+ title: this.$capitalize(this.$p.t('abschlusspruefung', 'freigabe'))
+ });
+ }
+ },
+ watch: {
+ selectedVertretung(newVal) {
+ this.zeitsperreData.vertretung_uid = newVal?.mitarbeiter_uid || null;
+ },
+ 'zeitsperreData.zeitsperretyp_kurzbz'(newVal) {
+ if (newVal === 'DienstV') {
+ // set first key as default
+ if (!this.zeitsperreData.bezeichnung) {
+ const firstKey = Object.keys(this.dienstverhinderungen)[0];
+ this.zeitsperreData.bezeichnung = firstKey;
+ }
+ } else {
+ this.zeitsperreData.bezeichnung = '';
+ }
+ },
+ '$p.user_language.value'(n, o) {
+ if (n !== o && o !== undefined && this.$refs.table.tableBuilt) {
+ this.updateTableTranslations();
+ this.reload();
+ }
+ }
+ },
+ created() {
+ this.$api.call(ApiAuthinfo.getAuthUID()).then(res => {
+
+ //check if there is a user, passed via route
+ const urlUid = this.$route?.query?.uid;
+ this.uid = urlUid ? urlUid : res.data.uid;
+
+ this.tabulatorOptions = {
+ ajaxURL: 'dummy',
+ ajaxRequestFunc: () =>
+ this.$api.call(ApiTimelocks.getTimelocksUser(this.uid)),
+ ajaxResponse: (url, params, response) => response.data,
+ columns: [
+ {title:"bezeichnung", field:"bezeichnung"},
+ {title:"Grund", field:"beschreibung"},
+ {title:"Von", field:"vondatum",
+ formatter: function (cell) {
+ const dateStr = cell.getValue();
+ if (!dateStr) return "";
+
+ const date = new Date(dateStr);
+ return date.toLocaleString("de-DE", {
+ day: "2-digit",
+ month: "2-digit",
+ year: "numeric",
+ });
+ }
+ },
+ {title:"Bis", field:"bisdatum",
+ formatter: function (cell) {
+ const dateStr = cell.getValue();
+ if (!dateStr) return "";
+
+ const date = new Date(dateStr);
+ return date.toLocaleString("de-DE", {
+ day: "2-digit",
+ month: "2-digit",
+ year: "numeric",
+ });
+ }
+ },
+ {title:"vonstunde", field:"vonstunde", visible: false},
+ {title:"bisstunde", field:"bisstunde", visible: false},
+ {title:"Vertretung", field:"vertretung"},
+ {title:"Erreichbarkeit", field:"erreichbarkeit_beschreibung"},
+ {title:"zeitsperre_id", field:"zeitsperre_id", visible: false},
+ {title:"mitarbeiter_uid", field:"mitarbeiter_uid", visible: false},
+ {title:"freigabeamum", field:"freigabeamum", visible: false,
+ formatter: function (cell) {
+ const value = cell.getValue();
+ return value === null
+ ? ''
+ : '
';
+ }
+ },
+ {title: 'Aktionen', field: 'actions',
+ minWidth: 150, // Ensures Action-buttons will be always fully displayed
+ formatter: (cell, formatterParams, onRendered) => {
+ let container = document.createElement('div');
+ container.className = "d-flex gap-2";
+
+ let button = document.createElement('button');
+ button.className = 'btn btn-outline-secondary btn-action';
+ button.innerHTML = '
';
+ button.title = this.$p.t('ui', 'bearbeiten');
+ button.addEventListener('click', (event) =>
+ this.actionEditZeitsperre(cell.getData().zeitsperre_id)
+ );
+ if(cell.getData().zeitsperretyp_kurzbz == 'DienstV' || cell.getData().zeitsperretyp_kurzbz == 'ZVerfueg'){
+ button.disabled = true;
+ }
+ //TODO(Manu) check if needed
+ if(this.typesTimeLocks.includes(cell.getData().zeitsperretyp_kurzbz) && (cell.getData().vondatum < this.timeRecordingLockedUntil)){
+ button.disabled = true;
+ }
+ container.append(button);
+
+ button = document.createElement('button');
+ button.className = 'btn btn-outline-secondary btn-action';
+ button.innerHTML = '
';
+ button.title = this.$p.t('ui', 'loeschen');
+ button.addEventListener('click', () =>
+ //this.deleteZeitsperre(cell.getData().zeitsperre_id)
+ this.actionDeleteZeitsperre(cell.getData().zeitsperre_id)
+ );
+ if(cell.getData().zeitsperretyp_kurzbz == 'Urlaub' || cell.getData().zeitsperretyp_kurzbz == 'ZVerfueg'){
+ button.disabled = true;
+ }
+ //TODO(Manu) check if needed
+ if(this.typesTimeLocks.includes(cell.getData().zeitsperretyp_kurzbz) && (cell.getData().vondatum < this.timeRecordingLockedUntil)){
+ button.disabled = true;
+ }
+ container.append(button);
+
+ return container;
+ },
+ frozen: true
+ },
+ ]
+ };
+ this.tabulatorEvents = [
+ {
+ event: 'tableBuilt',
+ handler: async() => {
+ await this.updateTableTranslations();
+ }
+ }
+ ];
+ });
+
+ this.$api
+ .call(ApiTimelocks.getTypenZeitsperren())
+ .then(result => {
+ this.listTypenZeitsperren = result.data;
+ })
+ .catch(this.$fhcAlert.handleSystemError);
+
+ this.$api
+ .call(ApiTimelocks.getTypenErreichbarkeit())
+ .then(result => {
+ this.listTypenErreichbarkeit = result.data;
+ })
+ .catch(this.$fhcAlert.handleSystemError);
+
+ this.$api
+ .call(ApiTimelocks.getStunden())
+ .then(result => {
+ this.listStunden = result.data;
+ })
+ .catch(this.$fhcAlert.handleSystemError);
+
+ },
+ template: /* html */`
+
+
{{$p.t('zeitsperren', 'header_zeitsperren')}} ({{uid}})
+
+
+
+
+
+
+
+ {{typ.beschreibung}}
+
+
+
+ Dienstfreistellungen nur in Absprache mit HR Service eintragen!
+
+
+
+
+
+
+
+ {{beschreibung}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ *
+
+ {{std.stunde}} ({{std.beginn}} - {{std.ende}})
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ *
+
+ {{std.stunde}} ({{std.beginn}} - {{std.ende}})
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{typ.beschreibung}}
+
+
+
+
+
+
+
+
+ {{$capitalize($p.t('zeitsperren', 'addZeitsperre'))}}
+
+
+
+ {{$capitalize($p.t('ui', 'abbrechen'))}}
+
+
+ {{$capitalize($p.t('zeitsperren', 'saveZeitsperre'))}}
+
+
+
+
+
+
+
+
+
+
+
+
+ {{$capitalize($p.t('alert', 'attention'))}}
+ {{$p.t('zeitsperren', 'info_zeitsperrenMoreDays')}}
+
+
+
+
+
+
+
+
+
+ `
+};
diff --git a/public/js/components/Cis/ZeitsperrenMitarbeiter/Details/ZeitsperrenMitarbeiteruid.js b/public/js/components/Cis/ZeitsperrenMitarbeiter/Details/ZeitsperrenMitarbeiteruid.js
new file mode 100644
index 000000000..731c745f6
--- /dev/null
+++ b/public/js/components/Cis/ZeitsperrenMitarbeiter/Details/ZeitsperrenMitarbeiteruid.js
@@ -0,0 +1,34 @@
+export default {
+ name: "timelocksMitarbeiteruid",
+ props: {
+ maUid: {
+ type: String
+ },
+ days: {
+ type: Number
+ },
+ title: {
+ type: String
+ }
+
+ },
+ methods: {
+ link(maUid, days){
+ this.$router.push({
+ name: 'ZeitsperrenMa',
+ params: {
+ type: 'ma',
+ id: maUid,
+ days: days
+ }
+ });
+ }
+ },
+ template: `
+
+ `,
+}
\ No newline at end of file
diff --git a/public/js/components/Cis/ZeitsperrenMitarbeiter/Details/listsZeitsperren.js b/public/js/components/Cis/ZeitsperrenMitarbeiter/Details/listsZeitsperren.js
new file mode 100644
index 000000000..bc2707e50
--- /dev/null
+++ b/public/js/components/Cis/ZeitsperrenMitarbeiter/Details/listsZeitsperren.js
@@ -0,0 +1,582 @@
+import {CoreFilterCmpt} from "../../../filter/Filter.js";
+import ApiMaTimelocks from "../../../../api/factory/zeitsperren.js";
+import FormForm from "../../../Form/Form.js";
+import FormInput from "../../../Form/Input.js";
+import PvAutoComplete from "../../../../../../index.ci.php/public/js/components/primevue/autocomplete/autocomplete.esm.min.js";
+
+export default {
+ name: "listsTimelocks",
+ components: {
+ FormForm,
+ FormInput,
+ PvAutoComplete,
+ CoreFilterCmpt
+ },
+ props: {
+ type: {
+ type: String,
+ default: 'all',
+ validator(value) {
+ return [
+ 'all',
+ 'fix',
+ 'lector',
+ 'oe',
+ 'ass',
+ 'stg',
+ 'ma'
+ ].includes(value)
+ }
+ },
+ tage: {
+ type: String
+ },
+ id: {
+ type: String
+ },
+ },
+ data(){
+ return {
+ interval: 14,
+ days: {},
+ arrayMaTimelocks: [],
+ listOes: [],
+ filteredOes: [],
+ selectedOe: null,
+ oe_kurzbz: null,
+ listStg: [],
+ filteredStg: [],
+ selectedStg: null,
+ studiengang_kz: null,
+ showTable: true,
+ fullName: null,
+ maUid: null,
+ validStgs: [],
+ validOes: []
+ }
+ },
+ methods: {
+ loadAllActiveZeitsperren(days){
+ this.$api
+ .call(ApiMaTimelocks.loadAllActiveZeitsperren(days))
+ .then(result => {
+ this.arrayMaTimelocks = result.data;
+ })
+ },
+ loadZeitsperrenFixeMa(days){
+ this.$api
+ .call(ApiMaTimelocks.loadAllZeitsperrenFixeMa(days))
+ .then(result => {
+ this.arrayMaTimelocks = result.data;
+ })
+ },
+ loadZeitsperrenLector(days){
+ this.$api
+ .call(ApiMaTimelocks.loadAllZeitsperrenLector(days))
+ .then(result => {
+ this.arrayMaTimelocks = result.data;
+ })
+ },
+ loadZeitsperrenOE(days, oe){
+ this.$router.push({
+ name: 'ZeitsperrenMa',
+ params: {
+ type: this.$route.params.type,
+ id: oe,
+ days: days
+ }
+ });
+ this.$api
+ .call(ApiMaTimelocks.loadAllZeitsperrenOE(days, oe))
+ .then(result => {
+ this.arrayMaTimelocks = result.data;
+ })
+ },
+ loadZeitsperrenAss(days){
+ this.$api
+ .call(ApiMaTimelocks.loadZeitsperrenAss(days))
+ .then(result => {
+ this.arrayMaTimelocks = result.data;
+ })
+ },
+ loadZeitsperrenStg(days, stg){
+ this.$router.push({
+ name: 'ZeitsperrenMa',
+ params: {
+ type: this.$route.params.type,
+ id: stg,
+ days: days
+ }
+ });
+ this.$api
+ .call(ApiMaTimelocks.loadZeitsperrenLectorStg(days, stg))
+ .then(result => {
+ this.arrayMaTimelocks = result.data;
+ })
+ },
+ loadZeitsperrenMa(days, uid){
+ this.$api
+ .call(ApiMaTimelocks.loadZeitsperrenMa(days, uid))
+ .then(result => {
+ this.arrayMaTimelocks = result.data;
+ })
+ },
+ createDays(countDays) {
+ const today = new Date();
+ //get Monday before today
+ const mondayBeforeToday = new Date(today);
+ const day = today.getDay();
+ const diff = day === 0 ? 6 : day - 1;
+
+ mondayBeforeToday.setDate(today.getDate() - diff);
+
+ this.days = Array.from({ length: countDays + 1 }, (_, i) => {
+ const date = new Date(mondayBeforeToday);
+ date.setDate(mondayBeforeToday.getDate() + i);
+
+ return {
+ date: date.toISOString().substring(0, 10), // 2026-07-13
+ weekday: date.toLocaleDateString('de-AT', { weekday: 'short' }),
+ day: date.toLocaleDateString('de-AT', {
+ day: '2-digit',
+ month: '2-digit'
+ })
+ };
+ });
+ },
+ createArrayMa(type){
+ this.showTable = true;
+ if(type == "fix") {
+ if(this.id && this.isZifferString(this.id))
+ this.interval = Number(this.id) < 100 ? Number(this.id) : this.interval;
+
+ this.loadZeitsperrenFixeMa(this.interval);
+ }
+ else if(type == "lector") {
+ if(this.id && this.isZifferString(this.id))
+ this.interval = Number(this.id) < 100 ? Number(this.id) : this.interval;
+ this.loadZeitsperrenLector(this.interval);
+ }
+ else if(type == "oe") {
+ this.loadOes().then(() => {
+ if(this.id && this.validOes.includes(this.id)) {
+ const countDays = this.tage ? Number(this.tage) : this.interval;
+
+ this.oe_temp = this.listOes.filter(
+ item => item.oe_kurzbz === this.id
+ );
+ this.selectedOe = this.oe_temp[0];
+
+ this.loadZeitsperrenOE(
+ countDays,
+ this.selectedOe.oe_kurzbz
+ );
+ } else {
+ if(this.id)
+ this.interval = Number(this.id) < 100
+ ? Number(this.id)
+ : this.interval;
+
+ this.showTable = false;
+ this.selectedOe = null;
+ }
+ });
+ }
+ else if(type == "stg") {
+ this.loadStgs().then(() => {
+ if(this.id && this.validStgs.includes(Number(this.id)))
+ {
+ const countDays= this.tage ? Number(this.tage) : this.interval;
+ this.stg_temp = this.listStg.filter(
+ item => item.studiengang_kz === Number(this.id)
+ );
+ this.selectedStg = this.stg_temp[0];
+ this.loadZeitsperrenStg(countDays, this.id)
+ }
+ else
+ {
+ if(this.id && this.isZifferString(this.id))
+ this.interval = Number(this.id) < 100 ? Number(this.id) : this.interval;
+ this.showTable = false;
+ this.selectedStg = null;
+ }
+ });
+ }
+ else if(type == "all") {
+ if(this.id && this.isZifferString(this.id))
+ this.interval = Number(this.id) < 100 ? Number(this.id) : this.interval;
+ this.loadAllActiveZeitsperren(this.interval);
+ }
+ else if(type == "ass") {
+ if(this.id && this.isZifferString(this.id))
+ this.interval = Number(this.id) < 100 ? Number(this.id) : this.interval;
+ this.loadZeitsperrenAss(this.interval);
+ }
+ else if(type == "ma") {
+ if(!this.id)
+ this.$fhcAlert.alertError(this.$p.t('ui', 'error_missingId', {id: "mitarbeiteruid"}));
+ const countDays= this.tage ? this.tage : this.interval;
+ this.loadZeitsperrenMa(countDays, this.id);
+ this.loadDetailsMa(this.id);
+ }
+ else
+ {
+ //route back if no type found
+ this.$router.push({
+ name: 'ZeitsperrenMa',
+ });
+ }
+ },
+ isBlocked(mitarbeiter, tag) {
+ if (!mitarbeiter?.sperren) {
+ return false;
+ }
+
+ return mitarbeiter.sperren.some(s =>
+ tag >= s.vondatum &&
+ tag <= s.bisdatum
+ );
+ },
+ getSperre(mitarbeiter, tag){
+ return mitarbeiter.sperren.find(s =>
+ tag >= s.vondatum &&
+ tag <= s.bisdatum
+ );
+ },
+ limitDays(event) {
+ let value = event.target.value.replace(/\D/g, '').slice(0, 2);
+ event.target.value = value;
+ this.interval = value ? Number(value) : null;
+ },
+ filterOes(event) {
+ const query = event.query.toLowerCase();
+
+ this.filteredOes = this.listOes.filter(item =>
+ item.label.toLowerCase().includes(query)
+ );
+ },
+ filterStg(event) {
+ const query = event.query.toLowerCase();
+
+ this.filteredStg = this.listStg.filter(item =>
+ item.label.toLowerCase().includes(query)
+ );
+ },
+ link(uid) {
+ return (
+ FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router +
+ "/Cis/Profil/View/"+
+ uid
+ );
+ },
+ loadDetailsMa(uid){
+ if(!uid)
+ this.$fhcAlert.alertError(this.$p.t('ui', 'error_fieldNotFound', {field: "UID Mitarbeiter"}));
+
+ this.$api
+ .call(ApiMaTimelocks.getDetailsMa(uid))
+ .then(result => {
+ this.fullName = result.data;
+ })
+ },
+ isZifferString(wert) {
+ //to check if the wert of the route param is a ziffer or a real String
+ return typeof wert === 'string' && wert.trim() !== '' && !isNaN(wert);
+ },
+ async loadOes() {
+ try {
+ const result = await this.$api.call(ApiMaTimelocks.getAllOes());
+ this.listOes = result.data.filter(item => item.aktiv);
+ this.validOes = this.listOes.map(item => item.oe_kurzbz);
+ } catch (e) {
+ this.$fhcAlert.handleSystemError(e);
+ }
+ return this.validOes;
+ },
+ async loadStgs() {
+ try {
+ const result = await this.$api.call(ApiMaTimelocks.getAllStg());
+ this.listStg = result.data;
+ this.validStgs = this.listStg.map(item => item.studiengang_kz);
+ } catch (e) {
+ this.$fhcAlert.handleSystemError(e);
+ }
+ return this.validOes;
+ },
+ checkRoute(){
+ if (this.type === 'ma' && !this.id) {
+ //const errorString = this.$p.t('ui', 'successSave');
+ const errorString = "Keine ID für Mitarbeiter übergeben";
+ return errorString;
+ }
+ if (this.type === 'stg' && !this.isZifferString(this.id)) {
+ //const errorString = this.$p.t('ui', 'successSave');
+ const errorString = "Studiengangskennzahl darf nur aus Zahlen bestehen";
+ return errorString;
+ }
+ }
+ },
+ computed: {
+ today(){
+ const today = new Date();
+ const day = today.toLocaleDateString('de-AT', {
+ day: '2-digit',
+ month: '2-digit',
+ year: 'numeric'
+ });
+ return day;
+ },
+ mondayBeforeToday(){
+ const today = new Date();
+ //get Monday before today
+ const mondayBeforeToday = new Date(today);
+ const day = today.getDay();
+ const diff = day === 0 ? 6 : day - 1;
+
+ mondayBeforeToday.setDate(today.getDate() - diff);
+
+ return mondayBeforeToday;
+ },
+ endInterval(){
+ const today = new Date();
+ const end = new Date(this.mondayBeforeToday);
+ end.setDate(end.getDate() + this.interval);
+
+ return end.toLocaleDateString('de-AT', {
+ day: '2-digit',
+ month: '2-digit',
+ year: 'numeric'
+ });
+ },
+ mitarbeiter() {
+ const map = {};
+
+ this.arrayMaTimelocks.forEach(z => {
+ if (!map[z.uid]) {
+ map[z.uid] = [];
+ }
+
+ map[z.uid].push(z);
+ });
+
+ return Object.entries(map).map(([mitarbeiter_uid, sperren]) => ({
+ mitarbeiter_uid,
+ sperren
+ }));
+ },
+ },
+ watch: {
+ interval: {
+ handler(newVal) {
+ if (newVal) {
+ if(this.type == "fix") {
+ this.loadZeitsperrenFixeMa(newVal);
+ }
+ else if(this.type == "lector") {
+ this.loadZeitsperrenLector(newVal);
+ }
+ else if(this.type == "oe") {
+ if(this.oe_kurzbz && this.oe_kurzbz !="")
+ this.loadZeitsperrenOE(newVal, this.oe_kurzbz);
+ }
+ else if(this.type == "all")
+ {
+ this.loadAllActiveZeitsperren(newVal);
+ }
+ else if(this.type == "ass") {
+ this.loadZeitsperrenAss(newVal);
+ }
+ else if(this.type == "stg") {
+ if(this.selectedStg )
+ this.loadZeitsperrenStg(newVal, this.studiengang_kz);
+ }
+ else if(this.type == "ma") {
+ this.loadZeitsperrenMa(newVal, this.maUid);
+ }
+ else
+ {
+ this.$fhcAlert.alertError(this.$p.t('ui', 'error_fieldNotFound', {field: "Typ Zeitsperre"}));
+ }
+ this.createDays(newVal);
+ }
+ },
+ deep: true,
+ },
+ selectedOe(newVal) {
+ this.oe_kurzbz = newVal?.oe_kurzbz || '';
+ },
+ oe_kurzbz(newVal){
+ {
+ if(newVal != "") {
+ this.loadZeitsperrenOE(this.interval, newVal);
+ this.showTable = true;
+ }
+ }
+ },
+ selectedStg(newVal) {
+ this.studiengang_kz = newVal?.studiengang_kz || '';
+ },
+ studiengang_kz(newVal){
+ {
+ if(newVal != "") {
+ this.loadZeitsperrenStg(this.interval, newVal);
+ this.showTable = true;
+ }
+ }
+ },
+ type(newVal){
+ {
+ if(newVal != "")
+ this.createArrayMa(newVal);
+ }
+ },
+ },
+ created(){
+ const error = this.checkRoute();
+ if (error) {
+ console.log(error);
+ this.$fhcAlert.alertError(error);
+ return;
+ }
+
+ this.createArrayMa(this.type);
+ if(this.tage && Number(this.tage) < 100) {
+ this.createDays(Number(this.tage));
+ this.interval = Number(this.tage);
+ }
+ else
+ this.createDays(this.interval);
+
+ if (this.type === 'oe') {
+ this.oe_kurzbz = this.id
+ } else if (this.type === 'stg'){
+ this.studiengang_kz = this.id;
+ }
+ },
+ template: /*html*/ `
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ day.weekday }}
+ {{ day.day }}
+
+
+
+
+
+ {{m.sperren[0].nachname}} {{m.sperren[0].vorname}}
+
+
+ {{$p.t('zeitsperren/abwesend')}}
+
+ E: {{ sperre.erreichbarkeit_kurzbz }}
+
+
+
+
+
+
+
{{$p.t('ui/keineEintraegeGefunden')}}
+
+
+
+
+ `,
+}
\ No newline at end of file
diff --git a/public/js/components/Cis/ZeitsperrenMitarbeiter/ZeitsperrenMa.js b/public/js/components/Cis/ZeitsperrenMitarbeiter/ZeitsperrenMa.js
new file mode 100644
index 000000000..15eef628a
--- /dev/null
+++ b/public/js/components/Cis/ZeitsperrenMitarbeiter/ZeitsperrenMa.js
@@ -0,0 +1,89 @@
+import listsZeitsperren from './Details/listsZeitsperren.js';
+import MitarbeiterZeitsperren from './Details/ZeitsperrenMitarbeiteruid.js';
+export const DEFAULT_INTERVAL_DAYS = 14;
+
+export default {
+ name: 'ZeitsperrenMa',
+ components: {
+ listsZeitsperren,
+ MitarbeiterZeitsperren
+ },
+ props: {
+ propsViewData: Object
+ },
+ data(){
+ return {
+ activeView: null,
+ buttons: [
+ {
+ key: 'all',
+ title: 'zeitsperren/btn_all_current',
+ class: 'btn-primary'
+ },
+ {
+ key: 'fix',
+ title: 'zeitsperren/btn_all_fix',
+ class: 'btn-secondary'
+ },
+ {
+ key: 'lector',
+ title: 'zeitsperren/btn_fix_lect',
+ class: 'btn-secondary'
+ },
+ {
+ key: 'oe',
+ title: 'zeitsperren/btn_oes',
+ class: 'btn-secondary'
+ },
+ {
+ key: 'ass',
+ title: 'zeitsperren/btn_all_ass',
+ class: 'btn-secondary'
+ },
+ {
+ key: 'stg',
+ title: 'zeitsperren/btn_lect_stg',
+ class: 'btn-secondary'
+ }
+ ],
+ }
+ },
+ methods: {
+ show(view) {
+ this.activeView = this.activeView === view ? null : view;
+ },
+ route(view) {
+ this.activeView = this.activeView === view ? null : view;
+ this.$router.push({
+ name: 'ZeitsperrenMa',
+ params: {
+ type: this.activeView,
+ days: DEFAULT_INTERVAL_DAYS
+ }
+ });
+ },
+ },
+ template: `
+
+
+
+ {{ $p.t(button.title) }}
+
+
+
+
+
+
+
+ `,
+}
\ No newline at end of file
diff --git a/public/js/components/DashboardWidget/News.js b/public/js/components/DashboardWidget/News.js
index 103bc4fa1..1813d1812 100644
--- a/public/js/components/DashboardWidget/News.js
+++ b/public/js/components/DashboardWidget/News.js
@@ -6,18 +6,17 @@ import ApiCms from '../../api/factory/cms.js';
const MAX_LOADED_NEWS = 30;
export default {
- name: "WidgetsNews",
+ name: 'WidgetsNews',
components: {
- BsModal
+ BsModal,
},
data: () => ({
allNewsList: [],
singleNews: {},
selected: null,
- size:0,
+ size: 0,
}),
- props: ['width', 'height']
- ,
+ props: ['width', 'height'],
mixins: [AbstractWidget],
computed: {
sizeClass() {
@@ -28,7 +27,6 @@ export default {
// let quantity = this.width;
let quantity = MAX_LOADED_NEWS;
-
if (this.width === 1) {
quantity = this.height === 1 ? 4 : MAX_LOADED_NEWS;
}
@@ -39,107 +37,129 @@ export default {
},
carouselItems() {
return this.allNewsList.reduce((acc, cur) => {
- const el = document.getElementById('card-'+cur.news_id)
+ const el = document.getElementById('card-' + cur.news_id);
acc.push(el);
- return acc
- }, [])
- }
+ return acc;
+ }, []);
+ },
},
methods: {
- updateNewsContentClasses:function(){
+ updateNewsContentClasses: function () {
Vue.nextTick(() => {
- document.querySelectorAll(".fhc-news-card-item .card-body, .fhc-news-card-item .card, .fhc-news-card-item .card-header").forEach((el) => {
- if (!el.classList.contains("border-0")) {
- el.classList.add("border-0");
- }
- });
- document.querySelectorAll(".fhc-news-card-item .card-header").forEach((el) => {
- if (!el.classList.contains("px-5")) {
- el.classList.add("px-5");
- }
- if (!el.classList.contains("fhc-primary")) {
- el.classList.add("fhc-primary");
- }
- if (!el.classList.contains("position-sticky")) {
- el.classList.add("position-sticky");
- }
- if (!el.classList.contains("top-0")) {
- el.classList.add("top-0");
- }
-
-
- });
- document.querySelectorAll(".fhc-news-card-item .card-header .row").forEach((el) => {
- if (!el.classList.contains("w-100")) {
- el.classList.add("w-100");
- }
- if (!el.classList.contains("align-items-center")) {
- el.classList.add("align-items-center");
- }
- });
- document.querySelectorAll(".fhc-news-card-item .card-header .row h2").forEach((el) => {
- if (!el.classList.contains("mb-0")) {
- el.classList.add("mb-0");
- }
- });
- })
+ document
+ .querySelectorAll(
+ '.fhc-news-card-item .card-body, .fhc-news-card-item .card, .fhc-news-card-item .card-header',
+ )
+ .forEach((el) => {
+ if (!el.classList.contains('border-0')) {
+ el.classList.add('border-0');
+ }
+ });
+ document
+ .querySelectorAll('.fhc-news-card-item .card-header')
+ .forEach((el) => {
+ if (!el.classList.contains('px-5')) {
+ el.classList.add('px-5');
+ }
+ if (!el.classList.contains('fhc-primary')) {
+ el.classList.add('fhc-primary');
+ }
+ if (!el.classList.contains('position-sticky')) {
+ el.classList.add('position-sticky');
+ }
+ if (!el.classList.contains('top-0')) {
+ el.classList.add('top-0');
+ }
+ });
+ document
+ .querySelectorAll('.fhc-news-card-item .card-header .row')
+ .forEach((el) => {
+ if (!el.classList.contains('w-100')) {
+ el.classList.add('w-100');
+ }
+ if (!el.classList.contains('align-items-center')) {
+ el.classList.add('align-items-center');
+ }
+ });
+ document
+ .querySelectorAll('.fhc-news-card-item .card-header .row h2')
+ .forEach((el) => {
+ if (!el.classList.contains('mb-0')) {
+ el.classList.add('mb-0');
+ }
+ });
+ });
},
formatDate: function (dateTime) {
const dt = new Date(dateTime);
- return numberPadding(dt.getDate()) + '.' + numberPadding((dt.getMonth() + 1)) + '.' + dt.getFullYear();
+ return (
+ numberPadding(dt.getDate()) +
+ '.' +
+ numberPadding(dt.getMonth() + 1) +
+ '.' +
+ dt.getFullYear()
+ );
},
formatTime: function (dateTime) {
const dt = new Date(dateTime);
- return numberPadding(dt.getHours()) + ':' + numberPadding(dt.getMinutes());
+ return (
+ numberPadding(dt.getHours()) + ':' + numberPadding(dt.getMinutes())
+ );
},
- isString(value){
+ isString(value) {
return Object.prototype.toString.call(value) === '[object String]';
},
- setNext(){
- const thisIndex = this.allNewsList.findIndex(n=>n.news_id == this.selected.news_id)
- const nextIndex = thisIndex == (this.allNewsList.length - 1) ? 0 : thisIndex + 1
+ setNext() {
+ const thisIndex = this.allNewsList.findIndex(
+ (n) => n.news_id == this.selected.news_id,
+ );
+ const nextIndex =
+ thisIndex == this.allNewsList.length - 1 ? 0 : thisIndex + 1;
- this.setSelected(this.allNewsList[nextIndex])
+ this.setSelected(this.allNewsList[nextIndex]);
this.updateNewsContentClasses();
-
},
setPrev() {
- const thisIndex = this.allNewsList.findIndex(n=>n.news_id == this.selected.news_id)
- const prevIndex = thisIndex ? thisIndex - 1 : this.allNewsList.length - 1
+ const thisIndex = this.allNewsList.findIndex(
+ (n) => n.news_id == this.selected.news_id,
+ );
+ const prevIndex = thisIndex ? thisIndex - 1 : this.allNewsList.length - 1;
- this.setSelected(this.allNewsList[prevIndex])
+ this.setSelected(this.allNewsList[prevIndex]);
this.updateNewsContentClasses();
},
getMenuItemClass(news) {
- let classString = ''
- if(this.selected && this.selected.news_id === news.news_id) {
- classString += 'selected'
+ let classString = '';
+ if (this.selected && this.selected.news_id === news.news_id) {
+ classString += 'selected';
}
- return classString
+ return classString;
},
async setSelected(news) {
- const clickedElement = document.getElementById('card-'+news.news_id);
+ const clickedElement = document.getElementById('card-' + news.news_id);
const clickedElementIndex = this.allNewsList.indexOf(news);
const oldElementIndex = this.allNewsList.indexOf(this.selected);
-
+
//if the clicked element is already active, do nothing
- if(clickedElementIndex === oldElementIndex) return;
+ if (clickedElementIndex === oldElementIndex) return;
//add prev/next class to the clicked element
- if(clickedElementIndex > oldElementIndex) {
+ if (clickedElementIndex > oldElementIndex) {
clickedElement.classList.add('carousel-item-next');
- }else{
+ } else {
clickedElement.classList.add('carousel-item-prev');
}
// move to clicked element
- await Vue.nextTick(() => { this.carouselInstance.to(clickedElementIndex); })
+ await Vue.nextTick(() => {
+ this.carouselInstance.to(clickedElementIndex);
+ });
this.selected = news;
},
contentURI: function (content_id) {
return (
FHC_JS_DATA_STORAGE_OBJECT.app_root +
FHC_JS_DATA_STORAGE_OBJECT.ci_router +
- "/CisVue/Cms/content/" +
+ '/CisVue/Cms/content/' +
content_id
);
},
@@ -147,7 +167,7 @@ export default {
return (
FHC_JS_DATA_STORAGE_OBJECT.app_root +
FHC_JS_DATA_STORAGE_OBJECT.ci_router +
- "/CisVue/Cms/news"
+ '/CisVue/Cms/news'
);
},
setSingleNews(singleNews) {
@@ -155,79 +175,78 @@ export default {
this.$refs.newsModal.show();
},
initCarouselInstance() {
- Vue.nextTick(()=> {
- if(this.$refs.carousel) { // carousel ref might not exist in every widget width/height
+ Vue.nextTick(() => {
+ if (this.$refs.carousel) {
+ // carousel ref might not exist in every widget width/height
this.carouselInstance = new bootstrap.Carousel(this.$refs.carousel, {
wrap: false, // keep this off even though it actually wraps
- interval: false
+ interval: false,
});
}
- })
+ });
},
initActiveItem() {
- Vue.nextTick(()=> {
- if (Array.isArray(this.$refs.carouselItems) && this.$refs.carouselItems.length >0) {
- this.$refs.carouselItems[0].classList.add("active")
+ Vue.nextTick(() => {
+ if (
+ Array.isArray(this.$refs.carouselItems) &&
+ this.$refs.carouselItems.length > 0
+ ) {
+ this.$refs.carouselItems[0].classList.add('active');
}
- })
- }
+ });
+ },
},
watch: {
width(newVal, oldVal) {
- if(oldVal == 1 && newVal > 1) { // carousel instance will have been disposed
- this.initCarouselInstance()
- this.initActiveItem()
+ if (oldVal == 1 && newVal > 1) {
+ // carousel instance will have been disposed
+ this.initCarouselInstance();
+ this.initActiveItem();
}
- }
+ },
},
created() {
- this.$emit("setConfig", false);
+ this.$emit('setConfig', false);
this.$api
- .call(ApiCms.news(MAX_LOADED_NEWS))
- .then(res => res.data)
+ .call(ApiCms.newsForWidget(MAX_LOADED_NEWS))
+ .then((res) => res.data)
.then((news) => {
this.allNewsList = Array.from(Object.values(news));
this.allNewsList.sort((a, b) => {
return new Date(b.datum) - new Date(a.datum);
});
- this.selected = this.allNewsList.length ? this.allNewsList[0] : null
- this.initActiveItem()
+ this.selected = this.allNewsList.length ? this.allNewsList[0] : null;
+ this.initActiveItem();
this.updateNewsContentClasses();
-
})
.catch((err) => {
- console.error("ERROR: ", err);
+ console.error('ERROR: ', err);
});
-
},
mounted() {
if (this.$refs.container) {
- new ResizeObserver(entries => {
+ new ResizeObserver((entries) => {
for (const entry of entries) {
- let w = entry.contentBoxSize ? entry.contentBoxSize[0].inlineSize : entry.contentRect.width;
+ let w = entry.contentBoxSize
+ ? entry.contentBoxSize[0].inlineSize
+ : entry.contentRect.width;
// TODO(chris): rework sizing
- if (w > 600)
- this.size = 3;
- else if (w > 350)
- this.size = 2;
- else if (w > 250)
- this.size = 1;
- else
- this.size = 0;
- }
+ if (w > 600) this.size = 3;
+ else if (w > 350) this.size = 2;
+ else if (w > 250) this.size = 1;
+ else this.size = 0;
+ }
}).observe(this.$refs.container);
}
-
- this.initCarouselInstance()
-
+ this.initCarouselInstance();
},
template: /*html*/ `