From cc302ed5a1d4749976961b881c9f8277fa6bfc0f Mon Sep 17 00:00:00 2001 From: Johann Hoffmann Date: Wed, 4 Feb 2026 17:32:17 +0100 Subject: [PATCH 01/29] lazyload signatur status for assistenz view to avoid worst case loading times due to 50 x 30mb signatur server payload --- .../controllers/api/frontend/v1/Abgabe.php | 44 ++++++++++++++----- .../models/education/Paabgabe_model.php | 6 +++ public/js/api/factory/abgabe.js | 8 ++++ .../Cis/Abgabetool/AbgabetoolAssistenz.js | 13 +++++- 4 files changed, 60 insertions(+), 11 deletions(-) diff --git a/application/controllers/api/frontend/v1/Abgabe.php b/application/controllers/api/frontend/v1/Abgabe.php index 5a6331584..1ff4fd13d 100644 --- a/application/controllers/api/frontend/v1/Abgabe.php +++ b/application/controllers/api/frontend/v1/Abgabe.php @@ -45,8 +45,9 @@ class Abgabe extends FHCAPI_Controller 'getProjektarbeitenForStudiengang' =>array('basis/abgabe_assistenz:rw'), 'getStudiengaenge' => array('basis/abgabe_assistenz:rw'), 'getStudentProjektarbeitAbgabeFile' => array('basis/abgabe_student:rw', 'basis/abgabe_lektor:rw', 'basis/abgabe_assistenz:rw'), - 'postStudentProjektarbeitZusatzdaten' => array('basis/abgabe_lektor:rw', 'basis/abgabe_assistenz:rw') - ]); + 'postStudentProjektarbeitZusatzdaten' => array('basis/abgabe_lektor:rw', 'basis/abgabe_assistenz:rw'), + 'getSignaturStatusForProjektarbeitAbgaben' => array('basis/abgabe_lektor:rw', 'basis/abgabe_assistenz:rw') + ]); $this->load->library('PhrasesLib'); $this->load->library('SignatureLib'); @@ -151,7 +152,7 @@ class Abgabe extends FHCAPI_Controller $ret = $this->ProjektarbeitModel->getProjektarbeitAbgabetermine($projektarbeit_id); foreach ($ret->retval as $termin) { - $this->checkAbgabeSignatur($termin, $projektarbeit); + $this->checkAbgabeSignatur($termin, $projektarbeit->student_uid); } $this->terminateWithSuccess(array($ret, $projektarbeitIsCurrent)); @@ -398,7 +399,7 @@ class Abgabe extends FHCAPI_Controller $this->terminateWithError($this->p->t('abgabetool', 'c4projektabgabeNichtGefunden'), 'general'); } - $this->checkAbgabeSignatur($paabgabe, $projektarbeit); + $this->checkAbgabeSignatur($paabgabe, $projektarbeit->student_uid); $signaturstatus = $paabgabe->signatur; // update projektarbeit cols @@ -892,10 +893,10 @@ class Abgabe extends FHCAPI_Controller $projektarbeit->abgabetermine = array_values(array_filter($projektabgaben, $filterFunc)); - // check the signature status for enduploads - foreach($projektarbeit->abgabetermine as $abgabe) { - $this->checkAbgabeSignatur($abgabe, $projektarbeit); - } +// // check the signature status for enduploads +// foreach($projektarbeit->abgabetermine as $abgabe) { +// $this->checkAbgabeSignatur($abgabe, $projektarbeit); +// } } $this->terminateWithSuccess(array($projektarbeiten, DOMAIN)); @@ -1021,10 +1022,33 @@ class Abgabe extends FHCAPI_Controller $this->terminateWithSuccess($result); } + // used to lazy load signatur status for assistenzen, since they could run into very long fetch times + // since they fetch the projektarbeiten with paabgaben included and could have a lot of huge endupload files + // in their stg resulting in huge loading times -> use this api call on opening detail component instead + public function getSignaturStatusForProjektarbeitAbgaben() { + $paabgabe_ids = $this->input->post('paabgabe_ids'); + $student_uid = $this->input->post('student_uid'); + + if ($paabgabe_ids === NULL || $student_uid === NULL || trim((string)$student_uid) === '') { + $this->terminateWithError($this->p->t('global', 'wrongParameters'), 'general'); + } + + $this->load->model('education/Paabgabe_model', 'PaabgabeModel'); + + $result = $this->PaabgabeModel->loadByIDs($paabgabe_ids); + $data = $this->getDataOrTerminateWithError($result); + + foreach($data as $paabgabetermin) { + $this->checkAbgabeSignatur($paabgabetermin, $student_uid); + } + + $this->terminateWithSuccess($data); + } + /** * helper function to check the signature status of uploaded files for zwischenabgabe & endupload */ - private function checkAbgabeSignatur($abgabe, $projektarbeit) { + private function checkAbgabeSignatur($abgabe, $student_uid) { $paabgabetypenToCheck = $this->config->item('SIGNATUR_CHECK_PAABGABETYPEN'); if(!in_array($abgabe->paabgabetyp_kurzbz, $paabgabetypenToCheck)) { @@ -1036,7 +1060,7 @@ class Abgabe extends FHCAPI_Controller return; } - $path = PAABGABE_PATH.$abgabe->paabgabe_id.'_'.$projektarbeit->student_uid.'.pdf'; + $path = PAABGABE_PATH.$abgabe->paabgabe_id.'_'.$student_uid.'.pdf'; $signaturVorhanden = null; // if frontend receives null -> indicates no file found at path if(file_exists($path)) { diff --git a/application/models/education/Paabgabe_model.php b/application/models/education/Paabgabe_model.php index aa61bbaae..a883043d3 100644 --- a/application/models/education/Paabgabe_model.php +++ b/application/models/education/Paabgabe_model.php @@ -108,4 +108,10 @@ class Paabgabe_model extends DB_Model return $this->execQuery($query, [$interval]); } + + public function loadByIDs($paabgabe_ids) { + $qry = "SELECT * FROM campus.tbl_paabgabe WHERE paabgabe_id IN ?"; + + return $this->execReadOnlyQuery($qry, [$paabgabe_ids]); + } } diff --git a/public/js/api/factory/abgabe.js b/public/js/api/factory/abgabe.js index f5659f3a0..c6f229973 100644 --- a/public/js/api/factory/abgabe.js +++ b/public/js/api/factory/abgabe.js @@ -132,5 +132,13 @@ export default { params: formData, config: {Headers: { "Content-Type": "multipart/form-data" }} }; + }, + getSignaturStatusForProjektarbeitAbgaben(paabgabe_ids, student_uid) { + return { + method: 'post', + url: '/api/frontend/v1/Abgabe/getSignaturStatusForProjektarbeitAbgaben', + params: {paabgabe_ids, student_uid}, + + }; } }; \ No newline at end of file diff --git a/public/js/components/Cis/Abgabetool/AbgabetoolAssistenz.js b/public/js/components/Cis/Abgabetool/AbgabetoolAssistenz.js index 7345b14e0..4c4485fb4 100644 --- a/public/js/components/Cis/Abgabetool/AbgabetoolAssistenz.js +++ b/public/js/components/Cis/Abgabetool/AbgabetoolAssistenz.js @@ -684,7 +684,18 @@ export const AbgabetoolAssistenz = { const pa = this.projektarbeiten.find(projektarbeit => projektarbeit.projektarbeit_id == details.projektarbeit_id) - // pa.isCurrent = res.data[1] + if(pa?.abgabetermine?.length) { + this.$api.call(ApiAbgabe.getSignaturStatusForProjektarbeitAbgaben(pa.abgabetermine.map(termin => termin.paabgabe_id), pa.student_uid)) + .then(res => { + if(res.meta.status === 'success') { + res.data.forEach(paabgabe => { + const termin = pa.abgabetermine.find(abgabe => abgabe.paabgabe_id == paabgabe.paabgabe_id) + if(termin && paabgabe.signatur !== undefined) termin.signatur = paabgabe.signatur + }) + } + }) + } + const paIsBenotet = pa.note !== null pa.abgabetermine.forEach(termin => { From 1eda652fba9ce7cab8912f648319334e4072c4f7 Mon Sep 17 00:00:00 2001 From: Johann Hoffmann Date: Wed, 4 Feb 2026 17:34:21 +0100 Subject: [PATCH 02/29] remove old code --- application/controllers/api/frontend/v1/Abgabe.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/application/controllers/api/frontend/v1/Abgabe.php b/application/controllers/api/frontend/v1/Abgabe.php index 1ff4fd13d..39c7ebae6 100644 --- a/application/controllers/api/frontend/v1/Abgabe.php +++ b/application/controllers/api/frontend/v1/Abgabe.php @@ -892,11 +892,6 @@ class Abgabe extends FHCAPI_Controller }; $projektarbeit->abgabetermine = array_values(array_filter($projektabgaben, $filterFunc)); - -// // check the signature status for enduploads -// foreach($projektarbeit->abgabetermine as $abgabe) { -// $this->checkAbgabeSignatur($abgabe, $projektarbeit); -// } } $this->terminateWithSuccess(array($projektarbeiten, DOMAIN)); From 6da19585ff836b304e994b34e6e691986c505776 Mon Sep 17 00:00:00 2001 From: Johann Hoffmann Date: Mon, 9 Feb 2026 13:45:01 +0100 Subject: [PATCH 03/29] optional sammelmail buttons assistenz abgabetool --- application/config/abgabe.php | 3 + .../controllers/api/frontend/v1/Abgabe.php | 28 +++++++- .../models/education/Projektarbeit_model.php | 22 ++++++ .../Cis/Abgabetool/AbgabetoolAssistenz.js | 70 +++++++++++++++++-- system/phrasesupdate.php | 52 ++++++++++++-- 5 files changed, 162 insertions(+), 13 deletions(-) diff --git a/application/config/abgabe.php b/application/config/abgabe.php index f9b043a34..5cf3042ed 100644 --- a/application/config/abgabe.php +++ b/application/config/abgabe.php @@ -36,3 +36,6 @@ $config['SIGNATUR_CHECK_PAABGABETYPEN'] = ['end']; // to be used as "https://moodle.technikum-wien.at/course/view.php?idnumber=dl{$stg_kz}" for stg specific moodle routing $config['STG_MOODLE_LINK'] = 'https://moodle.technikum-wien.at/course/view.php?idnumber=dl'; + +$config['ASSISTENZ_SAMMELMAIL_BUTTON_STUDENT'] = true; +$config['ASSISTENZ_SAMMELMAIL_BUTTON_BETREUER'] = true; diff --git a/application/controllers/api/frontend/v1/Abgabe.php b/application/controllers/api/frontend/v1/Abgabe.php index 39c7ebae6..c8f48b9df 100644 --- a/application/controllers/api/frontend/v1/Abgabe.php +++ b/application/controllers/api/frontend/v1/Abgabe.php @@ -87,11 +87,15 @@ class Abgabe extends FHCAPI_Controller $old_abgabe_beurteilung_link =$this->config->item('old_abgabe_beurteilung_link'); $turnitin_link = $this->config->item('turnitin_link'); $abgabetypenBetreuer = $this->config->item('ALLOWED_ABGABETYPEN_BETREUER'); + $ASSISTENZ_SAMMELMAIL_BUTTON_STUDENT = $this->config->item('ASSISTENZ_SAMMELMAIL_BUTTON_STUDENT'); + $ASSISTENZ_SAMMELMAIL_BUTTON_BETREUER = $this->config->item('ASSISTENZ_SAMMELMAIL_BUTTON_BETREUER'); $ret = array( 'old_abgabe_beurteilung_link' => $old_abgabe_beurteilung_link, 'turnitin_link' => $turnitin_link, - 'abgabetypenBetreuer' => $abgabetypenBetreuer + 'abgabetypenBetreuer' => $abgabetypenBetreuer, + 'ASSISTENZ_SAMMELMAIL_BUTTON_STUDENT' => $ASSISTENZ_SAMMELMAIL_BUTTON_STUDENT, + 'ASSISTENZ_SAMMELMAIL_BUTTON_BETREUER' => $ASSISTENZ_SAMMELMAIL_BUTTON_BETREUER ); $this->terminateWithSuccess($ret); @@ -762,7 +766,7 @@ class Abgabe extends FHCAPI_Controller /** * helper function to fetch the correct email for a projektarbeits erstbetreuer */ - private function getProjektbetreuerEmail($projektarbeit_id) { + private function getProjektbetreuerEmailByProjektarbeitID($projektarbeit_id) { $this->load->model('education/Projektarbeit_model', 'ProjektarbeitModel'); $result = $this->ProjektarbeitModel->getProjektbetreuerEmail($projektarbeit_id); $email = $this->getDataOrTerminateWithError($result, 'general'); @@ -771,6 +775,18 @@ class Abgabe extends FHCAPI_Controller } + /** + * helper function to fetch the correct email for a projektarbeits zweitbetreuer by their person id + * can be used for erstbetreuer aswell if necessary + */ + private function getProjektbetreuerEmailByPersonID($person_id) { + $this->load->model('education/Projektarbeit_model', 'ProjektarbeitModel'); + $result = $this->ProjektarbeitModel->getProjektbetreuerEmailByPersonID($person_id); + $email = $this->getDataOrTerminateWithError($result, 'general'); + + return $email[0]->uid ? $email[0]->uid.'@'.DOMAIN : $email[0]->private_email; + } + //TODO: SWITCH TO NOTEN API ONCE NOTENTOOL IS IN MASTER TO AVOID DUPLICATE API /** @@ -887,6 +903,12 @@ class Abgabe extends FHCAPI_Controller // map the abgaben into projektarbeiten foreach($projektarbeiten as $projektarbeit) { + $projektarbeit->betreuer_mail = $this->getProjektbetreuerEmailByProjektarbeitID($projektarbeit->projektarbeit_id); + + if($projektarbeit->zweitbetreuer_person_id !== null) { + $projektarbeit->zweitbetreuer_mail = $this->getProjektbetreuerEmailByPersonID($projektarbeit->zweitbetreuer_person_id); + } + $filterFunc = function($projektabgabe) use ($projektarbeit) { return $projektabgabe->projektarbeit_id == $projektarbeit->projektarbeit_id; }; @@ -1140,7 +1162,7 @@ class Abgabe extends FHCAPI_Controller $maildata['bewertunglink'] = $projektarbeitIsCurrent && $paabgabetyp_kurzbz == 'end' ? "

Zur Beurteilung der Arbeit

" : ""; $maildata['token'] = ""; - $email = $this->getProjektbetreuerEmail($projektarbeit_id); + $email = $this->getProjektbetreuerEmailByProjektarbeitID($projektarbeit_id); if(!$email) $this->terminateWithError($this->p->t('abgabetool', 'c4fehlerMailBegutachter'), 'general'); diff --git a/application/models/education/Projektarbeit_model.php b/application/models/education/Projektarbeit_model.php index d80878f6d..e2d4d1b70 100644 --- a/application/models/education/Projektarbeit_model.php +++ b/application/models/education/Projektarbeit_model.php @@ -244,6 +244,28 @@ class Projektarbeit_model extends DB_Model return $this->execReadOnlyQuery($qry, [$projektarbeit_id]); } + + public function getProjektbetreuerEmailByPersonID($person_id) { + $qry = "SELECT ( + SELECT kontakt + FROM public.tbl_kontakt + WHERE kontakttyp = 'email' + AND person_id = pers.person_id + ORDER BY + CASE WHEN zustellung THEN 0 ELSE 1 END, + insertamum DESC NULLS LAST + LIMIT 1 + ) AS private_email, mitarbeiter_uid as uid + FROM lehre.tbl_projektarbeit pa + JOIN lehre.tbl_projektbetreuer USING (projektarbeit_id) + JOIN public.tbl_person pers USING (person_id) + LEFT JOIN public.tbl_benutzer ben USING (person_id) + LEFT JOIN public.tbl_mitarbeiter ma ON ben.uid = ma.mitarbeiter_uid + WHERE (ben.aktiv OR ben.aktiv IS NULL) + AND person_id = ?"; + + return $this->execReadOnlyQuery($qry, [$person_id]); + } public function getProjektarbeitBenutzer($uid) { $qry="SELECT * FROM campus.vw_benutzer where uid=?"; diff --git a/public/js/components/Cis/Abgabetool/AbgabetoolAssistenz.js b/public/js/components/Cis/Abgabetool/AbgabetoolAssistenz.js index 4c4485fb4..20333ba41 100644 --- a/public/js/components/Cis/Abgabetool/AbgabetoolAssistenz.js +++ b/public/js/components/Cis/Abgabetool/AbgabetoolAssistenz.js @@ -77,6 +77,8 @@ export const AbgabetoolAssistenz = { phrasenResolved: false, turnitin_link: null, old_abgabe_beurteilung_link: null, + ASSISTENZ_SAMMELMAIL_BUTTON_STUDENT: null, + ASSISTENZ_SAMMELMAIL_BUTTON_BETREUER: null, saving: false, loading: false, abgabeTypeOptions: null, @@ -221,6 +223,32 @@ export const AbgabetoolAssistenz = { ]}; }, methods: { + sammelMailStudent() { + const emails = this.selectedData + .map(row => `${row.student_uid}@${this.domain}`) + .join(','); + + const subject = this.$p.t('abgabetool/c4sammelmailStudentBetreff', [this.selectedStudiengangOption?.bezeichnung]); + + const href = `mailto:${emails}?subject=${subject}`; + + window.location.href = href + }, + sammelMailBetreuer() { + const recipientList = []; + this.selectedData.forEach(row => { + if (row.betreuer_mail) recipientList.push(row.betreuer_mail); + if (row.zweitbetreuer_mail) recipientList.push(row.zweitbetreuer_mail); + }); + + // actually not necessary for email clients but looks better for assistenz if we avoid duplicates here + const uniqueRecipients = [...new Set(recipientList)]; + + const subject = this.$p.t('abgabetool/c4sammelmailBetreuerBetreff', [this.selectedStudiengangOption?.bezeichnung]); + const href = `mailto:${uniqueRecipients.join(',')}?subject=${encodeURIComponent(subject)}`; + + window.location.href = href; + }, selectHandler(e, cell) { const row = cell.getRow(); @@ -620,9 +648,8 @@ export const AbgabetoolAssistenz = { const mappedData = this.mapProjekteToTableData(this.projektarbeiten) - this.$refs.abgabeTable.tabulator.setColumns(this.abgabeTableOptions.columns) this.$refs.abgabeTable.tabulator.setData(mappedData) - + this.$refs.abgabeTable.tabulator.redraw(true) }).finally(()=>{ this.saving = false }) @@ -867,8 +894,8 @@ export const AbgabetoolAssistenz = { tableResolve(resolve) { this.tableBuiltResolve = resolve }, - buildMailToLink(abgabe) { - return 'mailto:' + abgabe.student_uid +'@'+ this.domain + buildMailToLink(projekt) { + return 'mailto:' + projekt.student_uid +'@'+ this.domain }, buildPKZ(projekt) { return `${projekt.student_uid} / ${projekt.matrikelnr}` @@ -941,6 +968,29 @@ export const AbgabetoolAssistenz = { this.calcMaxTableHeight() } }, + computed: { + uniqueBetreuerEmailCount() { + const emails = new Set(); + + this.selectedData.forEach(row => { + if (row.betreuer_mail) emails.add(row.betreuer_mail); + if (row.zweitbetreuer_mail) emails.add(row.zweitbetreuer_mail); + }); + + return emails.size; + }, + uniqueStudentEmailCount() { + const emails = new Set(); + + this.selectedData.forEach(row => { + if (row.student_uid) { + emails.add(row.student_uid); // actually dont need domain for this + } + }); + + return emails.size; + } + }, watch: { 'serienTermin.bezeichnung'(newVal) { if(newVal?.paabgabetyp_kurzbz === 'qualgate1' || newVal?.paabgabetyp_kurzbz === 'qualgate2') { @@ -987,6 +1037,8 @@ export const AbgabetoolAssistenz = { const res = results[0].value; this.turnitin_link = res.data?.turnitin_link; this.old_abgabe_beurteilung_link = res.data?.old_abgabe_beurteilung_link; + this.ASSISTENZ_SAMMELMAIL_BUTTON_STUDENT = res.data?.ASSISTENZ_SAMMELMAIL_BUTTON_STUDENT; + this.ASSISTENZ_SAMMELMAIL_BUTTON_BETREUER = res.data?.ASSISTENZ_SAMMELMAIL_BUTTON_BETREUER; } // 2. Studiengänge @@ -1292,6 +1344,16 @@ export const AbgabetoolAssistenz = { :useSelectionSpan="false" > From 67838eb630cc10968c3e3bedb3695574af3dd2ad Mon Sep 17 00:00:00 2001 From: Johann Hoffmann Date: Wed, 11 Feb 2026 13:39:23 +0100 Subject: [PATCH 05/29] load projektarbeit.note correctly for mitarbeiter; evaluate projektarbeit termin editability correctly and define a notenarray which does NOT count (currently "Nicht beurteilt" & "Noch nicht eingetragen"). such rules apply for betreuer, assistenz is allowed to do whatever they want since we never defined an actual business process anywhere and people do whatever they want anyways --- application/config/abgabe.php | 4 +++- .../controllers/api/frontend/v1/Abgabe.php | 5 +++- .../models/education/Projektarbeit_model.php | 2 +- .../Cis/Abgabetool/AbgabeMitarbeiterDetail.js | 23 +++++++++++++++---- .../Cis/Abgabetool/AbgabetoolAssistenz.js | 6 +++++ .../Cis/Abgabetool/AbgabetoolMitarbeiter.js | 16 ++++++++++++- 6 files changed, 48 insertions(+), 8 deletions(-) diff --git a/application/config/abgabe.php b/application/config/abgabe.php index 5cf3042ed..f806e1ef8 100644 --- a/application/config/abgabe.php +++ b/application/config/abgabe.php @@ -26,7 +26,9 @@ $config['RELEVANT_PAABGABETYPEN_SAMMELMAIL_ASSISTENZ'] = ['end']; $config['RELEVANT_PAABGABETYPEN_SAMMELMAIL_STUDENT'] = ['qualgate1', 'qualgate2', 'zwischen', 'note', 'abstract', 'end', 'enda']; //$config['ALLOWED_NOTEN_ABGABETOOL'] = ['Bestanden', 'Nicht bestanden']; $config['ALLOWED_NOTEN_ABGABETOOL'] = [10, 14]; // tbl_note pk - +// benotete projektarbeiten sperren weitere terminanlage & bearbeitung, diese noten sind ausnahmen dieser Regel +// wie zB "Nicht beurteilt" & "Noch nicht eingetragen" +$config['NONFINAL_NOTEN_ABGABETOOL'] = [7, 9]; $config['beurteilung_link_fallback'] = 'addons/fhtw/content/projektbeurteilung/projektbeurteilungDocumentExport.php?projektarbeit_id=?&betreuerart_kurzbz=?&person_id=?'; $config['PROJEKTARBEITSBEURTEILUNG_MAIL_BASELINK_ERSTBEGUTACHTER'] = 'index.ci.php/extensions/FHC-Core-Projektarbeitsbeurteilung/ProjektarbeitsbeurteilungErstbegutachter'; diff --git a/application/controllers/api/frontend/v1/Abgabe.php b/application/controllers/api/frontend/v1/Abgabe.php index c8f48b9df..b37c64713 100644 --- a/application/controllers/api/frontend/v1/Abgabe.php +++ b/application/controllers/api/frontend/v1/Abgabe.php @@ -801,7 +801,10 @@ class Abgabe extends FHCAPI_Controller $allowed_noten_abgabetool = $this->config->item('ALLOWED_NOTEN_ABGABETOOL'); - $this->terminateWithSuccess(array($noten, $allowed_noten_abgabetool)); + $nonfinal_noten_abgabetool = $this->config->item('NONFINAL_NOTEN_ABGABETOOL'); + + + $this->terminateWithSuccess(array($noten, $allowed_noten_abgabetool, $nonfinal_noten_abgabetool)); } /** diff --git a/application/models/education/Projektarbeit_model.php b/application/models/education/Projektarbeit_model.php index e2d4d1b70..5e453056d 100644 --- a/application/models/education/Projektarbeit_model.php +++ b/application/models/education/Projektarbeit_model.php @@ -299,7 +299,7 @@ class Projektarbeit_model extends DB_Model * FROM (SELECT tbl_person.vorname, tbl_person.nachname, tbl_studiengang.typ, tbl_studiengang.kurzbz, - tbl_projektarbeit.projekttyp_kurzbz, tbl_projekttyp.bezeichnung, tbl_projektarbeit.titel, tbl_projektarbeit.projektarbeit_id, + tbl_projektarbeit.projekttyp_kurzbz, tbl_projekttyp.bezeichnung, tbl_projektarbeit.titel, tbl_projektarbeit.projektarbeit_id, tbl_projektarbeit.note, tbl_projektbetreuer.person_id as betreuer_person_id, tbl_projektbetreuer.betreuerart_kurzbz, tbl_betreuerart.beschreibung AS betreuerart_beschreibung, tbl_benutzer.uid, tbl_student.matrikelnr, tbl_lehreinheit.studiensemester_kurzbz, public.tbl_student.student_uid FROM lehre.tbl_projektarbeit diff --git a/public/js/components/Cis/Abgabetool/AbgabeMitarbeiterDetail.js b/public/js/components/Cis/Abgabetool/AbgabeMitarbeiterDetail.js index 35633321d..42952d0df 100644 --- a/public/js/components/Cis/Abgabetool/AbgabeMitarbeiterDetail.js +++ b/public/js/components/Cis/Abgabetool/AbgabeMitarbeiterDetail.js @@ -21,6 +21,7 @@ export const AbgabeMitarbeiterDetail = { 'abgabeTypeOptions', 'abgabetypenBetreuer', 'allowedNotenOptions', + 'notenOptionsNonFinal', 'turnitin_link', 'old_abgabe_beurteilung_link' ], @@ -48,7 +49,7 @@ export const AbgabeMitarbeiterDetail = { label: Vue.computed(() => this.$p.t('abgabetool/c4newAbgabetermin')), icon: "fa fa-plus", command: this.openCreateNewAbgabeModal, - disabled: Vue.computed(() => this.projektarbeit?.betreuerart_kurzbz == 'Zweitbegutachter') + disabled: Vue.computed(() => !this.getAllowedToCreateNewTermin) }, { label: Vue.computed(() => this.$p.t('abgabetool/c4benoten')), @@ -478,6 +479,21 @@ export const AbgabeMitarbeiterDetail = { }, computed: { + getAllowedToCreateNewTermin() { + if(this.assistenzMode) return true + if(this.projektarbeit?.betreuerart_kurzbz == 'Zweitbegutachter') return false + if(this.projektarbeit?.note !== undefined && this.projektarbeit.note !== null) { + // check if the note is not defined as a non final projektarbeit note + const opt = this.notenOptionsNonFinal.find(opt => opt.note) + // if thats the case allow further work + if(opt) return true + // else the PA is to be considered finished + return false + } + + // normally should be allowed if no rules apply + return true + }, allowedToSaveZusatzdaten() { return this.form.schlagwoerter.length > 0 && this.form.schlagwoerter_en.length > 0 && this.form.abstract.length > 0 && this.form.abstract_en.length > 0 && this.form.seitenanzahl > 0 }, @@ -755,9 +771,8 @@ export const AbgabeMitarbeiterDetail = {
- - - diff --git a/public/js/components/Cis/Abgabetool/AbgabetoolAssistenz.js b/public/js/components/Cis/Abgabetool/AbgabetoolAssistenz.js index 0a4028a27..7fa78f7d1 100644 --- a/public/js/components/Cis/Abgabetool/AbgabetoolAssistenz.js +++ b/public/js/components/Cis/Abgabetool/AbgabetoolAssistenz.js @@ -38,6 +38,7 @@ export const AbgabetoolAssistenz = { return { abgabeTypeOptions: Vue.computed(() => this.abgabeTypeOptions), allowedNotenOptions: Vue.computed(() => this.allowedNotenOptions), + notenOptionsNonFinal: Vue.computed(() => this.notenOptionsNonFinal), turnitin_link: Vue.computed(() => this.turnitin_link), old_abgabe_beurteilung_link: Vue.computed(() => this.old_abgabe_beurteilung_link), abgabetypenBetreuer: Vue.computed(() => this.abgabeTypeOptions) @@ -86,6 +87,7 @@ export const AbgabetoolAssistenz = { notenOptions: null, allowedNotenFilterOptions: null, allowedNotenOptions: null, + notenOptionsNonFinal: null, serienTermin: Vue.reactive({ datum: new Date(), bezeichnung: { @@ -1089,6 +1091,10 @@ export const AbgabetoolAssistenz = { this.allowedNotenOptions = this.notenOptions.filter( opt => res.data[1].includes(opt.note) ); + + this.notenOptionsNonFinal = this.notenOptions.filter( + opt => res.data[2].includes(opt.note) + ) } this.allowedNotenFilterOptions = [ diff --git a/public/js/components/Cis/Abgabetool/AbgabetoolMitarbeiter.js b/public/js/components/Cis/Abgabetool/AbgabetoolMitarbeiter.js index df521d52d..67e1d09af 100644 --- a/public/js/components/Cis/Abgabetool/AbgabetoolMitarbeiter.js +++ b/public/js/components/Cis/Abgabetool/AbgabetoolMitarbeiter.js @@ -22,6 +22,7 @@ export const AbgabetoolMitarbeiter = { abgabeTypeOptions: Vue.computed(() => this.abgabeTypeOptions), abgabetypenBetreuer: Vue.computed(() => this.abgabetypenBetreuer), allowedNotenOptions: Vue.computed(() => this.allowedNotenOptions), + notenOptionsNonFinal: Vue.computed(() => this.notenOptionsNonFinal), turnitin_link: Vue.computed(() => this.turnitin_link), old_abgabe_beurteilung_link: Vue.computed(() => this.old_abgabe_beurteilung_link) } @@ -50,6 +51,7 @@ export const AbgabetoolMitarbeiter = { abgabeTypeOptions: null, notenOptions: null, allowedNotenOptions: null, + notenOptionsNonFinal: null, serienTermin: Vue.reactive({ datum: new Date(), bezeichnung: { @@ -301,7 +303,15 @@ export const AbgabetoolMitarbeiter = { pa.abgabetermine = res.data[0].retval pa.isCurrent = res.data[1] - const paIsBenotet = pa.note !== null + let paIsBenotet = false + if(pa.note !== undefined && pa !== null) { + // check if the note is not defined as a non final projektarbeit note + const opt = this.notenOptionsNonFinal.find(opt => opt.note) + // if thats the case allow further work + if(opt) paIsBenotet = false + // else the PA is to be considered finished + paIsBenotet = true + } pa.abgabetermine.forEach(termin => { termin.note = this.allowedNotenOptions.find(opt => opt.note == termin.note) @@ -471,6 +481,10 @@ export const AbgabetoolMitarbeiter = { this.allowedNotenOptions = this.notenOptions.filter( opt => res.data[1].includes(opt.note) ) + + this.notenOptionsNonFinal = this.notenOptions.filter( + opt => res.data[2].includes(opt.note) + ) } }).catch(e => { From 136d6f9f286c3d231560e9005c724cb528087544 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20=C3=96sterreicher?= Date: Wed, 11 Feb 2026 16:08:15 +0100 Subject: [PATCH 06/29] =?UTF-8?q?Fix=20von=20BFI=20=C3=BCbernommen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/anwesenheit.class.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/include/anwesenheit.class.php b/include/anwesenheit.class.php index f601d6b95..9493a3d4b 100644 --- a/include/anwesenheit.class.php +++ b/include/anwesenheit.class.php @@ -489,7 +489,7 @@ class anwesenheit extends basis_db gesamt AS gesamtstunden, anwesend, nichtanwesend, - trunc(100-(nichtanwesend/gesamt)*100,2) AS prozent + CASE WHEN gesamt = 0 THEN 100.00 ELSE trunc(100-(nichtanwesend/(gesamt))*100,2) END AS prozent FROM( SELECT @@ -499,9 +499,10 @@ class anwesenheit extends basis_db lehrveranstaltung_id, bezeichnung, student_uid, - COUNT(stundenplan_id) AS gesamt, - CASE WHEN anwesend.summe IS NULL THEN 0 ELSE anwesend.summe END AS anwesend, - CASE WHEN nichtanwesend.summe IS NULL THEN 0 ELSE nichtanwesend.summe END AS nichtanwesend + --COUNT(stundenplan_id) AS gesamts, + COALESCE(anwesend.summe, 0) + COALESCE(nichtanwesend.summe, 0) AS gesamt, + COALESCE(anwesend.summe, 0) AS anwesend, + COALESCE(nichtanwesend.summe, 0) AS nichtanwesend FROM ( From 3465e299f7ec81bd373d9806d14c31e1a437d19f Mon Sep 17 00:00:00 2001 From: ma0048 Date: Thu, 12 Feb 2026 08:16:31 +0100 Subject: [PATCH 07/29] tag - helper and formatter --- public/js/helpers/TagHelper.js | 124 ++++++++++++++++++ .../tabulator/filters/extendedHeaderFilter.js | 2 +- public/js/tabulator/formatter/tags.js | 67 ++++++++++ 3 files changed, 192 insertions(+), 1 deletion(-) create mode 100644 public/js/helpers/TagHelper.js create mode 100644 public/js/tabulator/formatter/tags.js diff --git a/public/js/helpers/TagHelper.js b/public/js/helpers/TagHelper.js new file mode 100644 index 000000000..9282aa167 --- /dev/null +++ b/public/js/helpers/TagHelper.js @@ -0,0 +1,124 @@ +export function addTagInTable(addedTag, rows, matchKey, tagsKey = "tags") +{ + if (!addedTag || !Array.isArray(addedTag.response)) + return; + + rows.forEach(row => + { + const rowData = row.getData(); + let updated = false; + + addedTag.response.forEach(tag => + { + if (rowData[matchKey] !== tag[matchKey]) + return; + + let tags; + try { + tags = JSON.parse(rowData[tagsKey] || "[]"); + } catch (e) { + tags = []; + } + + if (!Array.isArray(tags)) + tags = []; + + if (tags.some(t => t?.id === tag.id)) + return; + + let newTag = { ...addedTag, id: tag.id }; + + tags.unshift(newTag); + + rowData[tagsKey] = JSON.stringify(tags); + updated = true; + }); + + if (updated) + row.update(rowData); + }); +} + +export function deleteTagInTable(deletedTag, rows, tagsKeys = ['tags']) +{ + if (!Array.isArray(tagsKeys)) + tagsKeys = [tagsKeys]; + + rows.forEach(row => { + let rowData = row.getData(); + let updates = {}; + let changed = false; + + tagsKeys.forEach(key => { + let tags; + + try { + tags = JSON.parse(rowData[key] || "[]"); + } catch (e) { + tags = []; + } + + if (!Array.isArray(tags)) + return; + + let filtered = tags.filter(tag => tag?.id !== deletedTag); + + if (filtered.length !== tags.length) + { + updates[key] = JSON.stringify(filtered); + changed = true; + } + }); + + if (changed) { + row.update(updates); + row.reformat(); + } + }); +} + + +export function updateTagInTable(updatedTag, rows, fields = ['tags']) +{ + if (!Array.isArray(fields)) + fields = [fields]; + + rows.forEach(row => + { + const rowData = row.getData(); + let updated = false; + + fields.forEach(field => + { + if (!rowData[field]) + return; + + let fieldData; + try { + fieldData = JSON.parse(rowData[field] || "[]"); + } catch (e) { + return; + } + + if (!Array.isArray(fieldData)) + return; + + let index = fieldData.findIndex(tag => tag?.id === updatedTag.id); + + if (index !== -1) + { + fieldData[index] = { ...updatedTag }; + let updatedFieldData = JSON.stringify(fieldData); + + if (updatedFieldData !== rowData[field]) + { + rowData[field] = updatedFieldData; + updated = true; + } + } + }); + + if (updated) + row.update(rowData); + }); +} diff --git a/public/js/tabulator/filters/extendedHeaderFilter.js b/public/js/tabulator/filters/extendedHeaderFilter.js index 7bf86c119..35b66dc1c 100644 --- a/public/js/tabulator/filters/extendedHeaderFilter.js +++ b/public/js/tabulator/filters/extendedHeaderFilter.js @@ -146,7 +146,7 @@ export function tagHeaderFilter(headerValue, rowValue, rowData, filterParams) if (Array.isArray(data)) { combinedText = data - .filter(item => item?.done === false) + .filter(item => item?.done !== true) .map(item => `${item?.beschreibung} ${item?.notiz}`) .join(' '); } diff --git a/public/js/tabulator/formatter/tags.js b/public/js/tabulator/formatter/tags.js new file mode 100644 index 000000000..0d2f5004c --- /dev/null +++ b/public/js/tabulator/formatter/tags.js @@ -0,0 +1,67 @@ +export function tagFormatter(cell, tagComponent) +{ + let tags = cell.getValue(); + if (!tags) return; + + let container = document.createElement('div'); + container.className = "d-flex gap-1"; + + let parsedTags = JSON.parse(tags); + let maxVisibleTags = 2; + + const rowData = cell.getRow().getData(); + if (rowData._tagExpanded === undefined) { + rowData._tagExpanded = false; + } + + 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 => { + if (!tag) return; + let tagElement = document.createElement('span'); + tagElement.innerText = tag.beschreibung; + tagElement.title = tag.notiz; + tagElement.className = "tag " + tag.style; + if (tag.done) tagElement.className += " tag_done"; + + tagElement.addEventListener('click', (event) => { + event.stopPropagation(); + event.preventDefault(); + tagComponent.editTag(tag.id); + }); + + container.appendChild(tagElement); + }); + + if (parsedTags.length > maxVisibleTags) { + let toggle = document.createElement('button'); + toggle.innerText = (rowData._tagExpanded ? '- ' : '+ ') + (parsedTags.length - maxVisibleTags); + toggle.className = "display_all"; + toggle.title = rowData._tagExpanded ? "Tags ausblenden" : "Tags einblenden"; + + toggle.addEventListener('click', () => { + rowData._tagExpanded = !rowData._tagExpanded; + renderTags(); + }); + + container.appendChild(toggle); + } + }; + + renderTags(); + return container; +} \ No newline at end of file From 0a97e5781ec21c60241cca768b46d06bd8a0e2eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20=C3=96sterreicher?= Date: Thu, 12 Feb 2026 11:02:16 +0100 Subject: [PATCH 08/29] Nicht beurteilt aus Default Config entfernt --- application/config/abgabe.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/config/abgabe.php b/application/config/abgabe.php index f806e1ef8..82782b043 100644 --- a/application/config/abgabe.php +++ b/application/config/abgabe.php @@ -28,7 +28,7 @@ $config['RELEVANT_PAABGABETYPEN_SAMMELMAIL_STUDENT'] = ['qualgate1', 'qualgate2' $config['ALLOWED_NOTEN_ABGABETOOL'] = [10, 14]; // tbl_note pk // benotete projektarbeiten sperren weitere terminanlage & bearbeitung, diese noten sind ausnahmen dieser Regel // wie zB "Nicht beurteilt" & "Noch nicht eingetragen" -$config['NONFINAL_NOTEN_ABGABETOOL'] = [7, 9]; +$config['NONFINAL_NOTEN_ABGABETOOL'] = [9]; $config['beurteilung_link_fallback'] = 'addons/fhtw/content/projektbeurteilung/projektbeurteilungDocumentExport.php?projektarbeit_id=?&betreuerart_kurzbz=?&person_id=?'; $config['PROJEKTARBEITSBEURTEILUNG_MAIL_BASELINK_ERSTBEGUTACHTER'] = 'index.ci.php/extensions/FHC-Core-Projektarbeitsbeurteilung/ProjektarbeitsbeurteilungErstbegutachter'; From ed170645df97af47d341db6f132f220f5240c829 Mon Sep 17 00:00:00 2001 From: Harald Bamberger Date: Thu, 12 Feb 2026 11:27:50 +0100 Subject: [PATCH 09/29] use plsql function public.get_rolle_prestudent instead of local sql --- .../api/frontend/v1/stv/Student.php | 11 +++------- .../api/frontend/v1/stv/Students.php | 22 +++++-------------- 2 files changed, 9 insertions(+), 24 deletions(-) diff --git a/application/controllers/api/frontend/v1/stv/Student.php b/application/controllers/api/frontend/v1/stv/Student.php index 943577bb3..2721bbd6f 100644 --- a/application/controllers/api/frontend/v1/stv/Student.php +++ b/application/controllers/api/frontend/v1/stv/Student.php @@ -136,14 +136,9 @@ class Student extends FHCAPI_Controller ); } $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" + "public.get_rolle_prestudent(public.tbl_prestudent.prestudent_id, " + . $this->PrestudentModel->escape($studiensemester_kurzbz) + . ") AS statusofsemester" ); $this->PrestudentModel->addJoin('public.tbl_student s', 'prestudent_id', 'LEFT'); diff --git a/application/controllers/api/frontend/v1/stv/Students.php b/application/controllers/api/frontend/v1/stv/Students.php index 9dbea65f2..acacca052 100644 --- a/application/controllers/api/frontend/v1/stv/Students.php +++ b/application/controllers/api/frontend/v1/stv/Students.php @@ -801,14 +801,9 @@ class Students extends FHCAPI_Controller //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" + "public.get_rolle_prestudent(public.tbl_prestudent.prestudent_id, " + . $this->PrestudentModel->escape($studiensemester_kurzbz) + . ") AS statusofsemester" ); $this->addSelectPrioRel(); @@ -897,14 +892,9 @@ class Students extends FHCAPI_Controller //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" + "public.get_rolle_prestudent(public.tbl_prestudent.prestudent_id, " + . $this->PrestudentModel->escape($studiensemester_kurzbz) + . ") AS statusofsemester" ); $this->PrestudentModel->addSelect('UPPER(stg.typ || stg.kurzbz) AS studiengang'); From e016deb04276b598f4319774feb549debcd70eb4 Mon Sep 17 00:00:00 2001 From: Harald Bamberger Date: Thu, 12 Feb 2026 13:14:23 +0100 Subject: [PATCH 10/29] add more space between download and delete button --- public/js/components/Form/Upload/Dms/Item.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/js/components/Form/Upload/Dms/Item.js b/public/js/components/Form/Upload/Dms/Item.js index 45c60419e..cc8db7827 100644 --- a/public/js/components/Form/Upload/Dms/Item.js +++ b/public/js/components/Form/Upload/Dms/Item.js @@ -27,7 +27,7 @@ export default {
  • {{ modelValue.name }} - + - +
  • From 043b1bcf114047eb39ece39ad801a3fbe330b4d7 Mon Sep 17 00:00:00 2001 From: Johann Hoffmann Date: Thu, 12 Feb 2026 17:38:00 +0100 Subject: [PATCH 15/29] extracted email split method from stv/kontakt component to helperfile; adjusted that method to take subject param & make phrasen/alert call via parameter reference; --- .../Cis/Abgabetool/AbgabetoolAssistenz.js | 19 +++---- .../Details/Kontaktieren.js | 49 ++----------------- public/js/helpers/EmailHelpers.js | 45 +++++++++++++++++ 3 files changed, 56 insertions(+), 57 deletions(-) create mode 100644 public/js/helpers/EmailHelpers.js diff --git a/public/js/components/Cis/Abgabetool/AbgabetoolAssistenz.js b/public/js/components/Cis/Abgabetool/AbgabetoolAssistenz.js index c0c8d7ba8..a7da9da5a 100644 --- a/public/js/components/Cis/Abgabetool/AbgabetoolAssistenz.js +++ b/public/js/components/Cis/Abgabetool/AbgabetoolAssistenz.js @@ -7,6 +7,7 @@ import ApiAbgabe from '../../../api/factory/abgabe.js' import ApiStudiensemester from '../../../api/factory/studiensemester.js'; import AbgabeterminStatusLegende from "./StatusLegende.js"; import FhcOverlay from "../../Overlay/FhcOverlay.js"; +import { splitMailsHelper } from "../../../helpers/EmailHelpers.js" // spoofed date testing // const todayISO = '2025-08-08' @@ -226,18 +227,17 @@ export const AbgabetoolAssistenz = { ]}; }, methods: { - sammelMailStudent() { + sammelMailStudent(param) { + const emails = this.selectedData .map(row => `${row.student_uid}@${this.domain}`) .join(','); - + const uniqueRecipients = [...new Set(emails)]; const subject = this.$p.t('abgabetool/c4sammelmailStudentBetreff', [this.selectedStudiengangOption?.bezeichnung]); - - const href = `mailto:${emails}?subject=${subject}`; - - window.location.href = href + splitMailsHelper(uniqueRecipients, param.originalEvent, subject, this.$fhcAlert, this.$p) }, - sammelMailBetreuer() { + sammelMailBetreuer(param) { + const recipientList = []; this.selectedData.forEach(row => { if (row.betreuer_mail) recipientList.push(row.betreuer_mail); @@ -246,11 +246,8 @@ export const AbgabetoolAssistenz = { // actually not necessary for email clients but looks better for assistenz if we avoid duplicates here const uniqueRecipients = [...new Set(recipientList)]; - const subject = this.$p.t('abgabetool/c4sammelmailBetreuerBetreff', [this.selectedStudiengangOption?.bezeichnung]); - const href = `mailto:${uniqueRecipients.join(',')}?subject=${encodeURIComponent(subject)}`; - - window.location.href = href; + splitMailsHelper(uniqueRecipients, param.originalEvent, subject, this.$fhcAlert, this.$p) }, selectHandler(e, cell) { const row = cell.getRow(); diff --git a/public/js/components/Stv/Studentenverwaltung/Details/Kontaktieren.js b/public/js/components/Stv/Studentenverwaltung/Details/Kontaktieren.js index bd7554a47..43995b918 100644 --- a/public/js/components/Stv/Studentenverwaltung/Details/Kontaktieren.js +++ b/public/js/components/Stv/Studentenverwaltung/Details/Kontaktieren.js @@ -1,3 +1,4 @@ +import { splitMailsHelper } from "../../../../helpers/EmailHelpers.js" export default { name: "Kontaktieren", computed: { @@ -22,60 +23,16 @@ export default { }, methods: { - async splitMails(mails, event) { - let splititem = ","; - let maillist = mails.join(splititem); - let mailto = ""; - - if (maillist.length > 2024) - { - if (await this.$fhcAlert.confirm({message: this.$p.t('stv', 'zuvieleEMails') }) === false) - return; - } - - let firstrun = true; - let useBcc = event?.ctrlKey || event?.metaKey; - while (maillist.length > 0) - { - if (maillist.length > 2024) - { - let splitposition = maillist.lastIndexOf(splititem, 1900); - mailto = maillist.substring(0, splitposition); - maillist = maillist.substring(splitposition + 1); - } - else - { - mailto = maillist; - maillist = ""; - } - - let mailLink = useBcc ? `mailto:?bcc=${mailto}` : `mailto:${mailto}`; - - if (firstrun) - { - window.location.href = mailLink; - firstrun = false; - } - else - { - if (await this.$fhcAlert.confirm({message: this.$p.t('stv', 'weitereEMail')}) === true) - { - window.location.href = mailLink; - } - } - - } - }, internMail(event) { if (this.internMails.length) { - this.splitMails(this.internMails, event); + splitMailsHelper(this.privateMails, event, null, this.$fhcAlert, this.$p) } }, privateMail(event) { if (this.privateMails.length) { - this.splitMails(this.privateMails, event); + splitMailsHelper(this.privateMails, event, null, this.$fhcAlert, this.$p) } } }, diff --git a/public/js/helpers/EmailHelpers.js b/public/js/helpers/EmailHelpers.js new file mode 100644 index 000000000..87daa828a --- /dev/null +++ b/public/js/helpers/EmailHelpers.js @@ -0,0 +1,45 @@ +export async function splitMailsHelper(mails, event, subject, alertPluginRef, phrasenPluginRef) { + let splititem = ","; + let maillist = mails.join(splititem); + let mailto = ""; + // take subject line length + '?subject=' length into account + const subjectlength = subject && typeof subject === 'string' ? subject.length + 9 : 0 + if (maillist.length > 2024) + { + if (await alertPluginRef.confirm({message: phrasenPluginRef.t('stv', 'zuvieleEMails') }) === false) + return; + } + + let firstrun = true; + let useBcc = event?.ctrlKey || event?.metaKey; + while (maillist.length > 0) + { + if (maillist.length + subjectlength > 2024) + { + let splitposition = maillist.lastIndexOf(splititem, 1900); + mailto = maillist.substring(0, splitposition); + maillist = maillist.substring(splitposition + 1); + } + else + { + mailto = maillist; + maillist = ""; + } + + let mailLink = useBcc ? `mailto:?bcc=${mailto}` : `mailto:${mailto}`; + if(subject && typeof subject === 'string') mailLink += `?subject=${subject}` + if (firstrun) + { + window.location.href = mailLink; + firstrun = false; + } + else + { + if (await alertPluginRef.confirm({message: phrasenPluginRef.t('stv', 'weitereEMail')}) === true) + { + window.location.href = mailLink; + } + } + + } +} \ No newline at end of file From d9d15c1ed3d5b61bd06e296485885f4d97c24b32 Mon Sep 17 00:00:00 2001 From: ma0048 Date: Fri, 13 Feb 2026 11:10:16 +0100 Subject: [PATCH 16/29] neue tag farben --- public/css/tags.css | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/public/css/tags.css b/public/css/tags.css index 9e0d7ee4b..e92f415b2 100644 --- a/public/css/tags.css +++ b/public/css/tags.css @@ -51,6 +51,14 @@ background-color: #6d4c41; } +.tag_dark_grey { + background-color: #595959; +} + +.tag_light_grey { + background-color: #9a9a9a; +} + .tag_blau { background-color: #508498; } From 632866c8c420b9adab3305e73fe6f64410b32932 Mon Sep 17 00:00:00 2001 From: Johann Hoffmann Date: Fri, 13 Feb 2026 13:45:12 +0100 Subject: [PATCH 17/29] reset newTermin object when switching projektarbeit so they are assigned to the correct student --- .../Cis/Abgabetool/AbgabeMitarbeiterDetail.js | 18 ++++++++++++++++++ .../Cis/Abgabetool/AbgabetoolMitarbeiter.js | 1 - 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/public/js/components/Cis/Abgabetool/AbgabeMitarbeiterDetail.js b/public/js/components/Cis/Abgabetool/AbgabeMitarbeiterDetail.js index 42952d0df..b303a831e 100644 --- a/public/js/components/Cis/Abgabetool/AbgabeMitarbeiterDetail.js +++ b/public/js/components/Cis/Abgabetool/AbgabeMitarbeiterDetail.js @@ -643,6 +643,24 @@ export const AbgabeMitarbeiterDetail = { 'projektarbeit'(newVal) { // set invertedFixtermin field for UI/UX purposes -> avoid double negation in text + // reset newTermin object + const typ = this.abgabeTypeOptions.find(opt => opt.paabgabetyp_kurzbz === 'zwischen') + this.newTermin = { + 'paabgabe_id': -1, + 'projektarbeit_id': newVal.projektarbeit_id, + 'fixtermin': false, + 'invertedFixtermin': true, + 'kurzbz': '', + 'datum': new Date().toISOString().split('T')[0], + 'note': this.allowedNotenOptions.find(opt => opt.note == 9), + 'beurteilungsnotiz': '', + 'upload_allowed': typ.upload_allowed_default, + 'paabgabetyp_kurzbz': '', + 'bezeichnung': typ, + 'abgabedatum': null, + 'insertvon': this.viewData?.uid ?? '' + } + newVal?.abgabetermine?.forEach(termin => termin.invertedFixtermin = !termin.fixtermin) // default select german if projektarbeit sprache was null diff --git a/public/js/components/Cis/Abgabetool/AbgabetoolMitarbeiter.js b/public/js/components/Cis/Abgabetool/AbgabetoolMitarbeiter.js index 67e1d09af..ff414ee10 100644 --- a/public/js/components/Cis/Abgabetool/AbgabetoolMitarbeiter.js +++ b/public/js/components/Cis/Abgabetool/AbgabetoolMitarbeiter.js @@ -333,7 +333,6 @@ export const AbgabetoolMitarbeiter = { pa.student = `${pa.vorname} ${pa.nachname}` this.selectedProjektarbeit = pa - this.$refs.modalContainerAbgabeDetail.show() }).finally(()=>{this.loading = false}) From 60294dd8f2591031fe5b90b67a9e43a89d977ab0 Mon Sep 17 00:00:00 2001 From: Johann Hoffmann Date: Mon, 16 Feb 2026 03:22:39 +0100 Subject: [PATCH 18/29] paBenotet evaluation fix --- public/js/components/Cis/Abgabetool/AbgabetoolMitarbeiter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/js/components/Cis/Abgabetool/AbgabetoolMitarbeiter.js b/public/js/components/Cis/Abgabetool/AbgabetoolMitarbeiter.js index ff414ee10..f33333ea3 100644 --- a/public/js/components/Cis/Abgabetool/AbgabetoolMitarbeiter.js +++ b/public/js/components/Cis/Abgabetool/AbgabetoolMitarbeiter.js @@ -304,7 +304,7 @@ export const AbgabetoolMitarbeiter = { pa.isCurrent = res.data[1] let paIsBenotet = false - if(pa.note !== undefined && pa !== null) { + if(pa.note !== undefined && pa.note !== null) { // check if the note is not defined as a non final projektarbeit note const opt = this.notenOptionsNonFinal.find(opt => opt.note) // if thats the case allow further work From 3831f3c1d781ae1d9477e215d52a0006bba7b014 Mon Sep 17 00:00:00 2001 From: Johann Hoffmann Date: Mon, 16 Feb 2026 03:41:30 +0100 Subject: [PATCH 19/29] consistent use of :optionDisabled="getOptionDisabled" for paabgabetyp dropdowns in assistenz view --- .../components/Cis/Abgabetool/AbgabeMitarbeiterDetail.js | 1 + .../js/components/Cis/Abgabetool/AbgabetoolAssistenz.js | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/public/js/components/Cis/Abgabetool/AbgabeMitarbeiterDetail.js b/public/js/components/Cis/Abgabetool/AbgabeMitarbeiterDetail.js index b303a831e..971783746 100644 --- a/public/js/components/Cis/Abgabetool/AbgabeMitarbeiterDetail.js +++ b/public/js/components/Cis/Abgabetool/AbgabeMitarbeiterDetail.js @@ -729,6 +729,7 @@ export const AbgabeMitarbeiterDetail = { v-model="newTermin.bezeichnung" :options="getAllowedAbgabeTypeOptions" :optionLabel="getOptionLabelAbgabetyp" + :optionDisabled="getOptionDisabled" scrollHeight="300px">
    diff --git a/public/js/components/Cis/Abgabetool/AbgabetoolAssistenz.js b/public/js/components/Cis/Abgabetool/AbgabetoolAssistenz.js index a7da9da5a..34ddd3fc2 100644 --- a/public/js/components/Cis/Abgabetool/AbgabetoolAssistenz.js +++ b/public/js/components/Cis/Abgabetool/AbgabetoolAssistenz.js @@ -966,7 +966,10 @@ export const AbgabetoolAssistenz = { // this.loadProjektarbeiten() this.calcMaxTableHeight() - } + }, + getOptionDisabled(option) { + return !option.aktiv + }, }, computed: { emailItems() { @@ -1176,7 +1179,8 @@ export const AbgabetoolAssistenz = { :style="{'width': '100%'}" v-model="serienTermin.bezeichnung" :options="abgabeTypeOptions" - :optionLabel="getOptionLabelAbgabetyp"> + :optionLabel="getOptionLabelAbgabetyp" + :optionDisabled="getOptionDisabled"> From 5415180b2ce2b77d15918cefe436c1aa08f0c77d Mon Sep 17 00:00:00 2001 From: Harald Bamberger Date: Mon, 16 Feb 2026 14:18:59 +0100 Subject: [PATCH 20/29] fetch count and paginated data in one query --- application/models/system/Message_model.php | 105 ++++++++++---------- 1 file changed, 51 insertions(+), 54 deletions(-) diff --git a/application/models/system/Message_model.php b/application/models/system/Message_model.php index e0a185f9b..741c96ade 100644 --- a/application/models/system/Message_model.php +++ b/application/models/system/Message_model.php @@ -242,74 +242,71 @@ class Message_model extends DB_Model */ public function getMessagesForTable($person_id, $offset, $limit) { - $sql_base = " - SELECT + $sql = <<execQuery($sql, $parametersArray); - - if (isError($count)) - return $count; - - $count = ceil(current(getData($count))->count/$limit); - $sql = " - SELECT * FROM ( - " . $sql_base . " - ) a - ORDER BY insertamum DESC - LIMIT ? - OFFSET ? - "; + (SELECT COALESCE(titelpre,'') || ' ' || COALESCE(vorname,'') || ' ' || COALESCE(nachname,'') || ' ' || COALESCE(titelpost,'') FROM public.tbl_person WHERE person_id = fm.sender_id) as sender, + (SELECT COALESCE(titelpre,'') || ' ' || COALESCE(vorname,'') || ' ' || COALESCE(nachname,'') || ' ' || COALESCE(titelpost,'') FROM public.tbl_person WHERE person_id = fm.recipient_id) as recipient, + fm.sender_id, + fm.recipient_id, + ms.status, + ms.insertamum as statusdatum + from + filtered_messages fm + join + public.tbl_msg_message m on fm.message_id = m.message_id + join + lastmsgstatus ms on fm.message_id = ms.message_id and fm.recipient_id = ms.person_id + order by + m.insertamum DESC + limit ? + offset ?; +EOSQL; $parametersArray = array($person_id, $person_id, $limit, $offset); + $count = 0; $data = $this->execQuery($sql, $parametersArray); if (isError($data)) return $data; $data = getData($data); + if($data) + { + $count = ceil($data[0]->total_msgs / $limit); + } return success(['data' => $data, 'count' => $count]); } From 962cbf4e783958bf75822dbe988aba3097166f5f Mon Sep 17 00:00:00 2001 From: Harald Bamberger Date: Mon, 16 Feb 2026 15:16:49 +0100 Subject: [PATCH 21/29] join person table for sender and recipient instead of using subselect --- application/models/system/Message_model.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/application/models/system/Message_model.php b/application/models/system/Message_model.php index 741c96ade..33e3d9649 100644 --- a/application/models/system/Message_model.php +++ b/application/models/system/Message_model.php @@ -276,8 +276,8 @@ class Message_model extends DB_Model m.body AS body, m.insertamum AS insertamum, m.relationmessage_id AS relationmessage_id, - (SELECT COALESCE(titelpre,'') || ' ' || COALESCE(vorname,'') || ' ' || COALESCE(nachname,'') || ' ' || COALESCE(titelpost,'') FROM public.tbl_person WHERE person_id = fm.sender_id) as sender, - (SELECT COALESCE(titelpre,'') || ' ' || COALESCE(vorname,'') || ' ' || COALESCE(nachname,'') || ' ' || COALESCE(titelpost,'') FROM public.tbl_person WHERE person_id = fm.recipient_id) as recipient, + (COALESCE(ps.titelpre,'') || ' ' || COALESCE(ps.vorname,'') || ' ' || COALESCE(ps.nachname,'') || ' ' || COALESCE(ps.titelpost,'')) as sender, + (COALESCE(pr.titelpre,'') || ' ' || COALESCE(pr.vorname,'') || ' ' || COALESCE(pr.nachname,'') || ' ' || COALESCE(pr.titelpost,'')) as recipient, fm.sender_id, fm.recipient_id, ms.status, @@ -288,6 +288,10 @@ class Message_model extends DB_Model public.tbl_msg_message m on fm.message_id = m.message_id join lastmsgstatus ms on fm.message_id = ms.message_id and fm.recipient_id = ms.person_id + left join + public.tbl_person ps on ps.person_id = fm.sender_id + left join + public.tbl_person pr on pr.person_id = fm.recipient_id order by m.insertamum DESC limit ? From 0496eb7cc947c874281dad9b28df1db758734b70 Mon Sep 17 00:00:00 2001 From: Harald Bamberger Date: Mon, 16 Feb 2026 15:56:40 +0100 Subject: [PATCH 22/29] use union instead of or to avoid parallel seq scan --- application/models/system/Message_model.php | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/application/models/system/Message_model.php b/application/models/system/Message_model.php index 33e3d9649..3a5579cc7 100644 --- a/application/models/system/Message_model.php +++ b/application/models/system/Message_model.php @@ -251,9 +251,23 @@ class Message_model extends DB_Model join public.tbl_msg_recipient mr on mr.message_id = m.message_id where - m.person_id = ? or mr.person_id = ? + m.person_id = ? group by m.message_id, m.person_id, mr.person_id + + union + + select + m.message_id, m.person_id as sender_id, mr.person_id as recipient_id + from + public.tbl_msg_message m + join + public.tbl_msg_recipient mr on mr.message_id = m.message_id + where + mr.person_id = ? + group by + m.message_id, m.person_id, mr.person_id + ), lastmsgstatus as ( select ms.* From e12b7e1ed55baf978d9911301e2561dc2769f1e1 Mon Sep 17 00:00:00 2001 From: Harald Bamberger Date: Tue, 17 Feb 2026 08:06:30 +0100 Subject: [PATCH 23/29] add indexes for person_id to table msg_message and msg_recipient, ensure tabulator data request is made before requests of create msg components --- application/models/system/Message_model.php | 2 +- .../Messages/Details/TableMessages.js | 1 + public/js/components/Messages/Messages.js | 10 +++++-- system/dbupdate_3.4.php | 1 + .../71645_studvw_messagetab_ladezeit.php | 28 +++++++++++++++++++ 5 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 system/dbupdate_3.4/71645_studvw_messagetab_ladezeit.php diff --git a/application/models/system/Message_model.php b/application/models/system/Message_model.php index 3a5579cc7..19129b606 100644 --- a/application/models/system/Message_model.php +++ b/application/models/system/Message_model.php @@ -255,7 +255,7 @@ class Message_model extends DB_Model group by m.message_id, m.person_id, mr.person_id - union + union all select m.message_id, m.person_id as sender_id, mr.person_id as recipient_id diff --git a/public/js/components/Messages/Details/TableMessages.js b/public/js/components/Messages/Details/TableMessages.js index a55ddec63..6a4cf5ca0 100644 --- a/public/js/components/Messages/Details/TableMessages.js +++ b/public/js/components/Messages/Details/TableMessages.js @@ -243,6 +243,7 @@ export default { title: this.$p.t('global', 'aktionen') }); */ + this.$emit('tabulator_tablebuilt'); } }, { diff --git a/public/js/components/Messages/Messages.js b/public/js/components/Messages/Messages.js index 1f9afcb9e..5e247ddb5 100644 --- a/public/js/components/Messages/Messages.js +++ b/public/js/components/Messages/Messages.js @@ -56,6 +56,7 @@ export default { }, data() { return { + tablebuilt: false, isVisibleDiv: false, messageId: null } @@ -139,8 +140,10 @@ export default { }, resetMessageId(){ this.messageId = null; + }, + tableBuilt: function() { + this.tablebuilt = true; } - }, template: `
    @@ -155,6 +158,7 @@ export default { -
    +
    diff --git a/system/dbupdate_3.4.php b/system/dbupdate_3.4.php index 793930243..4ddb38203 100644 --- a/system/dbupdate_3.4.php +++ b/system/dbupdate_3.4.php @@ -91,6 +91,7 @@ require_once('dbupdate_3.4/69065_Projektarbeiten_Firmen_verwalten.php'); require_once('dbupdate_3.4/68744_StV_settings.php'); require_once('dbupdate_3.4/62889_reihungstest_ueberwachung_mit_constructor.php'); require_once('dbupdate_3.4/71399_dashboard_update_widget_paths.php'); +require_once('dbupdate_3.4/71645_studvw_messagetab_ladezeit.php'); // *** Pruefung und hinzufuegen der neuen Attribute und Tabellen echo '

    Pruefe Tabellen und Attribute!

    '; diff --git a/system/dbupdate_3.4/71645_studvw_messagetab_ladezeit.php b/system/dbupdate_3.4/71645_studvw_messagetab_ladezeit.php new file mode 100644 index 000000000..4ad88fba9 --- /dev/null +++ b/system/dbupdate_3.4/71645_studvw_messagetab_ladezeit.php @@ -0,0 +1,28 @@ +db_query("SELECT * FROM pg_class WHERE relname='idx_tbl_msg_message_person_id'")) +{ + if ($db->db_num_rows($result) == 0) + { + $qry = "CREATE INDEX idx_tbl_msg_message_person_id ON public.tbl_msg_message USING btree (person_id)"; + + if (! $db->db_query($qry)) + echo 'idx_tbl_msg_message_person_id: ' . $db->db_last_error() . '
    '; + else + echo 'Index idx_tbl_msg_message_person_id angelegt
    '; + } +} + +if ($result = $db->db_query("SELECT * FROM pg_class WHERE relname='idx_tbl_msg_recipient_person_id'")) +{ + if ($db->db_num_rows($result) == 0) + { + $qry = "CREATE INDEX idx_tbl_msg_recipient_person_id ON public.tbl_msg_recipient USING btree (person_id)"; + + if (! $db->db_query($qry)) + echo 'idx_tbl_msg_recipient_person_id: ' . $db->db_last_error() . '
    '; + else + echo 'Index idx_tbl_msg_recipient_person_id angelegt
    '; + } +} From 3d1aef617f109e048ceb53f0f6993ad8f1b0fce1 Mon Sep 17 00:00:00 2001 From: Harald Bamberger Date: Tue, 17 Feb 2026 08:13:07 +0100 Subject: [PATCH 24/29] add phrase error.opproject_does_not_exists in category kvp --- system/phrasesupdate.php | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/system/phrasesupdate.php b/system/phrasesupdate.php index 5dc9bc1c0..deb856663 100644 --- a/system/phrasesupdate.php +++ b/system/phrasesupdate.php @@ -19087,6 +19087,27 @@ array( ) ) ), + array( + 'app' => 'core', + 'category' => 'kvp', + 'phrase' => 'error.opproject_does_not_exists', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => "Es ist kein Openproject Projekt verknüpft.", + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => "No Openproject project is linked.", + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + //******************* KVP end array( 'app' => 'international', 'category' => 'international', From 4825c75b5d9e00c2504252bf34cf618811d9e497 Mon Sep 17 00:00:00 2001 From: Harald Bamberger Date: Tue, 17 Feb 2026 12:11:37 +0100 Subject: [PATCH 25/29] revert changes made in commit b1a1cdf23550fc42337a944d4661f72d582b809d --- public/js/api/factory/studiengang.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/public/js/api/factory/studiengang.js b/public/js/api/factory/studiengang.js index 12322cb3a..6d5ae15aa 100644 --- a/public/js/api/factory/studiengang.js +++ b/public/js/api/factory/studiengang.js @@ -16,10 +16,17 @@ */ export default { - getAllStudiensemesterAndAktOrNext() { + studiengangInformation() { return { method: 'get', - url: '/api/frontend/v1/Studiensemester/getStudiengangInfo' + url: '/api/frontend/v1/Studgang/getStudiengangInfo' }; }, + getStudiengangByKz(studiengang_kz) { + return { + method: 'get', + url: '/api/frontend/v1/organisation/StudiengangEP/getStudiengangByKz', + params: { studiengang_kz } + }; + } }; \ No newline at end of file From c58674d1333baf2bf981356924e054f85c424568 Mon Sep 17 00:00:00 2001 From: Alexei Karpenko Date: Tue, 17 Feb 2026 15:15:30 +0100 Subject: [PATCH 26/29] Projektarbeiten cancelVertrag permission check bugfix (added array_column to get oes) --- application/controllers/api/frontend/v1/stv/Vertrag.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/application/controllers/api/frontend/v1/stv/Vertrag.php b/application/controllers/api/frontend/v1/stv/Vertrag.php index f94fe795e..c2b0f713c 100644 --- a/application/controllers/api/frontend/v1/stv/Vertrag.php +++ b/application/controllers/api/frontend/v1/stv/Vertrag.php @@ -76,9 +76,7 @@ class Vertrag extends FHCAPI_Controller if (isError($allOe)) $this->terminateWithError(getError($allOe), self::ERROR_TYPE_GENERAL); - $allOe = hasData($allOe) ? getData($allOe) : []; - - $this->addMeta('oe', $allOe); + $allOe = hasData($allOe) ? array_column(getData($allOe), 'oe_kurzbz') : []; // * then check if the user has permissions to cancel the corresponding lv-organisational units if (!$this->permissionlib->isBerechtigtMultipleOe('admin', $allOe, 'suid') && From 1d8c4b7159bfb7206895ce5e22019e97dfa4a078 Mon Sep 17 00:00:00 2001 From: ma0048 Date: Tue, 17 Feb 2026 16:49:09 +0100 Subject: [PATCH 27/29] bug behoben, login wieder nur mit zugangscode moeglich --- cis/testtool/login.php | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/cis/testtool/login.php b/cis/testtool/login.php index 182506ac3..cfc1ba63b 100644 --- a/cis/testtool/login.php +++ b/cis/testtool/login.php @@ -340,13 +340,26 @@ else } } -if ((isset($_SESSION['prestudent_id']) && !isset($_SESSION['pruefling_id']) && - !isset($_SESSION['confirmation_needed']) && !isset($_SESSION['confirmed_code'])) || - (isset($_SESSION['confirmation_needed']) && $_SESSION['confirmation_needed'] === true && - isset($_SESSION['confirmed_code']) && $_SESSION['confirmed_code'] === true && - isset($_SESSION['externe_ueberwachung']) && $_SESSION['externe_ueberwachung'] === true && - isset($_SESSION['externe_ueberwachung_verified']) && $_SESSION['externe_ueberwachung_verified'] === true && - isset($_SESSION['prestudent_id']) && !isset($_SESSION['pruefling_id']))) +if ( + ( + isset($_SESSION['prestudent_id']) && !isset($_SESSION['pruefling_id']) && + !isset($_SESSION['confirmation_needed']) && !isset($_SESSION['confirmed_code']) && + !isset($_SESSION['externe_ueberwachung']) && !isset($_SESSION['externe_ueberwachung_verified']) + ) + || + ( + isset($_SESSION['confirmation_needed']) && $_SESSION['confirmation_needed'] === true && + isset($_SESSION['confirmed_code']) && $_SESSION['confirmed_code'] === true && + isset($_SESSION['prestudent_id']) && !isset($_SESSION['pruefling_id']) + ) + || + ( + isset($_SESSION['externe_ueberwachung']) && $_SESSION['externe_ueberwachung'] === true && + isset($_SESSION['externe_ueberwachung_verified']) && $_SESSION['externe_ueberwachung_verified'] === true && + isset($_SESSION['prestudent_id']) && !isset($_SESSION['pruefling_id']) + ) + +) { $pruefling = new pruefling(); From 7169cb68a2bbbf9d284ee99fabc366d7214ad954 Mon Sep 17 00:00:00 2001 From: Harald Bamberger Date: Thu, 19 Feb 2026 09:20:10 +0100 Subject: [PATCH 28/29] fix bug when sending multi messages introduced by loading time optimisation --- public/js/components/Messages/Messages.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/js/components/Messages/Messages.js b/public/js/components/Messages/Messages.js index 5e247ddb5..e1fb69dc3 100644 --- a/public/js/components/Messages/Messages.js +++ b/public/js/components/Messages/Messages.js @@ -158,7 +158,7 @@ export default {
    Date: Thu, 19 Feb 2026 18:34:27 +0100 Subject: [PATCH 29/29] fix duplicates in lvplan for special groups --- application/models/ressource/Stundenplan_model.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/application/models/ressource/Stundenplan_model.php b/application/models/ressource/Stundenplan_model.php index 067e2b790..d0a97ed9d 100644 --- a/application/models/ressource/Stundenplan_model.php +++ b/application/models/ressource/Stundenplan_model.php @@ -470,12 +470,12 @@ class Stundenplan_model extends DB_Model } foreach($studentlehrverbaende[$sem_date] as $key=>$lehrverband) { - $query .= "((sp.studiengang_kz = ".$this->escape($lehrverband->studiengang_kz)." AND sp.semester = ".$this->escape($lehrverband->semester)." AND sp.verband = ".$this->escape($lehrverband->verband)." AND sp.gruppe = ".$this->escape($lehrverband->gruppe)." AND sp.datum BETWEEN ".$this->escape($sem_date_range->start)." AND ".$this->escape($sem_date_range->ende).")"; + $query .= "(((sp.studiengang_kz = ".$this->escape($lehrverband->studiengang_kz)." AND sp.semester = ".$this->escape($lehrverband->semester)." AND sp.verband = ".$this->escape($lehrverband->verband)." AND sp.gruppe = ".$this->escape($lehrverband->gruppe)." AND sp.datum BETWEEN ".$this->escape($sem_date_range->start)." AND ".$this->escape($sem_date_range->ende).")"; // Eintraege fuer den ganzen Verband $query .= "OR (sp.studiengang_kz = ".$this->escape($lehrverband->studiengang_kz)." AND sp.semester = ".$this->escape($lehrverband->semester)." AND sp.verband = ".$this->escape($lehrverband->verband)." AND (sp.gruppe is null OR sp.gruppe='') AND sp.datum BETWEEN ".$this->escape($sem_date_range->start)." AND ".$this->escape($sem_date_range->ende).")"; // Eintraege fuer das ganze Semester $query .= "OR (sp.studiengang_kz = ".$this->escape($lehrverband->studiengang_kz)." AND sp.semester = ".$this->escape($lehrverband->semester)." AND (sp.verband is null OR sp.verband='') AND sp.datum BETWEEN ".$this->escape($sem_date_range->start) - ." AND ".$this->escape($sem_date_range->ende).")". $stringGroupLv. ")"; + ." AND ".$this->escape($sem_date_range->ende).")) AND gruppe_kurzbz is null)"; $query .="OR"; }