teilnoten/punkte berechnen für notenvorschläge; vorschläge übernehmen; noten freigabe mit passwort; prüfungen generisch anzeigen nach datum gruppiert; prüfungen anlegen/bearbeiten auf mapping termin1/2/3 möglich.

This commit is contained in:
Johann Hoffmann
2025-07-24 17:02:57 +02:00
parent fe7feeb74e
commit 6a3982347b
11 changed files with 1270 additions and 118 deletions
@@ -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']);
@@ -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));
}
+443 -12
View File
@@ -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));
}
}
@@ -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]);
}
}
@@ -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));
}
+3 -2
View File
@@ -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);
}
@@ -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',
+7 -13
View File
@@ -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 }
};
}
};
+16 -2
View File
@@ -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 }
};
}
};
@@ -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 '<div style="'+style+'">' + val + '</div>'
},
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 '<div style="'+style+'">' + val + '</div>'
else return ''
},
freigabeFormatter(cell) {
const value = cell.getValue()
if(value === 'ok') {
return '<div style="display: flex; justify-content: center; align-items: center; height: 100%">' +
'<i class="fa fa-circle-check" style="color:green"></i></div>'
} else if (value === 'offen') {
return '<div style="display: flex; justify-content: center; align-items: center; height: 100%">' +
'<i class="fa-regular fa-circle"></i></div>'
} else if (value === 'changed') {
return '<div style="display: flex; justify-content: center; align-items: center; height: 100%">' +
'<i class="fa fa-circle-check"></i></div>'
}
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 '<div style="white-space: pre-line;">'+val+'</div>'
},
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 '<div style="display: flex; justify-content: center; align-items: center; height: 100%">' +
'<i class="fa fa-arrow-right"></i></div>'
}
// can save a notenvorschlag -> colored
return '<div style="display: flex; justify-content: center; align-items: center; height: 100%">' +
'<i class="fa fa-arrow-right" style="color:#00649C"></i></div>'
},
@@ -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 += ('<span>'+g.text +'</span>'+ '<br/>')
else s.teilnote += ('<span style="color: red;">'+g.text +'</span>'+ '<br/>')
})
})
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 = {
</div>
</template>
<template v-slot:footer>
<button type="button" class="btn btn-primary" @click="saveNoteneingabe">{{ $p.t('global/noteneingabeBestätigen') }}</button>
<button type="button" class="btn btn-primary" @click="saveNoteneingabe">{{ $p.t('benotungstool/noteneingabeBestätigen') }}</button>
</template>
</bs-modal>
<bs-modal ref="modalContainerPruefung" class="bootstrap-prompt" dialogClass="modal-lg">
<template v-slot:title>{{ $p.t('benotungstool/createPruefungFor') }} {{pruefungStudent?.vorname}} {{pruefungStudent?.nachname}}</template>
<template v-slot:default>
<div class="row justify-content-center">
<div class="col-1 text-center">{{$p.t('benotungstool/c4date')}}:</div>
<div class="col-6">
<datepicker
v-model="selectedPruefungDate"
@update:model-value="handleChangePruefungDatum"
:clearable="false"
:time-picker="false"
:text-input="true"
:auto-apply="true">
</datepicker>
</div>
</div>
<div class="row justify-content-center mt-4">
<div class="col-1 text-center">{{$p.t('lehre/note')}}:</div>
<div class="col-6">
<Dropdown @change="handleChangePruefungNote" :placeholder="$p.t('lehre/note')"
:style="{'width': '100%'}" :optionLabel="getOptionLabelNotePruefung"
v-model="selectedPruefungNote" :options="notenOptionsLehre" showClear>
<template #optionsgroup="slotProps">
<div> {{ option.bezeichnung }} </div>
</template>
</Dropdown>
</div>
</div>
</template>
<template v-slot:footer>
<button type="button" class="btn btn-primary" @click="savePruefungEingabe">{{ $p.t('global/speichern') }}</button>
</template>
</bs-modal>
<div class="row">
<div class="col-8">
<div class="col-4">
<h2>{{$p.t('benotungstool/benotungstoolTitle')}}</h2>
<h4>{{ lv?.bezeichnung }}</h4>
</div>
@@ -306,6 +823,18 @@ export const Benotungstool = {
</Dropdown>
</div>
</div>
<div class="col-2">
<div class="col-lg-auto">
<Dropdown @change="leChanged" :style="{'width': '100%'}" :optionLabel="getOptionLabelLe"
v-model="selectedLehreinheit" :options="lehreinheiten" showClear>
<template #optionsgroup="slotProps">
<div> {{ option.infoString }} </div>
</template>
</Dropdown>
</div>
</div>
<div class="col-2">
<div class="col-lg-auto">
<Dropdown @change="ssChanged" :style="{'width': '100%'}" :optionLabel="getOptionLabel"
@@ -321,6 +850,7 @@ export const Benotungstool = {
<core-filter-cmpt
v-if="tabulatorCanBeBuilt"
@uuidDefined="handleUuidDefined"
:title="''"
ref="notenTable"
@@ -331,7 +861,7 @@ export const Benotungstool = {
>
<template #actions>
<button @click="openSaveModal" role="button" :class="getSaveBtnClass">
<i class="fa fa-save"></i>
{{$p.t('benotungstool/approveGrades')}} <i class="fa fa-save"></i>
</button>
</template>
</core-filter-cmpt>
+142 -2
View File
@@ -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 ------------------------------------------------------------------------------------