From 6a3982347b8fff5b1938bbb07532a5cc62f4c8e4 Mon Sep 17 00:00:00 2001 From: Johann Hoffmann Date: Thu, 24 Jul 2025 17:02:57 +0200 Subject: [PATCH] =?UTF-8?q?teilnoten/punkte=20berechnen=20f=C3=BCr=20noten?= =?UTF-8?q?vorschl=C3=A4ge;=20vorschl=C3=A4ge=20=C3=BCbernehmen;=20noten?= =?UTF-8?q?=20freigabe=20mit=20passwort;=20pr=C3=BCfungen=20generisch=20an?= =?UTF-8?q?zeigen=20nach=20datum=20gruppiert;=20pr=C3=BCfungen=20anlegen/b?= =?UTF-8?q?earbeiten=20auf=20mapping=20termin1/2/3=20m=C3=B6glich.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- application/controllers/Cis/Benotungstool.php | 6 + .../controllers/api/frontend/v1/Lehre.php | 14 +- .../controllers/api/frontend/v1/Noten.php | 455 +++++++++++- .../models/education/Lehreinheit_model.php | 22 + .../education/Lehrveranstaltung_model.php | 5 +- application/models/education/Note_model.php | 5 +- .../views/CisRouterView/CisRouterView.php | 1 + public/js/api/factory/lehre.js | 20 +- public/js/api/factory/noten.js | 18 +- .../Cis/Benotungstool/Benotungstool.js | 698 +++++++++++++++--- system/phrasesupdate.php | 144 +++- 11 files changed, 1270 insertions(+), 118 deletions(-) diff --git a/application/controllers/Cis/Benotungstool.php b/application/controllers/Cis/Benotungstool.php index 30d4ed0c1..81379b587 100644 --- a/application/controllers/Cis/Benotungstool.php +++ b/application/controllers/Cis/Benotungstool.php @@ -15,6 +15,8 @@ class Benotungstool extends Auth_Controller parent::__construct([ 'index' => self::PERM_LOGGED ]); + + $this->_ci =& get_instance(); } // ----------------------------------------------------------------------------------------------------------------- @@ -26,8 +28,12 @@ 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? $viewData = array( 'uid'=>getAuthUID(), + 'CIS_GESAMTNOTE_UEBERSCHREIBEN' => CIS_GESAMTNOTE_UEBERSCHREIBEN ); $this->load->view('CisRouterView/CisRouterView.php', ['viewData' => $viewData, 'route' => 'Benotungstool']); diff --git a/application/controllers/api/frontend/v1/Lehre.php b/application/controllers/api/frontend/v1/Lehre.php index 237f97090..5c821296f 100644 --- a/application/controllers/api/frontend/v1/Lehre.php +++ b/application/controllers/api/frontend/v1/Lehre.php @@ -32,7 +32,8 @@ class Lehre extends FHCAPI_Controller 'LV' => self::PERM_LOGGED, 'Pruefungen' => self::PERM_LOGGED, 'getLvViewData' => self::PERM_LOGGED, - 'getZugewieseneLv' => self::PERM_LOGGED + 'getZugewieseneLv' => self::PERM_LOGGED, + 'getLeForLv' => self::PERM_LOGGED ]); // Loads phrases system @@ -129,6 +130,17 @@ class Lehre extends FHCAPI_Controller $this->terminateWithSuccess($data); } + + public function getLeForLv() { + $lv_id = $this->input->get("lv_id",TRUE); + $sem_kurzbz = $this->input->get("sem_kurzbz",TRUE); + + $this->load->model('education/Lehreinheit_model', 'LehreinheitModel'); + +// $this->terminateWithSuccess($this->LehreinheitModel->getLesForLv($lv_id, $sem_kurzbz)); + $this->terminateWithSuccess($this->LehreinheitModel->getAllLehreinheitenForLvaAndMaUid($lv_id, getAuthUID(), $sem_kurzbz)); + } + diff --git a/application/controllers/api/frontend/v1/Noten.php b/application/controllers/api/frontend/v1/Noten.php index 9a5030d82..86c56d5ad 100644 --- a/application/controllers/api/frontend/v1/Noten.php +++ b/application/controllers/api/frontend/v1/Noten.php @@ -31,7 +31,10 @@ class Noten extends FHCAPI_Controller parent::__construct([ 'getStudentenNoten' => self::PERM_LOGGED, // todo: berechtigung 'getNoten' => self::PERM_LOGGED, - 'saveStudentenNoten' => self::PERM_LOGGED // todo: berechtigungen! + 'saveStudentenNoten' => self::PERM_LOGGED, // todo: berechtigungen! + 'getNotenvorschlagStudent' => self::PERM_LOGGED, + 'saveNotenvorschlag' => self::PERM_LOGGED, + 'saveStudentPruefung' => self::PERM_LOGGED ]); $this->load->library('AuthLib', null, 'AuthLib'); @@ -40,9 +43,16 @@ class Noten extends FHCAPI_Controller $this->loadPhrases([ 'global' ]); - + require_once(FHCPATH . 'include/mobilitaet.class.php'); + require_once(FHCPATH . 'include/student.class.php'); + require_once(FHCPATH . 'include/lvgesamtnote.class.php'); + require_once(FHCPATH . 'include/lehrveranstaltung.class.php'); + require_once(FHCPATH . 'include/lehreinheit.class.php'); + require_once(FHCPATH . 'include/studiengang.class.php'); + require_once(FHCPATH . 'include/pruefung.class.php'); + } - + public function getStudentenNoten() { $lv_id = $this->input->get("lv_id",TRUE); $sem_kurzbz = $this->input->get("sem_kurzbz",TRUE); @@ -58,18 +68,44 @@ class Noten extends FHCAPI_Controller // get studenten for lva & sem with zeugnisnote if available $studenten = $this->LehrveranstaltungModel->getStudentsByLv($sem_kurzbz, $lv_id); - $studentenData = $this->getDataOrTerminateWithError($studenten); +// $studentenData = $this->getDataOrTerminateWithError($studenten); + $studentenData = getData($studenten); + $this->addMeta('$studentenData', $studentenData); $func = function ($value) { return $value->uid; }; - + $grades = array(); $student_uids = array_map($func, $studentenData); foreach($student_uids as $uid) { $grades[$uid]['grades'] = []; + + $student = new student(); + $student->load($uid); + $student->result[]= $student; + $prestudent_id = $student->prestudent_id; + + $mobility = new mobilitaet(); + $mobility->loadPrestudent($prestudent_id); + $output = $mobility->result; + $eintrag = ''; + foreach ($output as $k) + { + if(($k->mobilitaetstyp_kurzbz == 'GS') && ($k->studiensemester_kurzbz == $sem_kurzbz)) + $eintrag = ' (d.d.)'; + } + $grades[$uid]['mobility'] = $eintrag; + + if ($lvgesamtnote = new lvgesamtnote($lv_id, $uid, $sem_kurzbz)) + { + $grades[$uid]['note_lv'] = $lvgesamtnote->note; + $grades[$uid]['freigabedatum'] = $lvgesamtnote->freigabedatum; + $grades[$uid]['benotungsdatum'] = $lvgesamtnote->benotungsdatum; + $grades[$uid]['punkte_lv'] = $lvgesamtnote->punkte; + } } - + // send $grades reference to moodle addon Events::trigger( 'moodleGrades', @@ -82,18 +118,81 @@ class Noten extends FHCAPI_Controller 'stsem' => $sem_kurzbz ] ); + $this->addMeta('$grades', $grades); + + // calculate notenvorschläge from teilnoten, TODO: seperate function + own endpoint + foreach($studentenData as $student) { + $g = $grades[$student->uid]['grades']; + $note_lv = $grades[$student->uid]['note_lv']; + + // overwrite any calculation with lv note once available + if(!is_null($note_lv)) { + $student->note_vorschlag = $note_lv; + } else if(count($g) > 0) { + + $notensumme = 0; + $notensumme_gewichtet = 0; + $gewichtsumme = 0; + $punktesumme = 0; + $punktesumme_gewichtet = 0; + $anzahlnoten = 0; + foreach($g as $teilnote) { + if (is_numeric($teilnote['grade']) || (is_null($teilnote['grade']) && is_numeric($teilnote['points']))) + { + $notensumme += $teilnote['grade']; + $punktesumme += $teilnote['points']; + $notensumme_gewichtet += $teilnote['grade'] * $teilnote['weight']; + $punktesumme_gewichtet += $teilnote['points'] * $teilnote['weight']; + $gewichtsumme += $teilnote['weight']; + $anzahlnoten += 1; + } + } + + // calculate grades points from notenschlüssel + if (CIS_GESAMTNOTE_PUNKTE) + { + if (defined('CIS_GESAMTNOTE_GEWICHTUNG') && CIS_GESAMTNOTE_GEWICHTUNG) + { + // Lehreinheitsgewichtung + $punkte_vorschlag = round($punktesumme_gewichtet / $gewichtsumme, 2); + $notenschluessel = new notenschluessel(); + $note_vorschlag = $notenschluessel->getNote($punkte_vorschlag, $lv_id, $sem_kurzbz); + } + else + { + $punkte_vorschlag = round($punktesumme / $anzahlnoten, 2); + $notenschluessel = new notenschluessel(); + $note_vorschlag = $notenschluessel->getNote($punkte_vorschlag, $lv_id, $sem_kurzbz); + } + } + else + { + if (defined('CIS_GESAMTNOTE_GEWICHTUNG') && CIS_GESAMTNOTE_GEWICHTUNG) + { + $note_vorschlag = round($notensumme_gewichtet / $gewichtsumme); + } + else + { + $note_vorschlag = round($notensumme / $anzahlnoten); + } + } + + $student->note_vorschlag = $note_vorschlag; + } + } // get all prüfungen with noten held in that semester in that lva $pruefungen = $this->PruefungModel->getPruefungenByLvStudiensemester($lv_id, $sem_kurzbz); - $pruefungenData = $this->getDataOrTerminateWithError($pruefungen); - + $pruefungenData = getData($pruefungen); +// $pruefungenData = $this->getDataOrTerminateWithError($pruefungen); + $this->addMeta('$pruefungenData', $pruefungenData); $this->terminateWithSuccess(array($studentenData, $pruefungenData, DOMAIN, $grades)); } public function getNoten() { $this->load->model('education/Note_model', 'NoteModel'); - $result = $this->NoteModel->getAll(); + $result = $this->NoteModel->getAllActive(); $noten = $this->getDataOrTerminateWithError($result); $this->terminateWithSuccess($noten); } @@ -101,13 +200,345 @@ class Noten extends FHCAPI_Controller public function saveStudentenNoten() { $result = $this->getPostJSON(); - if(!property_exists($result, 'password')) { + if(!property_exists($result, 'sem_kurzbz') || !property_exists($result, 'lv_id') || + !property_exists($result, 'password') || !property_exists($result, 'noten')) { + $this->terminateWithError($this->p->t('global', 'missingParameters'), 'general'); + } + + if(!$this->AuthLib->checkUserAuthByUsernamePassword(getAuthUID(), $result->password)->retval) { + $this->terminateWithError($this->p->t('global', 'wrongPassword'), 'general'); + } + + $lv_id = $result->lv_id; + $sem_kurzbz = $result->sem_kurzbz; + + $ret = []; + + // TODO: also do something similar when updating/creating a pruefung! + + foreach($result->noten as $note) { + $lvgesamtnote = new lvgesamtnote(); + if ($lvgesamtnote->load($lv_id, $note->uid, $sem_kurzbz)) + { + if ($lvgesamtnote->benotungsdatum > $lvgesamtnote->freigabedatum) + { + $lvgesamtnote->freigabedatum = date("Y-m-d H:i:s"); + $lvgesamtnote->freigabevon_uid = getAuthUID(); + if($lvgesamtnote->save()) { + $ret[] = array('uid' => $note->uid, 'freigabedatum' => $lvgesamtnote->freigabedatum, 'benotungsdatum' => $lvgesamtnote->benotungsdatum); + } + + if (defined('CIS_GESAMTNOTE_FREIGABEMAIL_NOTE') && CIS_GESAMTNOTE_FREIGABEMAIL_NOTE) + { + // TODO: infomail an studiengangsassistenz + // Enthalten sind MatrikelNr., Vorname, Nachname und Note der neuen oder geänderten Einträge. + } + + } + } + + } + + + $this->terminateWithSuccess($ret); + } + + public function getNotenvorschlagStudent() { + // TODO: Notenvorschlag laden allgemeiner Endpunkt, der im Backend mit Logik (z.B. Moodle) angepasst werden kann. + + $this->terminateWithSuccess(); + } + + public function saveStudentPruefung() { // einzelne pruefung speichern + $result = $this->getPostJSON(); + + if(!property_exists($result, 'datum') || !property_exists($result, 'lva_id') || + !property_exists($result, 'student_uid') || !property_exists($result, 'note')) { $this->terminateWithError($this->p->t('global', 'missingParameters'), 'general'); } - // TODO: send & save noten + $student_uid = $result->student_uid; + $note = $result->note; + $punkte = $result->punkte; + $datum = $result->datum; + $lva_id = $result->lva_id; + $lehreinheit_id = $result->lehreinheit_id; - $this.$this->terminateWithSuccess($this->AuthLib->checkUserAuthByUsernamePassword(getAuthUID(), $result->password)); +// $lehreinheit_id_pr = $result->lehreinheit_id_pr; // todo: very rare, refactor? + $stsem = $result->sem_kurzbz; + $typ = $result->typ; + + $jetzt = date("Y-m-d H:i:s"); + + // nachpruefungeintragen.php script calls query on campus.student_lehrveranstaltung to find a + // lehreinheit_id for lva_id -> lehreinheit should be determined prior to that in new benotungstool + // by retrieving it from students row in campus.vw_student_lehrveranstaltung earlier on + +// $lehreinheit_id = getLehreinheit($db, $lvid, $student_uid, $stsem); + $lehreinheit_id = $result->lehreinheit_id; + + $punkte = str_replace(',', '.', $punkte); + + if($punkte!='') + { + // Bei Punkteeingabe wird die Note nochmals geprueft und ggf korrigiert + $notenschluessel = new notenschluessel(); + $note_pruef = $notenschluessel->getNote($punkte, $lva_id, $stsem); + if($note_pruef!=$note) + { + $note = $note_pruef; + $note_dirty=true; + } + } + + if($note=='') + $note = 9; + + $old_note = $note; + + //Laden der Lehrveranstaltung + $lv_obj = new lehrveranstaltung(); + if(!$lv_obj->load($lva_id)) + die($lv_obj->errormsg); + + //Studiengang laden + $stg_obj = new studiengang($lv_obj->studiengang_kz); + +// $response = savePruefung($lvid, $student_uid, $stsem, $lehreinheit_id, $datum, $typ, $note, $punkte); + $savedPruefung = null; + $extraPruefung = null; + if($typ == "Termin2") { + + $pr = new Pruefung(); + + // Wenn eine Pruefung angelegt wird, wird zuerst eine Pruefung mit 1. Termin angelegt + // und dort die Zeugnisnote gespeichert + if($pr->getPruefungen($student_uid, "Termin1", $lva_id, $stsem)) + { + if ($pr->result) + { + // TODO: is this filler if branch really necessary? + $termin1 = 1; + } + else + { + $lvnote = new lvgesamtnote(); + // update Termin1 note + if ($lvnote->load($lva_id, $student_uid, $stsem)) + { + $pr_note = $lvnote->note; + $pr_punkte = $lvnote->punkte; + $benotungsdatum = $lvnote->benotungsdatum; + } + else // set Termin1 note to "noch nicht eingetragen" + { + $pr_note = 9; + $pr_punkte = ''; + $benotungsdatum = $jetzt; + } + + $pr_1 = new Pruefung(); + $pr_1->lehreinheit_id = $lehreinheit_id; + $pr_1->student_uid = $student_uid; + $pr_1->mitarbeiter_uid = getAuthUID(); + $pr_1->note = $pr_note; + $pr_1->punkte = $pr_punkte; + $pr_1->pruefungstyp_kurzbz = "Termin1"; + $pr_1->datum = $benotungsdatum; + $pr_1->anmerkung = ""; + $pr_1->insertamum = $jetzt; + $pr_1->insertvon = getAuthUID(); + $pr_1->updateamum = null; + $pr_1->updatevon = null; + $pr_1->ext_id = null; + $pr_1->new = true; + $pr_1->save(); + $extraPruefung = $pr_1; //"neu T1"; + } + + $prTermin2 = new Pruefung(); + $pr_2 = new Pruefung(); + + // Die Pruefung wird als Termin2 eingetragen + if ($prTermin2->getPruefungen($student_uid, 'Termin2', $lva_id, $stsem)) + { + if ($prTermin2->result) + { + $pr_2->load($prTermin2->result[0]->pruefung_id); + $pr_2->new = null; + $pr_2->updateamum = $jetzt; + $pr_2->updatevon = getAuthUID(); + $old_note = $pr_2->note; + $pr_2->note = $note; + $pr_2->punkte = $punkte; + $pr_2->datum = $datum; + $pr_2->anmerkung = ""; + $savedPruefung = $pr_2;//"update T2"; + } + else + { + $pr_2->lehreinheit_id = $lehreinheit_id; + $pr_2->student_uid = $student_uid; + $pr_2->mitarbeiter_uid = getAuthUID(); + $pr_2->note = $note; + $pr_2->punkte = $punkte; + $pr_2->pruefungstyp_kurzbz = $typ; + $pr_2->datum = $datum; + $pr_2->anmerkung = ""; + $pr_2->insertamum = $jetzt; + $pr_2->insertvon = getAuthUID(); + $pr_2->updateamum = null; + $pr_2->updatevon = null; + $pr_2->ext_id = null; + $pr_2->new = true; + $old_note = -1; + $savedPruefung = $pr_2;//"new T2"; + } + $pr_2->save(); + } + } + + } else if($typ == "Termin3") { + + $prTermin3 = new Pruefung(); + $pr_3 = new Pruefung(); + + if ($prTermin3->getPruefungen($student_uid, 'Termin3', $lva_id, $stsem)) + { + if ($prTermin3->result) + { + $pr_3->load($prTermin3->result[0]->pruefung_id); + $pr_3->new = null; + $pr_3->updateamum = $jetzt; + $pr_3->updatevon = getAuthUID(); + $old_note = $pr_3->note; + $pr_3->note = $note; + $pr_3->punkte = $punkte; + $pr_3->datum = $datum; + $pr_3->anmerkung = ""; + $savedPruefung = $pr_3; //"update T3"; + } + else + { + $pr_3->lehreinheit_id = $lehreinheit_id; + $pr_3->student_uid = $student_uid; + $pr_3->mitarbeiter_uid = getAuthUID(); + $pr_3->note = $note; + $pr_3->punkte = $punkte; + $pr_3->pruefungstyp_kurzbz = $typ; + $pr_3->datum = $datum; + $pr_3->anmerkung = ""; + $pr_3->insertamum = $jetzt; + $pr_3->insertvon = getAuthUID(); + $pr_3->updateamum = null; + $pr_3->updatevon = null; + $pr_3->ext_id = null; + $pr_3->new = true; + $old_note = -1; + $savedPruefung = $pr_3; //"new T3"; + } + $pr_3->save(); + } + } else { + $this->terminateWithError("typ is not termin2 or termin3", 'general'); + } + + + + //Gesamtnote updaten + $lvgesamtnote = new lvgesamtnote(); + if (!$lvgesamtnote->load($lva_id, $student_uid, $stsem)) + { + $lvgesamtnote->student_uid = $student_uid; + $lvgesamtnote->lehrveranstaltung_id = $lva_id; + $lvgesamtnote->studiensemester_kurzbz = $stsem; + $lvgesamtnote->note = $note; + $lvgesamtnote->punkte = $punkte; + $lvgesamtnote->mitarbeiter_uid = getAuthUID(); + $lvgesamtnote->benotungsdatum = $jetzt; + $lvgesamtnote->freigabedatum = null; + $lvgesamtnote->freigabevon_uid = null; + $lvgesamtnote->bemerkung = null; + $lvgesamtnote->updateamum = null; + $lvgesamtnote->updatevon = null; + $lvgesamtnote->insertamum = $jetzt; + $lvgesamtnote->insertvon = getAuthUID(); + $new = true; +// $response = "neu"; + } + else + { + $lvgesamtnote->note = $note; + $lvgesamtnote->punkte = $punkte; + $lvgesamtnote->benotungsdatum = $jetzt; + $lvgesamtnote->updateamum = $jetzt; + $lvgesamtnote->updatevon = getAuthUID(); + $new = false; +// if ($lvgesamtnote->freigabedatum) +// $response = "update_f"; +// else +// $response = "update"; + } + + $saved = $lvgesamtnote->save($new); + + $this->terminateWithSuccess(array($savedPruefung, $lvgesamtnote, $saved, $extraPruefung)); + } + + public function saveNotenvorschlag() { + $result = $this->getPostJSON(); + + if(!property_exists($result, 'lv_id') || !property_exists($result, 'sem_kurzbz') || + !property_exists($result, 'student_uid') || !property_exists($result, 'note')) { + $this->terminateWithError($this->p->t('global', 'missingParameters'), 'general'); + } + + $lv_id = $result->lv_id; + $student_uid = $result->student_uid; + $sem_kurzbz = $result->sem_kurzbz; + $note = $result->note; + + $lvgesamtnote = new lvgesamtnote(); + if (!$lvgesamtnote->load($lv_id, $student_uid, $sem_kurzbz)) + { + $lvgesamtnote->student_uid = $student_uid; + $lvgesamtnote->lehrveranstaltung_id = $lv_id; + $lvgesamtnote->studiensemester_kurzbz = $sem_kurzbz; + $lvgesamtnote->note = trim($note); + $lvgesamtnote->mitarbeiter_uid = getAuthUID(); + $lvgesamtnote->benotungsdatum = date("Y-m-d H:i:s"); + $lvgesamtnote->freigabedatum = null; + $lvgesamtnote->freigabevon_uid = null; + $lvgesamtnote->bemerkung = null; + $lvgesamtnote->updateamum = null; + $lvgesamtnote->updatevon = null; + $lvgesamtnote->insertamum = date("Y-m-d H:i:s"); + $lvgesamtnote->insertvon = getAuthUID(); + $lvgesamtnote->punkte =// TODO: deprecated? + $new = true; + $response = "neu"; + } + else + { + $lvgesamtnote->note = trim($note); + $lvgesamtnote->punkte = null; // TODO: deprecated? + $lvgesamtnote->benotungsdatum = date("Y-m-d H:i:s"); + $lvgesamtnote->updateamum = date("Y-m-d H:i:s"); + $lvgesamtnote->updatevon = getAuthUID(); + $new = false; + if ($lvgesamtnote->freigabedatum) + $response = "update_f"; + else + $response = "update"; + } + if (!$lvgesamtnote->save($new)) + $ret = $lvgesamtnote->errormsg; + else + $ret = $response; + + $lvgesamtnote->load($lv_id, $student_uid, $sem_kurzbz); + + $this->terminateWithSuccess(array($ret, $lvgesamtnote)); } } diff --git a/application/models/education/Lehreinheit_model.php b/application/models/education/Lehreinheit_model.php index d4bc7a22f..15024c023 100644 --- a/application/models/education/Lehreinheit_model.php +++ b/application/models/education/Lehreinheit_model.php @@ -303,4 +303,26 @@ EOSQL; return $this->execQuery($query, $params); } + + public function getAllLehreinheitenForLvaAndMaUid($lva_id, $ma_uid, $sem_kurzbz) + { + $query = "SELECT DISTINCT tbl_lehreinheitmitarbeiter.lehreinheit_id, tbl_lehreinheit.lehrveranstaltung_id, tbl_lehreinheit.lehrform_kurzbz, + tbl_lehreinheitmitarbeiter.mitarbeiter_uid, + tbl_lehreinheitgruppe.semester, + tbl_lehreinheitgruppe.verband, + tbl_lehreinheitgruppe.gruppe, + tbl_lehreinheitgruppe.gruppe_kurzbz, + tbl_lehrveranstaltung.kurzbz, + tbl_studiengang.kurzbzlang, + (SELECT COUNT(DISTINCT datum) FROM campus.vw_stundenplan WHERE lehreinheit_id = lehre.tbl_lehreinheit.lehreinheit_id) as termincount, + (SELECT COUNT(*) FROM campus.vw_student_lehrveranstaltung WHERE lehreinheit_id = lehre.tbl_lehreinheit.lehreinheit_id) as studentcount + FROM lehre.tbl_lehreinheit JOIN lehre.tbl_lehreinheitmitarbeiter USING(lehreinheit_id) + JOIN lehre.tbl_lehreinheitgruppe USING(lehreinheit_id) + JOIN lehre.tbl_lehrveranstaltung USING(lehrveranstaltung_id) + JOIN public.tbl_studiengang ON (tbl_lehreinheitgruppe.studiengang_kz = tbl_studiengang.studiengang_kz) + WHERE lehrveranstaltung_id = ? AND studiensemester_kurzbz = ? AND mitarbeiter_uid = ? + ORDER BY tbl_lehreinheitgruppe.gruppe_kurzbz"; + + return $this->execQuery($query, [$lva_id, $sem_kurzbz, $ma_uid]); + } } diff --git a/application/models/education/Lehrveranstaltung_model.php b/application/models/education/Lehrveranstaltung_model.php index 883c6d9ca..6fa38b36c 100644 --- a/application/models/education/Lehrveranstaltung_model.php +++ b/application/models/education/Lehrveranstaltung_model.php @@ -316,7 +316,8 @@ class Lehrveranstaltung_model extends DB_Model (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, - UPPER(tbl_studiengang.typ::varchar(1) || tbl_studiengang.kurzbz) as kuerzel, tbl_studiengang.orgform_kurzbz, vw_student_lehrveranstaltung.semester, vw_student_lehrveranstaltung.studiensemester_kurzbz, vw_student_lehrveranstaltung.bezeichnung + UPPER(tbl_studiengang.typ::varchar(1) || tbl_studiengang.kurzbz) as kuerzel, tbl_studiengang.orgform_kurzbz, vw_student_lehrveranstaltung.semester, vw_student_lehrveranstaltung.studiensemester_kurzbz, vw_student_lehrveranstaltung.bezeichnung, + campus.vw_student_lehrveranstaltung.lehreinheit_id FROM campus.vw_student_lehrveranstaltung @@ -343,7 +344,7 @@ class Lehrveranstaltung_model extends DB_Model $query .= " ORDER BY nachname, vorname, person_id, tbl_bisio.bis DESC"; - + return $this->execQuery($query, array($studiensemester_kurzbz, $lehrveranstaltung_id)); } diff --git a/application/models/education/Note_model.php b/application/models/education/Note_model.php index fe601b1f9..f5ffa2cd3 100644 --- a/application/models/education/Note_model.php +++ b/application/models/education/Note_model.php @@ -12,9 +12,10 @@ class Note_model extends DB_Model $this->pk = 'note'; } - public function getAll() { + public function getAllActive() { $qry ="SELECT * - FROM lehre.tbl_note"; + FROM lehre.tbl_note + WHERE aktiv = true"; return $this->execReadOnlyQuery($qry); } diff --git a/application/views/CisRouterView/CisRouterView.php b/application/views/CisRouterView/CisRouterView.php index 9d24d9a68..b433ab9bd 100644 --- a/application/views/CisRouterView/CisRouterView.php +++ b/application/views/CisRouterView/CisRouterView.php @@ -9,6 +9,7 @@ $includesArray = array( 'vue3' => true, 'primevue3' => true, 'customCSSs' => array( + 'vendor/vuejs/vuedatepicker_css/main.css', 'public/css/components/verticalsplit.css', 'public/css/components/searchbar/searchbar.css', 'public/css/Fhc.css', diff --git a/public/js/api/factory/lehre.js b/public/js/api/factory/lehre.js index f9306839e..05edd02ca 100644 --- a/public/js/api/factory/lehre.js +++ b/public/js/api/factory/lehre.js @@ -36,24 +36,18 @@ export default { url: `/api/frontend/v1/Lehre/Pruefungen/${lehrveranstaltung_id}` }; }, - getStudentenNoten(lv_id, sem_kurzbz) { - return { - method: 'get', - url: '/api/frontend/v1/Noten/getStudentenNoten', - params: { lv_id, sem_kurzbz } - }; - }, - getNoten(){ - return { - method: 'get', - url: '/api/frontend/v1/Noten/getNoten' - }; - }, getZugewieseneLv(uid, sem_kurzbz){ return { method: 'get', url: '/api/frontend/v1/Lehre/getZugewieseneLv', params: { uid, sem_kurzbz} }; + }, + getLeForLv(lv_id, sem_kurzbz) { + return { + method: 'get', + url: '/api/frontend/v1/Lehre/getLeForLv', + params: { lv_id, sem_kurzbz } + }; } }; \ No newline at end of file diff --git a/public/js/api/factory/noten.js b/public/js/api/factory/noten.js index e0343cb4b..d26a1a884 100644 --- a/public/js/api/factory/noten.js +++ b/public/js/api/factory/noten.js @@ -29,11 +29,25 @@ export default { url: '/api/frontend/v1/Noten/getNoten' }; }, - saveStudentenNoten(password) { + saveStudentenNoten(password, noten, lv_id, sem_kurzbz) { return { method: 'post', url: '/api/frontend/v1/Noten/saveStudentenNoten', - params: { password } + params: { password, noten, lv_id, sem_kurzbz } + }; + }, + saveNotenvorschlag(lv_id, sem_kurzbz, student_uid, note) { + return { + method: 'post', + url: '/api/frontend/v1/Noten/saveNotenvorschlag', + params: { lv_id, sem_kurzbz, student_uid, note } + }; + }, + saveStudentPruefung(student_uid, note, punkte, datum, lva_id, lehreinheit_id, sem_kurzbz, typ){ + return { + method: 'post', + url: '/api/frontend/v1/Noten/saveStudentPruefung', + params: { student_uid, note, punkte, datum, lva_id, lehreinheit_id, sem_kurzbz, typ } }; } }; \ No newline at end of file diff --git a/public/js/components/Cis/Benotungstool/Benotungstool.js b/public/js/components/Cis/Benotungstool/Benotungstool.js index 2f3dfe3be..2075082cb 100644 --- a/public/js/components/Cis/Benotungstool/Benotungstool.js +++ b/public/js/components/Cis/Benotungstool/Benotungstool.js @@ -3,6 +3,7 @@ import ApiLehre from "../../../api/factory/lehre.js"; import ApiNoten from "../../../api/factory/noten.js"; import ApiStudiensemester from "../../../api/factory/studiensemester.js"; import BsModal from '../../Bootstrap/Modal.js'; +import VueDatePicker from '../../vueDatepicker.js.php'; export const Benotungstool = { name: "Benotungstool", @@ -10,7 +11,8 @@ export const Benotungstool = { BsModal, CoreFilterCmpt, Dropdown: primevue.dropdown, - Password: primevue.password + Password: primevue.password, + Datepicker: VueDatePicker }, props: { lv_id: { @@ -32,9 +34,18 @@ export const Benotungstool = { }, data() { return { + selectedLehreinheit: null, + lehreinheiten: null, + tabulatorCanBeBuilt: false, + selectedPruefungNote: null, + selectedPruefungDate: new Date(), // v-model for pruefung edit datepicker + pruefungStudent: null, + pruefung: null, password: '', + changedNotenCounter: 0, tabulatorUuid: Vue.ref(0), domain: '', + teilnoten: null, lv: null, studenten: null, pruefungen: null, @@ -44,78 +55,311 @@ export const Benotungstool = { selectedLehrveranstaltung: null, tableBuiltResolve: null, notenOptions: null, + notenOptionsLehre: null, + notenOptionsPromise: null, tableBuiltPromise: null, - notenTableOptions: { - height: 700, - index: 'student_uid', - layout: 'fitDataStretch', - placeholder: this.$p.t('global/noDataAvailable'), - columns: [ - {title: Vue.computed(() => this.$p.t('benotungstool/c4mail')), field: 'email', formatter: this.mailFormatter, tooltip: false, widthGrow: 1}, - {title: 'UID', field: 'uid', tooltip: false, widthGrow: 1}, - {title: Vue.computed(() => this.$p.t('benotungstool/c4vorname')), field: 'vorname', tooltip: false, widthGrow: 1}, - {title: Vue.computed(() => this.$p.t('benotungstool/c4nachname')), field: 'nachname', widthGrow: 1}, - {title: Vue.computed(() => this.$p.t('benotungstool/c4teilnoten')), field: 'teilnote', widthGrow: 1}, - {title: Vue.computed(() => this.$p.t('benotungstool/c4note')), field: 'note_vorschlag', - editor: 'list', - editorParams: { - values: Vue.computed(()=>this.notenOptions.map(opt => { - return { - label: opt.bezeichnung, - value: opt.note - } - })) - }, - formatter: (cell) => { - const value = cell.getValue() - const match = this.notenOptions.find(opt => opt.note === value) - return match ? match.bezeichnung : value - }, - widthGrow: 1}, - {title: '', width: 50, hozAlign: 'center', formatter: this.arrowFormatter, cellClick: this.saveNote}, - {title: Vue.computed(() => this.$p.t('benotungstool/c4lvnote')), field: 'lv_note', - formatter: this.notenFormatter, - widthGrow: 1}, - {title: Vue.computed(() => this.$p.t('benotungstool/c4freigabe')), field: 'freigegeben', widthGrow: 1}, - {title: Vue.computed(() => this.$p.t('benotungstool/c4zeugnisnote')), field: 'note', formatter: this.notenFormatter, widthGrow: 1} - // {title: Vue.computed(() => this.$p.t('benotungstool/c4termin1')), field: 'termin1', widthGrow: 1}, - // {title: Vue.computed(() => this.$p.t('benotungstool/c4termin2')), field: 'termin2', widthGrow: 1}, - // {title: Vue.computed(() => this.$p.t('benotungstool/c4termin3')), field: 'termin3', widthGrow: 1} - ], - persistence: false, - }, + notenTableOptions: null, // built later when noten are available notenTableEventHandlers: [{ event: "tableBuilt", handler: async () => { this.tableBuiltResolve() } }, - { - event: "cellClick", - handler: async (e, cell) => { - - + { + event: "cellClick", + handler: async (e, cell) => { + + } + }, + { + event: "cellEdited", + handler: async (cell) => { + const field = cell.getField() + + if(field === 'note_vorschlag') { + const rowData = cell.getRow().getData(); + const newValue = cell.getValue(); + const original = rowData._originalNoteVorschlag; + // If nothing was selected, restore + if (newValue == null || newValue === "" || newValue === original) { + // revert value + cell.setValue(original, true); + } + + delete rowData._originalNoteVorschlag; // Clean up + + const row = cell.getRow() + row.reformat() // trigger reformat of arrow } } + } ]}; }, methods: { + getNotenTableOptions() { + return { + height: 700, + index: 'uid', + layout: 'fitDataStretch', + placeholder: this.$p.t('global/noDataAvailable'), + columns: [ + {title: Vue.computed(() => this.$p.t('benotungstool/c4mail')), field: 'email', formatter: this.mailFormatter, tooltip: false, widthGrow: 1}, + {title: 'UID', field: 'uid', tooltip: false, widthGrow: 1}, + {title: Vue.computed(() => this.$p.t('benotungstool/c4vorname')), field: 'vorname', tooltip: false, widthGrow: 1}, + {title: Vue.computed(() => this.$p.t('benotungstool/c4nachname')), field: 'nachname', widthGrow: 1}, + {title: Vue.computed(() => this.$p.t('benotungstool/c4teilnoten')), field: 'teilnote', widthGrow: 1, formatter: this.teilnotenFormatter}, + {title: Vue.computed(() => this.$p.t('benotungstool/c4note')), field: 'note_vorschlag', + editor: 'list', + editorParams: (cell) => { + // write original cell value into row to it can be retrieved if edit is cancelled without selection + const rowData = cell.getRow().getData(); + rowData._originalNoteVorschlag = cell.getValue(); + + return { + values: this.notenOptionsLehre.map(opt => ({ + label: opt.bezeichnung, + value: opt.note + })) + }; + }, + editable: (cell) => { + const rowData = cell.getRow().getData(); + const noteOption = this.notenOptions.find(opt => opt.note == rowData.note) + if(!noteOption) return true + + // also if student has any pruefungsnote disable noten selection + if(this.pruefungen.find(p => p.student_uid == rowData.uid)) return false + + return noteOption.lkt_ueberschreibbar + }, + formatter: (cell) => { + const rowData = cell.getRow().getData(); + const value = cell.getValue() + const match = this.notenOptions?.find(opt => opt.note == value) + const val = match ? match.bezeichnung : value + const p = this.pruefungen.find(p => p.student_uid == rowData.uid) + let style = '' + + if(val === undefined) return '' + if(p || !match?.lkt_ueberschreibbar) style = 'color: gray;font-style: italic; background-color: #f0f0f0;pointer-events: none;opacity: 0.6;user-select: none;cursor: not-allowed;' + return '
' + val + '
' + }, + widthGrow: 1}, + {title: '', width: 50, hozAlign: 'center', formatter: this.arrowFormatter, cellClick: this.saveNote}, + {title: Vue.computed(() => this.$p.t('benotungstool/c4lvnote')), field: 'lv_note', + formatter: this.notenFormatter, + widthGrow: 1}, + {title: Vue.computed(() => this.$p.t('benotungstool/c4freigabe')), field: 'freigegeben', widthGrow: 1, formatter: this.freigabeFormatter}, + {title: Vue.computed(() => this.$p.t('benotungstool/c4zeugnisnote')), field: 'note', formatter: this.notenFormatter, widthGrow: 1}, + {title: Vue.computed(() => this.$p.t('benotungstool/c4kommPruef')), field: 'kommPruef', widthGrow: 1, formatter: this.pruefungFormatter, hozAlign:"center", minWidth: 150} + // {title: Vue.computed(() => this.$p.t('benotungstool/c4termin1')), field: 'termin1', widthGrow: 1}, + // {title: Vue.computed(() => this.$p.t('benotungstool/c4termin2')), field: 'termin2', widthGrow: 1}, + // {title: Vue.computed(() => this.$p.t('benotungstool/c4termin3')), field: 'termin3', widthGrow: 1} + ], + persistence: false, + } + }, + parseDate(timestamp) { + if(!timestamp) return null + const [datePart, timePart] = timestamp.split(" "); + const [year, month, day] = datePart.split("-").map(Number); + const [hour, minute, second] = timePart.split(":").map(Number); + return new Date(year, month - 1, day, hour, minute, second); + }, + checkFreigabe(freigabedatum, benotungsdatum, uid) { + if(!freigabedatum) { + // check for change -> set freigabe to 'changed' on change + return 'offen' + } else if(benotungsdatum > freigabedatum) { + return 'changed' + } else { + return 'ok' + } + }, notenFormatter(cell) { const value = cell.getValue() - const match = this.notenOptions.find(opt => opt.note === value) - return match ? match.bezeichnung : value + const field = cell.getField() + let style = 'display: flex; justify-content: center; align-items: center; height: 100%;'; + // Wenn sich die Zeugnisnote von der von Ihnen freigegebenen Note unterscheidet, + // wird erstere rot umrandet markiert. + + + const data = cell.getData() + if(field == 'note' && data.note && data.note != data.lv_note) { + style += 'color:red; border-color:red; border-style:solid; border-width:1px;' + } + + const match = this.notenOptions.find(opt => opt.note == value) + const val = match ? match.bezeichnung : value + if(val) return '
' + val + '
' + else return '' + + }, + freigabeFormatter(cell) { + const value = cell.getValue() + + if(value === 'ok') { + return '
' + + '
' + } else if (value === 'offen') { + return '
' + + '
' + } else if (value === 'changed') { + return '
' + + '
' + } + + return value }, handlePasswordChanged(pw) { - console.log('pw:', pw) + // console.log('pw:', pw) }, - saveNote(e, cell) { + saveNote(e, cell) { // Notenvorschlag freigeben const row = cell.getRow() const data = row.getData() - row.update({ note: data.note_vorschlag }) + if(!data.note_vorschlag) return + this.$api.call(ApiNoten.saveNotenvorschlag(this.lv_id, this.sem_kurzbz, data.uid, data.note_vorschlag)) + .then((res) => { + if (res.meta.status === 'success') { + const s = this.studenten.find(s => s.uid === data.uid) + this.teilnoten[s.uid].note_lv = data.note_vorschlag + s.freigabedatum = this.parseDate(res.data[1]['freigabedatum']) + s.benotungsdatum = this.parseDate(res.data[1]['benotungsdatum']) + + s.freigegeben = this.checkFreigabe(s.freigabedatum, s.benotungsdatum, s.uid); + + row.update({ lv_note: data.note_vorschlag }) + row.update({ freigegeben: 'changed' }) + row.reformat() // trigger reformat of arrow + this.changedNotenCounter++; + } + }) + + + }, + teilnotenFormatter(cell) { + const val = cell.getValue() + return '
'+val+'
' + }, + pruefungFormatter(cell) { + const data = cell.getData() + + const noteDef = data.note ? this.notenOptions.find(n => n.note == data.note) : null + if(!data.note || !noteDef?.lkt_ueberschreibbar) { + return '' + } + + // TODO: check for some time limit maybe? old pruefungen can be changed/created + + // Create root row div + const rowDiv = document.createElement('div'); + rowDiv.className = 'row'; + rowDiv.style.display = 'flex'; + rowDiv.style.justifyContent = 'center'; + rowDiv.style.alignItems = 'center'; + rowDiv.style.height = '100%'; + + function createCol(content) { + const colDiv = document.createElement('div'); + colDiv.className = 'col-4'; + colDiv.style.justifyContent = 'center'; + colDiv.style.alignItems = 'center'; + colDiv.style.height = '100%'; + + if (typeof content === 'string') { + colDiv.textContent = content; + } else if (content instanceof HTMLElement) { + colDiv.appendChild(content); + } + + return colDiv; + } + + const field = cell.getColumn().getField() + if(data[field]) { + const dateParts = data[field].datum.split('-') + const date = `${dateParts[2]}.${dateParts[1]}.${dateParts[0]}` + + // First column (date) + rowDiv.appendChild(createCol(date)); + + // Second column (note_bezeichnung) + rowDiv.appendChild(createCol(data[field].note_bezeichnung || '')); + + if(field === 'kommPruef' || field === 'pruefungNr0') {// no actions on kommPruef allowed + rowDiv.appendChild(createCol('')); // append empty col4 to have formatting similar + return rowDiv + } + + // Third column (button) + const button = document.createElement('button'); + button.className = 'btn btn-outline-secondary'; + button.textContent = 'Change'; // TODO: phrase + button.addEventListener('click', () => { + this.openPruefungModal(data, data[field]); + }); + + rowDiv.appendChild(createCol(button)); + + return rowDiv; + + } else if (field !== 'kommPruef' && field !== 'pruefungNr0') { // return new btn action + const button = document.createElement('button'); + button.className = 'btn btn-outline-secondary'; + button.textContent = 'Add'; // TODO: phrase + button.addEventListener('click', () => { + this.openPruefungModal(data) + }); + + rowDiv.appendChild(createCol(button)); + + return rowDiv; + } else return '' + }, + openPruefungModal(student, pruefung = null) { + this.pruefungStudent = student + this.pruefung = pruefung + debugger + if(this.pruefung?.datum) { + const pruefungDateParts = this.pruefung?.datum?.split('-') + + // new date obj so datepicker picks ob the change by ref + const newDate = new Date() + newDate.setFullYear(pruefungDateParts[0]) + newDate.setMonth(pruefungDateParts[1]) + newDate.setDate(pruefungDateParts[2]) + this.selectedPruefungDate = newDate + } else { + const newDate = new Date() + newDate.setTime(Date.now()) + this.selectedPruefungDate = newDate + } + + if(this.pruefung?.note) { + this.selectedPruefungNote = this.notenOptions.find(n => n.note == this.pruefung.note) + } else { + this.selectedPruefungNote = null + } + + this.$refs.modalContainerPruefung.show() + }, + pruefungTitleFormatter(cell) { + const col = cell.getColumn() + if(col.getField() === "pruefungNr0") return this.$p.t('benotungstool/c4originalZnote') + return col.getDefinition().title; }, arrowFormatter(cell) { + const row = cell.getRow() + const data = row.getData() + + if(!data.note_vorschlag || (data.note_vorschlag == data.lv_note)) { // uncolored arrow + return '
' + + '
' + } + + // can save a notenvorschlag -> colored return '
' + '
' }, @@ -130,41 +374,103 @@ export const Benotungstool = { tableResolve(resolve) { this.tableBuiltResolve = resolve }, + notenOptionsResolve(resolve) { + this.notenOptionsResolve = resolve + }, setupData(data){ - this.studenten = data[0] - this.pruefungen = data[1] + this.studenten = data[0] ?? [] + this.studenten.forEach(s => s.pruefungen = []) + this.pruefungen = data[1] ?? [] this.domain = data[2] - this.teilnoten = data[3] - this.pruefungen.forEach(p => { + // contains notenvorschläge from moodle, lv_note + this.teilnoten = data[3] ?? [] + + // let pruefungenRegularColCount = 0; + const distinctPruefungsDates = [] + const cols = [...this.notenTableOptions.columns.slice(0, -1)]; + const kommCol = this.notenTableOptions.columns[this.notenTableOptions.columns.length - 1]; + + this.pruefungen?.forEach(p => { const student = this.studenten.find(s => s.uid === p.student_uid) if(!student) return - // TODO: fetch typen and remove hardcoded strings - // TODO: dynamic amount of termin columns! - // if(p.pruefungstyp_kurzbz == "Termin1") { - // student.termin1 = p - // } else if (p.pruefungstyp_kurzbz == "Termin2") { - // student.termin2 = p - // } else if (p.pruefungstyp_kurzbz == "Termin3") { - // student.termin3 = p - // } - // TODO: LE TEILNOTEN IN BACKEND LADEN, BERECHNEN UND VORSCHLAG PASTEN - // if(p.negativ) + if(!distinctPruefungsDates.includes(p.datum)) distinctPruefungsDates.push(p.datum) + + // seperate kommPruefungen from previous pruefungen counts since the column count variability always ends with this + if(p.pruefungstyp_kurzbz == 'kommPruef') { + student['kommPruef'] = p + } else { + student.pruefungen.push(p) + } + + // if(student.pruefungen.length > pruefungenRegularColCount) pruefungenRegularColCount = student.pruefungen.length }) - - this.studenten.forEach(s => { - s.email = this.buildMailToLink(s) + // TODO: check if note is überschreibbar! + this.studenten?.forEach(s => { + // sort students regular pruefungen by datum + s.pruefungen.sort((p1, p2) => { + if(p1.datum > p2.datum) { + return 1 + } else if (p1.datum < p2.datum) { + return -1 + } else { + return 0 + } + }) + // set the sorted pruefungen to their respective column fields + s.pruefungen.forEach((p, i) => { + s[p.datum] = p + }) + + s.email = this.buildMailToLink(s) + s.lv_note = this.teilnoten[s.uid].note_lv + s.freigabedatum = this.parseDate(this.teilnoten[s.uid]['freigabedatum']) + s.benotungsdatum = this.parseDate(this.teilnoten[s.uid]['benotungsdatum']) + s.freigegeben = this.checkFreigabe(s.freigabedatum, s.benotungsdatum, s.uid); + const grades = this.teilnoten[s.uid].grades s.teilnote = '' - grades.forEach(g => s.teilnote += g.text + ' ') + + grades.forEach(g => { + const notenOption = this.notenOptions.find(n=>n.note == g.grade) + if(notenOption.positiv) s.teilnote += (''+g.text +''+ '
') + else s.teilnote += (''+g.text +''+ '
') + }) + }) + + distinctPruefungsDates.sort((d1, d2) => { + if(d1 > d2) { + return 1 + } else if (d1 < d2) { + return -1 + } else { + return 0 + } + }) + distinctPruefungsDates.forEach((date, index)=>{ + // TODO date format dd.mm.yyyy + cols.push({ + title: date,//this.$p.t('benotungstool/pruefungNr', [index+1]), + field: date, + formatter: this.pruefungFormatter, + titleFormatter: this.pruefungTitleFormatter, + hozAlign:"center", + widthGrow: 1, + minWidth: 150 + }) + }) + + + cols.push(kommCol) // keep kommPruef Col as last this.$refs.notenTable.tabulator.clearSort() - this.$refs.notenTable.tabulator.setColumns(this.notenTableOptions.columns) + this.$refs.notenTable.tabulator.setColumns(cols) this.$refs.notenTable.tabulator.setData(this.studenten); + this.$refs.notenTable.tabulator.redraw(true); }, loadNoten(lv_id, sem_kurzbz) { this.$api.call(ApiNoten.getStudentenNoten(lv_id, sem_kurzbz)) @@ -184,10 +490,9 @@ export const Benotungstool = { this.notenTableOptions.height = window.visualViewport.height - rect.top this.$refs.notenTable.tabulator.setHeight(this.notenTableOptions.height) }, - async setupCreated() { + setupCreated() { // fetch lva dropdown this.$api.call(ApiLehre.getZugewieseneLv(this.viewData?.uid, this.sem_kurzbz)).then(res => { - console.log(res) this.lehrveranstaltungen = res.data // build dropdown option string @@ -198,24 +503,66 @@ export const Benotungstool = { this.selectedLehrveranstaltung = this.lehrveranstaltungen.find(lva => lva.lehrveranstaltung_id == this.lv_id) }) - //fetch sem_kurzbz dropdown + this.$api.call(ApiLehre.getLeForLv(this.lv_id, this.sem_kurzbz)).then(res => { + + const data = [] + // TODO: could be done on server in some shared function, copied from anw extension for now + res.data?.retval?.forEach(entry => { + + const existing = data.find(e => e.lehreinheit_id === entry.lehreinheit_id) + if (existing) { + // supplement info + existing.infoString += ', ' + if (entry.gruppe_kurzbz !== null) { + existing.infoString += entry.gruppe_kurzbz + } else { + existing.infoString += entry.kurzbzlang + '-' + entry.semester + + (entry.verband ? entry.verband : '') + + (entry.gruppe ? entry.gruppe : '') + } + } else { + // entries are supposed to be fetched ordered by non null gruppe_kurzbz first + // so a new entry will always start with those groups, others are appended afterwards + entry.infoString = entry.kurzbz + ' - ' + entry.lehrform_kurzbz + ' - ' + if (entry.gruppe_kurzbz !== null) { + entry.infoString += entry.gruppe_kurzbz + } else { + entry.infoString += entry.kurzbzlang + '-' + entry.semester + + (entry.verband ? entry.verband : '') + + (entry.gruppe ? entry.gruppe : '') + } + + data.push(entry) + } + }) + + data.forEach(entry => { + entry.infoString += ' | 👥' + entry.studentcount + ' | 📅' + entry.termincount + }) + + this.lehreinheiten = [...data] + + }) + + // fetch sem_kurzbz dropdown this.$api.call(ApiStudiensemester.getStudiensemester()).then(res => { this.studiensemester = res.data[0] this.selectedSemester = this.studiensemester.find(sem => sem.studiensemester_kurzbz === this.sem_kurzbz) }) // fetch noten dropdown - await this.$api.call(ApiNoten.getNoten()).then(res => { + this.$api.call(ApiNoten.getNoten()).then(res => { this.notenOptions = res.data + this.notenOptionsLehre = res.data.filter(n => n.lehre === true) + this.notenTableOptions = this.getNotenTableOptions() + this.tabulatorCanBeBuilt = true // because promises would be more work and not much better here }) }, async setupMounted() { this.tableBuiltPromise = new Promise(this.tableResolve) await this.tableBuiltPromise - this.loadNoten(this.lv_id, this.sem_kurzbz) - this.calcMaxTableHeight() }, @@ -241,8 +588,20 @@ export const Benotungstool = { } }) - // reload data - this.loadNoten(this.lv_id, e.value.studiensemester_kurzbz) + // diff lv_id -> reload zugewiesene lv + this.$api.call(ApiLehre.getZugewieseneLv(this.viewData?.uid, this.sem_kurzbz)).then(res => { + this.lehrveranstaltungen = res.data + + // build dropdown option string + this.lehrveranstaltungen.forEach(lva => { + lva.fullString = `${lva.stg_kurzbz} - ${lva.lv_semester}: ${lva.lv_bezeichnung}` + }) + + this.selectedLehrveranstaltung = this.lehrveranstaltungen.find(lva => lva.lehrveranstaltung_id == this.lv_id) + }).then(()=>{ + // reload data + this.loadNoten(this.lv_id, e.value.studiensemester_kurzbz) + }) }, getOptionLabel(option) { @@ -251,23 +610,146 @@ export const Benotungstool = { getOptionLabelLv(option) { return option.fullString }, - saveNoteneingabe() { + getOptionLabelLe(option) { + return option.infoString + }, + savePruefungEingabe() { + const year = this.selectedPruefungDate.getFullYear(); + const month = String(this.selectedPruefungDate.getMonth() + 1).padStart(2, '0'); // Months are 0-based + const day = String(this.selectedPruefungDate.getDate()).padStart(2, '0'); + const dateStr = `${year}-${month}-${day}`; - this.$api.call(ApiNoten.saveStudentenNoten(this.password)) + // TODO: test this hypothesis + // first pruefung is always "Termin2" since normal note counts as Termin1 + const pOffset = this.pruefung === null && this.pruefungStudent.pruefungen.length === 0 ? 2 : 1 + const typ = this.pruefung ? this.pruefung.pruefungstyp_kurzbz : ('Termin'+(this.pruefungStudent.pruefungen.length + pOffset)) + + this.$api.call(ApiNoten.saveStudentPruefung( + this.pruefungStudent.uid, + this.selectedPruefungNote.note, + this.pruefung?.punkte ?? '', + dateStr, + this.lv_id, + this.pruefungStudent.lehreinheit_id, + this.sem_kurzbz, + typ + )).then(res => { + if(res.meta.status === 'success') { + const s = this.studenten.find(s => s.uid === res.data[1]?.student_uid) + console.log('old student freigabedatum', s.freigabedatum) + console.log('old student benotungsdatum', s.benotungsdatum) + + s.freigabedatum = this.parseDate(res.data[1]?.['freigabedatum']) + s.benotungsdatum = this.parseDate(res.data[1]?.['benotungsdatum']) + + console.log('new student freigabedatum', s.freigabedatum) + console.log('new student benotungsdatum', s.benotungsdatum) + s.freigegeben = this.checkFreigabe(s.freigabedatum, s.benotungsdatum, s.uid); + + // todo: update student subject grade/lv_note + s.lv_note = res.data[1]?.note + + // find the exact student pruefung and update that + + // add new pruefung to row + if(res.data[0]?.new) { + // TODO: if new pruefung was of typ "Termin2", "Termin1" shadow pruefung is not available + this.handleAddNewTermin(res.data, s) + } else { // update existing + const oldIndex = s.pruefungen.findIndex(p => p.pruefung_id == res.data[0]?.pruefung_id) + if(oldIndex !== -1) { + s.pruefungen.splice(oldIndex, 1, res.data[0]) + s['pruefungNr'+oldIndex] = res.data[0] + } + } + + // // TODO: manual row update! + // const row = this.$refs.notenTable.tabulator.getRow(s.uid) + // row.update() + + this.$refs.notenTable.tabulator.redraw(true) + + this.$fhcAlert.alertSuccess('Prüfung gespeichert') // TODO: phrase + } + }) + + this.$refs.modalContainerPruefung.hide() + }, + handleAddNewTermin(data, student){ + + }, + saveNoteneingabe() { + this.$api.call(ApiNoten.saveStudentenNoten(this.password, this.changedNoten, this.lv_id, this.sem_kurzbz)) + .then((res) => { + if(res.meta.status === 'success') { + this.$fhcAlert.alertSuccess('Noten gespeichert') + } + + res.data.forEach(d => { + const s = this.studenten.find(s => s.uid === d.uid) + s.freigabedatum = this.parseDate(d.freigabedatum) + s.benotungsdatum = this.parseDate(d.benotungsdatum) + s.freigegeben = this.checkFreigabe(s.freigabedatum, s.benotungsdatum, s.uid); + }) + this.changedNotenCounter++; + + this.$refs.notenTable.tabulator.redraw(true) + }) this.$refs.modalContainerNotenSpeichern.hide() }, openSaveModal() { this.$refs.modalContainerNotenSpeichern.show() + }, + handleChangePruefungDatum(e) { + // console.log('handleChangePruefungDatum', e) + }, + handleChangePruefungNote(e) { + // console.log(e) + }, + getOptionLabelNotePruefung(option) { + return option.bezeichnung + }, + leChanged(e) { + this.selectedLehreinheit = e.value } }, watch: { - + selectedLehreinheit(newVal) { + if(!this.$refs.notenTable) return + this.$refs.notenTable.tabulator.clearFilter(); + if(newVal) this.$refs.notenTable.tabulator.setFilter("lehreinheit_id", "=", newVal.lehreinheit_id); + }, + getKommPruefCount(newVal) { + if(this.$refs.notenTable?.tabulator && newVal > 0) { + const kommPruefCol = this.$refs.notenTable?.tabulator.getColumn("kommPruef") + kommPruefCol.show() + } else if(this.$refs.notenTable?.tabulator && newVal == 0) { + const kommPruefCol = this.$refs.notenTable?.tabulator.getColumn("kommPruef") + kommPruefCol.hide() + } + } }, computed: { + getKommPruefCount(){ + let counter = 0 + this.studenten?.forEach(s => {if(s['kommPruef']){counter++}}) + return counter + }, getSaveBtnClass() { return "btn btn-primary ml-2" // return !this.changedData.length ? "btn btn-secondary ml-2" : "btn btn-primary ml-2" + }, + changedNoten() { + const v = this.changedNotenCounter // hack to trigger computed + const cs = this.studenten.reduce((acc, cur) => { + const teilnote = this.teilnoten[cur.uid] + if(teilnote.note_lv && (cur.benotungsdatum > cur.freigabedatum)) { + acc.push(cur) + } + return acc + }, []) + return cs } }, created() { @@ -287,12 +769,47 @@ export const Benotungstool = { + + + + + +
-
+

{{$p.t('benotungstool/benotungstoolTitle')}}

{{ lv?.bezeichnung }}

@@ -306,6 +823,18 @@ export const Benotungstool = {
+ +
+
+ + + +
+
+
diff --git a/system/phrasesupdate.php b/system/phrasesupdate.php index 367df502f..8f94090db 100644 --- a/system/phrasesupdate.php +++ b/system/phrasesupdate.php @@ -42159,7 +42159,7 @@ and represent the current state of research on the topic. The prescribed citatio array( 'app' => 'core', 'category' => 'benotungstool', - 'phrase' => 'c4termin3', + 'phrase' => 'c4kommPruef', 'insertvon' => 'system', 'phrases' => array( array( @@ -42295,7 +42295,147 @@ and represent the current state of research on the topic. The prescribed citatio 'insertvon' => 'system' ) ) - ) + ), + array( + 'app' => 'core', + 'category' => 'benotungstool', + 'phrase' => 'pruefungNr', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Prüfung Nr. {0}', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Examination No. {0}', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'benotungstool', + 'phrase' => 'noteneingabeSpeichern', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Noteneingabe speichern', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Save note entry', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'benotungstool', + 'phrase' => 'noteneingabeSpeichern', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Noteneingabe speichern', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Save note entry', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'benotungstool', + 'phrase' => 'noteneingabeBestätigen', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Noten bestätigen', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Confirm grades', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'benotungstool', + 'phrase' => 'createPruefungFor', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Prüfung erstellen für', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Create examination for', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'benotungstool', + 'phrase' => 'approveGrades', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Noten freigeben', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Approve Grades', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'benotungstool', + 'phrase' => 'c4originalZnote', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Ursprüngliche Zeugnisnote', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Original Transcript Grade', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), // CIS4 GESAMTNOTENEINGABE ENDE ------------------------------------------------------------------------------------