Merge branch 'master' into demo-cis40

This commit is contained in:
Harald Bamberger
2025-09-29 08:09:33 +02:00
45 changed files with 1054 additions and 310 deletions
+11
View File
@@ -0,0 +1,11 @@
<?php
/*It defines which tags are available in LVVerwaltung and whether they are editable
$config['lvverwaltung_tags'] = [
'tag_1' => ['readonly' => false],
'tag_1' => ['readonly' => true]
];
*/
$config['lvverwaltung_tags'] = [];
+11 -1
View File
@@ -64,7 +64,7 @@ $config['navigation_header'] = array(
'lehrveranstaltungen' => array(
'link' => site_url('lehre/lvplanung/LvTemplateUebersicht'),
'icon' => '',
'description' => 'Lehrveranstaltungen',
'description' => 'Lehrveranstaltungen Templates',
'sort' => 15
),
'reihungstest' => array(
@@ -81,6 +81,16 @@ $config['navigation_header'] = array(
'sort' => 30,
'requiredPermissions' => 'infocenter:r'
),
'lvverwaltung' => array(
'link' => site_url('LVVerwaltung'),
'icon' => '',
'description' => 'LV Verwaltung',
'requiredPermissions' => array(
'admin:r',
'assistenz:r'
),
'sort' => 35
),
'lehrauftrag' => array(
'link' => site_url('lehre/lehrauftrag/Lehrauftrag/Dashboard'),
'description' => 'Lehrauftrag',
@@ -116,7 +116,7 @@ class Gruppe extends FHCAPI_Controller
bezeichnung,
gid,
\'false\' as lehrverband');
$gruppen_result = $this->_ci->GruppeModel->loadWhere(array('sichtbar' => true, 'aktiv' => true, 'lehre' => true, 'direktinskription' => false));
$gruppen_result = $this->_ci->GruppeModel->loadWhere(array('sichtbar' => true, 'aktiv' => true, 'lehre' => true, 'direktinskription' => false, 'semester IS NOT NULL' => null));
$gruppen_array = array();
@@ -350,7 +350,7 @@ class Lehreinheit extends FHCAPI_Controller
$this->form_validation->set_rules($field, 'Start KW', 'integer|greater_than[0]|less_than_equal_to[53]');
break;
case 'gewicht':
$this->form_validation->set_rules($field, 'Gewicht', 'numeric');
$this->form_validation->set_rules($field, 'Gewicht', 'numeric|greater_than_equal_to[0]');
break;
case 'raumtyp':
$this->form_validation->set_rules($field, 'Raumtyp', 'required|max_length[16]');
@@ -365,7 +365,7 @@ class Lehreinheit extends FHCAPI_Controller
$this->form_validation->set_rules($field, 'LVNR', 'integer');
break;
case 'unr':
$this->form_validation->set_rules($field, 'UNR', 'integer');
$this->form_validation->set_rules($field, 'UNR', 'integer|greater_than_equal_to[0]');
break;
case 'lehre':
$this->form_validation->set_rules($field, 'Lehre', 'trim');
@@ -107,7 +107,8 @@ class Lektor extends FHCAPI_Controller
$this->form_validation->set_rules($field, 'Planstunden', 'integer|greater_than_equal_to[0]');
break;
case 'stundensatz':
$this->form_validation->set_rules($field, 'Stundensatz', 'numeric|greater_than_equal_to[0]');
$formData['stundensatz'] = str_replace(',', '.', $formData['stundensatz']);
$this->form_validation->set_rules($field, 'Stundensatz', 'callback__check_stundensatz');
break;
case 'faktor':
$this->form_validation->set_rules($field, 'Faktor', 'numeric|greater_than_equal_to[0]');
@@ -119,6 +120,7 @@ class Lektor extends FHCAPI_Controller
$this->form_validation->set_rules($field, 'Bis Melden', 'trim');
break;
case 'semesterstunden':
$formData['semesterstunden'] = str_replace(',', '.', $formData['semesterstunden']);
$this->form_validation->set_rules($field, 'Semesterstunden', 'callback__check_semesterstunden');
break;
case 'mitarbeiter_uid':
@@ -129,7 +131,7 @@ class Lektor extends FHCAPI_Controller
}
if (!$this->form_validation->run())
{
$this->terminateWithError($this->form_validation->error_array());
$this->terminateWithValidationErrors($this->form_validation->error_array());
}
if (isset($formData['semesterstunden']) && (!is_numeric($formData['semesterstunden']) || $formData['semesterstunden'] === ''))
@@ -145,9 +147,26 @@ class Lektor extends FHCAPI_Controller
$result = $this->_ci->lektorlib->updateLektorFromLehreinheit($lehreinheit_id, $mitarbeiter_uid, $formData);
if (isError($result)) $this->terminateWithError(getError($result));
$this->terminateWithSuccess("Erfolgreich geupdated");
$this->terminateWithSuccess($result);
}
public function _check_stundensatz($value)
{
$value = str_replace(',', '.', $value);
if (!is_numeric($value))
{
$this->form_validation->set_message('_check_decimal', 'Das Feld {field} muss eine Zahl sein.');
return false;
}
if ($value < 0 || $value >= 10000) {
$this->form_validation->set_message('_check_decimal', 'Das Feld {field} muss zwischen 0 und 10000 liegen.');
return false;
}
return true;
}
public function _check_semesterstunden($value)
{
if ($value === null || $value === '') {
@@ -171,6 +190,14 @@ class Lektor extends FHCAPI_Controller
);
return false;
}
if ($value > 999.99)
{
$this->form_validation->set_message(
'_check_semesterstunden',
'Das Feld {field} darf maximal 999,99 betragen.'
);
return false;
}
return true;
}
@@ -50,6 +50,11 @@ class Setup extends FHCAPI_Controller
'component' => APP_ROOT . 'public/js/components/LVVerwaltung/Tabs/Details.js',
'config' => []
);
$tabs['gruppen'] = array (
'title' => 'Gruppen',
'component' => APP_ROOT . 'public/js/components/LVVerwaltung/Tabs/Gruppen.js',
'config' => []
);
$tabs['lektor'] = array (
'title' => 'LektorInnenzuteilung',
'component' => APP_ROOT . 'public/js/components/LVVerwaltung/Tabs/Lektor.js',
@@ -20,5 +20,31 @@ class Tags extends Tag_Controller
'doneLehre' => self::BERECHTIGUNG_KURZBZ,
'deleteLehre' => self::BERECHTIGUNG_KURZBZ,
]);
$this->config->load('lvverwaltung');
}
public function getTag($readonly_tags = null)
{
parent::getTag($this->config->item('lvverwaltung_tags'));
}
public function getTags($tags = null)
{
parent::getTags($this->config->item('lvverwaltung_tags'));
}
public function addTag($withZuordnung = true, $updatable_tags = null)
{
parent::addTag(true, $this->config->item('lvverwaltung_tags'));
}
public function updateTag($updatable_tags = null)
{
parent::updateTag($this->config->item('lvverwaltung_tags'));
}
public function deleteTag($withZuordnung = true, $updatable_tags = null)
{
parent::deleteTag(true, $this->config->item('lvverwaltung_tags'));
}
public function doneTag($updatable_tags = null)
{
parent::doneTag($this->config->item('lvverwaltung_tags'));
}
}
@@ -434,7 +434,10 @@ class Kontakt extends FHCAPI_Controller
$this->FirmaModel->addJoin('public.tbl_firma f', 'ON (f.firma_id = st.firma_id)', 'LEFT');
$this->KontakttypModel->addJoin('public.tbl_kontakttyp kt', 'ON (public.tbl_kontakt.kontakttyp = kt.kontakttyp)');
$result = $this->KontaktModel->loadWhere(
array('person_id' => $person_id)
array(
'person_id' => $person_id,
'public.tbl_kontakt.kontakttyp !=' => 'hidden'
)
);
if (isError($result))
@@ -442,20 +445,18 @@ class Kontakt extends FHCAPI_Controller
$this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL);
}
$this->terminateWithSuccess((getData($result) ?: []));
}
public function getKontakttypen()
{
$this->load->model('person/Kontakttyp_model', 'KontakttypModel');
$this->KontakttypModel->addOrder('beschreibung', 'ASC');
$result = $this->KontakttypModel->loadWhere(array('kontakttyp !=' => 'hidden'));
$result = $this->KontakttypModel->load();
if (isError($result)) {
$this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL);
}
else
{
$this->terminateWithSuccess(getData($result) ?: []);
}
$data = $this->getDataOrTerminateWithError($result);
$this->terminateWithSuccess($data);
}
public function loadContact()
@@ -277,7 +277,17 @@ class Student extends FHCAPI_Controller
$update_person = array();
foreach ($array_allowed_props_person as $prop) {
$val = $this->input->post($prop);
if ($val !== null) {
if ($val === null)
{
continue;
}
if($prop == 'foto')
{
$fotoval = ($val == '') ? null : str_replace('data:image/jpeg;base64,', '', $val);
$update_person[$prop] = $fotoval;
}
else
{
$update_person[$prop] = $val;
}
}
@@ -362,6 +362,8 @@ class InfoCenter extends Auth_Controller
$data[self::ORIGIN_PAGE] = $origin_page;
$data[self::PREV_FILTER_ID] = $this->input->get(self::PREV_FILTER_ID);
$data['studiensemester'] = $this->variablelib->getVar('infocenter_studiensemester');
$this->load->view('system/infocenter/infocenterDetails.php', $data);
}
+2 -2
View File
@@ -393,10 +393,10 @@ abstract class Notiz_Controller extends FHCAPI_Controller
foreach ($result as $doc) {
$res = $this->dmslib->removeAll($doc->dms_id);
if (isError($result))
if (isError($res))
{
$this->db->trans_rollback();
$this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL);
$this->terminateWithError(getError($res), self::ERROR_TYPE_GENERAL);
}
}
+126 -24
View File
@@ -29,13 +29,36 @@ class Tag_Controller extends FHCAPI_Controller
$this->load->model('person/Notiz_model', 'NotizModel');
$this->load->model('system/Notiztyp_model', 'NotiztypModel');
$this->load->model('person/Notizzuordnung_model', 'NotizzuordnungModel');
$this->loadPhrases([
'ui'
]);
}
public function getTag()
public function getTag($readonly_tags = null)
{
$language = $this->_getLanguageIndex();
$id = $this->input->get('id');
if (is_array($readonly_tags) && !isEmptyArray($readonly_tags))
{
$readonly_tags = $this->_filterTag($readonly_tags, true);
foreach ($readonly_tags as $key => $tag)
{
$readonly_tags[$key] = $this->NotizModel->db->escape($tag);
}
$tags = '(' . implode(',', $readonly_tags) . ')';
$this->NotizModel->addSelect("
CASE
WHEN tbl_notiz_typ.typ_kurzbz IN $tags
THEN TRUE
ELSE FALSE
END as readonly
");
}
$this->NotizModel->addSelect(
"tbl_notiz.titel,
tbl_notiz.text,
@@ -54,7 +77,7 @@ class Tag_Controller extends FHCAPI_Controller
$this->NotizModel->addJoin('public.tbl_benutzer verfasserbenutzer', 'tbl_notiz.verfasser_uid = verfasserbenutzer.uid', 'LEFT');
$this->NotizModel->addJoin('public.tbl_person verfasserperson', 'verfasserbenutzer.person_id = verfasserperson.person_id', 'LEFT');
$this->NotizModel->addJoin('public.tbl_benutzer bearbeiterbenutzer', 'tbl_notiz.verfasser_uid = bearbeiterbenutzer.uid', 'LEFT');
$this->NotizModel->addJoin('public.tbl_benutzer bearbeiterbenutzer', 'tbl_notiz.bearbeiter_uid = bearbeiterbenutzer.uid', 'LEFT');
$this->NotizModel->addJoin('public.tbl_person bearbeiterperson', 'bearbeiterbenutzer.person_id = bearbeiterperson.person_id', 'LEFT');
$notiz = $this->NotizModel->loadWhere(array('notiz_id' => $id));
@@ -62,7 +85,7 @@ class Tag_Controller extends FHCAPI_Controller
$this->terminateWithSuccess(hasData($notiz) ? getData($notiz)[0] : array());
}
public function getTags()
public function getTags($tags = null)
{
$this->NotiztypModel->addSelect(
'typ_kurzbz as tag_typ_kurzbz,
@@ -73,19 +96,36 @@ class Tag_Controller extends FHCAPI_Controller
'
);
$this->NotiztypModel->addOrder('prioritaet');
if (is_array($tags) && !isEmptyArray($tags))
{
$tags = $this->_filterTag($tags, false);
$this->NotiztypModel->db->where_in('typ_kurzbz', $tags);
}
$notiztypen = $this->NotiztypModel->loadWhere(array('aktiv' => true));
$this->terminateWithSuccess(hasData($notiztypen) ? getData($notiztypen) : array());
}
public function addTag($withZuordnung = true)
public function addTag($withZuordnung = true, $updatable_tags = null)
{
$postData = $this->getPostJson();
$checkTyp = $this->NotiztypModel->loadWhere(array('typ_kurzbz' => $postData->tag_typ_kurzbz));
if (!hasData($checkTyp))
$this->terminateWithError('Error occurred', self::ERROR_TYPE_GENERAL);
if (isError($checkTyp))
$this->terminateWithError($this->p->t('ui', 'fehlerBeimLesen'));
if (!hasData($checkTyp))
$this->terminateWithError($this->p->t('ui', 'fehlerBeimLesen'));
if (is_array($updatable_tags) && !isEmptyArray($updatable_tags))
{
$tags = $this->_filterTag($updatable_tags, false);
if (!in_array($postData->tag_typ_kurzbz, $tags))
$this->terminateWithError($this->p->t('ui', 'keineBerechtigung'));
}
if ($withZuordnung)
{
@@ -125,48 +165,88 @@ class Tag_Controller extends FHCAPI_Controller
}
}
private function addNotiz($postData)
{
return $this->NotizModel->insert(array(
'titel' => 'TAG', //TODO klären
'text' => $postData->notiz,
'verfasser_uid' => $this->_uid,
'erledigt' => false,
'insertamum' => date('Y-m-d H:i:s'),
'insertvon' => $this->_uid,
'typ' => $postData->tag_typ_kurzbz
));
}
public function updateTag()
public function updateTag($updatable_tags = null)
{
$postData = $this->getPostJson();
$post_tag = $this->NotizModel->loadWhere(array('notiz_id' => $postData->id));
if (isError($post_tag))
$this->terminateWithError($this->p->t('ui', 'fehlerBeimLesen'));
if (!hasData($post_tag))
$this->terminateWithError($this->p->t('ui', 'fehlerBeimLesen'));
if (is_array($updatable_tags) && !isEmptyArray($updatable_tags))
{
$tags = $this->_filterTag($updatable_tags, false);
$post_tag_typ = getData($post_tag)[0]->typ;
if (!in_array($post_tag_typ, $tags))
$this->terminateWithError($this->p->t('ui', 'keineBerechtigung'));
}
$updateData = $this->NotizModel->update(array('notiz_id' => $postData->id),
array('text' => $postData->notiz,
'updateamum' => date('Y-m-d H:i:s'),
'updatevon' => $this->_uid,
'bearbeiter_uid' => $this->_uid,
)
)
);
$this->terminateWithSuccess($updateData);
}
public function doneTag()
public function doneTag($updatable_tags = null)
{
$postData = $this->getPostJson();
$post_tag = $this->NotizModel->loadWhere(array('notiz_id' => $postData->id));
if (isError($post_tag))
$this->terminateWithError($this->p->t('ui', 'fehlerBeimLesen'));
if (!hasData($post_tag))
$this->terminateWithError($this->p->t('ui', 'fehlerBeimLesen'));
if (is_array($updatable_tags) && !isEmptyArray($updatable_tags))
{
$tags = $this->_filterTag($updatable_tags, false);
$post_tag_typ = getData($post_tag)[0]->typ;
if (!in_array($post_tag_typ, $tags))
$this->terminateWithError($this->p->t('ui', 'keineBerechtigung'));
}
$updateData = $this->NotizModel->update(array('notiz_id' => $postData->id),
array('erledigt' => !$postData->done,
'text' => $postData->notiz,
'updateamum' => date('Y-m-d H:i:s'),
'updatevon' => $this->_uid,
'bearbeiter_uid' => $this->_uid,
)
);
$this->terminateWithSuccess($updateData);
}
public function deleteTag($withZuordnung = true)
public function deleteTag($withZuordnung = true, $updatable_tags = null)
{
$postData = $this->getPostJson();
$post_tag = $this->NotizModel->loadWhere(array('notiz_id' => $postData->id));
if (isError($post_tag))
$this->terminateWithError($this->p->t('ui', 'fehlerBeimLesen'));
if (!hasData($post_tag))
$this->terminateWithError($this->p->t('ui', 'fehlerBeimLesen'));
if (is_array($updatable_tags) && !isEmptyArray($updatable_tags))
{
$tags = $this->_filterTag($updatable_tags, false);
$post_tag_typ = getData($post_tag)[0]->typ;
if (!in_array($post_tag_typ, $tags))
$this->terminateWithError($this->p->t('ui', 'keineBerechtigung'));
}
$deleteNotiz = "";
if ($withZuordnung)
@@ -208,5 +288,27 @@ class Tag_Controller extends FHCAPI_Controller
return hasData($result) ? getData($result)[0]->index : 1;
}
private function _filterTag($tags, $readonly = true)
{
$filtered_tags = array_filter($tags, function ($tag) use ($readonly)
{
return isset($tag['readonly']) && $tag['readonly'] === $readonly;
});
return array_keys($filtered_tags);
}
private function addNotiz($postData)
{
return $this->NotizModel->insert(array(
'titel' => 'TAG', //TODO klären
'text' => $postData->notiz,
'verfasser_uid' => $this->_uid,
'erledigt' => false,
'insertamum' => date('Y-m-d H:i:s'),
'insertvon' => $this->_uid,
'typ' => $postData->tag_typ_kurzbz
));
}
}
+7 -7
View File
@@ -176,11 +176,11 @@ class LektorLib
if (!$echter_dv && (!$this->_ci->permissionlib->isBerechtigt('admin')))
{
if (!$this->LehrauftragAufFirma(isset($formData['mitarbeiter_uid']) ? $formData['mitarbeiter_uid'] : $mitarbeiter_uid))
return error("ACHTUNG: Die maximal erlaubte Semesterstundenanzahl des Lektors von $summe Stunden ($stundengrenze->stunden) wurde ueberschritten!\n Daten wurden NICHT gespeichert!\n\n");
return error("ACHTUNG: Die maximal erlaubte Semesterstundenanzahl des Lektors von $summe Stunden ($stundengrenze->stunden) wurde ueberschritten!\nDaten wurden NICHT gespeichert!\n\n");
}
else
{
$warning .= "ACHTUNG: Die maximal erlaubte Semesterstundenanzahl des Lektors von $summe Stunden ($stundengrenze->stunden) wurde ueberschritten!\n Daten wurden gespeichert!\n\n";
$warning .= "ACHTUNG: Die maximal erlaubte Semesterstundenanzahl des Lektors von $summe Stunden ($stundengrenze->stunden) wurde ueberschritten!\nDaten wurden gespeichert!\n\n";
}
$stunden_limit_result = $this->getStundenInstitut($mitarbeiter_uid, $lehreinheit->studiensemester_kurzbz, $oe_array);
@@ -190,7 +190,7 @@ class LektorLib
$stunden_limit_array = getData($stunden_limit_result);
foreach ($stunden_limit_array as $stunden_limit)
{
$warning .= $stunden_limit->summe . ' Stunden' . $stunden_limit->bezeichnung;
$warning .= $stunden_limit->summe . ' Stunden ' . $stunden_limit->bezeichnung . "\n";
}
}
}
@@ -206,7 +206,7 @@ class LektorLib
$benutzer_aktiv = getData($benutzer_result)[0]->aktiv;
if (!$benutzer_aktiv)
$warning .= "Achtung: Der/Die Benutzer*in ist inaktiv!\nBitte informieren Sie die Personalbteilung.\n\nDaten wurden gespeichert.\n\n";
$warning .= "Achtung: Der/Die Benutzer*in ist inaktiv!\nBitte informieren Sie die Personalbteilung.\nDaten wurden gespeichert.\n\n";
$updatableFields = array(
'semesterstunden',
@@ -236,9 +236,9 @@ class LektorLib
if (isError($result)) return $result;
if ($warning !== '') return error($warning);
if ($warning !== '') return success(['warning' => $warning]);
return success('Successfully updated Lehreinheit');
return success('Erfolgreich geupdated');
}
private function getMaxStunden($mitarbeiter_uid, $studiensemester_kurzbz, $studiengang_kz, $echter_dv)
@@ -280,7 +280,7 @@ class LektorLib
foreach ($stunden_limit_array as $stunden_limit)
{
$error .= $stunden_limit->summe . ' Stunden' . $stunden_limit->bezeichnung;
$error .= $stunden_limit->summe . ' Stunden ' . $stunden_limit->bezeichnung . "\n";
}
}
return error($error);
+22 -4
View File
@@ -214,7 +214,7 @@ class ProfilLib{
* @param integer $uid the userID used to get the kontakt information
* @return array all the kontakt information corresponding to a userID
*/
private function getKontaktInfo($pid)
private function getKontaktInfo($pid, $includehidden=false)
{
$this->ci->load->model("person/Kontakt_model","KontaktModel");
$this->ci->KontaktModel->addSelect(['kontakttyp', 'kontakt_id', 'kontakt', 'tbl_kontakt.anmerkung', 'tbl_kontakt.zustellung']);
@@ -222,7 +222,13 @@ class ProfilLib{
$this->ci->KontaktModel->addJoin('public.tbl_firma', 'firma_id', 'LEFT');
$this->ci->KontaktModel->addOrder('kontakttyp, kontakt, tbl_kontakt.updateamum, tbl_kontakt.insertamum');
$kontakte_res = $this->ci->KontaktModel->loadWhere(['person_id' => $pid]);
$params = array('person_id' => $pid);
if(!$includehidden)
{
$params['kontakttyp <>'] = 'hidden';
}
$kontakte_res = $this->ci->KontaktModel->loadWhere($params);
if(isError($kontakte_res)){
return error(getData($kontakte_res));
}
@@ -303,10 +309,22 @@ class ProfilLib{
private function getBenutzerFunktion($uid)
{
$this->ci->load->model("person/Benutzerfunktion_model","BenutzerfunktionModel");
$this->ci->BenutzerfunktionModel->addSelect(["tbl_benutzerfunktion.bezeichnung as Bezeichnung", "tbl_organisationseinheit.bezeichnung as Organisationseinheit", "datum_von as Gültig_von", "datum_bis as Gültig_bis", "wochenstunden as Wochenstunden"]);
$this->ci->BenutzerfunktionModel->addSelect([
"CASE WHEN (tbl_benutzerfunktion.bezeichnung IS NOT NULL AND tbl_benutzerfunktion.bezeichnung <> '' AND tbl_benutzerfunktion.bezeichnung <> tbl_funktion.beschreibung) THEN tbl_funktion.beschreibung || ' - ' || tbl_benutzerfunktion.bezeichnung ELSE tbl_funktion.beschreibung END as \"Bezeichnung\"",
"tbl_organisationseinheit.bezeichnung as Organisationseinheit",
"datum_von as Gültig_von",
"datum_bis as Gültig_bis",
"COALESCE(wochenstunden, '0'::numeric(5,2)) AS \"Wochenstunden\""
]);
$this->ci->BenutzerfunktionModel->addJoin("tbl_funktion", "funktion_kurzbz");
$this->ci->BenutzerfunktionModel->addJoin("tbl_organisationseinheit", "oe_kurzbz");
$benutzer_funktion_res = $this->ci->BenutzerfunktionModel->loadWhere(array('uid' => $uid));
$benutzer_funktion_res = $this->ci->BenutzerfunktionModel->loadWhere(
array(
'uid' => $uid,
'NOW()::date BETWEEN COALESCE(datum_von, \'1970-01-01\'::date) AND COALESCE(datum_bis, \'2170-12-01\'::date)' => null
)
);
if(isError($benutzer_funktion_res)){
return error(getData($benutzer_funktion_res));
}
@@ -694,10 +694,26 @@ EOSQL;
private function _getTagsCTE()
{
$this->load->config('lvverwaltung');
$tags = $this->config->item('tags');
$whereTags = '';
if (is_array($tags) && !isEmptyArray($tags))
{
$tags = array_keys($tags);
foreach ($tags as $key => $tag)
{
$tags[$key] = $this->db->escape($tag);
}
$whereTags = " AND tbl_notiz_typ.typ_kurzbz IN (" . implode(",", $tags) . ")";
}
return "tag_data_agg AS (
SELECT
lehreinheit_id,
COALESCE(json_agg(tag ORDER BY id), '[]'::json) AS tags
COALESCE(json_agg(tag ORDER BY done), '[]'::json) AS tags
FROM (
SELECT DISTINCT ON (public.tbl_notiz.notiz_id)
tbl_notiz.notiz_id AS id,
@@ -710,8 +726,9 @@ EOSQL;
FROM public.tbl_notizzuordnung
JOIN public.tbl_notiz ON tbl_notizzuordnung.notiz_id = tbl_notiz.notiz_id
JOIN public.tbl_notiz_typ ON tbl_notiz.typ = tbl_notiz_typ.typ_kurzbz
WHERE lehreinheit_id IN (SELECT lehreinheit_id FROM lehreinheiten)
) AS tag
WHERE lehreinheit_id IN (SELECT lehreinheit_id FROM lehreinheiten)"
. $whereTags.
") AS tag
GROUP BY lehreinheit_id
)";
}
+52 -64
View File
@@ -307,72 +307,60 @@ class Person_model extends DB_Model
public function checkDuplicate($person_id)
{
$qry = "SELECT person_id
FROM public.tbl_prestudent p
JOIN
(
SELECT DISTINCT ON(prestudent_id) *
FROM public.tbl_prestudentstatus
WHERE prestudent_id IN
(
SELECT prestudent_id
FROM public.tbl_prestudent
WHERE person_id IN
(
SELECT p2.person_id
FROM public.tbl_person p
JOIN public.tbl_person p2
ON lower(p.vorname) = lower(p2.vorname)
AND lower(p.nachname) = lower(p2.nachname)
AND p.gebdatum = p2.gebdatum
AND p.person_id = ?
)
)
ORDER BY prestudent_id, datum DESC, insertamum DESC
) ps USING(prestudent_id)
JOIN public.tbl_status USING(status_kurzbz)
$qry = "
WITH person AS (
SELECT *
FROM public.tbl_person
WHERE person_id = ?
),
allePersonen AS (
SELECT p.person_id
FROM public.tbl_person p
JOIN person
ON lower(p.vorname) = lower(person.vorname)
AND lower(p.nachname) = lower(person.nachname)
AND p.gebdatum = person.gebdatum
),
lastStatus AS (
SELECT DISTINCT ON (tbl_prestudentstatus.prestudent_id)
tbl_prestudentstatus.prestudent_id,
tbl_prestudentstatus.status_kurzbz,
tbl_prestudent.studiengang_kz,
tbl_prestudent.person_id
FROM public.tbl_prestudentstatus
JOIN public.tbl_prestudent USING (prestudent_id)
WHERE tbl_prestudent.person_id IN (SELECT person_id FROM allePersonen)
ORDER BY tbl_prestudentstatus.prestudent_id, tbl_prestudentstatus.datum DESC, tbl_prestudentstatus.insertamum DESC
),
interessenten AS (
SELECT *
FROM lastStatus
WHERE status_kurzbz = 'Interessent'
AND studiengang_kz IN
(
SELECT studiengang_kz
FROM public.tbl_prestudent p
JOIN
(
SELECT DISTINCT ON(prestudent_id) *
FROM public.tbl_prestudentstatus
WHERE prestudent_id IN
(
SELECT prestudent_id
FROM public.tbl_prestudent
WHERE person_id IN
(
SELECT p2.person_id
FROM public.tbl_person p
JOIN public.tbl_person p2
ON lower(p.vorname) = lower(p2.vorname)
AND lower(p.nachname) = lower(p2.nachname)
AND p.gebdatum = p2.gebdatum
AND p.person_id = ?
)
)
ORDER BY prestudent_id, datum DESC, insertamum DESC
) ps USING(prestudent_id)
JOIN public.tbl_status USING(status_kurzbz)
WHERE status_kurzbz = 'Abbrecher'
)
UNION
),
keineInteressenten AS (
SELECT *
FROM lastStatus
WHERE status_kurzbz != 'Interessent'
),
doppeltePerson AS (
SELECT p2.person_id
FROM tbl_person p1
JOIN tbl_prestudent ps ON p1.person_id = ps.person_id
INNER JOIN (
SELECT vorname, nachname, gebdatum, person.person_id
FROM tbl_person person
JOIN tbl_prestudent sps ON person.person_id = sps.person_id
) p2
ON (lower(p1.vorname) = lower(p2.vorname) AND lower(p1.nachname) = lower(p2.nachname) AND p1.gebdatum = p2.gebdatum)
WHERE p1.person_id != p2.person_id AND (p1.person_id = ?)";
FROM public.tbl_person p1
JOIN public.tbl_prestudent ps1 ON ps1.person_id = p1.person_id
JOIN public.tbl_person p2
ON lower(p1.vorname) = lower(p2.vorname)
AND lower(p1.nachname) = lower(p2.nachname)
AND p1.gebdatum = p2.gebdatum
WHERE p1.person_id = ?
AND p1.person_id <> p2.person_id
)
SELECT DISTINCT(interessenten.person_id)
FROM interessenten
JOIN keineInteressenten
ON interessenten.studiengang_kz = keineInteressenten.studiengang_kz
WHERE interessenten.person_id = ?
UNION
SELECT DISTINCT person_id
FROM doppeltePerson";
return $this->execQuery($qry, array($person_id, $person_id, $person_id));
}
@@ -18,6 +18,9 @@
<?php (!isset($notiz->kurzbzlang)) ?: print_r('(' . nl2br($notiz->kurzbzlang) . ') - ') ?>
<?php echo nl2br($notiz->text) ?>
</td>
<td>
<a href="mailto:<?php echo htmlspecialchars($notiz->email)?>?body=<?php echo rawurlencode($notiz->text);; ?>"><i class="fa fa-envelope"></i></a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
@@ -57,7 +57,7 @@
<div class="row<?php if ($lockedbyother) echo ' alert-danger' ?>">
<div class="col-lg-8">
<h3 class="page-header">
Infocenter Details: <?php echo $stammdaten->vorname.' '.$stammdaten->nachname ?>
Infocenter Details: <a target="_blank" title="Studentenverwaltung" href="<?php echo site_url('/Studentenverwaltung/' . $studiensemester . '/person/' . $stammdaten->person_id) ?>"><?php echo $stammdaten->vorname.' '.$stammdaten->nachname ?> <i class="fa fa-external-link" style="font-size:small"></i></a>
</h3>
</div>
<div class="col-lg-4">
+10
View File
@@ -28,3 +28,13 @@ textarea[name="anmerkung"] {
content: '\f073';
color: #f3c541;
}
.modal-dialog.modal-xxl {
max-width: 95% !important;
}
.tabulator-menu .tabulator-menu-item.tabulator-menu-item-submenu:after
{
border-color: black;
}
+153 -1
View File
@@ -1 +1,153 @@
@import '../../vendor/olifolkerd/tabulator6/dist/css/tabulator_simple.min.css';
@import '../../vendor/olifolkerd/tabulator6/dist/css/tabulator_simple.min.css';
.tabulator-row {
border-bottom: none;
}
.tabulator-row .tabulator-frozen,
.tabulator-row .tabulator-cell {
border-bottom: 1px solid #dee2e6;
}
.tabulator-row.tabulator-row-even {
background-color: transparent;
}
.tabulator-headers .tabulator-frozen,
.tabulator-row.tabulator-row-odd .tabulator-frozen,
.tabulator-row.tabulator-row-odd .tabulator-cell {
background-color: #fff;
}
.tabulator-row.tabulator-row-even .tabulator-frozen,
.tabulator-row.tabulator-row-even .tabulator-cell {
background-color: #f2f2f2;
}
.tabulator-row.tabulator-selectable:hover .tabulator-frozen,
.tabulator-row.tabulator-selectable:hover .tabulator-cell {
background-color: #ececec;
}
.tabulator-row.tabulator-selected .tabulator-frozen,
.tabulator-row.tabulator-selected .tabulator-cell {
background-color: #9abcea;
}
.tabulator-row.tabulator-selected:hover .tabulator-frozen,
.tabulator-row.tabulator-selected:hover .tabulator-cell {
background-color: #769bcc;
}
.tabulator .tabulator-col-resize-handle:last-of-type {
z-index: 999999;
}
/* classes for rows that are not selectable in the tabulator, except for rows that are used for calculation */
.tabulator-row.tabulator-unselectable:not(.tabulator-calcs) {
color: #adb5bd !important;
pointer-events: none !important;
}
.tabulator-row.tabulator-unselectable a {
pointer-events: auto !important;
}
/* using bootstrap background classes to style the background color of tabulator rows */
/* bg-warning */
/* odd-rows */
.tabulator-headers .tabulator-frozen.bg-warning,
.tabulator-row.tabulator-row-odd .tabulator-frozen.bg-warning,
.tabulator-row.tabulator-row-odd .tabulator-cell.bg-warning {
background-color: #fcf8e3;
}
/* even-rows */
.tabulator-headers .tabulator-frozen.bg-warning,
.tabulator-row.tabulator-row-even .tabulator-frozen.bg-warning,
.tabulator-row.tabulator-row-even .tabulator-cell.bg-warning {
background-color: #fcf8e3;
}
/* bg-success */
/* odd-rows */
.tabulator-headers .tabulator-frozen.bg-success,
.tabulator-row.tabulator-row-odd .tabulator-frozen.bg-success,
.tabulator-row.tabulator-row-odd .tabulator-cell.bg-success {
background-color: #dff0d8;
}
/* even-rows */
.tabulator-headers .tabulator-frozen.bg-success,
.tabulator-row.tabulator-row-even .tabulator-frozen.bg-success,
.tabulator-row.tabulator-row-even .tabulator-cell.bg-success {
background-color: #dff0d8;
}
/* bg-info */
/* odd-rows */
.tabulator-headers .tabulator-frozen.bg-info,
.tabulator-row.tabulator-row-odd .tabulator-frozen.bg-info,
.tabulator-row.tabulator-row-odd .tabulator-cell.bg-info {
background-color: #d9edf7;
}
/* even-rows */
.tabulator-headers .tabulator-frozen.bg-info,
.tabulator-row.tabulator-row-even .tabulator-frozen.bg-info,
.tabulator-row.tabulator-row-even .tabulator-cell.bg-info {
background-color: #d9edf7;
}
/* special bootstrap5 styling for tableWidget and their accordion-item ::after content */
.accordion-button::after{
content:none !important;
}
.tabulator {
font-size: 1rem;
}
.tabulator-initialfontsize .tabulator {
font-size: 14px;
}
.tabulator-row.tabulator-unselectable .tabulator-cell {
pointer-events: all;
}
.tabulator-tooltip {
color: #fff;
background-color: #000;
}
.tabulator-row.tabulator-unselectable:not(.tabulator-calcs) {
color: #777 !important;
}
.tabulator-cell .btn {
padding: 0 .375rem;
font-size: calc(1rem - 2px / 1.5); /* substract border (2 x 1px) modified by the line-height (1.5) */
border-radius: .2rem;
vertical-align: baseline;
}
.tabulator-row.tabulator-selectable:focus {
box-shadow: 0 0 0 .24rem rgba(13,110,253,.25);
z-index: 1;
outline: 0;
}
.btn-select-col-selected
{
background-color: #e6e6e6;
}
.accordion-item-dark, .accordion-item-dark:focus{
background-color:#CED4DA !important;
border-color:#ADB5BD !important;
box-shadow: none !important;
}
/**
* Make keyboard-focused list items look like the mouse-hovered list items
*/
.tabulator-edit-list .tabulator-edit-list-item.focused {
color: #fff;
background: #1d68cd;
}
+10 -27
View File
@@ -2,6 +2,8 @@ import raum_contentmittitel from './Content_types/Raum_contentmittitel.js'
import general from './Content_types/General.js'
import BsConfirm from "../../Bootstrap/Confirm.js";
import news_content from './Content_types/News_content.js';
import iframe_content from './Content_types/Iframe_content.js';
import ApiCms from '../../../api/factory/cms.js';
export default {
@@ -24,42 +26,23 @@ export default {
raum_contentmittitel,
news_content,
general,
iframe_content
},
data() {
return {
content_type: null,
content: null,
content_id_internal: this.content_id
};
},
methods: {
fetchContent(){
return this.$api
this.$api
.call(ApiCms.content(this.content_id_internal, this.version, this.sprache, this.sichtbar))
.then(res => {
this.content = res.data.content;
this.content_type = res.data.type;
document.querySelectorAll("#cms [data-confirm]").forEach((el) => {
el.addEventListener("click", (evt) => {
evt.preventDefault();
BsConfirm.popup(el.dataset.confirm)
.then(() => {
Axios.get(el.href)
.then((res) => {
// TODO(chris): check for success then show message and/or reload
location = location;
})
.catch((err) => console.error("ERROR:", err));
})
.catch(() => {
});
});
});
document.querySelectorAll("#cms [data-href]").forEach((el) => {
el.href = el.dataset.href.replace(
/^ROOT\//,
FHC_JS_DATA_STORAGE_OBJECT.app_root
);
this.$nextTick(function() {
this.content = res.data.content;
this.content_type = res.data.type;
});
});
}
@@ -83,6 +66,8 @@ export default {
return "raum_contentmittitel";
case "news":
return "news_content";
case "iframe":
return "iframe_content";
default:
return "general";
};
@@ -91,8 +76,6 @@ export default {
created() {
this.fetchContent();
},
mounted() {
},
template: /*html*/ `
<!-- div that contains the content -->
<div id="fhc-cms-content" v-if="content">
@@ -0,0 +1,31 @@
import { replaceRelativeLegacyLink } from "../../../../helpers/LegacyLinkReplaceHelper.js";
export default {
name: "iframe_content",
props: {
content: { type: String, required: true }
},
computed: {
srcUrl() {
const parser = new DOMParser()
const doc = parser.parseFromString(`<div>${this.content}</div>`, "text/html");
const iframe = doc.querySelector("iframe[src]");
if (!iframe)
return "";
let url = iframe.getAttribute("src") || "";
return replaceRelativeLegacyLink(url);
}
},
template: `
<div class="w-100">
<iframe
v-if="srcUrl"
:src="srcUrl"
style="width:100%; height:90vh; border:0; display:block;"
></iframe>
<div v-else class="alert alert-warning">Keine URL gefunden.</div>
</div>
`
};
@@ -11,6 +11,7 @@ import RoleInformation from "./ProfilComponents/RoleInformation.js";
import ProfilInformation from "./ProfilComponents/ProfilInformation.js";
import ApiProfilUpdate from '../../../api/factory/profilUpdate.js';
import { dateFilter } from '../../../tabulator/filters/Dates.js';
export default {
components: {
@@ -40,7 +41,7 @@ export default {
persistence: {
columns: false
},
height: 300,
minHeight: 300,
layout: "fitColumns",
responsiveLayout: "collapse",
responsiveLayoutCollapseUseFormatters: false,
@@ -74,18 +75,24 @@ export default {
{
title: Vue.computed(() => this.preloadedPhrasen.gueltigVonPhrase),
field: "Gültig_von",
headerFilter: true,
headerFilterFunc: 'dates',
headerFilter: dateFilter,
resizable: true,
minWidth: 200,
visible: true
visible: true,
formatter:"datetime",
formatterParams: this.datetimeFormatterParams()
},
{
title: Vue.computed(() => this.preloadedPhrasen.gueltigBisPhrase),
field: "Gültig_bis",
headerFilter: true,
headerFilterFunc: 'dates',
headerFilter: dateFilter,
resizable: true,
minWidth: 200,
visible: true
visible: true,
formatter:"datetime",
formatterParams: this.datetimeFormatterParams()
},
{
title: Vue.computed(() => this.preloadedPhrasen.wochenstundenPhrase),
@@ -102,7 +109,7 @@ export default {
persistence: {
columns: false
},
height: 300,
minHeight: 300,
layout: "fitColumns",
responsiveLayout: "collapse",
responsiveLayoutCollapseUseFormatters: false,
@@ -138,9 +145,12 @@ export default {
{
title: Vue.computed(() => this.preloadedPhrasen.ausgabedatumPhrase),
field: "Ausgegeben_am",
headerFilter: true,
headerFilterFunc: 'dates',
headerFilter: dateFilter,
minWidth: 200,
visible: true
visible: true,
formatter:"datetime",
formatterParams: this.datetimeFormatterParams()
},
],
}
@@ -211,6 +221,15 @@ export default {
setTableColumnTitles() { // reevaluates computed phrasen
if(this.$refs.betriebsmittelTable) this.$refs.betriebsmittelTable.tabulator.setColumns(this.betriebsmittel_table_options.columns)
if(this.$refs.funktionenTable) this.$refs.funktionenTable.tabulator.setColumns(this.funktionen_table_options.columns)
},
datetimeFormatterParams: function() {
const params = {
inputFormat:"yyyy-MM-dd",
outputFormat:"dd.MM.yyyy",
invalidPlaceholder:"(invalid date)",
timezone:FHC_JS_DATA_STORAGE_OBJECT.timezone
};
return params;
}
},
@@ -5,6 +5,8 @@ import RoleInformation from "./ProfilComponents/RoleInformation.js";
import ProfilEmails from "./ProfilComponents/ProfilEmails.js";
import ProfilInformation from "./ProfilComponents/ProfilInformation.js";
import { dateFilter } from '../../../tabulator/filters/Dates.js';
export default {
components: {
CoreFilterCmpt,
@@ -24,7 +26,7 @@ export default {
persistence: {
columns: false
},
height: 300,
minHeight: 300,
layout: "fitColumns",
responsiveLayout: "collapse",
responsiveLayoutCollapseUseFormatters: false,
@@ -60,18 +62,24 @@ export default {
{
title: Vue.computed(() => this.$p.t('global/gueltigVon')),
field: "Gültig_von",
headerFilter: true,
headerFilterFunc: 'dates',
headerFilter: dateFilter,
resizable: true,
minWidth: 200,
visible: true
visible: true,
formatter:"datetime",
formatterParams: this.datetimeFormatterParams()
},
{
title: Vue.computed(() => this.$p.t('global/gueltigBis')),
field: "Gültig_bis",
headerFilter: true,
headerFilterFunc: 'dates',
headerFilter: dateFilter,
resizable: true,
minWidth: 200,
visible: true
visible: true,
formatter:"datetime",
formatterParams: this.datetimeFormatterParams()
},
{
title: Vue.computed(() => this.$p.t('profil/wochenstunden')),
@@ -91,6 +99,15 @@ export default {
funktionenTableBuilt: function () {
this.$refs.funktionenTable.tabulator.setData(this.data.funktionen);
},
datetimeFormatterParams: function() {
const params = {
inputFormat:"yyyy-MM-dd",
outputFormat:"dd.MM.yyyy",
invalidPlaceholder:"(invalid date)",
timezone:FHC_JS_DATA_STORAGE_OBJECT.timezone
};
return params;
}
},
watch: {
'data.funktionen'(newVal) {
@@ -10,21 +10,34 @@ export default {
type: String,
}
},
inject: [
'studiengang_kz', // inject info that should not be displayed
],
inject: {
// inject info that should not be displayed
'studiengang_kz': {
from: 'studiengang_kz',
default: false
},
},
computed: {
getLinkGruppeListe() {
if(this.studiengang_kz === false) {
return '';
}
return this.data.gruppe?.value && this.data.verband?.value && this.data.semester?.value ? FHC_JS_DATA_STORAGE_OBJECT.app_root
+ 'cis/private/stud_in_grp.php?kz='+this.studiengang_kz+'&sem=' + this.data.semester.value
+ '&verband=' + this.data.verband.value + '&grp=' + this.data.gruppe.value : ''
},
getLinkVerbandListe() {
if(this.studiengang_kz === false) {
return '';
}
return this.data.verband?.value && this.data.semester?.value ? FHC_JS_DATA_STORAGE_OBJECT.app_root
+ 'cis/private/stud_in_grp.php?kz='+this.studiengang_kz+'&sem=' + this.data.semester.value
+ '&verband=' + this.data.verband.value : ''
},
getLinkSemesterListe() {
if(this.studiengang_kz === false) {
return '';
}
return this.data.semester?.value ? FHC_JS_DATA_STORAGE_OBJECT.app_root
+ 'cis/private/stud_in_grp.php?kz='+this.studiengang_kz+'&sem=' + this.data.semester.value : ''
}
@@ -16,7 +16,7 @@ export default {
mixins: [BsModal],
props: {
titel: {
type: Object,
type: String,
},
files: {
type: Array,
@@ -57,7 +57,7 @@ export default {
},
template: /*html*/`
<bs-modal v-show="!loading" ref="modalContainer" v-bind="$props" body-class="" dialog-class="modal-lg" class="bootstrap-alert" :backdrop="false">
<bs-modal ref="modalContainer" v-bind="$props" body-class="" dialog-class="modal-lg" class="bootstrap-alert" :backdrop="false">
<template #title>
<p style="opacity:0.8" class="ms-2" v-if="!updateID">{{$p.t('profilUpdate','profilBildUpdateMessage',[titel])}}</p>
</template>
@@ -11,6 +11,7 @@ import FetchProfilUpdates from "./ProfilComponents/FetchProfilUpdates.js";
import EditProfil from "./ProfilModal/EditProfil.js";
import ApiProfilUpdate from '../../../api/factory/profilUpdate.js';
import { dateFilter } from '../../../tabulator/filters/Dates.js';
export default {
components: {
@@ -39,7 +40,7 @@ export default {
persistence: {
columns: false
},
height: 200,
minHeight: 200,
layout: "fitColumns",
columns: [{
title: Vue.computed(() => this.preloadedPhrasen.zutrittsGruppenPhrase),
@@ -51,7 +52,7 @@ export default {
persistence: {
columns: false
},
height: 300,
minHeight: 300,
layout: "fitColumns",
responsiveLayout: "collapse",
responsiveLayoutCollapseUseFormatters: false,
@@ -85,9 +86,12 @@ export default {
{
title: Vue.computed(() =>this.preloadedPhrasen.ausgabedatum) ,
field: "Ausgegeben_am",
headerFilter: true,
headerFilterFunc: 'dates',
headerFilter: dateFilter,
minWidth: 200,
visible: true
visible: true,
formatter:"datetime",
formatterParams: this.datetimeFormatterParams()
},
],
},
@@ -160,6 +164,15 @@ export default {
this.$refs.editModal.show();
});
},
datetimeFormatterParams: function() {
const params = {
inputFormat:"yyyy-MM-dd",
outputFormat:"dd.MM.yyyy",
invalidPlaceholder:"(invalid date)",
timezone:FHC_JS_DATA_STORAGE_OBJECT.timezone
};
return params;
}
},
computed: {
@@ -102,6 +102,13 @@ export default {
redirectToLeitung(){
this.$emit('redirectToLeitung', {
person_id: this.leitungData.person_id});
},
getFotoSrc(foto) {
if(foto === null) {
return FHC_JS_DATA_STORAGE_OBJECT.app_root + 'skin/images/profilbild_dummy.jpg';
} else {
return 'data:image/jpeg;base64,' + foto;
}
}
},
template: `
@@ -116,7 +123,7 @@ export default {
<img
class="d-block h-100 rounded"
alt="Profilbild"
:src="'data:image/jpeg;base64,' + person.foto"
:src="getFotoSrc(person.foto)"
/>
<template v-if="person.foto_sperre">
@@ -38,33 +38,6 @@ export default {
},
template: `
<div>
<div class="row align-items-start mb-3">
<form-input
v-if="showLVID"
:label="$p.t('lehre', 'lehrveranstaltung_id')"
type="text"
container-class="col-3"
v-model="data.lehrveranstaltung_id"
name="lehrveranstaltung_id"
/>
<form-input
v-if="showGewichtung"
:label="$p.t('lehre', 'gewicht')"
type="text"
container-class="col-3"
v-model="data.gewicht"
name="gewicht"
/>
<form-input
:label="$p.t('lehre', 'detailanmerkung')"
type="textarea"
container-class="col-3"
v-model="formattedAnmerkung"
name="anmerkung"
id="anmerkung"
rows="4"
/>
</div>
<div class="row mb-3">
<form-input
:label="$p.t('lehre', 'lehrfach')"
@@ -96,55 +69,15 @@ export default {
{{ lehrform.bez_kurz }} {{ lehrform.bez }}
</option>
</form-input>
</div>
<div class="row mb-3">
<form-input
:label="$p.t('global', 'sprache')"
type="select"
:label="$p.t('lehre', 'detailanmerkung')"
type="textarea"
container-class="col-3"
v-model="data.sprache"
name="sprache"
>
<option
v-for="sprache in dropdowns.sprachen_array"
:key="sprache.sprache"
:value="sprache.sprache"
>
{{ sprache.sprache }}
</option>
</form-input>
<form-input
:label="$p.t('lehre', 'unr')"
type="text"
container-class="col-3"
v-model="data.unr"
name="unr"
/>
</div>
<div class="row mb-3">
<form-input
:label="$p.t('lehre', 'studiensemester')"
type="select"
container-class="col-3"
v-model="data.studiensemester_kurzbz"
name="studiensemester_kurzbz"
>
<option
v-for="semester in dropdowns.studiensemester_array"
:key="semester.studiensemester_kurzbz"
:value="semester.studiensemester_kurzbz"
>
{{ semester.studiensemester_kurzbz }}
</option>
</form-input>
<form-input
:label="$p.t('lehre', 'lehre')"
type="checkbox"
container-class="col-3"
v-model="data.lehre"
name="lehre"
v-model="formattedAnmerkung"
name="anmerkung"
id="anmerkung"
rows="10"
/>
</div>
@@ -179,30 +112,109 @@ export default {
{{ raumtyp.raumtyp_kurzbz }} {{ raumtyp.beschreibung }}
</option>
</form-input>
</div>
<div class="row mb-3">
<form-input
:label="$p.t('global', 'sprache')"
type="select"
container-class="col-3"
v-model="data.sprache"
name="sprache"
>
<option
v-for="sprache in dropdowns.sprachen_array"
:key="sprache.sprache"
:value="sprache.sprache"
>
{{ sprache.sprache }}
</option>
</form-input>
<form-input
:label="$p.t('lehre', 'studiensemester')"
type="select"
container-class="col-3"
v-model="data.studiensemester_kurzbz"
name="studiensemester_kurzbz"
>
<option
v-for="semester in dropdowns.studiensemester_array"
:key="semester.studiensemester_kurzbz"
:value="semester.studiensemester_kurzbz"
>
{{ semester.studiensemester_kurzbz }}
</option>
</form-input>
</div>
<div class="row mb-3">
<form-input
:label="$p.t('lehre', 'startkw')"
type="number"
container-class="col-2"
min="0"
container-class="col-1"
v-model="data.start_kw"
name="start_kw"
/>
<form-input
:label="$p.t('lehre', 'stundenblockung')"
type="number"
container-class="col-2"
min="0"
container-class="col-1"
v-model="data.stundenblockung"
name="stundenblockung"
/>
<form-input
:label="$p.t('lehre', 'wochenrhythmus')"
type="number"
container-class="col-2"
min="0"
container-class="col-1"
v-model="data.wochenrythmus"
name="wochenrythmus"
/>
<div class="col-3 d-flex align-items-end">
<form-input
:label="$p.t('lehre', 'lehre')"
type="checkbox"
v-model="data.lehre"
name="lehre"
/>
</div>
</div>
<div class="row mb-3">
<form-input
v-if="showLVID"
:label="$p.t('lehre', 'lehrveranstaltung_id')"
type="text"
container-class="col-2"
v-model="data.lehrveranstaltung_id"
name="lehrveranstaltung_id"
/>
<form-input
:label="$p.t('lehre', 'unr')"
type="text"
container-class="col-1"
v-model="data.unr"
name="unr"
/>
<form-input
v-if="showGewichtung"
:label="$p.t('lehre', 'gewicht')"
type="text"
container-class="col-1"
v-model="data.gewicht"
name="gewicht"
/>
</div>
</div>
`
@@ -50,7 +50,7 @@ export default{
let button = document.createElement('button');
container.className = "d-flex gap-1";
button.className = 'btn btn-outline-secondary btn-action';
button.className = 'btn btn-outline-secondary';
button.innerHTML = '<i class="fa fa-xmark"></i>';
button.title = this.$p.t('ui', 'loeschen');
button.addEventListener('click', (event) => {
@@ -63,7 +63,7 @@ export default{
{
button = document.createElement('button');
container.className = "d-flex gap-2";
button.className = 'btn btn-outline-secondary btn-action';
button.className = 'btn btn-outline-secondary';
button.innerHTML = '<i class="fa fa-calendar-xmark"></i>';
button.title = this.$p.t('lehre', 'auslvplanentfernen');
button.disabled = !cell.getData().verplant;
@@ -32,13 +32,7 @@ export default {
studiensemester_kurzbz: { type: String, required: false, default: null },
emp: { type: String, required: false, default: null }
},
computed: {
selectedStudiensemester() {
return this.studiensemester_kurzbz != null
? this.studiensemester_kurzbz
: this.defaultSemester;
}
},
provide() {
return {
currentSemester: Vue.computed(() => this.selectedStudiensemester),
@@ -70,12 +64,16 @@ export default {
emp() {
this.updateFilter();
},
studiensemester_kurzbz(newVal) {
this.selectedStudiensemester = newVal ?? this.defaultSemester;
}
},
data() {
return {
selected: [],
studiengang: "",
filter: {},
selectedStudiensemester: this.studiensemester_kurzbz ?? this.defaultSemester,
endpoint: ApiStudiengangTree,
dropdowns: {
studiensemester_array: [],
@@ -88,10 +86,12 @@ export default {
},
selectedStudiengang: '',
searchbaroptions: {
origin: 'lvverwaltung',
cssclass: "position-relative",
calcheightonly: true,
types: [
"mitarbeiter"
"mitarbeiter",
"mitarbeiter_ohne_zuordnung"
],
actions: {
employee: {
@@ -121,9 +121,9 @@ export default{
}
this.$refs.form.call(ApiLektor.update(updatedData))
.then(result => {
let error = result.data?.error;
if (error)
this.$fhcAlert.alertWarning(error)
let warning = result.data?.retval?.warning;
if (warning)
this.$fhcAlert.alertWarning(warning)
this.original = {...this.data};
if (this.changed.mitarbeiter_uid)
@@ -192,12 +192,13 @@ export default{
container-class="col-3"
dropdown
@complete="searchLektor"
name="lektorautocomplete"
></form-input>
<form-input
:label="$p.t('lehre', 'anmerkung')"
type="text"
container-class="col-3"
container-class="col-6"
v-model="data.anmerkung"
name="anmerkung"
>
@@ -207,7 +208,9 @@ export default{
<div class="row mb-3">
<form-input
:label="$p.t('lehre', 'las')"
type="text"
type="number"
min="0"
step="0.01"
container-class="col-3"
:disabled="data.vertrag?.vertragsstatus_kurzbz === 'akzeptiert'"
v-model="data.semesterstunden"
@@ -217,7 +220,9 @@ export default{
<form-input
:label="$p.t('lehre', 'planstunden')"
type="text"
type="number"
min="0"
step="0.01"
container-class="col-3"
v-model="data.planstunden"
name="planstunden"
@@ -225,13 +230,15 @@ export default{
</form-input>
</div>
<div class="row mb-3">
<div class="row mb-3 d-flex align-items-end">
<form-input
:label="data.default_stundensatz !== null
? $p.t('lehre', 'stundensatz') + ' (' + $p.t('lehre', 'default') + ': ' + data.default_stundensatz + ')'
: $p.t('lehre', 'stundensatz')"
type="text"
type="number"
min="0"
step="0.01"
container-class="col-3"
v-model="data.stundensatz"
:disabled="data.vertrag?.vertragsstatus_kurzbz === 'akzeptiert'"
@@ -85,7 +85,7 @@ export default{
button.className = 'btn btn-outline-secondary btn-action';
button.innerHTML = '<i class="fa fa-calendar-xmark"></i>';
button.disabled = !cell.getData().verplant;
button.title = this.$p.t('ui', 'auslvplanentfernen');
button.title = this.$p.t('lehre', 'auslvplanentfernen');
button.addEventListener('click', (event) => {
event.stopPropagation();
this.deleteLVPlan(cell.getData().mitarbeiter_uid, cell.getData().lehreinheit_id)
@@ -199,7 +199,7 @@ export default{
table-only
:side-menu="false"
reload
:new-btn-label="$p.t('lehre', 'addlektor')"
:new-btn-label="$p.t('lehre', 'addLektor')"
new-btn-show
@click:new="showAutocomplete = !showAutocomplete"
>
@@ -88,8 +88,14 @@ export default{
.catch(this.$fhcAlert.handleSystemError);
},
cancelVertrag()
async cancelVertrag()
{
if (await this.$fhcAlert.confirm({
message: this.$p.t('lehre', 'vertragConfirm'),
acceptLabel: this.$p.t('ui', 'ja').charAt(0).toUpperCase() + this.$p.t('ui', 'ja').slice(1),
acceptClass: 'btn btn-danger'}) === false)
return;
let needUpdate = {
vertrag_id: this.data.vertrag.vertrag_id,
mitarbeiter_uid: this.mitarbeiter_uid,
@@ -103,7 +109,6 @@ export default{
.catch(this.$fhcAlert.handleSystemError);
},
},
// language=HTML
template: `
<core-form ref="form">
<fieldset class="overflow-hidden" v-if="showVertragsdetails">
@@ -126,6 +131,7 @@ export default{
type="button"
class="btn btn-outline-secondary w-100"
@click="cancelVertrag"
:title="$p.t('lehre', 'cancelvertrag')"
>
<i class="fa-solid fa-ban"></i>
</button>
@@ -56,7 +56,13 @@ export default {
}
},
deep: true,
},
currentSemester: {
handler(newVal)
{
this.lv_info_default.studiensemester_kurzbz = newVal
this.lv_info = false;
}
}
},
data() {
@@ -137,6 +143,8 @@ export default {
selectableRows: true,
rowContextMenu: (component, e) => {
if (e.getData()?.lehreinheit_id === undefined)
return;
return [
{
label: "LV-Teil kopieren",
@@ -159,7 +167,7 @@ export default {
label: "Nur mit Gruppen",
action: (e, row) =>
{
this.copyLehreinheit(row, "halb");
this.copyLehreinheit(row, "gruppen");
},
},
{
@@ -233,6 +241,17 @@ export default {
const renderTags = () => {
container.innerHTML = '';
parsedTags = parsedTags.filter(item => item !== null);
parsedTags.sort((a, b) => {
let adone = a.done ? 1 : 0;
let bbone = b.done ? 1 : 0;
if (adone !== bbone)
{
return adone - bbone;
}
return b.id - a.id;
});
const tagsToShow = rowData._tagExpanded ? parsedTags : parsedTags.slice(0, maxVisibleTags);
tagsToShow.forEach(tag => {
@@ -445,7 +464,7 @@ export default {
if (!tagExists)
{
addedTag.id = tag.id;
tags.push({ ...addedTag });
tags.unshift({ ...addedTag });
targetRow.update({ tags: JSON.stringify(tags) });
targetRow.reformat();
}
@@ -659,19 +678,19 @@ export default {
<slot name="filterzuruecksetzen"></slot>
</template>
</core-filter-cmpt>
<bs-modal ref="lehreinheitModal" dialogClass="modal-lg">
<template #title>
<p class="fw-bold mt-3">{{$p.t('lehre', 'newlehreinheit')}}</p>
</template>
<bs-modal ref="lehreinheitModal" dialogClass="modal-xxl">
<template #title>
<p class="fw-bold mt-3">{{$p.t('lehre', 'newlehreinheit')}}</p>
</template>
<template v-if="lv_info">
<details-form :data="lv_info"/>
</template>
<template v-if="lv_info">
<details-form :data="lv_info"/>
</template>
<template #footer>
<button type="button" class="btn btn-primary" @click="addNewLehreinheit">{{$p.t('ui', 'speichern')}}</button>
</template>
</bs-modal>
<template #footer>
<button type="button" class="btn btn-primary" @click="addNewLehreinheit">{{$p.t('ui', 'speichern')}}</button>
</template>
</bs-modal>
`
};
@@ -50,7 +50,6 @@ export default {
:modelValue="lv[0]"
:config="configLVTabs"
:default="$route.params.tab"
style="flex: 1 1 0%; height: 0%"
@changed="reload"
/>
</div>
@@ -111,23 +111,14 @@ export default {
<button type="submit" class="btn btn-primary position-absolute top-0 end-0" :disabled="!changedLength">{{$p.t('ui', 'speichern')}}</button>
</div>
<fieldset class="overflow-hidden">
<legend>{{this.$p.t('lehre', 'lehreinheit')}}</legend>
<div class="core-header d-flex flex-column w-100 overflow-auto pb-3 gap-0">
<legend>{{ this.$p.t('lehre', 'lehreinheit') }}</legend>
<small v-if="data" v-html="data.lehreinheit_id" class="text-muted"></small>
</div>
<template v-if="data">
<details-form :data="data"/>
</template>
</fieldset>
</core-form>
<fieldset class="overflow-hidden">
<div class="row">
<div class="col-6">
<legend>{{this.$p.t('lehre', 'gruppen')}}</legend>
<gruppen-table ref="gruppen_table" :lehreinheit_id="modelValue.lehreinheit_id"></gruppen-table>
</div>
<div class="col-6">
<legend>{{this.$p.t('lehre', 'assignedPersons')}}</legend>
<gruppen-direkt-table ref="gruppen_direkt_table" :lehreinheit_id="modelValue.lehreinheit_id"></gruppen-direkt-table>
</div>
</div>
</fieldset>`
`
};
@@ -0,0 +1,39 @@
import GruppenTable from '../Details/Gruppen.js';
import GruppenDirektTable from '../Details/Direktinskription.js';
export default {
name: "LVTabGruppen",
components: {
GruppenTable,
GruppenDirektTable,
},
props: {
modelValue: Object,
config: {
type: Object,
default: {}
},
},
inject: {
dropdowns: {
from: 'dropdowns'
}
},
template: `
<fieldset class="overflow-hidden">
<div class="row">
<div class="col-6">
<legend>{{this.$p.t('lehre', 'gruppen')}}</legend>
<gruppen-table ref="gruppen_table" :lehreinheit_id="modelValue.lehreinheit_id"></gruppen-table>
</div>
<div class="col-6">
<legend>{{this.$p.t('lehre', 'assignedPersons')}}</legend>
<gruppen-direkt-table ref="gruppen_direkt_table" :lehreinheit_id="modelValue.lehreinheit_id"></gruppen-direkt-table>
</div>
</div>
</fieldset>
`
};
@@ -300,8 +300,7 @@ export default{
this.$api
.call(ApiStvContact.getTypes())
.then(result => {
//this.kontakttypen = result.data;
this.kontakttypen = result.data.filter(item => item.kontakttyp !== 'hidden');
this.kontakttypen = result.data;
})
.catch(this.$fhcAlert.handleSystemError);
},
@@ -326,7 +325,7 @@ export default{
v-model="contactData.kontakttyp">
>
<option value="">keine Auswahl</option>
<option v-for="typ in kontakttypen" :key="typ.kontakttyp_kurzbz" :value="typ.kontakttyp" >{{typ.beschreibung}}</option>
<option v-for="typ in kontakttypen" :key="typ.kontakttyp" :value="typ.kontakttyp">{{typ.beschreibung}}</option>
</form-input>
</div>
@@ -455,4 +454,4 @@ export default{
>
</core-filter-cmpt>
</div>`
};
};
+7 -3
View File
@@ -86,6 +86,7 @@ export default {
this.tagData.updateamum = this.formatDateTime(item.updateamum)
this.tagData.bearbeiter = item.bearbeiter;
this.tagData.verfasser = item.verfasser;
this.tagData.readonly = item.readonly;
if (item && item.notiz_id)
{
@@ -154,7 +155,8 @@ export default {
let postData = {
id: this.selectedTagId,
done: !this.tagData.done
done: !this.tagData.done,
notiz: this.tagData.notiz,
}
this.$api.call(this.endpoint.doneTag(postData))
this.$emit("updated", this.tagData);
@@ -182,7 +184,8 @@ export default {
verfasser: "",
updateamum: "",
bearbeiter: "",
response: ""
response: "",
readonly: false
};
this.selectedTagId = null;
this.mode = "create";
@@ -230,6 +233,7 @@ export default {
v-model="tagData.notiz"
type="textarea"
field="notiz"
:readonly="tagData.readonly"
placeholder="Notiz..."
></form-input>
<div class="modificationdate">
@@ -243,7 +247,7 @@ export default {
</div>
</div>
</template>
<template #footer>
<template #footer v-if="!tagData.readonly">
<div class="d-flex justify-content-between w-100">
<div>
<button
+1 -1
View File
@@ -706,7 +706,7 @@ export const CoreFilterCmpt = {
</button>
<span v-if="$slots.actions && tabulatorHasSelector && useSelectionSpan">
<span v-if="countOnly">{{ selectedData.length }} ausgewählt</span>
<span v-else> Mit {{ selectedData.length }} ausgewählten:</span>
<span v-else id="selected-info-text"> Mit {{ selectedData.length }} ausgewählten:</span>
</span>
<slot name="actions" v-bind="{selected: tabulatorHasSelector ? selectedData : []}"></slot>
<slot name="search"></slot>
@@ -146,12 +146,13 @@ export function tagHeaderFilter(headerValue, rowValue, rowData, filterParams)
if (Array.isArray(data))
{
combinedText = data
.filter(item => item?.done === false)
.map(item => `${item?.beschreibung} ${item?.notiz}`)
.join(' ');
}
else if (typeof data === 'object' && data !== null)
{
combinedText = `${data?.beschreibung} ${data?.notiz}`;
combinedText = data?.erledigt === false ? `${data?.beschreibung} ${data?.notiz}` : '';
}
else
{
+1
View File
@@ -77,6 +77,7 @@ require_once('dbupdate_3.4/55614_perm_verwaltetoe.php');
require_once('dbupdate_3.4/25999_C4_dashboard.php');
require_once('dbupdate_3.4/61730_Dashboard_Anpassungen.php');
require_once('dbupdate_3.4/40128_search.php');
require_once('dbupdate_3.4/63436_cis4_iframe_component.php');
require_once('dbupdate_3.4/60882_lehrfaecherverteilung_favorites.php');
require_once('dbupdate_3.4/66982_berufsschule.php');
@@ -0,0 +1,81 @@
<?php
$xsd= <<<EOD
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
<xs:element name="content">
<xs:complexType>
<xs:sequence>
<xs:element name="url" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
EOD;
$xslt_xhtml= <<<EOD
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" encoding="UTF-8"/>
<xsl:template match="content">
<xsl:choose>
<xsl:when test="string(url)">
<iframe
src="{url}"
frameborder="0"
style="width:100%; height:90vh; border:0; display:block;"
>
</iframe>
</xsl:when>
<xsl:otherwise>
<div class="alert alert-warning">Keine URL im Inhalt gefunden.</div>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
EOD;
$xslt_xhtml_c4= <<<EOD
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" encoding="UTF-8"/>
<xsl:template match="content">
<xsl:choose>
<xsl:when test="string(url)">
<iframe
src="{url}"
frameborder="0"
style="width:100%; height:90vh; border:0; display:block;"
>
</iframe>
</xsl:when>
<xsl:otherwise>
<div class="alert alert-warning">Keine URL im Inhalt gefunden.</div>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
EOD;
if ($result = @$db->db_query("SELECT * FROM campus.tbl_template WHERE template_kurzbz='iframe'"))
{
if ($db->db_num_rows($result) == 0)
{
$sql= <<<EOD
INSERT INTO campus.tbl_template
(template_kurzbz, bezeichnung, xsd, xslt_xhtml, xslfo_pdf, xslt_xhtml_c4)
VALUES
('iframe','iFrame Content ', '{$xsd}', '{$xslt_xhtml}' , NULL, '{$xslt_xhtml_c4}');
EOD;
if (!$db->db_query($sql))
{
echo '<strong>campus.tbl_template: ' . $db->db_last_error() . '</strong><br>';
}
else
{
echo ' campus.tbl_template: Template "iframe" hinzugefügt.<br>';
}
}
}
+120
View File
@@ -3024,6 +3024,46 @@ $phrases = array(
)
)
),
array(
'app' => 'core',
'category' => 'lehre',
'phrase' => 'cancelvertrag',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Vertrag stornieren',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Cancel Contract',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'lehre',
'phrase' => 'vertragConfirm',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Möchten Sie den Vertrag wirklich stornieren?',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Do you really want to cancel the contract?',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'lehre',
@@ -43319,6 +43359,86 @@ array(
)
)
),
array(
'app' => 'pep',
'category' => 'ui',
'phrase' => 'lehrauftrag',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Lehrauftrag',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Teaching lectureship',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'pep',
'category' => 'ui',
'phrase' => 'kategorie',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Kategorie',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Category',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'pep',
'category' => 'ui',
'phrase' => 'project',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Kategorie',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Category',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'pep',
'category' => 'ui',
'phrase' => 'lventwicklung',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'LV-Weiterentwicklung',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Course Development',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'pep',
'category' => 'ui',