diff --git a/application/controllers/Cis/Benotungstool.php b/application/controllers/Cis/Benotungstool.php index c22ed42f3..94faeff98 100644 --- a/application/controllers/Cis/Benotungstool.php +++ b/application/controllers/Cis/Benotungstool.php @@ -27,22 +27,8 @@ class Benotungstool extends Auth_Controller */ public function index() { - - - // TODO: check if related CIS config is also loaded when being routed in Cis4 by vuerouter - // TODO: check if new benotungstool should be configurable the exact same way? - // TODO: move this to some config api? - $viewData = array( 'uid'=>getAuthUID(), - 'CIS_GESAMTNOTE_UEBERSCHREIBEN' => CIS_GESAMTNOTE_UEBERSCHREIBEN, - 'CIS_GESAMTNOTE_PRUEFUNG_KOMMPRUEF' => CIS_GESAMTNOTE_PRUEFUNG_KOMMPRUEF, - 'CIS_GESAMTNOTE_PRUEFUNG_TERMIN3' => CIS_GESAMTNOTE_PRUEFUNG_TERMIN3, - 'CIS_GESAMTNOTE_PRUEFUNG_TERMIN2' => CIS_GESAMTNOTE_PRUEFUNG_TERMIN2, - 'CIS_GESAMTNOTE_PRUEFUNG_MOODLE_LE_NOTE' => CIS_GESAMTNOTE_PRUEFUNG_MOODLE_LE_NOTE, - 'CIS_GESAMTNOTE_PUNKTE' => CIS_GESAMTNOTE_PUNKTE, - 'CIS_GESAMTNOTE_GEWICHTUNG' => CIS_GESAMTNOTE_GEWICHTUNG, - 'CIS_ANWESENHEITSLISTE_NOTENLISTE_ANZEIGEN' => CIS_ANWESENHEITSLISTE_NOTENLISTE_ANZEIGEN ); $this->load->view('CisRouterView/CisRouterView.php', ['viewData' => $viewData, 'route' => 'Benotungstool']); diff --git a/application/controllers/api/frontend/v1/Noten.php b/application/controllers/api/frontend/v1/Noten.php index d4321c5a5..6244e3a62 100644 --- a/application/controllers/api/frontend/v1/Noten.php +++ b/application/controllers/api/frontend/v1/Noten.php @@ -37,7 +37,8 @@ class Noten extends FHCAPI_Controller 'saveStudentPruefung' => array('lehre/benotungstool:rw'), 'createPruefungen' => array('lehre/benotungstool:rw'), 'saveNotenvorschlagBulk' => array('lehre/benotungstool:rw'), - 'savePruefungenBulk' => array('lehre/benotungstool:rw') + 'savePruefungenBulk' => array('lehre/benotungstool:rw'), + 'getCisConfig' => array('lehre/benotungstool:rw') ]); $this->load->library('AuthLib', null, 'AuthLib'); @@ -66,6 +67,31 @@ class Noten extends FHCAPI_Controller } + public function getCisConfig() { + $this->terminateWithSuccess( + array( + // Punkte bei der Noteneingabe anzeigen + 'CIS_GESAMTNOTE_PUNKTE' => CIS_GESAMTNOTE_PUNKTE, + // Gibt an ob der Lektor erneut eine LVNote eintragen kann wenn bereits eine Zeugnisnote vorhanden ist (true | false) DEFAULT true + 'CIS_GESAMTNOTE_UEBERSCHREIBEN' => CIS_GESAMTNOTE_UEBERSCHREIBEN, + // Gewichtung der Lehreinheiten bei Noteneintragung true|false + 'CIS_GESAMTNOTE_GEWICHTUNG' => CIS_GESAMTNOTE_GEWICHTUNG, + // Bei Gesamtnote eine zusaetzliche Spalte fuer den 2. Termin anzeigen + 'CIS_GESAMTNOTE_PRUEFUNG_TERMIN2' => CIS_GESAMTNOTE_PRUEFUNG_TERMIN2, + // Bei Gesamtnote eine zusaetzliche Spalte fuer den 3. Termin anzeigen + // Erfordert den Eintrag "Termin3" in der Tabelle lehre.tbl_pruefungstyp + 'CIS_GESAMTNOTE_PRUEFUNG_TERMIN3' => CIS_GESAMTNOTE_PRUEFUNG_TERMIN3, + // Bei Gesamtnote eine zusaetzliche Spalte fuer die kommissionelle Pruefung anlegen + 'CIS_GESAMTNOTE_PRUEFUNG_KOMMPRUEF' => CIS_GESAMTNOTE_PRUEFUNG_KOMMPRUEF, + // Bei Gesamtnote eine zusaetzliche Spalte fuer die kommissionelle Pruefung anlegen + 'CIS_GESAMTNOTE_PRUEFUNG_MOODLE_NOTE' => CIS_GESAMTNOTE_PRUEFUNG_MOODLE_NOTE, + // Bei Gesamtnote die Spalte fuer die Quelle der Noten anzeigen (Moodle oder LE) + 'CIS_GESAMTNOTE_PRUEFUNG_MOODLE_LE_NOTE' => CIS_GESAMTNOTE_PRUEFUNG_MOODLE_LE_NOTE, + // Gibt an ob die Note im Notenfreigabemail enthalten ist oder nicht + 'CIS_GESAMTNOTE_FREIGABEMAIL_NOTE' => CIS_GESAMTNOTE_FREIGABEMAIL_NOTE + ) + ); + } /** * GET METHOD @@ -93,6 +119,12 @@ class Noten extends FHCAPI_Controller $grades = array(); $student_uids = array_map($func, $studentenData); + + $funcpre = function ($value) { + return $value->prestudent_id; + }; + + $prestudent_ids = array_map($funcpre, $studentenData); if(count($student_uids) > 0) { $mobres = $this->MobilitaetModel->getMobilityZusatzForUids($student_uids); @@ -146,8 +178,14 @@ class Noten extends FHCAPI_Controller $this->addMeta('getExternalGradesError', $t->getMessage()); } + // assign the anw% to the students in the studentData loop + $anwresult = $this->getAnwesenheiten($prestudent_ids, $lv_id, $sem_kurzbz); + // calculate notenvorschläge from teilnoten foreach($studentenData as $student) { + + $student->anwquote = $anwresult[$student->prestudent_id]; + $g = $grades[$student->uid]['grades']; $note_lv = $grades[$student->uid]['note_lv']; @@ -214,7 +252,7 @@ class Noten extends FHCAPI_Controller $pruefungen = $this->LePruefungModel->getPruefungenByLvStudiensemester($lv_id, $sem_kurzbz); $pruefungenData = getData($pruefungen); - $this->terminateWithSuccess(array($studentenData, $pruefungenData, DOMAIN, $grades)); + $this->terminateWithSuccess(array($studentenData, $pruefungenData, DOMAIN, $grades, $anwresult)); } /** @@ -536,6 +574,15 @@ class Noten extends FHCAPI_Controller private function savePruefungstermin($typ, $student_uid, $lva_id, $stsem, $lehreinheit_id, $note, $punkte, $datum) { + // extra check if the student has lvnote and a zeugnisnote in the relevant lva + $resultLV = $this->LvgesamtnoteModel->getLvGesamtNoten($lva_id, $student_uid, $stsem); + + $lvgesamtnoteData = getData($resultLV); + $this->addMeta('lvgesamtnoteData', $lvgesamtnoteData); + + // allocating pruefungen before lv note is forbidden + if($lvgesamtnoteData == null) return $this->p->t('benotungstool', 'c4keineLvNoteEingetragen'); + $status = []; // send $grades reference to moodle addon @@ -928,6 +975,32 @@ class Noten extends FHCAPI_Controller $this->terminateWithSuccess($ret); } + + private function getAnwesenheiten($prestudent_ids, $lv_id, $sem_kurzbz) { + + $anwesenheiten = []; + try { + $downloadFunc = function ($anwesenheitenResult) use (&$anwesenheiten) { + // map result rows by prestudent_uid to retrieve them by that key later on + foreach ($anwesenheitenResult as $anw) { + $anwesenheiten[$anw->prestudent_id] = $anw->sum; + } + }; + + Events::trigger( + 'getAnwesenheitenForLvAndSemester', + $prestudent_ids, + $lv_id, + $sem_kurzbz, + $downloadFunc + ); + } catch (Throwable $t) { + $this->addMeta('getAnwesenheitenForLvAndSemester', $t->getMessage()); + } + + return $anwesenheiten; + + } } diff --git a/application/models/education/Lehrveranstaltung_model.php b/application/models/education/Lehrveranstaltung_model.php index 093b2b02c..8604de233 100644 --- a/application/models/education/Lehrveranstaltung_model.php +++ b/application/models/education/Lehrveranstaltung_model.php @@ -312,7 +312,7 @@ class Lehrveranstaltung_model extends DB_Model { $query = "SELECT distinct on(nachname, vorname, person_id) vorname, nachname, matrikelnr, - tbl_studentlehrverband.semester, tbl_studentlehrverband.verband, tbl_studentlehrverband.gruppe, + tbl_studentlehrverband.semester, tbl_studentlehrverband.verband, tbl_studentlehrverband.gruppe, tbl_student.prestudent_id, (SELECT status_kurzbz FROM public.tbl_prestudentstatus WHERE prestudent_id=tbl_student.prestudent_id ORDER BY datum DESC, insertamum DESC, ext_id DESC LIMIT 1) as status, tbl_bisio.bisio_id, tbl_bisio.von, tbl_bisio.bis, tbl_student.studiengang_kz AS stg_kz_student, tbl_zeugnisnote.note, tbl_mitarbeiter.mitarbeiter_uid, tbl_person.matr_nr, tbl_benutzer.uid, diff --git a/public/js/api/factory/noten.js b/public/js/api/factory/noten.js index 2ac91ad21..51e1900e6 100644 --- a/public/js/api/factory/noten.js +++ b/public/js/api/factory/noten.js @@ -16,6 +16,12 @@ */ export default { + getCisConfig(){ + return { + method: 'get', + url: '/api/frontend/v1/Noten/getCisConfig' + }; + }, getStudentenNoten(lv_id, sem_kurzbz) { return { method: 'get', diff --git a/public/js/components/Cis/Benotungstool/Benotungstool.js b/public/js/components/Cis/Benotungstool/Benotungstool.js index fb4bede2a..cf0ed83e0 100644 --- a/public/js/components/Cis/Benotungstool/Benotungstool.js +++ b/public/js/components/Cis/Benotungstool/Benotungstool.js @@ -44,6 +44,7 @@ export const Benotungstool = { }, data() { return { + config: null, // cis config neuesPruefungsdatumModalVisible: false, loading: false, selectedUids: [], // shared selection state @@ -362,7 +363,8 @@ export const Benotungstool = { // add col to table const cols = [...this.notenTableOptions.columns.slice(0, -1)]; - const kommCol = this.notenTableOptions.columns[this.notenTableOptions.columns.length - 1]; + let kommCol = null + if(this.config?.CIS_GESAMTNOTE_PRUEFUNG_KOMMPRUEF) kommCol = this.notenTableOptions.columns[this.notenTableOptions.columns.length - 1]; // TODO: could reuse cols instead of recreating all from a variable maybe this.distinctPruefungsDates.forEach((date, index)=>{ @@ -385,7 +387,7 @@ export const Benotungstool = { }) }) - cols.push(kommCol) // keep kommPruef Col as last + if(this.config?.CIS_GESAMTNOTE_PRUEFUNG_KOMMPRUEF) cols.push(kommCol) // keep kommPruef Col as last // redraw table this.loading = false @@ -516,7 +518,8 @@ export const Benotungstool = { {title: Vue.computed(() => this.$p.t('benotungstool/c4mail')), field: 'email', formatter: this.mailFormatter, tooltip: false, visible: false, widthGrow: 1, variableHeight: true}, {title: Vue.computed(() => this.$p.t('benotungstool/c4antrittCountv2')), field: 'hoechsterAntritt', tooltip: false, widthGrow: 1}, {title: Vue.computed(() => this.$p.t('benotungstool/c4vorname')), field: 'vorname', headerFilter: true, tooltip: false, widthGrow: 1}, - {title: Vue.computed(() => this.$p.t('benotungstool/c4nachname')), field: 'nachname', headerFilter: true, widthGrow: 1}, + {title: Vue.computed(() => this.$p.t('benotungstool/c4nachname')), field: 'nachname', headerFilter: true, widthGrow: 1}, + {title: Vue.computed(() => this.$p.t('benotungstool/c4anwesenheitsquote')), field: 'anwquote', widthGrow: 1, formatter: this.percentFormatter}, {title: Vue.computed(() => this.$p.t('benotungstool/c4mobility')), field: 'mobility_zusatz', headerFilter: true, widthGrow: 1, visible: false}, {title: Vue.computed(() => this.$p.t('benotungstool/c4teilnoten')), field: 'teilnote', widthGrow: 1, formatter: this.teilnotenFormatter, variableHeight: true}, {title: Vue.computed(() => this.$p.t('benotungstool/c4note')), field: 'note_vorschlag', @@ -953,6 +956,11 @@ export const Benotungstool = { return '
' + '
' }, + percentFormatter(cell) { + const data = cell.getData() + const val = data.anwquote ?? '-' + return '
'+ val + ' %
' + }, buildMailToLink(student){ return 'mailto:' + student.uid +'@'+ this.domain }, @@ -988,7 +996,8 @@ export const Benotungstool = { // let pruefungenRegularColCount = 0; this.distinctPruefungsDates = [] const cols = [...this.notenTableOptions.columns.slice(0, -1)]; - const kommCol = this.notenTableOptions.columns[this.notenTableOptions.columns.length - 1]; + let kommCol = null + if(this.config?.CIS_GESAMTNOTE_PRUEFUNG_KOMMPRUEF) kommCol = this.notenTableOptions.columns[this.notenTableOptions.columns.length - 1]; this.pruefungen?.forEach(p => { const dateParts = p.datum.split('-') @@ -1091,7 +1100,7 @@ export const Benotungstool = { }) }) - cols.push(kommCol) // keep kommPruef Col as last + if(this.config?.CIS_GESAMTNOTE_PRUEFUNG_KOMMPRUEF) cols.push(kommCol) // keep kommPruef Col as last this.loading = false @@ -1119,11 +1128,16 @@ export const Benotungstool = { if(!tableDataSet) return const rect = tableDataSet.getBoundingClientRect(); - this.notenTableOptions.height = window.visualViewport.height - rect.top + this.notenTableOptions.height = window.visualViewport.height - rect.top - 50 this.$refs.notenTable.tabulator.setHeight(this.notenTableOptions.height) }, setupCreated() { this.loading = true + // fetch cis config regarding gesamtnoteneingabe + this.$api.call(ApiNoten.getCisConfig()).then(res => { + this.config = res.data + }) + // fetch lva dropdown this.$api.call(ApiLehre.getZugewieseneLv(this.viewData?.uid, this.sem_kurzbz)).then(res => { this.lehrveranstaltungen = res.data @@ -1341,7 +1355,8 @@ export const Benotungstool = { // add col to table const cols = [...this.notenTableOptions.columns.slice(0, -1)]; - const kommCol = this.notenTableOptions.columns[this.notenTableOptions.columns.length - 1]; + let kommCol = null + if(this.config?.CIS_GESAMTNOTE_PRUEFUNG_KOMMPRUEF) kommCol = this.notenTableOptions.columns[this.notenTableOptions.columns.length - 1]; // TODO: could reuse cols instead of recreating all from a variable maybe @@ -1367,7 +1382,7 @@ export const Benotungstool = { }) }) - cols.push(kommCol) // keep kommPruef Col as last + if(this.config?.CIS_GESAMTNOTE_PRUEFUNG_KOMMPRUEF) cols.push(kommCol) // keep kommPruef Col as last // set Cols this.$refs.notenTable.tabulator.clearSort() @@ -1443,17 +1458,46 @@ export const Benotungstool = { this.sem_kurzbz, )).then(res => { if(res.meta.status === "success") { - this.$fhcAlert.alertDefault( - 'success', - 'Info', - this.$p.t('benotungstool/pruefungAngelegtAn', [dateStrFront]), - true - ) - this.handleAddNewPruefungenResponse(res, uids) + // iterate over response data + // -> alert successful pruefungen + // -> alert denied pruefungen + reason + + let uidListSuccess = '' + let uidListError = '' + const successData = [] + Object.keys(res.data).forEach(student_uid => { + const student = res.data[student_uid] + // actual pruefung has been allocated + if(student.savedPruefung || student.extraPruefung) { + uidListSuccess += ' ' + student_uid + + // keep res.data format intact for handleResponse method + successData[student_uid] = student + } else { // there should be an error message why no pruefungen where allocated for this person, many reasons possible + uidListError += student_uid + ' - ' + student +'\n'// student variable is the error message here + } + }) + + if(uidListError != '') { + this.$fhcAlert.alertError( + this.$p.t('benotungstool/c4pruefungAnlageError', [dateStrFront]) + ': ' + uidListError + ' ' + ) + } + + if(uidListSuccess != '') { + this.$fhcAlert.alertDefault( + 'success', + 'Info', + this.$p.t('benotungstool/pruefungAngelegtAn', [dateStrFront]) + ': ' + uidListSuccess, + true + ) + + this.handleAddNewPruefungenResponse({data: successData}, uids) + } } - }) + }).finally(()=>this.loading = false) }, getAntrittCountStudent(student) { // checks for existence of a prüfung with a note that resolves to a @@ -1516,6 +1560,7 @@ export const Benotungstool = { if(newVal) this.$refs.notenTable.tabulator.setFilter("lehreinheit_id", "=", newVal.lehreinheit_id); }, getKommPruefCount(newVal) { + if(!this.config.CIS_GESAMTNOTE_PRUEFUNG_KOMMPRUEF) return 0 if(this.$refs.notenTable?.tabulator && newVal > 0) { const kommPruefCol = this.$refs.notenTable?.tabulator.getColumn("kommPruef") kommPruefCol.show() @@ -1696,24 +1741,24 @@ export const Benotungstool = { - - + + + + + + + - - - - - - +
diff --git a/system/phrasesupdate.php b/system/phrasesupdate.php index 3647ade5f..58f1ca652 100644 --- a/system/phrasesupdate.php +++ b/system/phrasesupdate.php @@ -51647,19 +51647,7 @@ and represent the current state of research on the topic. The prescribed citatio

Wenn alle einzutragenden Noten vermerkt sind (Nachtragung jederzeit möglich) können diese per Klick auf Freigabe für die Studiengangsassistenz freigegeben werden.

- Aus Gründen der erhöhten Sicherheit ist bei der Freigabe der Noten die Eingabe Ihres Passwortes erforderlich. -

- Zulässige Noten: 1-5, Nicht beurteilt (nb), Noch nicht eingetragen (), Bestanden (b), Mit Erfolg teilgenommen (met), entschuldigt (en), unentschuldigt (ue) -

- Bei der Freigabe wird ein Info-Email an Sie und die zuständige Studiengangsassistenz geschickt. Enthalten sind Mat.Nr., Vor- und Zuname sowie die Note der neuen oder geänderten Einträge. -

- Freigegebene Einträge sind mit einem grün hinterlegten Häkchen gekennzeichnet. -

- Wenn Sie bereits freigegebene Noten verändern, werden diese mit einem grau hinterlegten Häkchen markiert (als Hinweis für Sie, dass die Assistenz bislang darüber noch nicht per Mail informiert wurde. Sie sieht allerdings diese neue Note sofort in ihrer Oberfläche) -

- Freigegebene Noten kann die Assistenz nun als Zeugnisnote übernehmen (wird im nächsten Feld für Sie zur Kontrolle angezeigt) -

- Wenn sich die Zeugnisnote von der von Ihnen freigegebenen Note unterscheidet wird erstere rot umrandet markiert.', + Aus Gründen der erhöhten Sicherheit ist bei der Freigabe der Noten die Eingabe Ihres Passwortes erforderlich.', 'description' => '', 'insertvon' => 'system' ), @@ -51669,19 +51657,7 @@ and represent the current state of research on the topic. The prescribed citatio

Once all grades to be entered have been recorded (additions are possible at any time), they can be released to the program assistant by clicking on "Release."

- For increased security reasons, you will be required to enter your password when releasing grades.2) -

- Permissible grades: 1-5, Not Assessed (nb), Not Yet Entered (), Passed (b), Successfully Participated (met), Excused (en), Unexcused (ue) -

- When the grade is released, an information email will be sent to you and the responsible program assistant. This email contains the student ID number, first and last name, and the grade for the new or changed entries. -

- Released entries are marked with a green check mark. -

- If you change grades that have already been approved, they will be marked with a gray checkmark (indicating that the assistant has not yet been notified by email. However, they will immediately see this new grade in their interface). -

- The assistant can now adopt approved grades as the grade on their report card (this will be displayed in the next field for your review). -

- If the report card grade differs from the grade you approved, the former will be highlighted with a red border.', + For increased security reasons, you will be required to enter your password when releasing grades.2)', 'description' => '', 'insertvon' => 'system' ) @@ -51727,6 +51703,66 @@ and represent the current state of research on the topic. The prescribed citatio ) ) ), + array( + 'app' => 'core', + 'category' => 'benotungstool', + 'phrase' => 'c4anwesenheitsquote', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Anwesenheit', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Attendance', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'benotungstool', + 'phrase' => 'c4keineLvNoteEingetragen', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Keine LV Note eingetragen', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'No Subject Grade entered', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'benotungstool', + 'phrase' => 'c4pruefungAnlageError', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Prüfungstermin anlegen für {0} fehlgeschlagen', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Not possible to allocate exam on {0}', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), // CIS4 GESAMTNOTENEINGABE ENDE ------------------------------------------------------------------------------------