diff --git a/application/config/stv.php b/application/config/stv.php index e03c00084..84b148362 100644 --- a/application/config/stv.php +++ b/application/config/stv.php @@ -61,7 +61,11 @@ $config['tabs'] = 'notes' => [ //if true, the count of Messages will be shown in the header of the Tab Messages 'showCountNotes' => true - ] + ], + 'combinePeople' => [ + //multitab should only be shown with this length of selection + 'validCountMulti' => 2, + ], ]; // List of fields to show when ZGV_DOKTOR_ANZEIGEN is defined @@ -117,5 +121,6 @@ $config['students_tab_order'] = [ 'status', 'groups', 'finalexam', + 'combinePeople', 'archive', ]; diff --git a/application/controllers/Cis/Pub.php b/application/controllers/Cis/Pub.php index bebc844ab..de456145b 100644 --- a/application/controllers/Cis/Pub.php +++ b/application/controllers/Cis/Pub.php @@ -14,7 +14,7 @@ class Pub extends Auth_Controller { parent::__construct( array( - 'bild' => ['basis/cis:r'] + 'bild' => ['basis/cis:r', 'assistenz:r'] ) ); } diff --git a/application/controllers/api/frontend/v1/stv/Aufnahmetermine.php b/application/controllers/api/frontend/v1/stv/Aufnahmetermine.php index 86b739c90..26033908d 100644 --- a/application/controllers/api/frontend/v1/stv/Aufnahmetermine.php +++ b/application/controllers/api/frontend/v1/stv/Aufnahmetermine.php @@ -298,7 +298,7 @@ class Aufnahmetermine extends FHCAPI_Controller $reihungstestangetreten = (isset($formData['reihungstestangetreten']) && !empty($formData['reihungstestangetreten'])) ? $formData['reihungstestangetreten'] - : null; + : false; $aufnahmegruppe_kurzbz = (isset($formData['aufnahmegruppe_kurzbz']) && !empty($formData['aufnahmegruppe_kurzbz'])) ? $formData['aufnahmegruppe_kurzbz'] diff --git a/application/controllers/api/frontend/v1/stv/Config.php b/application/controllers/api/frontend/v1/stv/Config.php index 97d626246..3bf48bf5b 100644 --- a/application/controllers/api/frontend/v1/stv/Config.php +++ b/application/controllers/api/frontend/v1/stv/Config.php @@ -33,6 +33,8 @@ class Config extends FHCAPI_Controller { // TODO(chris): permissions parent::__construct([ + 'get' => ['admin:r', 'assistenz:r'], + 'set' => ['admin:r', 'assistenz:r'], 'filter' => ['admin:r', 'assistenz:r'], 'student' => ['admin:r', 'assistenz:r'], 'students' => ['admin:r', 'assistenz:r'] @@ -55,6 +57,95 @@ class Config extends FHCAPI_Controller } /** + * get App config + */ + public function get() + { + $this->load->model('system/Variable_model', 'VariableModel'); + $this->load->config('stv'); + + $config = []; + + #number_displayed_past_studiensemester + $result = $this->VariableModel->getVariables(getAuthUID(), ['number_displayed_past_studiensemester']); + $data = $this->getDataOrTerminateWithError($result); + + $number_displayed_past_studiensemester_default = $this->config->item('number_displayed_past_studiensemester_default'); + + $config['number_displayed_past_studiensemester'] = [ + "type" => "number", + "label" => $this->p->t('stv', 'settings_no_displayed_past_sem'), + "value" => $data['number_displayed_past_studiensemester'] + ?? $number_displayed_past_studiensemester_default + ]; + + #font_size + $result = $this->VariableModel->getVariables(getAuthUID(), ['stv_font_size']); + $data = $this->getDataOrTerminateWithError($result); + $config['font_size'] = [ + "type" => "select", + "label" => $this->p->t('stv', 'settings_fontsize'), + "value" => $data['stv_font_size'] ?? "fs_normal", + "options" => [ + "fs_xx-small" => $this->p->t('stv', 'settings_fontsize_xx-small'), + "fs_x-small" => $this->p->t('stv', 'settings_fontsize_x-small'), + "fs_small" => $this->p->t('stv', 'settings_fontsize_small'), + "fs_normal" => $this->p->t('stv', 'settings_fontsize_normal'), + "fs_big" => $this->p->t('stv', 'settings_fontsize_big'), + "fs_huge" => $this->p->t('stv', 'settings_fontsize_huge') + ] + ]; + + #others + Events::trigger('stv_config_get', function & () use (&$config) { + return $config; + }); + + $this->terminateWithSuccess($config); + } + + /** + * set App config + */ + public function set() + { + $this->load->model('system/Variable_model', 'VariableModel'); + $this->load->library('form_validation'); + + $this->form_validation->set_rules( + 'number_displayed_past_studiensemester', + $this->p->t('stv', 'settings_no_displayed_past_sem'), + 'required|integer' + ); + $this->form_validation->set_rules( + 'font_size', + $this->p->t('stv', 'settings_fontsize'), + 'required|in_list[fs_xx-small,fs_x-small,fs_small,fs_normal,fs_big,fs_huge]' + ); + + Events::trigger('stv_config_validation', $this->form_validation); + + if (!$this->form_validation->run()) + $this->terminateWithValidationErrors($this->form_validation->error_array()); + + + $this->VariableModel->setVariable( + getAuthUID(), + 'number_displayed_past_studiensemester', + $this->input->post('number_displayed_past_studiensemester') + ); + $this->VariableModel->setVariable( + getAuthUID(), + 'stv_font_size', + $this->input->post('font_size') + ); + + Events::trigger('stv_config_set', $this->input); + + $this->terminateWithSuccess(); + } + + /* * Get the config for the student filters * * @return void @@ -407,6 +498,15 @@ class Config extends FHCAPI_Controller ] ]; + if($this->permissionlib->isBerechtigt('basis/person')) + { + $result['combinePeople'] = [ + 'title' => $this->p->t('stv', 'tab_combine_people'), + 'component' => './Stv/Studentenverwaltung/Details/CombinePeople.js', + 'config' => $config['combinePeople'] + ]; + } + $result['kontaktieren'] = [ 'title' => $this->p->t('stv', 'tab_kontaktieren'), 'component' => absoluteJsImportUrl('public/js/components/Stv/Studentenverwaltung/Details/Kontaktieren.js'), diff --git a/application/controllers/api/frontend/v1/stv/GemeinsameStudien.php b/application/controllers/api/frontend/v1/stv/GemeinsameStudien.php index 97dad48fd..8f3d6419a 100644 --- a/application/controllers/api/frontend/v1/stv/GemeinsameStudien.php +++ b/application/controllers/api/frontend/v1/stv/GemeinsameStudien.php @@ -83,7 +83,7 @@ class GemeinsameStudien extends FHCAPI_Controller { $this->load->model('organisation/Studiensemester_model', 'StudiensemesterModel'); - $this->StudiensemesterModel->addOrder('studienjahr_kurzbz', 'DESC'); + $this->StudiensemesterModel->addOrder('start', 'DESC'); $result = $this->StudiensemesterModel->load(); $data = $this->getDataOrTerminateWithError($result); $this->terminateWithSuccess($data); diff --git a/application/controllers/api/frontend/v1/stv/Gruppen.php b/application/controllers/api/frontend/v1/stv/Gruppen.php index c45165b41..b10cfb328 100644 --- a/application/controllers/api/frontend/v1/stv/Gruppen.php +++ b/application/controllers/api/frontend/v1/stv/Gruppen.php @@ -147,7 +147,8 @@ class Gruppen extends FHCAPI_Controller 'lehre' => true, 'sichtbar' => true, 'aktiv' => true, - 'direktinskription' => false + 'direktinskription' => false, + 'generiert' => false ]); $data = $this->getDataOrTerminateWithError($result); diff --git a/application/controllers/api/frontend/v1/stv/Projektarbeit.php b/application/controllers/api/frontend/v1/stv/Projektarbeit.php index 75478332f..9cc604184 100644 --- a/application/controllers/api/frontend/v1/stv/Projektarbeit.php +++ b/application/controllers/api/frontend/v1/stv/Projektarbeit.php @@ -268,10 +268,6 @@ class Projektarbeit extends FHCAPI_Controller { $this->form_validation->set_data($formData); - $this->form_validation->set_rules('titel', 'Titel', 'required', [ - 'required' => $this->p->t('ui', 'error_fieldRequired', ['field' => 'Titel']) - ]); - $this->form_validation->set_rules('projekttyp_kurzbz', 'Projekttyp', 'required', [ 'required' => $this->p->t('ui', 'error_fieldRequired', ['field' => 'Projekttyp']) ]); diff --git a/application/controllers/api/frontend/v1/stv/Pruefung.php b/application/controllers/api/frontend/v1/stv/Pruefung.php index e205c85b8..4521c2033 100644 --- a/application/controllers/api/frontend/v1/stv/Pruefung.php +++ b/application/controllers/api/frontend/v1/stv/Pruefung.php @@ -18,6 +18,8 @@ if (! defined('BASEPATH')) exit('No direct script access allowed'); +use \DateTime as DateTime; + /** * This controller operates between (interface) the JS (GUI) and the back-end * Provides data to the ajax get calls about addresses @@ -111,7 +113,7 @@ class Pruefung extends FHCAPI_Controller // Load language phrases $this->loadPhrases([ - 'global', 'ui','lehre' + 'global', 'ui', 'lehre', 'exam' ]); } @@ -172,174 +174,11 @@ class Pruefung extends FHCAPI_Controller * * @param lehrveranstaltung_id, student_uid, lehreinheit_id * - * @return values on success - * retval 0: pruefung inserted - * reval 1: pruefung and zeugnisnote inserted - * retval 2: pruefung inserted, no insert Zeugnisnote - * (change after date of examination) - * retval 3: pruefung of type Termin2 inserted - * and pruefung of type Termin1 as well - * retval 5: prueufungen Termin 2 and 1 inserted - * and no insert Zeugnisnote (change after date of examination) + * @return void */ public function insertPruefung() { - $authUID = getAuthUID(); - - $this->load->library('form_validation'); - - $this->form_validation->set_rules('lehrveranstaltung_id', $this->p->t('lehre', 'lehrveranstaltung'), 'required', [ - 'required' => $this->p->t('ui', 'error_fieldRequired', ['field' => $this->p->t('lehre', 'lehrveranstaltung')]), - ]); - $this->form_validation->set_rules('lehreinheit_id', $this->p->t('lehre', 'lehreinheit'), 'required', [ - 'required' => $this->p->t('ui', 'error_fieldRequired', ['field' => $this->p->t('lehre', 'lehreinheit')]), - ]); - $this->form_validation->set_rules('pruefungstyp_kurzbz', $this->p->t('lehre', 'pruefung'), 'required', [ - 'required' => $this->p->t('ui', 'error_fieldRequired', ['field' => $this->p->t('global', 'typ')]), - ]); - $this->form_validation->set_rules( - 'datum', - $this->p->t('global', 'datum'), - ['is_valid_date'] - ); - - if ($this->form_validation->run() == false) - { - $this->terminateWithValidationErrors($this->form_validation->error_array()); - } - - //calculate studiensemester_kurzbz this from lehreinheit (case newPruefung) - $studiensemester_kurzbz = $this->input->post('studiensemester_kurzbz'); - if (!$studiensemester_kurzbz) - { - $this->load->model('education/Lehreinheit_model', 'LehreinheitModel'); - - $result = $this->LehreinheitModel->load($this->input->post('lehreinheit_id')); - - $lehreinheit = $this->getDataOrTerminateWithError($result); - $studiensemester_kurzbz = current($lehreinheit)->studiensemester_kurzbz; - - if (isError($result)) - { - $this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL); - } - } - - $result = $this->PruefungModel->insert([ - 'lehreinheit_id' => $this->input->post('lehreinheit_id'), - 'student_uid' => $this->input->post('student_uid'), - 'mitarbeiter_uid' => $this->input->post('mitarbeiter_uid'), - 'datum' => $this->input->post('datum'), - 'pruefungstyp_kurzbz' => $this->input->post('pruefungstyp_kurzbz'), - 'note' => $this->input->post('note'), - 'anmerkung' => $this->input->post('anmerkung'), - 'insertamum' => date('c'), - 'insertvon' => $authUID, - ]); - - $this->getDataOrTerminateWithError($result); - - //check if existing zeugnisnote - $this->load->model('education/Zeugnisnote_model', 'ZeugnisnoteModel'); - - $result = $this->ZeugnisnoteModel->loadWhere(array( - 'lehrveranstaltung_id' => $this->input->post('lehrveranstaltung_id'), - 'student_uid' => $this->input->post('student_uid'), - 'studiensemester_kurzbz' => $studiensemester_kurzbz)); - - if (isError($result)) - { - $this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL); - } - - if (!hasData($result)) - { - //insert zeugnisnote, if not existing - $result = $this->ZeugnisnoteModel->insert(array( - 'lehrveranstaltung_id' => $this->input->post('lehrveranstaltung_id'), - 'student_uid' => $this->input->post('student_uid'), - 'studiensemester_kurzbz' => $studiensemester_kurzbz, - 'note' => $this->input->post('note'), - 'uebernahmedatum' => date('c'), - 'benotungsdatum' => $this->input->post('datum'), - 'insertamum' => date('c'), - 'insertvon' => $authUID - )); - - if (isError($result)) - { - $this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL); - } - $this->terminateWithSuccess(1); - } - - $return_code = 0; - - //handling Termin1 if not existing - if($this->input->post('pruefungstyp_kurzbz') == "Termin2") - { - $resultP = $this->PruefungModel->loadWhere(array( - 'lehreinheit_id' => $this->input->post('lehreinheit_id'), - 'student_uid' => $this->input->post('student_uid'), - 'pruefungstyp_kurzbz' => 'Termin1')); - - if (isError($resultP)) - { - $this->terminateWithError(getError($resultP), self::ERROR_TYPE_GENERAL); - } - if(!hasData($resultP)) - { - //check if existing Zeugnisnote - $this->load->model('education/Zeugnisnote_model', 'ZeugnisnoteModel'); - $this->ZeugnisnoteModel->addJoin('lehre.tbl_lehreinheit', 'lehrveranstaltung_id'); - - $resultP = $this->ZeugnisnoteModel->loadWhere(array( - 'lehrveranstaltung_id' => $this->input->post('lehrveranstaltung_id'), - 'student_uid' => $this->input->input->post('student_uid'), - 'lehre.tbl_zeugnisnote.studiensemester_kurzbz' => $studiensemester_kurzbz)); - if (isError($resultP)) - { - $this->terminateWithError(getError($resultP), self::ERROR_TYPE_GENERAL); - } - if (!hasData($resultP)) - { - $this->terminateWithError("Zeugnisnote existiert nicht", self::ERROR_TYPE_GENERAL); - } - $dataNote = current(getData($resultP)); - - $resultN = $this->PruefungModel->insert([ - 'lehreinheit_id' => $this->input->post('lehreinheit_id'), - 'student_uid' => $this->input->post('student_uid'), - 'mitarbeiter_uid' => $this->input->post('mitarbeiter_uid'), - 'datum' => $dataNote->benotungsdatum, - 'pruefungstyp_kurzbz' => 'Termin1', - 'note' => $dataNote->note, - 'punkte' => $dataNote->punkte, - 'anmerkung' => 'automatisiert aus Zeugnisnote erstellt', - 'insertamum' => date('c'), - 'insertvon' => $authUID, - ]); - - if (isError($resultN)) { - $this->terminateWithError(getError($resultN), self::ERROR_TYPE_GENERAL); - } - $return_code = 3; - } - } - - $note = current(getData($result)); - $uebernahmedatum = new DateTime($note->uebernahmedatum); - $benotungsdatum = new DateTime($note->benotungsdatum); - - $checkDate = $uebernahmedatum === '' || $benotungsdatum > $uebernahmedatum - ? $benotungsdatum - : $uebernahmedatum; - - if ($checkDate >= $this->input->post('datum') && $note !== $note->note) - { - $this->terminateWithSuccess($return_code + 2); - } - $this->terminateWithSuccess($return_code + 2); + $this->insertOrUpdatePruefung(); } /** @@ -348,8 +187,6 @@ class Pruefung extends FHCAPI_Controller * @param pruefung_id * * @return success or error - * - * no impact on lehre.tbl_zeugnisnote */ public function updatePruefung($pruefung_id) { @@ -359,48 +196,7 @@ class Pruefung extends FHCAPI_Controller if (!$oldpruefung) show_404(); // Pruefung that should be updated does not exist - $authUID = getAuthUID(); - - $this->load->library('form_validation'); - - $this->form_validation->set_rules('lehrveranstaltung_id', $this->p->t('lehre', 'lehrveranstaltung'), 'required', [ - 'required' => $this->p->t('ui', 'error_fieldRequired', ['field' => $this->p->t('lehre', 'lehrveranstaltung')]), - ]); - $this->form_validation->set_rules('lehreinheit_id', $this->p->t('lehre', 'lehreinheit'), 'required', [ - 'required' => $this->p->t('ui', 'error_fieldRequired', ['field' => $this->p->t('lehre', 'lehreinheit')]), - ]); - $this->form_validation->set_rules('pruefungstyp_kurzbz', $this->p->t('lehre', 'pruefung'), 'required', [ - 'required' => $this->p->t('ui', 'error_fieldRequired', ['field' => $this->p->t('global', 'typ')]), - ]); - $this->form_validation->set_rules( - 'datum', - $this->p->t('global', 'datum'), - ['is_valid_date'] - ); - - if ($this->form_validation->run() == false) - { - $this->terminateWithValidationErrors($this->form_validation->error_array()); - } - - $result = $this->PruefungModel->update( - [ - 'pruefung_id' => $pruefung_id - ], - [ 'lehreinheit_id' => $this->input->post('lehreinheit_id'), - 'student_uid' => $this->input->post('student_uid'), - 'mitarbeiter_uid' => $this->input->post('mitarbeiter_uid'), - 'note' => $this->input->post('note'), - 'pruefungstyp_kurzbz' => $this->input->post('pruefungstyp_kurzbz'), - 'datum' => $this->input->post('datum'), - 'anmerkung' => $this->input->post('anmerkung'), - 'updatevon' => $authUID, - 'updateamum' => date('c'), - ] - ); - $this->getDataOrTerminateWithError($result); - - return $this->outputJsonSuccess(true); + $this->insertOrUpdatePruefung($pruefung_id); } /** @@ -574,4 +370,198 @@ class Pruefung extends FHCAPI_Controller return $this->terminateWithSuccess($data); } + + protected function insertOrUpdatePruefung($pruefung_id=null) + { + $authUID = getAuthUID(); + + $this->load->library('form_validation'); + + $this->form_validation->set_rules('lehreinheit_id', $this->p->t('lehre', 'lehreinheit'), 'required', [ + 'required' => $this->p->t('ui', 'error_fieldRequired', ['field' => $this->p->t('lehre', 'lehreinheit')]), + ]); + $this->form_validation->set_rules('pruefungstyp_kurzbz', $this->p->t('lehre', 'pruefung'), 'required', [ + 'required' => $this->p->t('ui', 'error_fieldRequired', ['field' => $this->p->t('global', 'typ')]), + ]); + $this->form_validation->set_rules( + 'datum', + $this->p->t('global', 'datum'), + ['is_valid_date'] + ); + + if ($this->form_validation->run() == false) + { + $this->terminateWithValidationErrors($this->form_validation->error_array()); + } + + $this->load->model('education/Zeugnisnote_model', 'ZeugnisnoteModel'); + + $this->PruefungModel->db->trans_start(); + + if ($this->input->post('pruefungstyp_kurzbz') == "Termin2") + { + //Wenn ein 2. Termin angelegt wird, und kein 1. Termin vorhanden ist, + //dann wird auch ein 1. Termin angelegt mit der derzeitigen Zeugnisnote + $resultP = $this->PruefungModel->loadWhere(array( + 'lehreinheit_id' => $this->input->post('lehreinheit_id'), + 'student_uid' => $this->input->post('student_uid'), + 'pruefungstyp_kurzbz' => 'Termin1')); + + $termin1 = $this->getDataOrTerminateWithError($resultP); + if (!$termin1) + { + //check if existing Zeugnisnote + $this->ZeugnisnoteModel->addJoin('lehre.tbl_lehreinheit', 'lehrveranstaltung_id'); + + $this->ZeugnisnoteModel->db->where( + 'lehre.tbl_zeugnisnote.studiensemester_kurzbz', + 'lehre.tbl_lehreinheit.studiensemester_kurzbz', + false + ); + $resultP = $this->ZeugnisnoteModel->loadWhere(array( + 'lehrveranstaltung_id' => $this->input->post('lehrveranstaltung_id'), + 'student_uid' => $this->input->post('student_uid') + )); + + $zeugnisnoten = $this->getDataOrTerminateWithError($resultP); + if ($zeugnisnoten) + { + $zeugnisnote = current($zeugnisnoten); + + $resultN = $this->PruefungModel->insert([ + 'lehreinheit_id' => $this->input->post('lehreinheit_id'), + 'student_uid' => $this->input->post('student_uid'), + 'mitarbeiter_uid' => $this->input->post('mitarbeiter_uid'), + 'datum' => $zeugnisnote->benotungsdatum, + 'pruefungstyp_kurzbz' => 'Termin1', + 'note' => $zeugnisnote->note, + 'punkte' => $zeugnisnote->punkte, + 'anmerkung' => 'automatisiert aus Zeugnisnote erstellt', + 'insertamum' => date('c'), + 'insertvon' => $authUID, + ]); + + $this->getDataOrTerminateWithError($resultN); + } + //Wenn keine Zeugnisnote vorhanden ist, dann wird kein + //1.Termin angelegt + } + } + + if(intval($pruefung_id) > 0) + { + $result = $this->PruefungModel->update( + [ + 'pruefung_id' => $pruefung_id + ], + [ 'lehreinheit_id' => $this->input->post('lehreinheit_id'), + 'student_uid' => $this->input->post('student_uid'), + 'mitarbeiter_uid' => $this->input->post('mitarbeiter_uid'), + 'note' => $this->input->post('note'), + 'pruefungstyp_kurzbz' => $this->input->post('pruefungstyp_kurzbz'), + 'datum' => $this->input->post('datum'), + 'anmerkung' => $this->input->post('anmerkung'), + 'updatevon' => $authUID, + 'updateamum' => date('c'), + ] + ); + } + else + { + $result = $this->PruefungModel->insert([ + 'lehreinheit_id' => $this->input->post('lehreinheit_id'), + 'student_uid' => $this->input->post('student_uid'), + 'mitarbeiter_uid' => $this->input->post('mitarbeiter_uid'), + 'datum' => $this->input->post('datum'), + 'pruefungstyp_kurzbz' => $this->input->post('pruefungstyp_kurzbz'), + 'note' => $this->input->post('note'), + 'anmerkung' => $this->input->post('anmerkung'), + 'insertamum' => date('c'), + 'insertvon' => $authUID, + 'punkte' => $this->input->post('punkte') ? str_replace(',', '.', $this->input->post('punkte')) : null + ]); + } + + $this->getDataOrTerminateWithError($result); + + //get studiensemester_kurzbz and lehreveranstaltung_id from lehreinheit + $this->load->model('education/Lehreinheit_model', 'LehreinheitModel'); + + $result = $this->LehreinheitModel->load($this->input->post('lehreinheit_id')); + + $lehreinheiten = $this->getDataOrTerminateWithError($result); + + if (!$lehreinheiten) { + $this->terminateWithValidationErrors([ + 'lehreinheit_id' => $this->p->t('ui', 'error_fieldNotFound', [ + 'field' => $this->p->t('lehre', 'lehreinheit') + ]) + ]); + } + $lehreinheit = current($lehreinheiten); + $studiensemester_kurzbz = $lehreinheit->studiensemester_kurzbz; + $lehrveranstaltung_id = $lehreinheit->lehrveranstaltung_id; + + //check if existing zeugnisnote + $result = $this->ZeugnisnoteModel->loadWhere(array( + 'lehrveranstaltung_id' => $lehrveranstaltung_id, + 'student_uid' => $this->input->post('student_uid'), + 'studiensemester_kurzbz' => $studiensemester_kurzbz + )); + + $zeugnisnoten = $this->getDataOrTerminateWithError($result); + + if (!$zeugnisnoten) + { + //insert zeugnisnote, if not existing + $result = $this->ZeugnisnoteModel->insert(array( + 'lehrveranstaltung_id' => $lehrveranstaltung_id, + 'student_uid' => $this->input->post('student_uid'), + 'studiensemester_kurzbz' => $studiensemester_kurzbz, + 'note' => $this->input->post('note'), + 'uebernahmedatum' => date('c'), + 'benotungsdatum' => $this->input->post('datum'), + 'insertamum' => date('c'), + 'insertvon' => $authUID, + 'punkte' => $this->input->post('punkte') ? str_replace(',', '.', $this->input->post('punkte')) : null + )); + + $this->getDataOrTerminateWithError($result); + + $this->PruefungModel->db->trans_complete(); + $this->terminateWithSuccess(); + } + + $note = current($zeugnisnoten); + $uebernahmedatum = new DateTime($note->uebernahmedatum); + $benotungsdatum = new DateTime($note->benotungsdatum); + $pruefungsdatum = new DateTime($this->input->post('datum')); + + $checkDate = $note->uebernahmedatum === '' || $benotungsdatum > $uebernahmedatum + ? $benotungsdatum + : $uebernahmedatum; + + if ($checkDate > $pruefungsdatum && $this->input->post('note') !== $note->note) + { + $this->PruefungModel->db->trans_complete(); + $this->terminateWithSuccess($this->p->t('exam', 'hinweis_changeAfterExamDate')); + } + + //update zeugnisnote, if existing and valid datum + $result = $this->ZeugnisnoteModel->update([ + 'lehrveranstaltung_id' => $lehrveranstaltung_id, + 'student_uid' => $this->input->post('student_uid'), + 'studiensemester_kurzbz' => $studiensemester_kurzbz + ], [ + 'note' => $this->input->post('note'), + 'uebernahmedatum' => date('c'), + 'benotungsdatum' => $this->input->post('datum'), + 'updateamum' => date('c'), + 'updatevon' => $authUID, + 'punkte' => $this->input->post('punkte') ? str_replace(',', '.', $this->input->post('punkte')) : null + ]); + + $this->PruefungModel->db->trans_complete(); + $this->terminateWithSuccess(); + } } diff --git a/application/controllers/api/frontend/v1/stv/Students.php b/application/controllers/api/frontend/v1/stv/Students.php index 12440f036..9dbea65f2 100644 --- a/application/controllers/api/frontend/v1/stv/Students.php +++ b/application/controllers/api/frontend/v1/stv/Students.php @@ -765,6 +765,86 @@ class Students extends FHCAPI_Controller $this->terminateWithSuccess($data); } + /** + * @param string $studiensemester_kurzbz + * + * @return void + */ + public function search($studiensemester_kurzbz) + { + $this->addMeta('ci_method', __FUNCTION__); + $this->addMeta('ci_params', array( + 'studiensemester_kurzbz' => $studiensemester_kurzbz + )); + + $this->load->library('SearchLib', [ 'config' => 'searchstv' ]); + $this->load->library('form_validation'); + + $this->form_validation->set_rules('searchstr', 'searchstr', 'required'); + $this->form_validation->set_rules('types[]', 'types', 'required'); + + if (!$this->form_validation->run()) + $this->terminateWithValidationErrors($this->form_validation->error_array()); + + $result = $this->searchlib->search($this->input->post('searchstr'), $this->input->post('types')); + + $data = $this->getDataOrTerminateWithError($result); + + + $this->load->model('crm/Prestudent_model', 'PrestudentModel'); + + $this->prepareQuery($studiensemester_kurzbz); + + $this->PrestudentModel->addSelect("COALESCE(v.semester::text, CASE WHEN public.get_rolle_prestudent(tbl_prestudent.prestudent_id, NULL) IN ('Aufgenommener', 'Bewerber', 'Wartender', 'interessent') THEN public.get_absem_prestudent(tbl_prestudent.prestudent_id, NULL)::text ELSE ''::text END) AS semester", false); + $this->PrestudentModel->addSelect('v.verband'); + $this->PrestudentModel->addSelect('v.gruppe'); + + //add status per semester + $this->PrestudentModel->addSelect( + "( + SELECT status_kurzbz + FROM public.tbl_prestudentstatus pss + WHERE pss.prestudent_id = public.tbl_prestudent.prestudent_id + AND pss.studiensemester_kurzbz = " . $this->PrestudentModel->escape($studiensemester_kurzbz) . " + ORDER BY GREATEST(pss.datum, '0001-01-01') DESC + LIMIT 1 + ) AS statusofsemester" + ); + + $this->addSelectPrioRel(); + + $this->addFilter($studiensemester_kurzbz); + + $prestudent_ids = []; + $student_uids = []; + $this->addMeta('data', $data); + foreach ($data as $row) { + $dataset = json_decode($row->data); + if ($row->type == 'prestudent') { + $prestudent_ids[] = $dataset->prestudent_id; + } elseif ($row->type == 'student') { + $student_uids[] = $dataset->uid; + } + } + + if ($prestudent_ids && $student_uids) { + $this->PrestudentModel->db->where_in('tbl_prestudent.prestudent_id', $prestudent_ids); + $this->PrestudentModel->db->or_where_in('s.student_uid', $student_uids); + } elseif ($prestudent_ids) { + $this->PrestudentModel->db->where_in('tbl_prestudent.prestudent_id', $prestudent_ids); + } elseif ($student_uids) { + $this->PrestudentModel->db->where_in('s.student_uid', $student_uids); + } else { + $this->terminateWithSuccess([]); + } + + $result = $this->PrestudentModel->load(); + + $data = $this->getDataOrTerminateWithError($result); + + $this->terminateWithSuccess($data); + } + /** * @param string|null $studiensemester_kurzbz * @param string $type diff --git a/application/controllers/api/frontend/v1/stv/Verband.php b/application/controllers/api/frontend/v1/stv/Verband.php index 9fcd97c91..eb25a548b 100644 --- a/application/controllers/api/frontend/v1/stv/Verband.php +++ b/application/controllers/api/frontend/v1/stv/Verband.php @@ -165,7 +165,17 @@ class Verband extends FHCAPI_Controller $this->StudiengangModel->addDistinct(); $this->StudiengangModel->addSelect("CONCAT(" . $this->StudiengangModel->escape($link) . ", semester) AS link", false); - $this->StudiengangModel->addSelect("CONCAT(UPPER(CONCAT(typ, kurzbz)), '-', semester, (SELECT CASE WHEN bezeichnung IS NULL OR bezeichnung='' THEN ''::TEXT ELSE CONCAT(' (', bezeichnung, ')') END FROM public.tbl_lehrverband WHERE studiengang_kz=v.studiengang_kz AND semester=v.semester ORDER BY verband, gruppe LIMIT 1)) AS name", false); + $this->StudiengangModel->addSelect("CONCAT( + UPPER(CONCAT(typ, kurzbz)), + '-', + semester, + ( + SELECT CASE WHEN bezeichnung IS NULL OR bezeichnung='' THEN ''::TEXT ELSE CONCAT(' (', bezeichnung, ')') END + FROM public.tbl_lehrverband + WHERE studiengang_kz=v.studiengang_kz AND semester=v.semester + ORDER BY verband, gruppe LIMIT 1 + ) + ) AS name", false); $this->StudiengangModel->addSelect('semester'); $this->StudiengangModel->addSelect($this->StudiengangModel->escape($studiengang_kz) . '::integer AS stg_kz', false); @@ -173,6 +183,7 @@ class Verband extends FHCAPI_Controller $this->StudiengangModel->addOrder('semester'); if ($org_form !== null) { + $this->StudiengangModel->addSelect("v.orgform_kurzbz"); $this->StudiengangModel->db->group_start(); $this->StudiengangModel->db->where('v.semester', 0); $this->StudiengangModel->db->or_where('v.orgform_kurzbz', $org_form); @@ -188,6 +199,7 @@ class Verband extends FHCAPI_Controller array_unshift($list, [ 'name' => 'PreStudent', 'link' => $link . 'prestudent', + 'no_sem_reload' => true, 'stg_kz' => (int)$studiengang_kz, 'children' => $this->getStdSem($link . 'prestudent/', $studiengang_kz) ]); @@ -216,7 +228,6 @@ class Verband extends FHCAPI_Controller $list = array_merge($list, $result); } } - } $this->terminateWithSuccess($list); } diff --git a/application/controllers/api/v1/person/Person.php b/application/controllers/api/v1/person/Person.php index 6a373137f..935fbae62 100644 --- a/application/controllers/api/v1/person/Person.php +++ b/application/controllers/api/v1/person/Person.php @@ -233,10 +233,10 @@ class Person extends API_Controller //Quersumme bilden for ($i = 0; $i < 10; $i++) { - $erg += $gewichtung[$i] * $tmpSvnr{$i}; + $erg += $gewichtung[$i] * $tmpSvnr[$i]; } - if ($tmpSvnr{3} != ($erg % 11)) //Vergleichen der Pruefziffer mit Quersumme Modulo 11 + if ($tmpSvnr[3] != ($erg % 11)) //Vergleichen der Pruefziffer mit Quersumme Modulo 11 { return error('SVNR ist ungueltig'); } @@ -244,7 +244,7 @@ class Person extends API_Controller if (mb_strlen($person['svnr']) == 12) { $last = substr($person['svnr'], 10, 12); - if ($last{0} != 'v' || !is_numeric($last{1})) + if ($last[0] != 'v' || !is_numeric($last[1])) { return error('SVNR ist ungueltig'); } diff --git a/application/controllers/system/infocenter/InfoCenter.php b/application/controllers/system/infocenter/InfoCenter.php index 1a4f566e6..eaa207ff1 100644 --- a/application/controllers/system/infocenter/InfoCenter.php +++ b/application/controllers/system/infocenter/InfoCenter.php @@ -22,6 +22,7 @@ class InfoCenter extends Auth_Controller const REIHUNGSTESTABSOLVIERT_PAGE = 'reihungstestAbsolviert'; const ABGEWIESEN_PAGE = 'abgewiesen'; const AUFGENOMMEN_PAGE = 'aufgenommen'; + const ONBOARDING_PAGE = 'onboarding'; const SHOW_DETAILS_PAGE = 'showDetails'; const SHOW_ZGV_DETAILS_PAGE = 'showZGVDetails'; const ZGV_UBERPRUEFUNG_PAGE = 'ZGVUeberpruefung'; @@ -116,6 +117,7 @@ class InfoCenter extends Auth_Controller 'index' => 'infocenter:r', 'freigegeben' => 'infocenter:r', 'abgewiesen' => 'infocenter:r', + 'onboarding' => 'infocenter:r', 'aufgenommen' => 'infocenter:r', 'reihungstestAbsolviert' => 'infocenter:r', 'showDetails' => 'infocenter:r', @@ -230,6 +232,13 @@ class InfoCenter extends Auth_Controller $this->load->view('system/infocenter/infocenterAbgewiesen.php'); } + + public function onboarding() + { + $this->_setNavigationMenu(self::ONBOARDING_PAGE); // define the navigation menu for this page + + $this->load->view('system/infocenter/onboarding.php'); + } /** * Aufgenommene page of the InfoCenter tool @@ -1553,6 +1562,7 @@ class InfoCenter extends Auth_Controller $reihungstestAbsolviertLink = site_url(self::INFOCENTER_URI.'/'.self::REIHUNGSTESTABSOLVIERT_PAGE); $abgewiesenLink = site_url(self::INFOCENTER_URI.'/'.self::ABGEWIESEN_PAGE); $aufgenommenLink = site_url(self::INFOCENTER_URI.'/'.self::AUFGENOMMEN_PAGE); + $onboardingLink = site_url(self::INFOCENTER_URI.'/'.self::ONBOARDING_PAGE); $currentFilterId = $this->input->get(self::FILTER_ID); if (isset($currentFilterId)) @@ -1561,6 +1571,7 @@ class InfoCenter extends Auth_Controller $reihungstestAbsolviertLink .= '?'.self::PREV_FILTER_ID.'='.$currentFilterId; $abgewiesenLink .= '?'.self::PREV_FILTER_ID.'='.$currentFilterId; $aufgenommenLink .= '?'.self::PREV_FILTER_ID.'='.$currentFilterId; + $onboardingLink .= '?'.self::PREV_FILTER_ID.'='.$currentFilterId; } $this->navigationlib->setSessionMenu( @@ -1624,6 +1635,18 @@ class InfoCenter extends Auth_Controller '', // target 40 // sort ), + 'ohnePrestudent' => $this->navigationlib->oneLevel( + 'Electronic Onboarding', // description + $onboardingLink, // link + null, // children + 'users', // icon + null, // subscriptDescription + false, // expand + null, // subscriptLinkClass + null, // subscriptLinkValue + '', // target + 50 // sort + ), ) ); } @@ -1650,6 +1673,8 @@ class InfoCenter extends Auth_Controller $link = site_url(self::ZGV_UEBERPRUEFUNG_URI); if ($origin_page === self::ABGEWIESEN_PAGE) $link = site_url(self::INFOCENTER_URI.'/'.self::ABGEWIESEN_PAGE); + if ($origin_page === self::ONBOARDING_PAGE) + $link = site_url(self::INFOCENTER_URI.'/'.self::ONBOARDING_PAGE); if ($origin_page === self::AUFGENOMMEN_PAGE) $link = site_url(self::INFOCENTER_URI.'/'.self::AUFGENOMMEN_PAGE); @@ -1691,6 +1716,7 @@ class InfoCenter extends Auth_Controller $freigegebenLink = site_url(self::INFOCENTER_URI.'/'.self::FREIGEGEBEN_PAGE); $absolviertLink = site_url(self::INFOCENTER_URI.'/'.self::REIHUNGSTESTABSOLVIERT_PAGE); $abgewiesenLink = site_url(self::INFOCENTER_URI.'/'.self::ABGEWIESEN_PAGE); + $onboardingLink = site_url(self::INFOCENTER_URI.'/'.self::ONBOARDING_PAGE); $prevFilterId = $this->input->get(self::PREV_FILTER_ID); if (isset($prevFilterId)) { @@ -1767,6 +1793,24 @@ class InfoCenter extends Auth_Controller ) ); } + if($page == self::ONBOARDING_PAGE) + { + $this->navigationlib->setSessionElementMenu( + 'onboarding', + $this->navigationlib->oneLevel( + 'Electronic Onboarding', // description + $onboardingLink, // link + null, // children + 'users', // icon + null, // subscriptDescription + false, // expand + null, // subscriptLinkClass + null, // subscriptLinkValue + '', // target + 50 // sort + ) + ); + } } /** diff --git a/application/views/Studentenverwaltung.php b/application/views/Studentenverwaltung.php index 01e611657..1cd28d735 100644 --- a/application/views/Studentenverwaltung.php +++ b/application/views/Studentenverwaltung.php @@ -22,7 +22,8 @@ 'public/css/components/function.css' ], 'customJSs' => [ - 'vendor/vuejs/vuedatepicker_js/vue-datepicker.iife.js' + 'vendor/vuejs/vuedatepicker_js/vue-datepicker.iife.js', + 'vendor/moment/luxonjs/luxon.min.js' #'vendor/npm-asset/primevue/tree/tree.min.js', #'vendor/npm-asset/primevue/toast/toast.min.js' ], @@ -53,6 +54,8 @@ $configArray = [ active-addons="" stv-root="" cis-root="" + avatar-url="" + logout-url="" :permissions="" :config="" > diff --git a/application/views/system/infocenter/onboarding.php b/application/views/system/infocenter/onboarding.php new file mode 100644 index 000000000..1f5bae847 --- /dev/null +++ b/application/views/system/infocenter/onboarding.php @@ -0,0 +1,47 @@ +load->view( + 'templates/FHC-Header', + array( + 'title' => 'Info Center', + 'jquery3' => true, + 'jqueryui1' => true, + 'jquerycheckboxes1' => true, + 'bootstrap3' => true, + 'fontawesome4' => true, + 'sbadmintemplate3' => true, + 'tablesorter2' => true, + 'ajaxlib' => true, + 'filterwidget' => true, + 'navigationwidget' => true, + 'dialoglib' => true, + 'phrases' => array( + 'person' => array('vorname', 'nachname'), + 'ui' => array('bitteEintragWaehlen') + ), + 'customCSSs' => array('public/css/sbadmin2/tablesort_bootstrap.css', 'public/css/infocenter/infocenterPersonDataset.css'), + 'customJSs' => array('public/js/bootstrapper.js', 'public/js/infocenter/rueckstellung.js', 'public/js/infocenter/infocenterPersonDataset.js') + ) + ); +?> + +
+ + widgetlib->widget('NavigationWidget'); ?> + +
+
+
+
+ +
+
+
+ load->view('system/infocenter/onboardingData.php'); ?> +
+
+
+
+ +load->view('templates/FHC-Footer'); ?> diff --git a/application/views/system/infocenter/onboardingData.php b/application/views/system/infocenter/onboardingData.php new file mode 100644 index 000000000..5ee66fdde --- /dev/null +++ b/application/views/system/infocenter/onboardingData.php @@ -0,0 +1,116 @@ +>0 as bezeichnung + FROM public.tbl_rueckstellung + JOIN public.tbl_rueckstellung_status USING(status_kurzbz) + JOIN public.tbl_person sp ON tbl_rueckstellung.person_id = sp.person_id + WHERE tbl_rueckstellung.rueckstellung_id = + ( + SELECT srueck.rueckstellung_id + FROM public.tbl_rueckstellung srueck + WHERE srueck.person_id = tbl_rueckstellung.person_id + AND datum_bis >= NOW() + ORDER BY srueck.datum_bis DESC LIMIT 1 + ) + ) rueck ON rueck.person_id = p.person_id + WHERE p.person_id NOT IN (SELECT person_id FROM public.tbl_prestudent)'; + + $filterWidgetArray = array( + 'query' => $query, + 'app' => InfoCenter::APP, + 'datasetName' => 'onboarding', + 'filter_id' => $this->input->get('filter_id'), + 'requiredPermissions' => 'infocenter', + 'datasetRepresentation' => 'tablesorter', + 'checkboxes' => 'PersonId', + 'additionalColumns' => array('Details'), + 'columnsAliases' => array( + 'PersonId', + ucfirst($this->p->t('person', 'vorname')) , + ucfirst($this->p->t('person', 'nachname')), + ucfirst($this->p->t('global', 'sperrdatum')), + ucfirst($this->p->t('global', 'gesperrtVon')), + ucfirst($this->p->t('infocenter', 'rueckstelldatum')), + ucfirst($this->p->t('infocenter', 'rueckstellgrund')), + ), + + 'formatRow' => function($datasetRaw) { + /* NOTE: Dont use $this here for PHP Version compatibility */ + $datasetRaw->{'Details'} = sprintf( + 'Details', + site_url('system/infocenter/InfoCenter/showDetails'), + $datasetRaw->{'PersonId'}, + 'onboarding', + (isset($_GET['fhc_controller_id']) ? $_GET['fhc_controller_id'] : ''), + (isset($_GET['filter_id']) ? $_GET['filter_id'] : '') + ); + + if ($datasetRaw->{'LockDate'} == null) + { + $datasetRaw->{'LockDate'} = '-'; + } + + if ($datasetRaw->{'LockUser'} == null) + { + $datasetRaw->{'LockUser'} = '-'; + } + + if ($datasetRaw->{'HoldDate'} == null) + { + $datasetRaw->{'HoldDate'} = '-'; + } + else + { + $datasetRaw->{'HoldDate'} = date_format(date_create($datasetRaw->{'HoldDate'}), 'Y-m-d H:i'); + } + + if ($datasetRaw->{'Rueckstellgrund'} === null) + { + $datasetRaw->{'Rueckstellgrund'} = '-'; + } + + return $datasetRaw; + }, + + 'markRow' => function($datasetRaw) { + + if ($datasetRaw->LockDate != null) + { + return FilterWidget::DEFAULT_MARK_ROW_CLASS; + } + } + + + + ); + + echo $this->widgetlib->widget('FilterWidget', $filterWidgetArray); +?> diff --git a/content/statistik/notenspiegel.php b/content/statistik/notenspiegel.php index 1a7651296..0901b834f 100644 --- a/content/statistik/notenspiegel.php +++ b/content/statistik/notenspiegel.php @@ -62,6 +62,11 @@ if(!$rechte->isBerechtigt('student/noten',$studiengang_kz, 's')) $semester = isset($_GET['semester'])?$_GET['semester']:''; $typ = isset($_GET['typ'])?$_GET['typ']:''; +if(isset($_GET['studiensemester']) && preg_match('/[WS]S[0-9]{4}/', $_GET['studiensemester'])) +{ + $semester_aktuell = $_GET['studiensemester']; +} + if($semester=='') die('Bitte ein Semester auswaehlen'); diff --git a/content/statistik/notenspiegel_erweitert.php b/content/statistik/notenspiegel_erweitert.php index 2849a1895..67097ed22 100644 --- a/content/statistik/notenspiegel_erweitert.php +++ b/content/statistik/notenspiegel_erweitert.php @@ -64,6 +64,11 @@ if (!$rechte->isBerechtigt('student/noten', $studiengang_kz, 's')) $semester = isset($_GET['semester']) ? $_GET['semester'] : ''; $typ = isset($_GET['typ']) ? $_GET['typ'] : ''; +if(isset($_GET['studiensemester']) && preg_match('/[WS]S[0-9]{4}/', $_GET['studiensemester'])) +{ + $semester_aktuell = $_GET['studiensemester']; +} + if ($semester == '') die('Bitte ein Semester auswaehlen'); diff --git a/include/File/CSV.php b/include/File/CSV.php index c4c4d44ad..ea67181f1 100644 --- a/include/File/CSV.php +++ b/include/File/CSV.php @@ -178,7 +178,7 @@ class File_CSV return $field; } - if ($quote && $field{0} == $quote && $field{strlen($field)-1} == $quote) { + if ($quote && $field[0] == $quote && $field[strlen($field) - 1] == $quote) { return substr($field, 1, -1); } return $field; @@ -230,7 +230,7 @@ class File_CSV } elseif ($c == "\n" || $c == "\r") { $sub = ($prev == "\r") ? 2 : 1; if ((strlen($buff) >= $sub) && - ($buff{strlen($buff) - $sub} == $quote)) + ($buff[strlen($buff) - $sub] == $quote)) { $in_quote = false; } @@ -312,9 +312,9 @@ class File_CSV $last =& $fields[count($fields) - 1]; // Fallback to read the line with readQuoted when guess // that the simple explode won't work right - if (($last{strlen($last) - 1} == "\n" - && $last{0} == $conf['quote'] - && $last{strlen(rtrim($last)) - 1} != $conf['quote']) + if (($last[strlen($last) - 1] == "\n" + && $last[0] == $conf['quote'] + && $last[strlen(rtrim($last)) - 1] != $conf['quote']) || (count($fields) != $conf['fields']) // XXX perhaps there is a separator inside a quoted field @@ -511,4 +511,4 @@ class File_CSV return true; } } -?> \ No newline at end of file +?> diff --git a/include/File/Util.php b/include/File/Util.php index 941c61441..5474bc2a7 100644 --- a/include/File/Util.php +++ b/include/File/Util.php @@ -96,7 +96,7 @@ class File_Util { if (File_Util::isAbsolute($path)) { if (FILE_WIN32) { - return substr($path, $path{3} == '\\' ? 4 : 3); + return substr($path, $path[3] == '\\' ? 4 : 3); } return ltrim($path, '/'); } @@ -182,7 +182,7 @@ class File_Util if (FILE_WIN32) { return preg_match('/^[a-zA-Z]:(\\\|\/)/', $path); } - return ($path{0} == '/') || ($path{0} == '~'); + return ($path[0] == '/') || ($path[0] == '~'); } /** @@ -244,11 +244,11 @@ class File_Util } else { $cwd = getcwd(); $drive = substr($cwd, 0, 2); - if ($path{0} !== $separator{0}) { + if ($path[0] !== $separator[0]) { $path = substr($cwd, 3) . $separator . $path; } } - } elseif ($path{0} !== $separator) { + } elseif ($path[0] !== $separator) { $path = getcwd() . $separator . $path; } @@ -323,7 +323,7 @@ class File_Util $entries = array(); for ($dir = dir($path); false !== $entry = $dir->read(); ) { - if ($list & FILE_LIST_DOTS || $entry{0} !== '.') { + if ($list & FILE_LIST_DOTS || $entry[0] !== '.') { $isRef = ($entry === '.' || $entry === '..'); $isDir = $isRef || is_dir($path .'/'. $entry); if ( ((!$isDir && $list & FILE_LIST_FILES) || diff --git a/include/filter.class.php b/include/filter.class.php index c3d1d6ac1..7100e6562 100644 --- a/include/filter.class.php +++ b/include/filter.class.php @@ -240,7 +240,7 @@ class filter extends basis_db { //$value->value = preg_replace('/(.*?)selected=.selected./', '$1', $value->value); //$value->value = preg_replace('/(.*?)\s*[selected=?selected?]/', '$1', $value->value); - //$value->value = preg_replace('/(.*?)\s*selected="selected"\s*/', '${1}', $value->value); + //$value->value = preg_replace('/(.*?)\s*selected="selected"\s*/', '$[1]', $value->value); //$value->value = preg_replace('/^\s*selected=.*?selected.*?\s*/', ' ', $value->value); //$value->value = str_replace('selected=', '', $value->value); diff --git a/include/securimage/WavFile.php b/include/securimage/WavFile.php index 6d84fc725..d56dbd942 100644 --- a/include/securimage/WavFile.php +++ b/include/securimage/WavFile.php @@ -1532,7 +1532,7 @@ class WavFile } else { // replace for ($i = 0; $i < $sampleBytes; ++$i) { - $this->_samples{$offset + $i} = $sampleBinary{$i}; + $this->_samples[$offset + $i] = $sampleBinary[$i]; } } diff --git a/include/securimage/securimage.php b/include/securimage/securimage.php index 51d876672..820e8b92f 100644 --- a/include/securimage/securimage.php +++ b/include/securimage/securimage.php @@ -1435,7 +1435,7 @@ class Securimage $length = strlen($code['display']); for($i = 0; $i < $length; ++$i) { - $letter = $code['display']{$i}; + $letter = $code['display'][$i]; $letters[] = $letter; } } diff --git a/public/css/Cis4/Cis.css b/public/css/Cis4/Cis.css index ff1b181dd..2bb467048 100644 --- a/public/css/Cis4/Cis.css +++ b/public/css/Cis4/Cis.css @@ -407,6 +407,7 @@ html { background-color: var(--fhc-background); border-color: var(--fhc-border); padding: var(--fhc-cis-main-py) var(--fhc-cis-main-px); + min-width: 0; /* fix flex-grow with tabulator exceeding width */ } #cis-main .fa-arrow-up-right-from-square { @@ -854,4 +855,4 @@ html { #cis-main .modal-footer { background-color: var(--fhc-secondary); -} \ No newline at end of file +} diff --git a/public/css/Studentenverwaltung.css b/public/css/Studentenverwaltung.css index 56e99b937..eb6becc15 100644 --- a/public/css/Studentenverwaltung.css +++ b/public/css/Studentenverwaltung.css @@ -11,6 +11,24 @@ html { font-size: .875em; } +html.fs_xx-small { + font-size: .5em; +} +html.fs_x-small { + font-size: .625em; +} +html.fs_small { + font-size: .75em; +} +html.fs_normal { + font-size: .875em; +} +html.fs_big { + font-size: 1em; +} +html.fs_huge { + font-size: 1.125em; +} #appMenu { width: 300px; @@ -43,6 +61,12 @@ html { flex: 1 1 auto; } +#nav-user-btn img { + object-fit: contain; + height: 2.5rem; + width: 2.5rem; +} + .tabulator-row.disabled.tabulator-row-odd .tabulator-cell { color: var(--gray-400); } @@ -160,4 +184,4 @@ html { .tiny-90 div.tox.tox-tinymce { height: 90% !important; -} \ No newline at end of file +} diff --git a/public/css/components/AppMenu.css b/public/css/components/AppMenu.css index b980c1efc..e142858f8 100644 --- a/public/css/components/AppMenu.css +++ b/public/css/components/AppMenu.css @@ -16,11 +16,15 @@ padding: .5rem 1rem; text-decoration: none; } +.fhc-app-menu li a.disabled { + --bs-link-opacity: .5; +} .fhc-app-menu li a.active, .fhc-app-menu li a:hover { --bs-link-color-rgb: var(--bs-link-hover-color-rgb); background: var(--surface-hover); } +.fhc-app-menu li a.disabled, .fhc-app-menu li a.active { pointer-events: none; } diff --git a/public/js/api/factory/stv/config.js b/public/js/api/factory/stv/config.js new file mode 100644 index 000000000..c54b2f8b2 --- /dev/null +++ b/public/js/api/factory/stv/config.js @@ -0,0 +1,32 @@ +/** + * Copyright (C) 2025 fhcomplete.org + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +export default { + get() { + return { + method: 'get', + url: 'api/frontend/v1/stv/config/get' + }; + }, + set(params) { + return { + method: 'post', + url: 'api/frontend/v1/stv/config/set', + params + }; + } +}; \ No newline at end of file diff --git a/public/js/api/factory/stv/students.js b/public/js/api/factory/stv/students.js index 07d4453d8..cae2f31b2 100644 --- a/public/js/api/factory/stv/students.js +++ b/public/js/api/factory/stv/students.js @@ -46,6 +46,14 @@ export default { url: url }; }, + search(params, studiensemester_kurzbz) { + return { + method: 'post', + url: 'api/frontend/v1/stv/students/search/' + + encodeURIComponent(studiensemester_kurzbz), + params + }; + }, verband(relative_path) { return { method: 'get', diff --git a/public/js/apps/Studentenverwaltung.js b/public/js/apps/Studentenverwaltung.js index 5eda16dd6..e6f77d5f5 100644 --- a/public/js/apps/Studentenverwaltung.js +++ b/public/js/apps/Studentenverwaltung.js @@ -148,6 +148,44 @@ const router = VueRouter.createRouter({ next(); } }, + { + name: 'search', + path: `/${ciPath}/studentenverwaltung/:studiensemester_kurzbz/search/:searchstr`, + component: FhcStudentenverwaltung, + props(route) { + return { + url_studiensemester_kurzbz: route.params.studiensemester_kurzbz, + url_mode: 'search', + url_prestudent_id: route.params.searchstr + }; + }, + beforeEnter(to, from, next) { + const isSemester = /^[WS]S\d{4}$/.test(to.params.studiensemester_kurzbz); + if (!isSemester) { + return next({name: 'index'}); + } + next(); + } + }, + { + name: 'search_w_types', + path: `/${ciPath}/studentenverwaltung/:studiensemester_kurzbz/search/:types/:searchstr`, + component: FhcStudentenverwaltung, + props(route) { + return { + url_studiensemester_kurzbz: route.params.studiensemester_kurzbz, + url_mode: 'search', + url_prestudent_id: route.params.type + '/' + route.params.searchstr + }; + }, + beforeEnter(to, from, next) { + const isSemester = /^[WS]S\d{4}$/.test(to.params.studiensemester_kurzbz); + if (!isSemester) { + return next({name: 'index'}); + } + next(); + } + }, { path: '/:pathMatch(.*)*', redirect: { diff --git a/public/js/components/AppConfig.js b/public/js/components/AppConfig.js new file mode 100644 index 000000000..b6b6aaeac --- /dev/null +++ b/public/js/components/AppConfig.js @@ -0,0 +1,135 @@ +/** + * Copyright (C) 2025 fhcomplete.org + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import BsModal from "./Bootstrap/Modal.js"; +import FhcForm from "./Form/Form.js"; +import FormInput from "./Form/Input.js"; + + +export default { + name: 'AppConfig', + components: { + BsModal, + FhcForm, + FormInput + }, + emits: [ + 'update:modelValue' + ], + props: { + modelValue: { + type: Object, + required: true + }, + endpoints: { + type: Object, + required: true + } + }, + data() { + return { + setup: {}, + tempValues: {} + }; + }, + watch: { + '$p.user_language.value'(n, o) { + if (n !== o && o !== undefined && Object.keys(this.setup).length) { + this.$api + .call(this.endpoints.get()) + .then(res => { + this.setup = {}; + Object.keys(res.data).forEach(key => { + const binding = { ...res.data[key] }; + delete binding.value; + delete binding.options; + const options = res.data[key].options; + this.setup[key] = { + binding, + options + }; + }); + }) + .catch(this.$fhcAlert.handleSystemErrors); + } + } + + }, + methods: { + update() { + this.$refs.form + .call(this.endpoints.set(this.tempValues)) + .then(() => { + this.$emit('update:modelValue', { ...this.tempValues }); + this.$refs.modal.hide(); + this.$fhcAlert.alertSuccess(this.$p.t('ui/settings_saved')); + }) + .catch(this.$fhcAlert.handleSystemErrors); + } + }, + created() { + this.$api + .call(this.endpoints.get()) + .then(res => { + Object.keys(res.data).forEach(key => { + const binding = { ...res.data[key] }; + delete binding.value; + delete binding.options; + const options = res.data[key].options; + this.tempValues[key] = res.data[key].value; + this.setup[key] = { + binding, + options + }; + }); + this.$emit('update:modelValue', { ...this.tempValues }); + }) + .catch(this.$fhcAlert.handleSystemErrors); + }, + template: /* html */` + + + + + + + ` +}; diff --git a/public/js/components/AppMenu.js b/public/js/components/AppMenu.js index 33d35f8df..9e85debcd 100644 --- a/public/js/components/AppMenu.js +++ b/public/js/components/AppMenu.js @@ -64,5 +64,6 @@ export default { {{ menu.description }} + ` }; diff --git a/public/js/components/Calendar/Base/Grid.js b/public/js/components/Calendar/Base/Grid.js index c232dd955..3418a9151 100644 --- a/public/js/components/Calendar/Base/Grid.js +++ b/public/js/components/Calendar/Base/Grid.js @@ -316,7 +316,7 @@ export default { template: /* html */`
", dataTreeChildIndent: 15, dataTreeStartExpanded: false, - persistenceID: 'core-message', + persistenceID: 'core-message-2025112401', locale: 'de', "langs": { "de":{ //German language definition diff --git a/public/js/components/Stv/Studentenverwaltung.js b/public/js/components/Stv/Studentenverwaltung.js index a1e38875d..e3a750e0c 100644 --- a/public/js/components/Stv/Studentenverwaltung.js +++ b/public/js/components/Stv/Studentenverwaltung.js @@ -16,8 +16,10 @@ */ import CoreSearchbar from "../searchbar/searchbar.js"; +import NavLanguage from "../navigation/Language.js"; import VerticalSplit from "../verticalsplit/verticalsplit.js"; import AppMenu from "../AppMenu.js"; +import AppConfig from "../AppConfig.js"; import StvVerband from "./Studentenverwaltung/Verband.js"; import StvList from "./Studentenverwaltung/List.js"; import StvDetails from "./Studentenverwaltung/Details.js"; @@ -26,14 +28,17 @@ import StvStudiensemester from "./Studentenverwaltung/Studiensemester.js"; import ApiSearchbar from "../../api/factory/searchbar.js"; import ApiStv from "../../api/factory/stv.js"; import ApiStvVerband from '../../api/factory/stv/verband.js'; +import ApiStvConfig from '../../api/factory/stv/config.js'; export default { name: 'Studentenverwaltung', components: { CoreSearchbar, + NavLanguage, VerticalSplit, AppMenu, + AppConfig, StvVerband, StvList, StvDetails, @@ -45,6 +50,8 @@ export default { permissions: Object, stvRoot: String, cisRoot: String, + avatarUrl: String, + logoutUrl: String, activeAddons: String, // semicolon separated list of active addons url_studiensemester_kurzbz: String, url_mode: String, @@ -76,15 +83,19 @@ export default { }, configShowAufnahmegruppen: this.config.showAufnahmegruppen, configAllowUebernahmePunkte: this.config.allowUebernahmePunkte, - configUseReihungstestPunkte: this.config.useReihungstestPunkte + configUseReihungstestPunkte: this.config.useReihungstestPunkte, + appConfig: Vue.computed(() => this.appconfig) } }, data() { return { + appconfig: {}, + configEndpoints: ApiStvConfig, selected: [], searchbaroptions: { origin: 'stv', calcheightonly: true, + nolivesearch: true, types: { student: Vue.computed(() => this.$p.t('search/type_student')), prestudent: Vue.computed(() => this.$p.t('search/type_prestudent')) @@ -123,6 +134,8 @@ export default { studiengangKz: undefined, studiengangKuerzel: '', studiensemesterKurzbz: this.defaultSemester, + selected_semester: undefined, + selected_orgform: undefined, lists: { nations: [], sprachen: [], @@ -131,12 +144,60 @@ export default { verbandEndpoint: ApiStvVerband } }, + computed: { + appMenuExtraItems() { + const extraItems = []; + + if (this.studiengangKz !== undefined && this.selected_semester !== undefined) { + const studiengang_kz = String(this.studiengangKz); + const semester = String(this.selected_semester); + const orgform = this.selected_orgform || ''; + + extraItems.push({ + link: FHC_JS_DATA_STORAGE_OBJECT.app_root + + 'content/statistik/notenspiegel.php?typ=xls' + + '&studiengang_kz=' + studiengang_kz + + '&semester=' + semester + + '&studiensemester=' + this.studiensemesterKurzbz + + '&orgform=' + orgform, + description: 'stv/grade_report_xls' + }); + extraItems.push({ + link: FHC_JS_DATA_STORAGE_OBJECT.app_root + + 'content/statistik/notenspiegel_erweitert.php?typ=xls' + + '&studiengang_kz=' + studiengang_kz + + '&semester=' + semester + + '&studiensemester=' + this.studiensemesterKurzbz + + '&orgform=' + orgform, + description: 'stv/grade_report_xls_extended' + }); + extraItems.push({ + link: FHC_JS_DATA_STORAGE_OBJECT.app_root + + 'content/statistik/notenspiegel.php?typ=html' + + '&studiengang_kz=' + studiengang_kz + + '&semester=' + semester + + '&studiensemester=' + this.studiensemesterKurzbz + + '&orgform=' + orgform, + description: 'stv/grade_report_html' + }); + } + + return extraItems; + } + }, watch: { 'url_studiensemester_kurzbz': function (newVal, oldVal) { if (newVal !== oldVal) { this.studiensemesterKurzbz = newVal; - this.$refs.stvList.updateUrl(); - this.$refs.details.reload(); + if(this.$route.name === 'search') + { + this.handleSearchUrl(); + } + else + { + this.$refs.stvList.updateUrl(); + this.$refs.details.reload(); + } } }, 'url_studiengang': function (newVal, oldVal) { @@ -146,6 +207,25 @@ export default { }, 'url_mode': function () { this.handlePersonUrl(); + }, + url_prestudent_id() { + this.handlePersonUrl(); + }, + 'appconfig.font_size'() { + // add to html class + const classList = Object.keys(this.$refs.config.setup.font_size.options); + classList.forEach(cn => document.documentElement.classList.remove(cn)); + document.documentElement.classList.add(this.appconfig.font_size); + // recalc Tabulator heights + if (this.$el) { + const tabulatorEls = this.$el.querySelectorAll('.tabulator'); + for (const el of tabulatorEls) { + const tabulators = Tabulator.findTable(el); + if (tabulators) { + tabulators[0].searchRows().forEach(row => row.normalizeHeight()); + } + } + } } }, methods: { @@ -159,7 +239,7 @@ export default { } }, buildPrestudentSearchResultLink(data) { - return this.$fhcApi.getUri( + return this.$api.getUri( '/studentenverwaltung' + '/' + this.studiensemesterKurzbz + '/prestudent/' @@ -167,7 +247,7 @@ export default { ); }, buildStudentSearchResultLink(data) { - return this.$fhcApi.getUri( + return this.$api.getUri( '/studentenverwaltung' + '/' + this.studiensemesterKurzbz + '/student/' @@ -175,14 +255,14 @@ export default { ); }, buildPersonSearchResultLink(data) { - return this.$fhcApi.getUri( + return this.$api.getUri( '/studentenverwaltung' + '/' + this.studiensemesterKurzbz + '/person/' + data.person_id ); }, - onSelectVerband( {link, studiengang_kz}) { + onSelectVerband({ link, studiengang_kz, semester, orgform_kurzbz }) { let urlpath = String(link); if (!urlpath.match(/\/prestudent/)) { @@ -191,6 +271,8 @@ export default { this.$refs.stvList.updateUrl(ApiStv.students.verband(urlpath)); this.studiengangKz = studiengang_kz; + this.selected_semester = semester; + this.selected_orgform = orgform_kurzbz; const stg = this.lists.stgs.find((element) => { return (element.studiengang_kz === this.studiengangKz); }); @@ -223,9 +305,6 @@ export default { studiensemester_kurzbz: v } }); - - this.$refs.stvList.updateUrl(); - this.$refs.details.reload(); }, reloadList() { this.$refs.stvList.reload(); @@ -249,6 +328,37 @@ export default { ApiStv.students.person(this.$route.params.person_id, 'CURRENT_SEMESTER'), true ); + } else if (this.$route.params.searchstr) { + this.handleSearchUrl(); + } + else + { + this.clearTabulator(); + } + }, + handleSearchUrl() { + const searchsettings = { + searchstr: this.$route.params.searchstr, + types: this.$route.params.types?.split('+') || [] + }; + + // init into student list + this.$refs.stvList.updateUrl( + ApiStv.students.search(searchsettings, this.studiensemesterKurzbz) + ); + + // init into searchbar + this.$refs.searchbar.searchsettings.searchstr = searchsettings.searchstr; + this.$refs.searchbar.searchsettings.types = searchsettings.types; + this.$nextTick(this.blurSearchbar); + }, + clearTabulator() { + if(['index', 'studiensemester'].includes(this.$route.name)) + { + if(this.$refs?.stvList?.$refs?.table?.tabulator) + { + this.$refs.stvList.$refs.table.tabulator.setData([]); + } } }, checkUrlStudiengang() { @@ -269,6 +379,42 @@ export default { }); } } + else + { + this.studiengangKz = undefined; + this.studiengangKuerzel = ''; + this.clearTabulator(); + } + }, + onSearch(e) { + const searchsettings = { ...this.$refs.searchbar.searchsettings }; + if (searchsettings.searchstr.length >= 2) { + this.blurSearchbar(); + + if (!searchsettings.types.length || searchsettings.types.length == this.$refs.searchbar.types.length) { + this.$router.push({ + name: 'search', + params: { + studiensemester_kurzbz: this.studiensemesterKurzbz, + searchstr: searchsettings.searchstr + } + }); + } else { + this.$router.push({ + name: 'search_w_types', + params: { + studiensemester_kurzbz: this.studiensemesterKurzbz, + searchstr: searchsettings.searchstr, + types: searchsettings.types.join('+') + } + }); + } + } + }, + blurSearchbar() { + this.$refs.searchbar.$refs.input.blur(); + this.$refs.searchbar.abort(); + this.$refs.searchbar.hideresult(); } }, created() { @@ -376,10 +522,58 @@ export default { +
@@ -389,14 +583,38 @@ export default {
@@ -411,5 +629,6 @@ export default {
+
` }; diff --git a/public/js/components/Stv/Studentenverwaltung/Details.js b/public/js/components/Stv/Studentenverwaltung/Details.js index 7bd028f3c..dca08c07f 100644 --- a/public/js/components/Stv/Studentenverwaltung/Details.js +++ b/public/js/components/Stv/Studentenverwaltung/Details.js @@ -41,25 +41,34 @@ export default { return Object.fromEntries(Object.entries(this.configStudents).filter(([ , value ]) => !value.showOnlyWithUid && !value.showOnlyWithUid)); } }, + watch: { + '$p.user_language.value'(n, o) { + if (n !== o && o !== undefined) + this.loadConfig(); + } + }, methods: { + loadConfig() { + this.$api + .call(ApiStvApp.configStudent()) + .then(result => { + this.configStudent = result.data; + }) + .catch(this.$fhcAlert.handleSystemError); + this.$api + .call(ApiStvApp.configStudents()) + .then(result => { + this.configStudents = result.data; + }) + .catch(this.$fhcAlert.handleSystemError); + }, reload() { if (this.$refs.tabs?.$refs?.current?.reload) this.$refs.tabs.$refs.current.reload(); } }, created() { - this.$api - .call(ApiStvApp.configStudent()) - .then(result => { - this.configStudent = result.data; - }) - .catch(this.$fhcAlert.handleSystemError); - this.$api - .call(ApiStvApp.configStudents()) - .then(result => { - this.configStudents = result.data; - }) - .catch(this.$fhcAlert.handleSystemError); + this.loadConfig(); }, template: `
diff --git a/public/js/components/Stv/Studentenverwaltung/Details/Abschlusspruefung/Abschlusspruefung.js b/public/js/components/Stv/Studentenverwaltung/Details/Abschlusspruefung/Abschlusspruefung.js index 6e862fc01..6ed710c29 100644 --- a/public/js/components/Stv/Studentenverwaltung/Details/Abschlusspruefung/Abschlusspruefung.js +++ b/public/js/components/Stv/Studentenverwaltung/Details/Abschlusspruefung/Abschlusspruefung.js @@ -163,12 +163,12 @@ export default { frozen: true }, ], - layout: 'fitDataFill', + layout: 'fitDataStretchFrozen', layoutColumnsOnNewData: false, height: 'auto', minHeight: '200', index: 'abschlusspruefung_id', - persistenceID: 'stv-details-finalexam' + persistenceID: 'stv-details-finalexam-2025112401' }, tabulatorEvents: [ { diff --git a/public/js/components/Stv/Studentenverwaltung/Details/Anrechnungen/Anrechnungen.js b/public/js/components/Stv/Studentenverwaltung/Details/Anrechnungen/Anrechnungen.js index 4c29cec3e..a2029b0b9 100644 --- a/public/js/components/Stv/Studentenverwaltung/Details/Anrechnungen/Anrechnungen.js +++ b/public/js/components/Stv/Studentenverwaltung/Details/Anrechnungen/Anrechnungen.js @@ -107,10 +107,10 @@ export default { frozen: true }, ], - layout: 'fitDataFill', + layout: 'fitDataStretchFrozen', height: '500', index: 'anrechnung_id', - persistenceID: 'stv-details-anrechnungen' + persistenceID: 'stv-details-anrechnungen-2025112401' }, tabulatorEvents: [ { diff --git a/public/js/components/Stv/Studentenverwaltung/Details/Aufnahmetermine/Aufnahmetermine.js b/public/js/components/Stv/Studentenverwaltung/Details/Aufnahmetermine/Aufnahmetermine.js index 266c6774e..d03569df4 100644 --- a/public/js/components/Stv/Studentenverwaltung/Details/Aufnahmetermine/Aufnahmetermine.js +++ b/public/js/components/Stv/Studentenverwaltung/Details/Aufnahmetermine/Aufnahmetermine.js @@ -120,12 +120,12 @@ export default { frozen: true } ], - layout: 'fitDataFill', + layout: 'fitDataStretchFrozen', layoutColumnsOnNewData: false, height: 'auto', minHeight: 200, index: 'aufnahmetermin_id', - persistenceID: 'stv-details-table_admission-dates' + persistenceID: 'stv-details-table_admission-dates-2025112401' }, tabulatorEvents: [ { diff --git a/public/js/components/Stv/Studentenverwaltung/Details/CombinePeople.js b/public/js/components/Stv/Studentenverwaltung/Details/CombinePeople.js new file mode 100644 index 000000000..81c1a6860 --- /dev/null +++ b/public/js/components/Stv/Studentenverwaltung/Details/CombinePeople.js @@ -0,0 +1,83 @@ +export default { + name: "TabCombinePeople", + inject: { + cisRoot: { + from: 'cisRoot' + }, + }, + props: { + modelValue: Object, + }, + data(){ + return { + iframeUrl: null, + viewLoaded: false + } + }, + computed: { + personIds() { + return Array.isArray(this.modelValue) + ? this.modelValue.map(e => e.person_id) + : [this.modelValue.person_id]; + }, + detailStringPerson1(){ + let person1 = this.modelValue[0]; + return person1.vorname + " " + person1.nachname + "(" + person1.person_id + ")"; + }, + detailStringPerson2(){ + let person2 = this.modelValue[1]; + return person2.vorname + " " + person2.nachname + "(" + person2.person_id+ ")"; + }, + + }, + methods: { + combinePeople(){ + this.viewLoaded = true; + let person1_id = this.personIds[0]; + let person2_id = this.personIds[1]; + + if(person1_id == person2_id) { + return this.$fhcAlert.alertError(this.$p.t('stv', 'error_combinePeople_samePerson')); + } + + let linkCombinePeople = this.cisRoot + 'vilesci/stammdaten/personen_wartung.php?person_id_1=' + person1_id + '&person_id_2='+ person2_id; + this.openLink(linkCombinePeople); + }, + openLink(url) { + this.iframeUrl = url; + }, + goBack(){ + this.viewLoaded = false; + this.iframeUrl = null; + } + }, + template: /*html*/ ` +
+ +
+

Personen zusammenlegen

+
+
+

{{$p.t('stv', 'question_combine_people', { person1: detailStringPerson1, person2: detailStringPerson2 })}}

+ +
+
+ ungültige Anzahl: {{this.modelValue.length}} +
+
+
+
+ +
+ + + + +
+ ` + }; \ No newline at end of file diff --git a/public/js/components/Stv/Studentenverwaltung/Details/Mobility/List/Purpose.js b/public/js/components/Stv/Studentenverwaltung/Details/Mobility/List/Purpose.js index 296d29e8d..f877eea78 100644 --- a/public/js/components/Stv/Studentenverwaltung/Details/Mobility/List/Purpose.js +++ b/public/js/components/Stv/Studentenverwaltung/Details/Mobility/List/Purpose.js @@ -71,10 +71,10 @@ export default { frozen: true }, ], - layout: 'fitColumns', + layout: 'fitDataStretchFrozen', layoutColumnsOnNewData: false, height: 200, - persistenceID: 'core-mobility-purpose' + persistenceID: 'core-mobility-purpose-2025112401' }, tabulatorEvents: [ { diff --git a/public/js/components/Stv/Studentenverwaltung/Details/Mobility/List/Support.js b/public/js/components/Stv/Studentenverwaltung/Details/Mobility/List/Support.js index 3ad523073..d61c60007 100644 --- a/public/js/components/Stv/Studentenverwaltung/Details/Mobility/List/Support.js +++ b/public/js/components/Stv/Studentenverwaltung/Details/Mobility/List/Support.js @@ -69,10 +69,10 @@ export default { frozen: true }, ], - layout: 'fitColumns', + layout: 'fitDataStretchFrozen', layoutColumnsOnNewData: false, height: 200, - persistenceID: 'core-mobility-support' + persistenceID: 'core-mobility-support-2025112401' }, tabulatorEvents: [ { diff --git a/public/js/components/Stv/Studentenverwaltung/Details/Mobility/Mobility.js b/public/js/components/Stv/Studentenverwaltung/Details/Mobility/Mobility.js index a2b54e7b5..ae7f522b5 100644 --- a/public/js/components/Stv/Studentenverwaltung/Details/Mobility/Mobility.js +++ b/public/js/components/Stv/Studentenverwaltung/Details/Mobility/Mobility.js @@ -109,12 +109,12 @@ export default { frozen: true }, ], - layout: 'fitDataFill', + layout: 'fitDataStretchFrozen', layoutColumnsOnNewData: false, height: 'auto', minHeight: 200, index: 'bisio_id', - persistenceID: 'stv-details-table_mobiliy' + persistenceID: 'stv-details-table_mobiliy-2025112401' }, tabulatorEvents: [ { diff --git a/public/js/components/Stv/Studentenverwaltung/Details/Noten/Zeugnis.js b/public/js/components/Stv/Studentenverwaltung/Details/Noten/Zeugnis.js index ce912c240..4d0996b6b 100644 --- a/public/js/components/Stv/Studentenverwaltung/Details/Noten/Zeugnis.js +++ b/public/js/components/Stv/Studentenverwaltung/Details/Noten/Zeugnis.js @@ -217,9 +217,13 @@ export default { }, columns, height: '100%', + layout: 'fitDataStretchFrozen', selectable: 1, selectableRangeMode: 'click', - persistenceID: 'stv-details-noten-zeugnis' + persistenceID: 'stv-details-noten-zeugnis-2025112401', + persistence:{ + columns: ["width", "visible", "frozen"] + } }; } }, @@ -287,4 +291,4 @@ export default {
` -}; \ No newline at end of file +}; diff --git a/public/js/components/Stv/Studentenverwaltung/Details/Prestudent/MultiStatus.js b/public/js/components/Stv/Studentenverwaltung/Details/Prestudent/MultiStatus.js index 9f55d26c2..2828faa59 100644 --- a/public/js/components/Stv/Studentenverwaltung/Details/Prestudent/MultiStatus.js +++ b/public/js/components/Stv/Studentenverwaltung/Details/Prestudent/MultiStatus.js @@ -227,15 +227,15 @@ export default{ const rowData = row.getData(); if (this.dataMeldestichtag && this.dataMeldestichtag > rowData.datum) { - row.getElement().classList.add('disabled'); + row.getElement().classList.add('text-black','text-opacity-50','fst-italic'); } }, - layout: 'fitDataFill', + layout: 'fitDataStretchFrozen', layoutColumnsOnNewData: false, height: 'auto', selectable: false, index: 'statusId', - persistenceID: 'stv-multistatus' + persistenceID: 'stv-multistatus-2025112401' }, tabulatorEvents: [ { diff --git a/public/js/components/Stv/Studentenverwaltung/Details/Projektarbeit/Details.js b/public/js/components/Stv/Studentenverwaltung/Details/Projektarbeit/Details.js index a51342c7c..63b80ba37 100644 --- a/public/js/components/Stv/Studentenverwaltung/Details/Projektarbeit/Details.js +++ b/public/js/components/Stv/Studentenverwaltung/Details/Projektarbeit/Details.js @@ -40,6 +40,9 @@ export default { } return lehreinheiten; + }, + firmenverwaltungLink(){ + return FHC_JS_DATA_STORAGE_OBJECT.app_root + 'vilesci/stammdaten/firma_frameset.html'; } }, props: { @@ -255,18 +258,25 @@ export default {
diff --git a/public/js/components/Stv/Studentenverwaltung/Details/Projektarbeit/Projektarbeit.js b/public/js/components/Stv/Studentenverwaltung/Details/Projektarbeit/Projektarbeit.js index d4f941e4b..084b5daad 100644 --- a/public/js/components/Stv/Studentenverwaltung/Details/Projektarbeit/Projektarbeit.js +++ b/public/js/components/Stv/Studentenverwaltung/Details/Projektarbeit/Projektarbeit.js @@ -240,7 +240,7 @@ export default { frozen: true }, ], - //layout: 'fitDataFill', + layout: 'fitDataStretchFrozen', height: 'auto', minHeight: '200', selectable: 1, @@ -248,7 +248,7 @@ export default { persistence:{ columns: true, //persist column layout }, - persistenceID: 'stv-details-projektarbeit' + persistenceID: 'stv-details-projektarbeit-2025112401' } return options; } @@ -396,8 +396,8 @@ export default { diff --git a/public/js/components/Stv/Studentenverwaltung/Details/Projektarbeit/Projektbetreuer.js b/public/js/components/Stv/Studentenverwaltung/Details/Projektarbeit/Projektbetreuer.js index adcf2f2a8..4bb007b76 100644 --- a/public/js/components/Stv/Studentenverwaltung/Details/Projektarbeit/Projektbetreuer.js +++ b/public/js/components/Stv/Studentenverwaltung/Details/Projektarbeit/Projektbetreuer.js @@ -115,7 +115,7 @@ export default { frozen: true }, ], - //layout: 'fitDataFill', + layout: 'fitDataStretchFrozen', layoutColumnsOnNewData: false, height: 'auto', minHeight: '100', @@ -125,7 +125,7 @@ export default { persistence:{ columns: true, //persist column layout }, - persistenceID: 'stv-details-projektbetreuer' + persistenceID: 'stv-details-projektbetreuer-2025112401' }, tabulatorEvents: [ { diff --git a/public/js/components/Stv/Studentenverwaltung/Details/Pruefung/Pruefunglist.js b/public/js/components/Stv/Studentenverwaltung/Details/Pruefung/Pruefunglist.js index 4dd3139ab..62cd47547 100644 --- a/public/js/components/Stv/Studentenverwaltung/Details/Pruefung/Pruefunglist.js +++ b/public/js/components/Stv/Studentenverwaltung/Details/Pruefung/Pruefunglist.js @@ -46,8 +46,6 @@ export default{ {title: "Punkte", field: "punkte", visible: false}, { title: 'Aktionen', field: 'actions', - minWidth: 150, - maxWidth: 150, formatter: (cell, formatterParams, onRendered) => { let container = document.createElement('div'); container.className = "d-flex gap-2"; @@ -93,7 +91,7 @@ export default{ layoutColumnsOnNewData: false, height: 'auto', index: 'pruefung_id', - persistenceID: 'stv-details-pruefung-list' + persistenceID: 'stv-details-pruefung-list-2025112402' }, tabulatorEvents: [ { @@ -148,7 +146,6 @@ export default{ listMas: [], listMarks: [], zeugnisData: [], - checkData:[], filter: false, statusNew: true, isStartDropDown: false, @@ -181,7 +178,7 @@ export default{ this.pruefungData.student_uid = this.uid; this.pruefungData.note = 9; - this.pruefungData.datum = new Date(); + this.pruefungData.datum = luxon.DateTime.now().setZone(FHC_JS_DATA_STORAGE_OBJECT.timezone).toISODate(); this.pruefungData.pruefungstyp_kurzbz = null; if(lv_id){ this.pruefungData.lehrveranstaltung_id = lv_id; @@ -193,7 +190,7 @@ export default{ this.isStartDropDown = false; this.loadPruefung(pruefung_id).then(() => { this.pruefungData.note = 9; - this.pruefungData.datum = new Date(); + this.pruefungData.datum = luxon.DateTime.now().setZone(FHC_JS_DATA_STORAGE_OBJECT.timezone).toISODate(); this.pruefungData.pruefungstyp_kurzbz = null; this.pruefungData.anmerkung = null; this.prepareDropdowns(); @@ -229,9 +226,8 @@ export default{ return this.$refs.examData .call(ApiStvExam.addPruefung(this.pruefungData)) .then(response => { - this.checkData = response.data; - if (this.checkData === 2 || this.checkData === 5) - this.$fhcAlert.alertInfo(this.$p.t('exam', 'hinweis_changeAfterExamDate')); + if (response.data) + this.$fhcAlert.alertDefault('info', 'Info', response.data, true); else this.$fhcAlert.alertSuccess(this.$p.t('ui', 'successSave')); this.hideModal('pruefungModal'); @@ -243,12 +239,13 @@ export default{ }); }, updatePruefung(pruefung_id){ - this.checkChangeAfterExamDate(); return this.$refs.examData .call(ApiStvExam.updatePruefung(pruefung_id, this.pruefungData)) .then(response => { - this.checkData = response.data; - this.$fhcAlert.alertSuccess(this.$p.t('ui', 'successSave')); + if (response.data) + this.$fhcAlert.alertDefault('info', 'Info', response.data, true); + else + this.$fhcAlert.alertSuccess(this.$p.t('ui', 'successSave')); this.hideModal('pruefungModal'); this.resetModal(); }).catch(this.$fhcAlert.handleSystemError) @@ -266,27 +263,6 @@ export default{ else this.showHint = false; }, - checkChangeAfterExamDate() { - const data = { - student_uid: this.pruefungData.student_uid, - studiensemester_kurzbz: this.pruefungData.studiensemester_kurzbz, - lehrveranstaltung_id: this.pruefungData.lehrveranstaltung_id - }; - return this.$api - .call(ApiStvExam.checkZeugnisnoteLv(data)) - .then(result => { - this.zeugnisData = result.data; - let checkDate = this.zeugnisData[0].uebernahmedatum === '' || - this.zeugnisData[0].benotungsdatum > this.zeugnisData[0].uebernahmedatum - ? this.zeugnisData[0].benotungsdatum - : this.zeugnisData[0].uebernahmedatum; - if (checkDate >= this.pruefungData.datum - && this.pruefungData.note !== this.zeugnisData[0].note) { - this.$fhcAlert.alertInfo(this.$p.t('exam', 'hinweis_changeAfterExamDate')); - } - }) - .catch(this.$fhcAlert.handleSystemError); - }, deletePruefung(pruefung_id) { return this.$api .call(ApiStvExam.deletePruefung(pruefung_id)) @@ -534,6 +510,7 @@ export default{ container-class="mb-3" type="DatePicker" v-model="pruefungData.datum" + model-type="yyyy-MM-dd" name="datum" :label="$p.t('global/datum')" auto-apply diff --git a/public/js/components/Stv/Studentenverwaltung/List.js b/public/js/components/Stv/Studentenverwaltung/List.js index 2614ebc1f..953462a74 100644 --- a/public/js/components/Stv/Studentenverwaltung/List.js +++ b/public/js/components/Stv/Studentenverwaltung/List.js @@ -2,6 +2,8 @@ import {CoreFilterCmpt} from "../../filter/Filter.js"; import ListNew from './List/New.js'; import ListFilter from './List/Filter.js'; +import { capitalize } from '../../../helpers/StringHelpers.js'; + import draggable from '../../../directives/draggable.js'; export default { @@ -133,7 +135,17 @@ export default { { return Promise.resolve({ data: []}); } - return this.$api.call({method: 'post', url, params}); + /** + * NOTE(chris): Because of a bug in Tabulator + * we need to get the params from elsewhere. + * @see https://github.com/olifolkerd/tabulator/issues/4318 + */ + const apiconfig = { + ...this.tabulatorOptions.ajaxConfig, + url: this.tabulatorOptions.ajaxURL, + params: this.tabulatorOptions.ajaxParams + }; + return this.$api.call(apiconfig); }, ajaxResponse: (url, params, response) => { return response?.data; @@ -175,7 +187,7 @@ export default { count: 0, filteredcount: 0, selectedcount: 0, - currentEndpointRawUrl: '' + currentEndpoint: null } }, computed: { @@ -228,7 +240,84 @@ export default { return "StudentList_" + today + ".csv"; } }, + watch: { + '$p.user_language.value'(n, o) { + if (n !== o && o !== undefined && this.$refs.table.tableBuilt) { + this.translateTabulator(); + } + } + }, methods: { + translateTabulator() { + this.$p + .loadCategory(['global', 'person', 'lehre', 'ui', 'profilUpdate', 'admission', 'stv']) + .then(() => { + const translations = { + uid: capitalize(this.$p.t('person/uid')), + titelpre: capitalize(this.$p.t('person/titelpre')), + nachname: capitalize(this.$p.t('person/nachname')), + vorname: capitalize(this.$p.t('person/vorname')), + wahlname: capitalize(this.$p.t('person/wahlname')), + vornamen: capitalize(this.$p.t('person/vornamen')), + titelpost: capitalize(this.$p.t('person/titelpost')), + ersatzkennzeichen: capitalize(this.$p.t('person/ersatzkennzeichen')), + gebdatum: capitalize(this.$p.t('person/geburtsdatum')), + geschlecht: capitalize(this.$p.t('person/geschlecht')), + semester: capitalize(this.$p.t('lehre/sem')), + verband: capitalize(this.$p.t('lehre/verb')), + gruppe: capitalize(this.$p.t('lehre/grp')), + studiengang: capitalize(this.$p.t('lehre/studiengang')), + studiengang_kz: capitalize(this.$p.t('lehre/studiengang_kz')), + matrikelnr: capitalize(this.$p.t('person/personenkennzeichen')), + person_id: capitalize(this.$p.t('person/person_id')), + status: capitalize(this.$p.t('global/status')), + status_datum: capitalize(this.$p.t('profilUpdate/statusDate')), + status_bestaetigung: capitalize(this.$p.t('global/status_bestaetigung')), + mail_privat: capitalize(this.$p.t('person/email_private')), + mail_intern: capitalize(this.$p.t('person/email_intern')), + anmerkungen: capitalize(this.$p.t('stv/notes_person')), + anmerkung: capitalize(this.$p.t('stv/notes_prestudent')), + orgform_kurzbz: capitalize(this.$p.t('lehre/orgform')), + aufmerksamdurch_kurzbz: capitalize(this.$p.t('person/aufmerksamDurch')), + punkte: capitalize(this.$p.t('admission/gesamtpunkte')), + aufnahmegruppe_kurzbz: capitalize(this.$p.t('stv/aufnahmegruppe_kurzbz')), + dual: capitalize(this.$p.t('lehre/dual_short')), + matr_nr: capitalize(this.$p.t('person/matrikelnummer')), + studienplan_bezeichnung: capitalize(this.$p.t('lehre/studienplan')), + prestudent_id: capitalize(this.$p.t('ui/prestudent_id')), + priorisierung_relativ: capitalize(this.$p.t('lehre/prioritaet')), + mentor: capitalize(this.$p.t('stv/mentor')), + bnaktiv: capitalize(this.$p.t('person/aktiv')) + }; + + /** NOTE(chris): + * use this approach because updateDefinition + * on the Tabulator columns is way slower and + * freezes up the GUI. + */ + // Overwrite definition for column show/hide + this.$refs.table.tabulator.getColumns().forEach(col => { + const trans = translations[col.getField()]; + if (!trans) + return; + col.getDefinition().title = trans; + }); + // Overwrite node in dom + this.$refs.table.tabulator.element + .querySelectorAll('.tabulator-col[tabulator-field]') + .forEach(el => { + const field = el.getAttribute('tabulator-field'); + if (!translations[field]) + return; + + const title = el.querySelector('.tabulator-col-title'); + if (!title) + return; + + title.innerText = translations[field]; + }); + }); + }, reload() { this.$refs.table.reloadTable(); }, @@ -273,16 +362,20 @@ export default { updateUrl(endpoint, first) { this.lastSelected = first ? undefined : this.selected; - if( endpoint === undefined ) + console.log('function param endpoint: ' + JSON.stringify(endpoint)); + console.log('current endpoint: ' + JSON.stringify(this.currentEndpoint)); + + if( endpoint === undefined && this.currentEndpoint === null) { - endpoint = {url: this.currentEndpointRawUrl}; - } - else if( endpoint.url === undefined ) + endpoint = { url: '' }; + } + else if( endpoint === undefined ) { - endpoint.url = this.currentEndpointRawUrl; - } else + endpoint = JSON.parse(JSON.stringify(this.currentEndpoint)); + } + else { - this.currentEndpointRawUrl = endpoint.url; + this.currentEndpoint = JSON.parse(JSON.stringify(endpoint)); } endpoint.url = endpoint.url.replace( @@ -290,20 +383,25 @@ export default { encodeURIComponent(this.currentSemester) ); - const params = {}; - if (this.filter.length) + const params = (endpoint?.params !== undefined) ? endpoint.params : {}; + let method = (endpoint?.method !== undefined) ? endpoint.method : 'get'; + if (this.filter.length && !endpoint.url.match(/\/search\//)) + { params.filter = this.filter; + method = 'post'; + } + this.tabulatorOptions.ajaxURL = endpoint.url; + this.tabulatorOptions.ajaxParams = { ...params }; + this.tabulatorOptions.ajaxConfig = {method}; if (!this.$refs.table.tableBuilt) { - if (!this.$refs.table.tabulator) { - this.tabulatorOptions.ajaxURL = endpoint.url; - this.tabulatorOptions.ajaxParams = params; - } else + if (this.$refs.table.tabulator) { this.$refs.table.tabulator.on("tableBuilt", () => { - this.$refs.table.tabulator.setData(endpoint.url, params); + this.$refs.table.tabulator.setData(endpoint.url, params, method); }); + } } else - this.$refs.table.tabulator.setData(endpoint.url, params); + this.$refs.table.tabulator.setData(endpoint.url, params, method); }, dragCleanup(evt) { if (evt.dataTransfer.dropEffect == 'none') @@ -402,6 +500,7 @@ export default { */` :new-btn-label="$p.t('stv/action_new')" @click:new="actionNewPrestudent" + @table-built="translateTabulator" >