Merge branch 'feature-76657/AbgabetoolFinetuning' into cis40_2026-02_rc_abgabetool_finetuning

This commit is contained in:
Harald Bamberger
2026-06-18 14:46:05 +02:00
17 changed files with 1209 additions and 318 deletions
+1 -1
View File
@@ -29,7 +29,7 @@ $config['ALLOWED_NOTEN_ABGABETOOL'] = [10, 14]; // tbl_note pk
// benotete projektarbeiten sperren weitere terminanlage & bearbeitung, diese noten sind ausnahmen dieser Regel
// wie zB "Nicht beurteilt" & "Noch nicht eingetragen"
$config['NONFINAL_NOTEN_ABGABETOOL'] = [9];
$config['beurteilung_link_fallback'] = 'addons/fhtw/content/projektbeurteilung/projektbeurteilungDocumentExport.php?projektarbeit_id=?&betreuerart_kurzbz=?&person_id=?';
$config['beurteilung_link_fallback'] = 'cis/private/lehre/projektbeurteilungDocumentExport.php?projektarbeit_id=?&betreuerart_kurzbz=?&person_id=?';
$config['PROJEKTARBEITSBEURTEILUNG_MAIL_BASELINK_ERSTBEGUTACHTER'] = 'index.ci.php/extensions/FHC-Core-Projektarbeitsbeurteilung/ProjektarbeitsbeurteilungErstbegutachter';
$config['PROJEKTARBEITSBEURTEILUNG_MAIL_BASELINK_ZWEITBEGUTACHTER'] = 'index.ci.php/extensions/FHC-Core-Projektarbeitsbeurteilung/ProjektarbeitsbeurteilungErstbegutachter';
@@ -49,7 +49,9 @@ class Abgabe extends FHCAPI_Controller
'getStudiengaenge' => array('basis/abgabe_assistenz:rw'),
'getStudentProjektarbeitAbgabeFile' => array('basis/abgabe_student:rw', 'basis/abgabe_lektor:rw', 'basis/abgabe_assistenz:rw'),
'postStudentProjektarbeitZusatzdaten' => array('basis/abgabe_lektor:rw', 'basis/abgabe_assistenz:rw'),
'getSignaturStatusForProjektarbeitAbgaben' => array('basis/abgabe_lektor:rw', 'basis/abgabe_assistenz:rw')
'getSignaturStatusForProjektarbeitAbgaben' => array('basis/abgabe_lektor:rw', 'basis/abgabe_assistenz:rw'),
'sendZweitbetreuerTokenMail' => array('basis/abgabe_lektor:rw', 'basis/abgabe_assistenz:rw'),
'fetchProjektarbeitenHistory' => array('basis/abgabe_assistenz:rw')
]);
$this->load->library('PhrasesLib');
@@ -422,9 +424,16 @@ class Abgabe extends FHCAPI_Controller
$this->checkAbgabeSignatur($paabgabe, $projektarbeit->student_uid);
$signaturstatus = $paabgabe->signatur;
// update projektarbeit cols
$this->ProjektarbeitModel->updateProjektarbeit($projektarbeit_id, $sprache, $abstract, $abstract_en
, $schlagwoerter, $schlagwoerter_en, $seitenanzahl);
// update projektarbeit cols with zusatzdaten AND abgabedatum!
$this->ProjektarbeitModel->update($projektarbeit->projektarbeit_id, array(
'sprache' => $sprache,
'seitenanzahl' => $seitenanzahl,
'abgabedatum' => date('Y-m-d'),
'schlagwoerter_en' => $schlagwoerter_en,
'schlagwoerter' => $schlagwoerter,
'abstract' => $abstract,
'abstract_en' => $abstract_en
));
// update paabgabe datum
@@ -727,7 +736,7 @@ class Abgabe extends FHCAPI_Controller
$showAllBool = false;
}
$projektarbeiten = $this->ProjektarbeitModel->getMitarbeiterProjektarbeiten(getAuthUID(), $showAllBool);
$projektarbeiten = $this->ProjektarbeitModel->getMitarbeiterProjektarbeiten(getAuthPersonId(), $showAllBool);
$mapFunc = function($projektarbeit) {
@@ -742,18 +751,15 @@ class Abgabe extends FHCAPI_Controller
forEach($projektarbeiten->retval as $pa) {
$result = $this->ProjektarbeitModel->getProjektbetreuerAnrede($pa->betreuer_person_id);
$anredeArr = $this->getDataOrTerminateWithError($result, 'general');
$pa->betreuer = $anredeArr[0];
$oldLink = ''; // show this when paIsCurrent == false -> moodle course template
$newLink = ''; // get curated path for betreuer type
$returnFunc = function ( $resultOld, $resultNew) use (&$oldLink, &$newLink) {
$returnFunc = function ($resultOld, $resultNew) use (&$oldLink, &$newLink) {
$newLink = $resultNew;
$oldLink = $resultOld;
};
Events::trigger('projektbeurteilung_formular_link', $pa->betreuerart_kurzbz, APP_ROOT, $pa->projektarbeit_id, $pa->student_uid, $returnFunc);
$own_betreuerart_kurzbz = $pa->betreuer_person_id == getAuthPersonId() ? $pa->betreuer_betreuerart_kurzbz : $pa->zweitbetreuer_betreuerart_kurzbz;
Events::trigger('projektbeurteilung_formular_link', $own_betreuerart_kurzbz, APP_ROOT, $pa->projektarbeit_id, $pa->student_uid, $returnFunc);
$pa->beurteilungLinkNew = $newLink;
$pa->beurteilungLinkOld = $oldLink;
@@ -841,12 +847,22 @@ class Abgabe extends FHCAPI_Controller
// load existing entry of paabgabe and check if note has changed to negativ, to avoid sending when
// only notiz has changed.
// TODO: what if paabgabe is a qualgate1, is benotet negativ and then its type is changed to gate2?
$existingResult = $this->PaabgabeModel->load($paabgabe_id);
$existingPaabgabeArr = getData($existingResult);
if(count($existingPaabgabeArr) > 0) $existingPaabgabe = $existingPaabgabeArr[0];
if($existingPaabgabe->note !== null || $existingPaabgabe->abgabedatum !== null) {
// check if a change of paabgabetyp is being attempted -> not allowed at this point
if($paabgabetyp_kurzbz !== $existingPaabgabe->paabgabetyp_kurzbz) {
$this->terminateWithError($this->p->t('abgabetool', 'c4abgabetypAendernNichtErlaubt'));
}
// check if a change of deadline aka datum is being attempted -> notallowed at this point
if($datum !== $existingPaabgabe->datum) {
$this->terminateWithError($this->p->t('abgabetool', 'c4datumAendernNichtErlaubt'));
}
}
$result = $this->PaabgabeModel->update(
$paabgabe_id,
array(
@@ -1207,10 +1223,10 @@ class Abgabe extends FHCAPI_Controller
private function getProjektbetreuerEmailByProjektarbeitID($projektarbeit_id) {
$this->load->model('education/Projektarbeit_model', 'ProjektarbeitModel');
$result = $this->ProjektarbeitModel->getProjektbetreuerEmail($projektarbeit_id);
if(count($result->retval) > 0) {
if(hasData($result)) {
$email = getData($result);
return $email[0]->uid ? $email[0]->uid.'@'.DOMAIN : $email[0]->private_email;
} else return '';
} else return null;
}
@@ -1221,9 +1237,12 @@ class Abgabe extends FHCAPI_Controller
private function getProjektbetreuerEmailByPersonID($person_id) {
$this->load->model('education/Projektarbeit_model', 'ProjektarbeitModel');
$result = $this->ProjektarbeitModel->getProjektbetreuerEmailByPersonID($person_id);
$email = $this->getDataOrTerminateWithError($result, 'general');
return $email[0]->uid ? $email[0]->uid.'@'.DOMAIN : $email[0]->private_email;
if(hasData($result)) {
$email = getData($result);
return $email[0]->uid ? $email[0]->uid.'@'.DOMAIN : $email[0]->private_email;
} else {
return null;
}
}
//TODO: SWITCH TO NOTEN API ONCE NOTENTOOL IS IN MASTER TO AVOID DUPLICATE API
@@ -1466,9 +1485,15 @@ class Abgabe extends FHCAPI_Controller
$this->terminateWithError($this->p->t('abgabetool', 'c4noZuordnungBetreuerStudent'), 'general');
}
// update projektarbeit cols
$this->ProjektarbeitModel->updateProjektarbeit($projektarbeit_id,$sprache,$abstract,$abstract_en
,$schlagwoerter, $schlagwoerter_en, $seitenanzahl);
// update projektarbeit cols with zusatzdaten only
$this->ProjektarbeitModel->update($projektarbeit_id, array(
'sprache' => $sprache,
'seitenanzahl' => $seitenanzahl,
'schlagwoerter_en' => $schlagwoerter_en,
'schlagwoerter' => $schlagwoerter,
'abstract' => $abstract,
'abstract_en' => $abstract_en
));
$this->logLib->logInfoDB(array('zusatzdatenEditMitarbeiter', array(
'updatevon' => getAuthUID(),
@@ -1504,6 +1529,58 @@ class Abgabe extends FHCAPI_Controller
$this->terminateWithSuccess($data);
}
public function sendZweitbetreuerTokenMail() {
$projektarbeit_id = $this->input->post('projektarbeit_id');
$bperson_id = $this->input->post('bperson_id');
$student_uid = $this->input->post('student_uid');
if ($projektarbeit_id === NULL || trim((string)$projektarbeit_id) === ''
|| $bperson_id === NULL || trim((string)$bperson_id) === ''
|| $student_uid === NULL || trim((string)$student_uid) === '') {
$this->terminateWithError($this->p->t('global', 'wrongParameters'), 'general');
}
$this->checkProjektarbeitForFinishedStatus($projektarbeit_id);
$zugeordnet = $this->checkZuordnung($projektarbeit_id, getAuthUID());
if(!$zugeordnet) {
$this->terminateWithError($this->p->t('abgabetool', 'c4noZuordnungBetreuerStudent'));
}
$this->sendUploadEmailZweitbegutachterToken($bperson_id, $projektarbeit_id, $student_uid);
}
// basically what getStudentProjektarbeiten intends to do for the student page but
// designed for the assistenz timeline component -> more extensive loading upfront
// with every abgabetermin fetched here aswell
public function fetchProjektarbeitenHistory() {
$student_uid = $this->input->post('student_uid');
$this->load->model('education/Projektarbeit_model', 'ProjektarbeitModel');
$projektarbeiten = $this->ProjektarbeitModel->getProjektarbeitenForStudent($student_uid);
$mapFunc = function($projektarbeit) {
return $projektarbeit->projektarbeit_id;
};
$projektarbeiten_ids = array_map($mapFunc, $projektarbeiten->retval);
if(count($projektarbeiten_ids) > 0) {
$ret = $this->ProjektarbeitModel->getProjektarbeitenAbgabetermine($projektarbeiten_ids);
$projektabgaben = $this->getDataOrTerminateWithError($ret, 'general');
}
forEach($projektarbeiten->retval as $pa) {
$filterFunc = function($projektabgabe) use ($pa) {
return $projektabgabe->projektarbeit_id == $pa->projektarbeit_id;
};
$pa->abgabetermine = array_values(array_filter($projektabgaben, $filterFunc));
}
$this->terminateWithSuccess($projektarbeiten->retval);
}
/**
* helper function to check the signature status of uploaded files for zwischenabgabe & endupload
*/
@@ -1569,13 +1646,6 @@ class Abgabe extends FHCAPI_Controller
$this->terminateWithError($this->p->t('abgabetool','c4fehlerAktualitaetProjektarbeitv2'), 'general');
}
// Link to Abgabetool
if (defined('CIS4') && CIS4) {
$ci3BootstrapFilePath = "cis.php";
} else {
$ci3BootstrapFilePath = "index.ci.php";
}
$path = $this->config->item('URL_MITARBEITER');
$url = APP_ROOT.$path;
@@ -1587,7 +1657,6 @@ class Abgabe extends FHCAPI_Controller
// 1. Begutachter mail ohne Token
$mail_baselink = APP_ROOT.$this->config->item('PROJEKTARBEITSBEURTEILUNG_MAIL_BASELINK_ERSTBEGUTACHTER');
// $mail_baselink = APP_ROOT."index.ci.php/extensions/FHC-Core-Projektarbeitsbeurteilung/ProjektarbeitsbeurteilungErstbegutachter";
$mail_fulllink = "$mail_baselink?projektarbeit_id=".$projektarbeit_id."&uid=".$studentUser->uid;
$projekttyp_kurzbz = $projektarbeit->projekttyp_kurzbz;
$subject = $projektarbeit->projekttyp_kurzbz == 'Diplom' ? 'Masterarbeitsbetreuung' : 'Bachelorarbeitsbetreuung';
@@ -1607,7 +1676,7 @@ class Abgabe extends FHCAPI_Controller
$email = $this->getProjektbetreuerEmailByProjektarbeitID($projektarbeit_id);
if(!$email) $this->terminateWithError($this->p->t('abgabetool', 'c4fehlerMailBegutachterv2'), 'general');
$mailres = sendSanchoMail(
'ParbeitsbeurteilungEndupload',
$maildata,
@@ -1625,66 +1694,101 @@ class Abgabe extends FHCAPI_Controller
// 2. Begutachter mail, wenn Endabgabe, mit Token wenn extern
if ($paabgabetyp_kurzbz == 'end')
{
// Zweitbegutachter holen
$this->load->model('education/Projektbetreuer_model', 'ProjektbetreuerModel');
$zweitbegutachterRetval = getData($this->ProjektbetreuerModel->getZweitbegutachterWithToken($bperson_id, $projektarbeit_id, $studentUser->uid));
$this->sendUploadEmailZweitbegutachterToken($bperson_id, $projektarbeit_id, $studentUser->uid);
if ($zweitbegutachterRetval && count($zweitbegutachterRetval) > 0)
}
}
}
private function sendUploadEmailZweitbegutachterToken($bperson_id, $projektarbeit_id, $student_uid) {
$this->load->model('education/Projektarbeit_model', 'ProjektarbeitModel');
$projektarbeitArr = $this->getDataOrTerminateWithError($this->ProjektarbeitModel->load($projektarbeit_id));
if(count($projektarbeitArr) > 0) {
$projektarbeit = $projektarbeitArr[0];
} else {
$this->terminateWithError($this->p->t('abgabetool','c4projektarbeitNichtGefunden'), 'general');
}
// Zweitbegutachter holen
$this->load->model('education/Projektbetreuer_model', 'ProjektbetreuerModel');
$zweitbegutachterRetval = getData($this->ProjektbetreuerModel->getZweitbegutachterWithToken($bperson_id, $projektarbeit_id, $student_uid));
$projektarbeitIsCurrent = false;
$returnFunc = function ($result) use (&$projektarbeitIsCurrent) {
$projektarbeitIsCurrent = $result;
};
Events::trigger('projektarbeit_is_current', $projektarbeit_id, $returnFunc);
if(!$projektarbeitIsCurrent) {
$this->terminateWithError($this->p->t('abgabetool','c4fehlerAktualitaetProjektarbeitv2'), 'general');
}
if ($zweitbegutachterRetval && count($zweitbegutachterRetval) > 0)
{
foreach ($zweitbegutachterRetval as $begutachter)
{
// token generieren, wenn noch nicht vorhanden und notwendig (wird in methode überprüft)
$tokenGenRes = $this->ProjektbetreuerModel->generateZweitbegutachterToken($begutachter->person_id, $projektarbeit_id);
if (!$tokenGenRes)
{
foreach ($zweitbegutachterRetval as $begutachter)
{
// token generieren, wenn noch nicht vorhanden und notwendig (wird in methode überprüft)
$tokenGenRes = $this->ProjektbetreuerModel->generateZweitbegutachterToken($begutachter->person_id, $projektarbeit_id);
if (!$tokenGenRes)
{
$this->terminateWithError($this->p->t('abgabetool', 'c4fehlerMailZweitBegutachterv2'), 'general');
}
$begutachterMitTokenRetval = getData($this->ProjektbetreuerModel->getZweitbegutachterWithToken($bperson_id, $projektarbeit_id, $studentUser->uid, $begutachter->person_id));
if (!$begutachterMitTokenRetval && count($begutachterMitTokenRetval) <= 0)
{
$this->terminateWithError($this->p->t('abgabetool', 'c4fehlerMailZweitBegutachterv2'), 'general');
}
$begutachterMitToken = $begutachterMitTokenRetval[0];
$path = $begutachterMitToken->betreuerart_kurzbz == 'Zweitbegutachter' ? 'ProjektarbeitsbeurteilungZweitbegutachter' : 'ProjektarbeitsbeurteilungErstbegutachter';
$mail_baselink = APP_ROOT."index.ci.php/extensions/FHC-Core-Projektarbeitsbeurteilung/$path";
$mail_fulllink = "$mail_baselink?projektarbeit_id=".$projektarbeit_id."&uid=".$studentUser->uid;
$intern = isset($begutachterMitToken->uid);
$mail_link = $intern ? $mail_fulllink : $mail_baselink;
$zweitbetmaildata = array();
$zweitbetmaildata['geehrt'] = "geehrte" . ($begutachterMitToken->anrede == "Herr" ? "r" : "");
$zweitbetmaildata['anrede'] = $begutachterMitToken->anrede;
$zweitbetmaildata['betreuer_voller_name'] = $begutachterMitToken->voller_name;
$zweitbetmaildata['student_anrede'] = $maildata['student_anrede'];
$zweitbetmaildata['student_voller_name'] = $maildata['student_voller_name'];
$zweitbetmaildata['abgabetyp'] = $abgabetyp;
$zweitbetmaildata['parbeituebersichtlink'] = $intern ? $maildata['parbeituebersichtlink'] : "";
$zweitbetmaildata['bewertunglink'] = $projektarbeitIsCurrent ? "<p><a href='$mail_link'>Zur Beurteilung der Arbeit</a></p>" : "";
$zweitbetmaildata['token'] = $projektarbeitIsCurrent && isset($begutachterMitToken->zugangstoken) && !$intern ? "<p>Zugangstoken: " . $begutachterMitToken->zugangstoken . "</p>" : "";
$mailres = sendSanchoMail(
'ParbeitsbeurteilungEndupload',
$zweitbetmaildata,
$begutachterMitToken->email,
$subject,
'sancho_header_min_bw.jpg',
'sancho_footer_min_bw.jpg',
get_uid()."@".DOMAIN
);
if (!$mailres)
{
$this->terminateWithError($this->p->t('abgabetool', 'c4fehlerMailBegutachterv2'), 'general');
}
}
$this->terminateWithError($this->p->t('abgabetool', 'c4fehlerMailZweitBegutachterv2'), 'general');
}
$begutachterMitTokenRetval = getData($this->ProjektbetreuerModel->getZweitbegutachterWithToken($bperson_id, $projektarbeit_id, $student_uid, $begutachter->person_id));
if (!$begutachterMitTokenRetval && count($begutachterMitTokenRetval) <= 0)
{
$this->terminateWithError($this->p->t('abgabetool', 'c4fehlerMailZweitBegutachterv2'), 'general');
}
$begutachterMitToken = $begutachterMitTokenRetval[0];
$studentUser = $this->ProjektarbeitModel->getProjektarbeitBenutzer($student_uid)->retval[0];
$path = $begutachterMitToken->betreuerart_kurzbz == 'Zweitbegutachter' ? 'ProjektarbeitsbeurteilungZweitbegutachter' : 'ProjektarbeitsbeurteilungErstbegutachter';
$mail_baselink = APP_ROOT."index.ci.php/extensions/FHC-Core-Projektarbeitsbeurteilung/$path";
$mail_fulllink = "$mail_baselink?projektarbeit_id=".$projektarbeit_id."&uid=".$student_uid;
$intern = isset($begutachterMitToken->uid);
$mail_link = $intern ? $mail_fulllink : $mail_baselink;
// automatic email ensures that, client only exposes this method if that happened already
$paabgabetyp_kurzbz = 'end';
$abgabetyp = $paabgabetyp_kurzbz == 'end' ? 'Endabgabe' : 'Zwischenabgabe';
$path = $this->config->item('URL_MITARBEITER');
$url = APP_ROOT.$path;
$subject = $projektarbeit->projekttyp_kurzbz == 'Diplom' ? 'Masterarbeitsbetreuung' : 'Bachelorarbeitsbetreuung';
$zweitbetmaildata = array();
$zweitbetmaildata['geehrt'] = "geehrte" . ($begutachterMitToken->anrede == "Herr" ? "r" : "");
$zweitbetmaildata['anrede'] = $begutachterMitToken->anrede;
$zweitbetmaildata['betreuer_voller_name'] = $begutachterMitToken->voller_name;
$zweitbetmaildata['student_anrede'] = $studentUser->anrede;
$zweitbetmaildata['student_voller_name'] = trim($studentUser->titelpre." ".$studentUser->vorname." ".$studentUser->nachname." ".$studentUser->titelpost);
$zweitbetmaildata['abgabetyp'] = $abgabetyp;
$zweitbetmaildata['parbeituebersichtlink'] = $intern ? "<p><a href='$url'>Zur Projektarbeitsübersicht</a></p>" : "";
$zweitbetmaildata['bewertunglink'] = $projektarbeitIsCurrent ? "<p><a href='$mail_link'>Zur Beurteilung der Arbeit</a></p>" : "";
$zweitbetmaildata['token'] = $projektarbeitIsCurrent && isset($begutachterMitToken->zugangstoken) && !$intern ? "<p>Zugangstoken: " . $begutachterMitToken->zugangstoken . "</p>" : "";
$this->addMeta('$zweitbetmaildata', $zweitbetmaildata);
$mailres = sendSanchoMail(
'ParbeitsbeurteilungEndupload',
$zweitbetmaildata,
$begutachterMitToken->email,
$subject,
'sancho_header_min_bw.jpg',
'sancho_footer_min_bw.jpg',
get_uid()."@".DOMAIN
);
if (!$mailres)
{
$this->terminateWithError($this->p->t('abgabetool', 'c4fehlerMailBegutachterv2'), 'general');
}
}
}
}
@@ -287,10 +287,7 @@ class Projektarbeit extends FHCAPI_Controller
*/
public function getNoten()
{
$this->NoteModel->addOrder('notenwert', 'ASC');
$this->NoteModel->addOrder('bezeichnung', 'ASC');
$result = $this->NoteModel->load();
$result = $this->NoteModel->getAllActive();
if (isError($result)) return $this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL);
@@ -187,7 +187,10 @@ class Projektarbeit_model extends DB_Model
campus.tbl_paabgabetyp.bezeichnung,
campus.tbl_paabgabetyp.benotbar,
campus.tbl_paabgabe.abgabedatum,
campus.tbl_paabgabe.insertvon
campus.tbl_paabgabe.insertvon,
campus.tbl_paabgabe.insertamum,
campus.tbl_paabgabe.updatevon,
campus.tbl_paabgabe.updateamum
FROM campus.tbl_paabgabe JOIN campus.tbl_paabgabetyp USING(paabgabetyp_kurzbz)
WHERE campus.tbl_paabgabe.projektarbeit_id = ?
ORDER BY campus.tbl_paabgabe.datum";
@@ -207,7 +210,18 @@ class Projektarbeit_model extends DB_Model
campus.tbl_paabgabetyp.paabgabetyp_kurzbz,
campus.tbl_paabgabetyp.bezeichnung,
campus.tbl_paabgabe.abgabedatum,
campus.tbl_paabgabe.insertvon
campus.tbl_paabgabe.insertvon,
campus.tbl_paabgabe.updatevon,
campus.tbl_paabgabe.insertamum,
campus.tbl_paabgabe.updateamum,
(SELECT p.vorname || ' ' || p.nachname
FROM public.tbl_benutzer b
JOIN public.tbl_person p USING(person_id)
WHERE b.uid = campus.tbl_paabgabe.insertvon) AS insertvon_fullname,
(SELECT p.vorname || ' ' || p.nachname
FROM public.tbl_benutzer b
JOIN public.tbl_person p USING(person_id)
WHERE b.uid = campus.tbl_paabgabe.updatevon) AS updatevon_fullname
FROM campus.tbl_paabgabe JOIN campus.tbl_paabgabetyp USING(paabgabetyp_kurzbz)
WHERE campus.tbl_paabgabe.projektarbeit_id IN ?
ORDER BY campus.tbl_paabgabe.datum";
@@ -240,7 +254,8 @@ class Projektarbeit_model extends DB_Model
LEFT JOIN public.tbl_benutzer ben USING (person_id)
LEFT JOIN public.tbl_mitarbeiter ma ON ben.uid = ma.mitarbeiter_uid
WHERE (ben.aktiv OR ben.aktiv IS NULL)
AND projektarbeit_id = ?";
AND projektarbeit_id = ?
AND lehre.tbl_projektbetreuer.betreuerart_kurzbz = ANY('{Erstbetreuer,Erstbegutachter,Senatsvorsitz}')";
return $this->execReadOnlyQuery($qry, [$projektarbeit_id]);
}
@@ -294,34 +309,134 @@ class Projektarbeit_model extends DB_Model
* Get a List of Projektarbeiten of a mitarbeiter with zuordnung
* used by the mitarbeiter cis4 abgabetool.
*/
public function getMitarbeiterProjektarbeiten($uid, $showAll){
$qry = "SELECT
*
FROM
(SELECT tbl_person.vorname, tbl_person.nachname, tbl_studiengang.typ, tbl_studiengang.kurzbz,
tbl_projektarbeit.projekttyp_kurzbz, tbl_projekttyp.bezeichnung, tbl_projektarbeit.titel, tbl_projektarbeit.projektarbeit_id, tbl_projektarbeit.note,
tbl_projektbetreuer.person_id as betreuer_person_id, tbl_projektbetreuer.betreuerart_kurzbz, tbl_betreuerart.beschreibung AS betreuerart_beschreibung,
tbl_benutzer.uid, tbl_student.matrikelnr, tbl_lehreinheit.studiensemester_kurzbz, public.tbl_student.student_uid
FROM lehre.tbl_projektarbeit
LEFT JOIN lehre.tbl_projektbetreuer using(projektarbeit_id)
LEFT JOIN lehre.tbl_betreuerart using(betreuerart_kurzbz)
LEFT JOIN public.tbl_benutzer on(uid=student_uid)
LEFT JOIN public.tbl_student on(public.tbl_benutzer.uid=public.tbl_student.student_uid)
LEFT JOIN public.tbl_person on(tbl_benutzer.person_id=tbl_person.person_id)
LEFT JOIN lehre.tbl_lehreinheit using(lehreinheit_id)
LEFT JOIN lehre.tbl_lehrveranstaltung using(lehrveranstaltung_id)
LEFT JOIN public.tbl_studiengang on(lehre.tbl_lehrveranstaltung.studiengang_kz=public.tbl_studiengang.studiengang_kz)
LEFT JOIN lehre.tbl_projekttyp USING (projekttyp_kurzbz)
WHERE (projekttyp_kurzbz='Bachelor' OR projekttyp_kurzbz='Diplom')
AND tbl_projektbetreuer.person_id IN (SELECT person_id FROM public.tbl_benutzer
WHERE public.tbl_benutzer.person_id=lehre.tbl_projektbetreuer.person_id
AND public.tbl_benutzer.uid= ? )
".($showAll?'':' AND public.tbl_benutzer.aktiv AND lehre.tbl_projektarbeit.note IS NULL ')."
public function getMitarbeiterProjektarbeiten($person_id, $showAll){
$qry = "WITH secondary_betreuer AS (
SELECT DISTINCT ON (pb.projektarbeit_id)
pb.projektarbeit_id,
pb.person_id AS zweitbetreuer_person_id,
b.uid AS zweitbetreuer_benutzer_uid,
pb.betreuerart_kurzbz AS zweitbetreuer_betreuerart_kurzbz,
ba.beschreibung AS zweitbetreuer_betreuerart_beschreibung,
p.titelpre AS zweitbetreuer_titelpre,
p.vorname AS zweitbetreuer_vorname,
p.nachname AS zweitbetreuer_nachname,
p.titelpost AS zweitbetreuer_titelpost,
trim(
COALESCE(p.titelpre, '') || ' ' ||
COALESCE(p.vorname, '') || ' ' ||
COALESCE(p.nachname, '') || ' ' ||
COALESCE(p.titelpost, '')
) AS zweitbetreuer_full_name
FROM lehre.tbl_projektbetreuer pb
JOIN public.tbl_person p ON p.person_id = pb.person_id
LEFT JOIN public.tbl_benutzer b ON b.person_id = p.person_id
LEFT JOIN lehre.tbl_betreuerart ba ON ba.betreuerart_kurzbz = pb.betreuerart_kurzbz
WHERE pb.betreuerart_kurzbz = ANY('{Zweitbetreuer,Zweitbegutachter,Senatsmitglied}')
ORDER BY pb.projektarbeit_id
),
primary_betreuer AS (
SELECT DISTINCT ON (pb.projektarbeit_id)
pb.projektarbeit_id,
pb.person_id AS betreuer_person_id,
pb.betreuerart_kurzbz AS betreuer_betreuerart_kurzbz,
ba.beschreibung AS betreuer_betreuerart_beschreibung,
p.titelpre AS betreuer_titelpre,
p.vorname AS betreuer_vorname,
p.nachname AS betreuer_nachname,
p.titelpost AS betreuer_titelpost,
trim(
COALESCE(p.titelpre, '') || ' ' ||
COALESCE(p.vorname, '') || ' ' ||
COALESCE(p.nachname, '') || ' ' ||
COALESCE(p.titelpost, '')
) AS betreuer_full_name
FROM lehre.tbl_projektbetreuer pb
JOIN public.tbl_person p ON p.person_id = pb.person_id
LEFT JOIN public.tbl_benutzer b ON b.person_id = p.person_id
LEFT JOIN lehre.tbl_betreuerart ba ON ba.betreuerart_kurzbz = pb.betreuerart_kurzbz
WHERE pb.betreuerart_kurzbz = ANY('{Erstbetreuer,Erstbegutachter,Senatsvorsitz}')
ORDER BY pb.projektarbeit_id
)
SELECT
*
FROM
(SELECT DISTINCT ON (tbl_projektarbeit.projektarbeit_id)
student_person.vorname, student_person.nachname,
tbl_studiengang.typ, tbl_studiengang.kurzbz,
tbl_projektarbeit.projekttyp_kurzbz, tbl_projekttyp.bezeichnung,
tbl_projektarbeit.titel, tbl_projektarbeit.projektarbeit_id, tbl_projektarbeit.note,
student_benutzer.uid, tbl_student.matrikelnr, tbl_lehreinheit.studiensemester_kurzbz, public.tbl_student.student_uid,
(
SELECT orgform_kurzbz
FROM public.tbl_prestudentstatus
WHERE prestudent_id = (
SELECT prestudent_id FROM public.tbl_student
WHERE student_uid = student_benutzer.uid LIMIT 1
)
ORDER BY datum DESC, insertamum DESC, ext_id DESC
LIMIT 1
) AS organisationsform,
(
SELECT COALESCE(tbl_studienplan.orgform_kurzbz,
tbl_prestudentstatus.orgform_kurzbz,
tbl_studiengang.orgform_kurzbz)
FROM public.tbl_prestudent
JOIN public.tbl_prestudentstatus USING (prestudent_id)
JOIN public.tbl_studiensemester USING (studiensemester_kurzbz)
JOIN public.tbl_studiengang USING (studiengang_kz)
LEFT JOIN lehre.tbl_studienplan USING (studienplan_id)
WHERE prestudent_id = public.tbl_student.prestudent_id
ORDER BY tbl_prestudentstatus.datum DESC
LIMIT 1
) AS orgform,
(
SELECT status_kurzbz
FROM public.tbl_prestudentstatus
WHERE prestudent_id = public.tbl_student.prestudent_id
ORDER BY datum DESC, insertamum DESC, ext_id DESC
LIMIT 1
) AS studienstatus,
pb.betreuer_person_id,
pb.betreuer_betreuerart_kurzbz,
pb.betreuer_betreuerart_beschreibung,
pb.betreuer_titelpre,
pb.betreuer_vorname,
pb.betreuer_nachname,
pb.betreuer_titelpost,
pb.betreuer_full_name,
sb.zweitbetreuer_person_id,
sb.zweitbetreuer_benutzer_uid,
sb.zweitbetreuer_betreuerart_kurzbz,
sb.zweitbetreuer_betreuerart_beschreibung,
sb.zweitbetreuer_titelpre,
sb.zweitbetreuer_vorname,
sb.zweitbetreuer_nachname,
sb.zweitbetreuer_titelpost,
sb.zweitbetreuer_full_name
FROM lehre.tbl_projektarbeit
LEFT JOIN public.tbl_benutzer student_benutzer ON student_benutzer.uid = lehre.tbl_projektarbeit.student_uid
LEFT JOIN public.tbl_person student_person ON student_benutzer.person_id = student_person.person_id
LEFT JOIN public.tbl_student ON student_benutzer.uid = public.tbl_student.student_uid
LEFT JOIN lehre.tbl_lehreinheit USING (lehreinheit_id)
LEFT JOIN lehre.tbl_lehrveranstaltung USING (lehrveranstaltung_id)
LEFT JOIN public.tbl_studiengang ON public.tbl_student.studiengang_kz = public.tbl_studiengang.studiengang_kz
LEFT JOIN lehre.tbl_projekttyp USING (projekttyp_kurzbz)
LEFT JOIN lehre.tbl_projektbetreuer USING (projektarbeit_id)
LEFT JOIN primary_betreuer pb ON pb.projektarbeit_id = tbl_projektarbeit.projektarbeit_id
LEFT JOIN secondary_betreuer sb ON sb.projektarbeit_id = tbl_projektarbeit.projektarbeit_id
WHERE (projekttyp_kurzbz='Bachelor' OR projekttyp_kurzbz='Diplom')
AND (pb.betreuer_person_id = ? OR sb.zweitbetreuer_person_id = ?)
".($showAll?'':' AND student_benutzer.aktiv AND lehre.tbl_projektarbeit.note IS NULL ')."
AND betreuerart_kurzbz IN ('Betreuer', 'Begutachter', 'Erstbegutachter', 'Zweitbegutachter', 'Erstbetreuer', 'Senatsvorsitz', 'Senatsmitglied')
ORDER BY tbl_projektarbeit.projektarbeit_id, betreuerart_kurzbz desc) as xy
ORDER BY tbl_projektarbeit.projektarbeit_id, betreuerart_kurzbz desc) as xy
ORDER BY nachname;";
return $this->execReadOnlyQuery($qry, array($uid));
return $this->execReadOnlyQuery($qry, array($person_id, $person_id));
}
/**
@@ -449,7 +564,7 @@ class Projektarbeit_model extends DB_Model
LEFT JOIN lehre.tbl_projektbetreuer USING (projektarbeit_id)
LEFT JOIN public.tbl_person betreuer_person ON betreuer_person.person_id = lehre.tbl_projektbetreuer.person_id
LEFT JOIN public.tbl_benutzer betreuer_benutzer ON betreuer_person.person_id = betreuer_benutzer.person_id
LEFT JOIN secondary_betreuer sb ON sb.projektarbeit_id = tbl_projektarbeit.projektarbeit_id -- ← THE NEW LINE
LEFT JOIN secondary_betreuer sb ON sb.projektarbeit_id = tbl_projektarbeit.projektarbeit_id
WHERE (projekttyp_kurzbz = 'Bachelor' OR projekttyp_kurzbz = 'Diplom')
AND student_benutzer.aktiv
@@ -469,6 +584,129 @@ class Projektarbeit_model extends DB_Model
return $this->execReadOnlyQuery($new_qry, array($studiengang_kz));
}
public function getProjektarbeitenForStudent($student_uid) {
$new_qry = "WITH secondary_betreuer AS (
SELECT DISTINCT ON (pb.projektarbeit_id)
pb.projektarbeit_id,
pb.person_id AS zweitbetreuer_person_id,
pb.betreuerart_kurzbz AS zweitbetreuer_betreuerart_kurzbz,
ba.beschreibung AS zweitbetreuer_betreuerart_beschreibung,
p.titelpre AS zweitbetreuer_titelpre,
p.vorname AS zweitbetreuer_vorname,
p.nachname AS zweitbetreuer_nachname,
p.titelpost AS zweitbetreuer_titelpost,
trim(
COALESCE(p.titelpre, '') || ' ' ||
COALESCE(p.vorname, '') || ' ' ||
COALESCE(p.nachname, '') || ' ' ||
COALESCE(p.titelpost, '')
) AS zweitbetreuer_full_name
FROM lehre.tbl_projektbetreuer pb
JOIN public.tbl_person p ON p.person_id = pb.person_id
LEFT JOIN public.tbl_benutzer b ON b.person_id = p.person_id
LEFT JOIN lehre.tbl_betreuerart ba ON ba.betreuerart_kurzbz = pb.betreuerart_kurzbz
WHERE pb.betreuerart_kurzbz = ANY('{Zweitbetreuer,Zweitbegutachter,Senatsmitglied}')
ORDER BY pb.projektarbeit_id -- DISTINCT ON needs this to be deterministic
)
SELECT DISTINCT ON (tmp.projektarbeit_id)
*,
campus.get_betreuer_details(tmp.zweitbetreuer_person_id) AS zweitbetreuer_full_name,
campus.get_betreuer_details(tmp.betreuer_person_id) AS erstbetreuer_full_name
FROM (
SELECT DISTINCT ON (tbl_projektarbeit.projektarbeit_id)
tbl_projektarbeit.projekttyp_kurzbz,
tbl_projektarbeit.titel,
tbl_projektarbeit.projektarbeit_id,
tbl_studiengang.typ,
tbl_studiengang.kurzbz,
student_benutzer.uid AS student_uid,
student_person.vorname AS student_vorname,
student_person.nachname AS student_nachname,
public.tbl_student.matrikelnr,
tbl_lehreinheit.studiensemester_kurzbz,
betreuer_benutzer.uid AS betreuer_benutzer_uid,
betreuer_person.titelpre AS betreuer_titelpre,
betreuer_person.vorname AS betreuer_vorname,
betreuer_person.nachname AS betreuer_nachname,
betreuer_person.titelpost AS betreuer_titelpost,
lehre.tbl_projektbetreuer.betreuerart_kurzbz AS betreuerart,
lehre.tbl_projektbetreuer.person_id AS betreuer_person_id,
lehre.tbl_projektarbeit.sprache,
lehre.tbl_projektarbeit.seitenanzahl,
lehre.tbl_projektarbeit.kontrollschlagwoerter,
lehre.tbl_projektarbeit.schlagwoerter,
lehre.tbl_projektarbeit.schlagwoerter_en,
lehre.tbl_projektarbeit.abstract,
lehre.tbl_projektarbeit.abstract_en,
lehre.tbl_projektarbeit.insertamum,
lehre.tbl_projektarbeit.note,
sb.zweitbetreuer_person_id,
sb.zweitbetreuer_betreuerart_kurzbz,
sb.zweitbetreuer_betreuerart_beschreibung,
sb.zweitbetreuer_full_name,
sb.zweitbetreuer_titelpre,
sb.zweitbetreuer_vorname,
sb.zweitbetreuer_nachname,
sb.zweitbetreuer_titelpost,
(
SELECT orgform_kurzbz
FROM public.tbl_prestudentstatus
WHERE prestudent_id = (
SELECT prestudent_id FROM public.tbl_student
WHERE student_uid = student_benutzer.uid LIMIT 1
)
ORDER BY datum DESC, insertamum DESC, ext_id DESC
LIMIT 1
) AS organisationsform,
(
SELECT COALESCE(tbl_studienplan.orgform_kurzbz,
tbl_prestudentstatus.orgform_kurzbz,
tbl_studiengang.orgform_kurzbz)
FROM public.tbl_prestudent
JOIN public.tbl_prestudentstatus USING (prestudent_id)
JOIN public.tbl_studiensemester USING (studiensemester_kurzbz)
JOIN public.tbl_studiengang USING (studiengang_kz)
LEFT JOIN lehre.tbl_studienplan USING (studienplan_id)
WHERE prestudent_id = public.tbl_student.prestudent_id
ORDER BY tbl_prestudentstatus.datum DESC
LIMIT 1
) AS orgform,
(
SELECT status_kurzbz
FROM public.tbl_prestudentstatus
WHERE prestudent_id = public.tbl_student.prestudent_id
ORDER BY datum DESC, insertamum DESC, ext_id DESC
LIMIT 1
) AS studienstatus
FROM lehre.tbl_projektarbeit
LEFT JOIN public.tbl_benutzer student_benutzer ON student_benutzer.uid = lehre.tbl_projektarbeit.student_uid
LEFT JOIN public.tbl_person student_person ON student_benutzer.person_id = student_person.person_id
LEFT JOIN public.tbl_student ON student_benutzer.uid = public.tbl_student.student_uid
LEFT JOIN lehre.tbl_lehreinheit USING (lehreinheit_id)
LEFT JOIN lehre.tbl_lehrveranstaltung USING (lehrveranstaltung_id)
LEFT JOIN public.tbl_studiengang ON public.tbl_student.studiengang_kz = public.tbl_studiengang.studiengang_kz
LEFT JOIN lehre.tbl_projekttyp USING (projekttyp_kurzbz)
LEFT JOIN lehre.tbl_projektbetreuer USING (projektarbeit_id)
LEFT JOIN public.tbl_person betreuer_person ON betreuer_person.person_id = lehre.tbl_projektbetreuer.person_id
LEFT JOIN public.tbl_benutzer betreuer_benutzer ON betreuer_person.person_id = betreuer_benutzer.person_id
LEFT JOIN secondary_betreuer sb ON sb.projektarbeit_id = tbl_projektarbeit.projektarbeit_id
WHERE (projekttyp_kurzbz = 'Bachelor' OR projekttyp_kurzbz = 'Diplom')
AND student_benutzer.aktiv
AND lehre.tbl_projektbetreuer.betreuerart_kurzbz IN (
'Erstbegutachter', 'Begutachter', 'Betreuer', 'Erstbetreuer', 'Senatsvorsitz'
)
AND public.tbl_student.student_uid = ?
ORDER BY tbl_projektarbeit.projektarbeit_id DESC, student_person.nachname ASC
) as tmp";
return $this->execReadOnlyQuery($new_qry, array($student_uid));
}
/**
*
@@ -396,3 +396,13 @@
opacity: 0;
}
}
.p-timeline-event-opposite {
flex: 0 0 90px;
max-width: 90px;
}
.p-timeline-event-content {
flex: 1 1 auto;
min-width: 0;
}
+16
View File
@@ -161,6 +161,22 @@ export default {
url: '/api/frontend/v1/Abgabe/postStudentProjektarbeitTitel',
params: {projektarbeit_id, titel},
};
},
sendZweitbetreuerTokenMail(projektarbeit_id, betreuer_person_id, student_uid) {
return {
method: 'post',
url: '/api/frontend/v1/Abgabe/sendZweitbetreuerTokenMail',
params: {projektarbeit_id, bperson_id: betreuer_person_id, student_uid},
};
},
fetchProjektarbeitenHistory(student_uid) {
return {
method: 'post',
url: '/api/frontend/v1/Abgabe/fetchProjektarbeitenHistory',
params: {student_uid},
};
}
};
@@ -2,7 +2,8 @@ import BsModal from '../../Bootstrap/Modal.js';
import VueDatePicker from '../../vueDatepicker.js.php';
import ApiAbgabe from '../../../api/factory/abgabe.js'
import { getDateStyleClass } from "./getDateStyleClass.js";
import { compareISODateValues, formatISODate, getViennaTodayISO } from "./dateUtils.js";
import {compareISODateValues, formatDateTime, formatISODate, getViennaTodayISO} from "./dateUtils.js";
export const AbgabeMitarbeiterDetail = {
name: "AbgabeMitarbeiterDetail",
@@ -77,6 +78,19 @@ export const AbgabeMitarbeiterDetail = {
}
},
methods: {
getSavedTerminInfoString(termin) {
const isUpdate = termin.updateamum != null;
const fullname = isUpdate
? termin.updatevon_fullname
: termin.insertvon_fullname;
const datetime = isUpdate
? termin.updateamum
: termin.insertamum;
return this.$p.t('ui/savedAtByV3', [formatDateTime(datetime), fullname])
},
terminIsInvalid(termin) {
return termin.note?.positiv == false && !termin.beurteilungsnotiz
},
@@ -817,7 +831,7 @@ export const AbgabeMitarbeiterDetail = {
<div class="col-12 col-md-9">
<Dropdown
:style="{'width': '100%'}"
:disabled="!termin.allowedToSave"
:disabled="!termin.allowedToSave || termin.abgabedatum !== null || termin.noteBackend"
:placeholder="getPlaceholderTermin(termin)"
v-model="termin.bezeichnung"
@change="handleChangeAbgabetyp(termin)"
@@ -905,7 +919,7 @@ export const AbgabeMitarbeiterDetail = {
<div class="col-12 col-md-3 fw-bold align-content-center">
{{ $capitalize( $p.t('abgabetool/c4actions') )}}
</div>
<div class="col-12 col-md-9">
<div class="col-12 col-md-5">
<div class="row">
<div class="col-auto">
<button v-if="termin.allowedToSave && !terminIsInvalid(termin)" style="max-height: 40px;" class="btn btn-primary border-0" @click="saveTermin(termin)">
@@ -933,6 +947,9 @@ export const AbgabeMitarbeiterDetail = {
</div>
</div>
</div>
<div class="col-12 col-md-4 align-content-center text-end text-muted small">
{{getSavedTerminInfoString(termin)}}
</div>
</div>
</AccordionTab>
</template>
@@ -410,7 +410,7 @@ export const AbgabeStudentDetail = {
<div v-if="projektarbeit">
<h5>{{$capitalize( $p.t('abgabetool/c4abgabeStudentenbereich') )}}</h5>
<h5>{{$capitalize( $p.t('abgabetool/c4abgabeStudentenbereichv2') )}}</h5>
<div class="row">
<div class="col-8">
<p>{{$capitalize( $p.t('person/student') ) }}: {{projektarbeit?.student}}</p>
@@ -658,7 +658,7 @@ export const AbgabeStudentDetail = {
</label>
<Textarea
v-model="editingTitel"
rows="10"
rows="2"
maxlength="1024"
class="form-control w-100"
@keydown.enter.prevent="saveTitel"
@@ -0,0 +1,251 @@
import AbgabeterminStatusLegende from "./StatusLegende.js";
import {formatDateTime} from "./dateUtils.js";
export const AbgabeStudentTimeline = {
name: "AbgabeStudentTimeline",
components: {
AbgabeterminStatusLegende,
Timeline: primevue.timeline,
},
props: {
projekte: { type: Array, default: () => [] },
notenOptions: { type: Array, default: () => [] },
formatDateFn: { type: Function, required: true }
},
data() {
return {
legendExpanded: false,
expandedProjects: {}
}
},
computed: {
student() {
return this.projekte?.[0] ?? null
}
},
watch: {
projekte: {
immediate: true,
handler(val) {
// open all projects by default whenever the student changes
const state = {}
val?.forEach(p => { state[p.projektarbeit_id] = true })
this.expandedProjects = state
this.legendExpanded = false
}
}
},
methods: {
getNoteBezeichnung(projektarbeit) {
if(projektarbeit.note && this.notenOptions) {
const noteOpt = this.notenOptions.find(opt => opt.note == projektarbeit.note)
return noteOpt?.bezeichnung
} else {
return ''
}
},
getSavedTerminInfoString(termin) {
const isUpdate = termin.updateamum != null;
const fullname = isUpdate
? termin.updatevon_fullname
: termin.insertvon_fullname;
const datetime = isUpdate
? termin.updateamum
: termin.insertamum;
return this.$p.t('ui/savedAtByV3', [formatDateTime(datetime), fullname])
},
getItemBezeichnung(item) {
if (!item?.bezeichnung) return ''
return item.bezeichnung?.bezeichnung ?? item.bezeichnung
},
getItemNote(item) {
if (!item?.note) return ''
if (item.note?.bezeichnung) return item.note.bezeichnung
return this.notenOptions?.find(n => n.note == item.note)?.bezeichnung ?? String(item.note)
},
getIconClass(dateStyle) {
return ({
verspaetet: 'fa-solid fa-triangle-exclamation',
verpasst: 'fa-solid fa-calendar-xmark',
abzugeben: 'fa-solid fa-hourglass-half',
standard: 'fa-solid fa-clock',
abgegeben: 'fa-solid fa-paperclip',
beurteilungerforderlich: 'fa-solid fa-list-check',
bestanden: 'fa-solid fa-check',
nichtbestanden: 'fa-solid fa-circle-exclamation',
})[dateStyle] ?? ''
},
getBetreuerLabel(projekt) {
return projekt.erstbetreuer_full_name
|| (projekt.betreuer_vorname ? `${projekt.betreuer_vorname} ${projekt.betreuer_nachname}`.trim() : null)
},
toggleProject(id) {
this.expandedProjects[id] = !this.expandedProjects[id]
}
},
template: `
<div v-if="student">
<div class="d-flex align-items-baseline gap-2 mb-3 pb-2 border-bottom">
<span class="fw-bold fs-6">{{ student.student_vorname }} {{ student.student_nachname }}</span>
<span class="text-muted small">{{ student.student_uid }}</span>
<span v-if="student.matrikelnr" class="text-muted small ms-auto">{{ student.matrikelnr }}</span>
</div>
<div v-for="projekt in projekte" :key="projekt.projektarbeit_id" class="mb-2">
<button
class="btn btn-sm w-100 text-start d-flex align-items-center gap-2 rounded bg-light border-0 py-2 px-3"
@click="toggleProject(projekt.projektarbeit_id)"
>
<i
:class="expandedProjects[projekt.projektarbeit_id] ? 'fa-solid fa-chevron-down' : 'fa-solid fa-chevron-right'"
style="width: 12px; flex-shrink: 0;"
></i>
<span class="fw-semibold text-truncate flex-grow-1" :title="projekt.titel">
{{ projekt.titel || projekt.projekttyp_kurzbz || projekt.projektarbeit_id }}
</span>
<span
v-if="projekt.note"
class="small fw-semibold mx-2 flex-shrink-0"
>
{{ getNoteBezeichnung(projekt) }}
</span>
<span class="text-muted small me-1 flex-shrink-0">{{ projekt.studiensemester_kurzbz }}</span>
<span class="badge bg-secondary flex-shrink-0">{{ projekt.abgabetermine?.length ?? 0 }}</span>
</button>
<div v-show="expandedProjects[projekt.projektarbeit_id]" class="px-2 pt-1">
<div v-if="getBetreuerLabel(projekt)" class="text-muted small px-1 mb-2">
{{ projekt.betreuerart || $capitalize($p.t('abgabetool/c4erstbetreuerv2')) }}:
{{ getBetreuerLabel(projekt) }}
</div>
<Timeline :value="projekt.abgabetermine" align="right">
<template #marker="slotProps">
<!-- padding:0 overrides the 34px accordion left-padding from the -header CSS class -->
<div
:class="slotProps.item.dateStyle + '-header'"
style="height: 26px; width: 26px; padding: 0; border-radius: 4px; display: flex; align-items: center; justify-content: center; flex-shrink: 0;"
>
<i :class="getIconClass(slotProps.item.dateStyle)" style="font-size: 0.75rem;"></i>
</div>
</template>
<template #opposite="slotProps">
<div class="text-end small text-nowrap text-muted">
{{ formatDateFn(slotProps.item.datum) }}
</div>
</template>
<template #content="slotProps">
<div class="small pb-1">
<div class="d-flex align-items-center gap-1 fw-semibold">
<div
:class="slotProps.item.dateStyle + '-header'"
style="height:20px;width:20px;padding:0;border-radius:3px;display:flex;align-items:center;justify-content:center;flex-shrink:0;"
>
<i :class="getIconClass(slotProps.item.dateStyle)" style="font-size:.65rem;"></i>
</div>
<span>
{{ getItemBezeichnung(slotProps.item) }}
</span>
<i
v-if="slotProps.item.fixtermin"
class="fa-solid fa-lock text-muted"
title="Fixtermin"
></i>
<i
v-if="slotProps.item.abgabedatum"
class="fa-solid fa-file text-muted"
:title="$capitalize($p.t('abgabetool/c4abgabedatum'))"
></i>
<div class="flex-grow-1 text-end">
<span
v-if="slotProps.item.noteBackend?.bezeichnung"
class="fw-bold"
>
{{ slotProps.item.noteBackend?.positiv
? $capitalize($p.t('abgabetool/c4positivBenotet'))
: $capitalize($p.t('abgabetool/c4negativBenotet'))
}}
</span>
<span
v-else-if="slotProps.item.bezeichnung?.benotbar"
class="fw-bold text-muted"
>
{{ $capitalize($p.t('abgabetool/c4notYetGraded')) }}
</span>
</div>
</div>
<div
v-if="slotProps.item.kurzbz"
class="text-muted fst-italic ms-4"
>
{{ slotProps.item.kurzbz }}
</div>
<div
v-if="slotProps.item.abgabedatum"
class="small text-muted ms-4"
>
{{ $capitalize($p.t('abgabetool/c4abgabedatum')) }}:
{{ formatDateFn(slotProps.item.abgabedatum) }}
</div>
<div
v-if="slotProps.item.beurteilungsnotiz"
class="small ms-4 mt-1 text-muted"
>
{{ slotProps.item.beurteilungsnotiz }}
</div>
<div
v-if="slotProps.item.insertamum"
class="small text-muted ms-4 mt-1"
style="font-size: .72rem;"
>
<i class="fa-solid fa-clock-rotate-left me-1"></i>
{{ getSavedTerminInfoString(slotProps.item) }}
</div>
</div>
</template>
</Timeline>
</div>
</div>
<div class="mt-3 border-top pt-2">
<button
class="btn btn-link btn-sm p-0 text-muted text-decoration-none d-flex align-items-center gap-1"
@click="legendExpanded = !legendExpanded"
>
<i :class="legendExpanded ? 'fa-solid fa-chevron-up' : 'fa-solid fa-chevron-down'" style="font-size: 0.7rem;"></i>
Legende
</button>
<div v-show="legendExpanded" class="mt-2">
<AbgabeterminStatusLegende />
</div>
</div>
</div>
`
}
export default AbgabeStudentTimeline;
@@ -7,15 +7,17 @@ import ApiAbgabe from '../../../api/factory/abgabe.js'
import ApiStudiensemester from '../../../api/factory/studiensemester.js';
import AbgabeterminStatusLegende from "./StatusLegende.js";
import FhcOverlay from "../../Overlay/FhcOverlay.js";
import AbgabeStudentTimeline from "./AbgabeStudentTimeline.js";
import { splitMailsHelper } from "../../../helpers/EmailHelpers.js"
import { getDateStyleClass} from "./getDateStyleClass.js";
import { dateFilter } from '../../../tabulator/filters/Dates.js';
import { dateFilter } from '../../../tabulator/filters/DatesManual.js';
import { compareISODateValues, formatISODate, getViennaTodayISO, toViennaDate } from "./dateUtils.js";
export const AbgabetoolAssistenz = {
name: "AbgabetoolAssistenz",
components: {
AbgabeterminStatusLegende,
AbgabeStudentTimeline,
BsModal,
BsOffcanvas,
CoreFilterCmpt,
@@ -76,7 +78,7 @@ export const AbgabetoolAssistenz = {
colLayoutRestoredFlat: false,
sortRestoredFlat: false,
stateRestoredFlat: false,
timelineProjekt: null,
timelineProjekte: [],
selectedStudiengangOption: null,
studiengaengeOptions: null,
detailIsFullscreen: false,
@@ -209,7 +211,7 @@ export const AbgabetoolAssistenz = {
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4orgformv2'))), field: 'orgform', headerFilter: true, formatter: this.centeredTextFormatter, minWidth: 50, visible: false},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4projekttyp'))), field: 'projekttyp_kurzbz', formatter: this.centeredTextFormatter, minWidth: 150, visible: false},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4stg'))), field: 'stg', headerFilter: true, formatter: this.centeredTextFormatter, minWidth: 50, visible: false},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4note'))), field: 'note_bez', headerFilter: true, visible: false, minWidth: 200, formatter: this.centeredTextFormatter},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4note'))), field: 'note_bez', headerFilter: true, sorter: this.notenSorter, visible: false, minWidth: 200, formatter: this.centeredTextFormatter},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4sem'))), field: 'studiensemester_kurzbz', headerFilter: true, visible: false, formatter: this.centeredTextFormatter, minWidth: 100},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4titel'))), field: 'titel', headerFilter: true, formatter: this.centeredTextFormatter, minWidth: 100, visible: false},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4erstbetreuerv2'))), field: 'erstbetreuer', headerFilter: true, formatter: this.centeredTextFormatter, minWidth: 100, visible: false},
@@ -431,6 +433,7 @@ export const AbgabetoolAssistenz = {
if (!val) return '';
return val?.bezeichnung ?? this.notenOptions?.find(n => n.note == val)?.bezeichnung ?? val;
},
sorter: this.notenSorterFlat,
minWidth: 100,
tooltip: false,
headerFilter: this.notenHeaderFilterEditor,
@@ -519,6 +522,18 @@ export const AbgabetoolAssistenz = {
};
},
methods: {
notenSorterFlat(a, b, aRow, bRow, column, dir, sorterParams) {
// flat table has their own sort since the field is called sligthly different in that context
// since note would be bestanden/nicht bestanden and that hardly needs sorting
const aData = aRow.getData()
const bData = bRow.getData()
return aData.pa_note - bData.pa_note
},
notenSorter(a, b, aRow, bRow, column, dir, sorterParams) {
const aData = aRow.getData()
const bData = bRow.getData()
return aData.note - bData.note
},
notenHeaderFilterEditor(cell, onRendered, success, cancel, editorParams) {
if (!this.notenOptions) return;
@@ -611,7 +626,7 @@ export const AbgabetoolAssistenz = {
return filterVal.some(val => val == noteId); // loose equality: filter vals are numbers, noteId might be string
},
handleFilterActiveChanged(active) {
if(!active && this.allSemOption) {
if(!active && this.allSemOption && this.stateRestored) {
this.curSem = this.allSemOption
}
},
@@ -1361,6 +1376,11 @@ export const AbgabetoolAssistenz = {
// calculate Abgabetermin time diff to now and assign last and next to projekt
projekt.abgabetermine.forEach(termin => {
// only set this if it has not been set yet and abgabetermin has a note (qgate)
if(!termin.noteBackend && termin.note) {
termin.noteBackend = termin.note
}
termin.bezeichnung = this.abgabeTypeOptions.find(opt => opt.paabgabetyp_kurzbz === termin.paabgabetyp_kurzbz)
// while already looping through each termin, calculate datestyle beforehand
@@ -1778,7 +1798,8 @@ export const AbgabetoolAssistenz = {
if(this.notenOptions && projekt.note) {
const opt = this.notenOptions.find(n => n.note == projekt.note)
// TODO: mehrsprachig englisch
// TODO: mehrsprachig englisch -> nevermind the english field in
// notenoption->bezeichnung_mehrsprachig is ALWAYS german
projekt.note_bez = opt.bezeichnung
}
@@ -1851,20 +1872,17 @@ export const AbgabetoolAssistenz = {
if(typeof termin.note !== 'object') {
termin.note = this.allowedNotenOptions.find(opt => opt.note == termin.note)
}
// only set this if it has not been set yet and abgabetermin has a note (qgate)
if(!termin.noteBackend && termin.note) {
termin.noteBackend = termin.note
}
termin.file = []
// assistenz should be able to edit every abgabe
// update 21-01-2026: actually blocking operations on finished projektarbeiten seems like a decent idea
const terminHasAbgabe = termin.abgabedatum != null
const terminHasNote = termin.noteBackend
termin.allowedToSave = paIsBenotet ? false : true
// assistenz are not allowed to delete deadlines with existing submissions
termin.allowedToDelete = paIsBenotet ? false : !termin.abgabedatum && !termin.note
termin.allowedToDelete = paIsBenotet || terminHasNote || terminHasAbgabe ? false : true
})
@@ -1876,21 +1894,54 @@ export const AbgabetoolAssistenz = {
this.$refs.modalContainerAbgabeDetail.show()
},
openTimeline(val) {
const projekt = this.projektarbeiten.find(p => p.projektarbeit_id == val.projektarbeit_id)
if(!projekt) {
this.$api.call(ApiAbgabe.fetchProjektarbeitenHistory(val.student_uid)).then(res => {
console.log(res)
this.$fhcAlert.alertInfo('Keine projektarbeit gefunden')
return
}
projekt.abgabetermine.forEach(termin => {
// show note only on termine with abgabetypen which are benotbar
const terminTypOpt = this.abgabeTypeOptions.find(opt => opt.paabgabetyp_kurzbz == termin.paabgabetyp_kurzbz)
termin.benotbar = terminTypOpt.benotbar
res.data.forEach(projekt => {
projekt.abgabetermine?.forEach(termin => {
// only set this if it has not been set yet and abgabetermin has a note (qgate)
if(!termin.noteBackend && termin.note) {
termin.noteBackend = this.notenOptions.find(opt => termin.note == opt.note)
}
termin.dateStyle = getDateStyleClass(termin, this.notenOptions)
const terminTypOpt = this.abgabeTypeOptions.find(opt => opt.paabgabetyp_kurzbz == termin.paabgabetyp_kurzbz)
if (terminTypOpt) termin.benotbar = terminTypOpt.benotbar
})
})
// keep the history API stub for future use
this.timelineProjekte = res.data
this.$refs.drawer.show()
})
this.timelineProjekt = projekt
this.$refs.drawer.show()
},
// openTimeline(val) {
//
// this.$api.call(ApiAbgabe.fetchProjektarbeitenHistory(val.student_uid)).then(res => {
// console.log(res)
// })
//
// const projekt = this.projektarbeiten.find(p => p.projektarbeit_id == val.projektarbeit_id)
// if(!projekt) {
//
// this.$fhcAlert.alertInfo('Keine projektarbeit gefunden')
//
// return
// }
//
// projekt.abgabetermine.forEach(termin => {
// // show note only on termine with abgabetypen which are benotbar
// const terminTypOpt = this.abgabeTypeOptions.find(opt => opt.paabgabetyp_kurzbz == termin.paabgabetyp_kurzbz)
// termin.benotbar = terminTypOpt.benotbar
// })
//
// this.timelineProjekt = projekt
// this.$refs.drawer.show()
// },
paabgabetypFormatter(cell) {
const key = cell.getValue()
return this.$p.t('abgabetool/c4paatyp' + key)
@@ -2100,8 +2151,14 @@ export const AbgabetoolAssistenz = {
if (!this.projektarbeiten) return [];
return this.projektarbeiten.flatMap(pa =>
pa.abgabetermine.map(termin => {
const allowedToSave = pa.note !== null ? false : true
const allowedToDelete = pa.note !== null ? false : !termin.abgabedatum && !termin.note
const terminHasAbgabe = termin.abgabedatum != null
const terminHasNote = termin.noteBackend
// IN multiedit changing anything for a termin with an existing note is not allowed anymore, to avoid
// confusing UX behaviour why some fields could be edited and others not -> just edit in detail view
const allowedToSave = pa.note !== null || terminHasNote || terminHasAbgabe ? false : true
const allowedToDelete = pa.note !== null || terminHasNote || terminHasAbgabe ? false : true
return {
allowedToSave,
allowedToDelete,
@@ -2554,102 +2611,11 @@ export const AbgabetoolAssistenz = {
{{ $p.t('abgabetool/c4projektarbeitTimelineTitle') }}
</template>
<div class="row" style="margin-bottom: 12px;">
<Inplace
closable
:closeButtonProps="{
style: {
position: 'absolute',
top: '80px',
right: '80px',
zIndex: 1
}
}"
>
<template #display> {{ $capitalize($p.t('abgabetool/showStudentDetails'))}} </template>
<template #content>
<div class="col-auto">
<div class="row">
<div class="col-3">Student: </div>
<div class="col-7">{{timelineProjekt?.student_vorname}} {{timelineProjekt?.student_nachname}}</div>
</div>
<div class="row">
<div class="col-3">Uid: </div>
<div class="col-7">{{timelineProjekt?.student_uid}}</div>
</div>
<div class="row">
<div class="col-3">{{timelineProjekt?.betreuerart}}: </div>
<div class="col-7">{{timelineProjekt?.erstbetreuer_full_name}}</div>
</div>
<div class="row">
<div class="col-3">Titel: </div>
<div class="col-7">{{timelineProjekt?.titel}}</div>
</div>
</div>
</template>
</Inplace>
</div>
<Timeline
:value="timelineProjekt?.abgabetermine"
align="right"
>
<template #marker="slotProps">
<div :class="slotProps.item.dateStyle + '-header'" style="height: 48px; width:48px; padding: 0px; display: flex; align-items: center; justify-content: center;">
<i v-if="slotProps.item.dateStyle == 'verspaetet'" class="fa-solid fa-triangle-exclamation"></i>
<i v-else-if="slotProps.item.dateStyle == 'verpasst'" class="fa-solid fa-calendar-xmark"></i>
<i v-else-if="slotProps.item.dateStyle == 'abzugeben'" class="fa-solid fa-hourglass-half"></i>
<i v-else-if="slotProps.item.dateStyle == 'standard'" class="fa-solid fa-clock"></i>
<i v-else-if="slotProps.item.dateStyle == 'abgegeben'" class="fa-solid fa-paperclip"></i>
<i v-else-if="slotProps.item.dateStyle == 'beurteilungerforderlich'" class="fa-solid fa-list-check"></i>
<i v-else-if="slotProps.item.dateStyle == 'bestanden'" class="fa-solid fa-check"></i>
<i v-else-if="slotProps.item.dateStyle == 'nichtbestanden'" class="fa-solid fa-circle-exclamation"></i>
</div>
</template>
<template #opposite="slotProps">
<div class="row g-1">
<div class="col-5 fw-semibold text-end">
{{ $capitalize($p.t('abgabetool/c4zieldatumv2')) }}:
</div>
<div class="col-7">
{{ formatDate(slotProps.item.datum) }}
</div>
</div>
</template>
<template #content="slotProps">
<div class="row g-1">
<div class="col-5 fw-semibold text-end">
{{ $capitalize($p.t('abgabetool/c4abgabetyp')) }}:
</div>
<div class="col-7">
{{ getItemBezeichnung(slotProps.item) }}
</div>
<div class="col-5 fw-semibold text-end">
{{ $capitalize($p.t('abgabetool/c4abgabedatum')) }}:
</div>
<div class="col-7">
{{ formatDate(slotProps.item.abgabedatum) }}
</div>
<div v-if="slotProps.item.benotbar" class="col-5 fw-semibold text-end">
{{ $capitalize($p.t('abgabetool/c4note')) }}:
</div>
<div v-if="slotProps.item.benotbar" class="col-7">
{{ getItemNote(slotProps.item) }}
</div>
</div>
<hr/>
</template>
</Timeline>
<template #footer>
<AbgabeterminStatusLegende></AbgabeterminStatusLegende>
</template>
<AbgabeStudentTimeline
:projekte="timelineProjekte"
:notenOptions="notenOptions"
:formatDateFn="formatDate"
/>
</BsOffcanvas>
<div id="abgabetable" style="max-height:40vw;">
@@ -2713,6 +2679,7 @@ export const AbgabetoolAssistenz = {
>
<template #actions>
<Dropdown
v-if="curSem"
@change="semesterChanged"
:placeholder="$capitalize($p.t('lehre/studiensemester'))"
:style="{'scroll-behavior': 'auto !important'}"
@@ -137,15 +137,31 @@ export const AbgabetoolMitarbeiter = {
cssClass: 'sticky-col',
visible: true
},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4details'))), field: 'details', formatter: this.formAction, headerFilter: false, headerSort: false, minWidth: 85, visible: true, tooltip: false, cssClass: 'sticky-col'},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4details'))), field: 'details', formatter: this.formAction, headerFilter: false, headerSort: false, minWidth: 140, visible: true, tooltip: false, cssClass: 'sticky-col'},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4personenkennzeichen'))), headerFilter: true, field: 'pkz', formatter: this.pkzTextFormatter, minWidth: 140, visible: false,tooltip: false},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4vorname'))), field: 'vorname', headerFilter: true, formatter: this.centeredTextFormatter, minWidth: 100,visible: false},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4nachname'))), field: 'nachname', headerFilter: true, formatter: this.centeredTextFormatter, minWidth: 100,visible: true},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4studstatus'))), field: 'studienstatus', headerFilter: true, formatter: this.centeredTextFormatter, minWidth: 150, visible: false},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4orgformv2'))), field: 'orgform', headerFilter: true, formatter: this.centeredTextFormatter, minWidth: 50, visible: false},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4projekttyp'))), field: 'projekttyp_kurzbz', formatter: this.centeredTextFormatter, minWidth: 100,visible: true},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4stg'))), field: 'stg', headerFilter: true, formatter: this.centeredTextFormatter, minWidth: 50, visible: true},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4note'))), field: 'note_bez', headerFilter: true, visible: false, sorter: this.notenSorter, minWidth: 200, formatter: this.centeredTextFormatter},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4sem'))), field: 'studiensemester_kurzbz', headerFilter: true, formatter: this.centeredTextFormatter, visible: true, minWidth: 100},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4titel'))), field: 'titel', headerFilter: true, formatter: this.centeredTextFormatter, minWidth: 100, width: 500, visible: true},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4betreuerartv2'))), field: 'betreuerart_beschreibung',formatter: this.centeredTextFormatter, visible: true, minWidth: 100, width: 200},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4erstbetreuerv2'))), field: 'betreuer_full_name', headerFilter: true, formatter: this.centeredTextFormatter, minWidth: 100, visible: false},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4erstbetreuerTitelPre'))), field: 'betreuer_titelpre', headerFilter: true, formatter: this.centeredTextFormatter, minWidth: 100, visible: false},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4erstbetreuerVorname'))), field: 'betreuer_vorname', headerFilter: true, formatter: this.centeredTextFormatter, minWidth: 100, visible: true},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4erstbetreuerNachname'))), field: 'betreuer_nachname', headerFilter: true, formatter: this.centeredTextFormatter, minWidth: 100, visible: true},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4erstbetreuerTitelPost'))), field: 'betreuer_titelpost', headerFilter: true, formatter: this.centeredTextFormatter, minWidth: 100, visible: false},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4zweitbetreuerv2'))), field: 'zweitbetreuer_full_name', headerFilter: true, formatter: this.centeredTextFormatter, minWidth: 100, visible: false},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4zweitbetreuerTitelPre'))), field: 'zweitbetreuer_titelpre', headerFilter: true, formatter: this.centeredTextFormatter, minWidth: 100, visible: false},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4zweitbetreuerVorname'))), field: 'zweitbetreuer_vorname', headerFilter: true, formatter: this.centeredTextFormatter, minWidth: 100, visible: false},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4zweitbetreuerNachname'))), field: 'zweitbetreuer_nachname', headerFilter: true, formatter: this.centeredTextFormatter, minWidth: 100, visible: false},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4zweitbetreuerTitelPost'))), field: 'zweitbetreuer_titelpost', headerFilter: true, formatter: this.centeredTextFormatter, minWidth: 100, visible: false},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4prevAbgabetermin'))),
headerFilter: dateFilter,
headerFilterFunc: this.headerFilterTerminCol,
@@ -248,6 +264,11 @@ export const AbgabetoolMitarbeiter = {
]};
},
methods: {
notenSorter(a, b, aRow, bRow, column, dir, sorterParams) {
const aData = aRow.getData()
const bData = bRow.getData()
return aData.note - bData.note
},
async openBenotung(type, link) {
if(type === 'new') {
window.open(link, '_blank')
@@ -277,6 +298,7 @@ export const AbgabetoolMitarbeiter = {
}
},
formAction(cell) {
const actionButtons = document.createElement('div');
actionButtons.className = "d-flex gap-3";
actionButtons.style.display = "flex";
@@ -316,9 +338,27 @@ export const AbgabetoolMitarbeiter = {
} else if(data.abgabetermine?.find(termin => termin.paabgabetyp_kurzbz == 'end' && termin.abgabedatum !== null) && data.beurteilungLinkOld) {
actionButtons.append(createButton('fa fa-user-check', 'abgabetool/c4benoten', () => this.openBenotung('old', data.beurteilungLinkOld)))
}
if(this.checkForZweitbetreuerTokenMailAvailability(data)) {
actionButtons.append(createButton('fa fa-envelope-open-text', 'abgabetool/c4zweitBegutachterTokenMailSenden', () => this.sendZweitbetreuerToken(data)))
}
return actionButtons;
},
checkForZweitbetreuerTokenMailAvailability(data) {
const hasEndabgabeWithUpload = !!data.abgabetermine.find(termin => termin.abgabedatum !== null && termin.paabgabetyp_kurzbz == 'end')
const hasZweitbetreuerWithoutBenutzerUid = data.zweitbetreuer_person_id !== null && data.zweitbetreuer_benutzer_uid === null
return hasEndabgabeWithUpload && hasZweitbetreuerWithoutBenutzerUid
},
sendZweitbetreuerToken(data) {
this.$api.call(ApiAbgabe.sendZweitbetreuerTokenMail(data.projektarbeit_id, data.betreuer_person_id, data.student_uid))
.then(res => {
if(res.meta.status == 'success') this.$fhcAlert.alertSuccess(this.$p.t('abgabetool/c4zweitBegutachterTokenMailSuccess'))
})
},
getDateStyleHtml(dateStyle) {
const iconMap = {
'verspaetet': '<i class="fa-solid fa-triangle-exclamation"></i>',
@@ -1174,14 +1214,19 @@ export const AbgabetoolMitarbeiter = {
return (projekt.typ + projekt.kurzbz)?.toUpperCase()
},
setupData(data){
this.domain = data[1]
this.projektarbeiten = data[0]?.retval?.map(projekt => {
this.checkAbgabetermineProjektarbeit(projekt)
projekt.selectable = projekt.betreuerart_kurzbz !== 'Zweitbegutachter'
if(this.notenOptions && projekt.note) {
const opt = this.notenOptions.find(n => n.note == projekt.note)
// TODO: mehrsprachig englisch -> nevermind the english field in
// notenoption->bezeichnung_mehrsprachig is ALWAYS german
projekt.note_bez = opt?.bezeichnung
}
return {
...projekt,
details: {
@@ -1249,7 +1294,7 @@ export const AbgabetoolMitarbeiter = {
}
this.serienTermin.upload_allowed = newVal.upload_allowed_default
},
}
},
computed: {
countsToHTML() {
@@ -402,7 +402,7 @@ export const AbgabetoolStudent = {
</label>
<Textarea
v-model="editingTitel"
rows="10"
rows="2"
maxlength="1024"
class="form-control w-100"
@keydown.enter.prevent="saveTitel"
@@ -11,6 +11,13 @@ export function formatISODate(dateParam) {
return date.isValid ? date.toFormat('dd.MM.yyyy') : '';
}
export function formatDateTime(dateParam) {
if (!dateParam) return '';
const date = luxon.DateTime.fromSQL(dateParam);
return date.isValid ? date.toFormat("dd.MM.yyyy HH:mm") : '';
}
export function toViennaDate(dateParam) {
if (!dateParam) return null;
@@ -293,6 +293,21 @@ export default {
this.$refs.projektbetreuerTable.tabulator.replaceData(this.addIds(result.data));
})
.catch(this.$fhcAlert.handleSystemError);
// get other initial data
this.$api
.call(ApiStvProjektbetreuer.getBetreuerarten())
.then(result => {
this.arrBetreuerart = result.data;
})
.catch(this.$fhcAlert.handleSystemError);
this.$api
.call(ApiStvProjektbetreuer.getNoten())
.then(result => {
this.arrNoten = result.data;
})
.catch(this.$fhcAlert.handleSystemError);
} else {
this.emptyBetreuerList();
}
@@ -547,6 +562,9 @@ export default {
</form-form>
<button class="btn btn-primary" v-show="betreuerFormOpened" @click="saveProjektbetreuer">
{{ $p.t('projektarbeit', 'betreuerSpeichernv2') }}
</button>
<!-- <div class = "mt-5" v-if="beurteilungDownloadLink !== null">
<div class="mb-1">
<a :href="beurteilungDownloadLink" class="btn btn-primary d-block" :class="{ 'disabled' : beurteilungDownloadLink === ''}">
-1
View File
@@ -1,6 +1,5 @@
export async function splitMailsHelper(mails, event, subject, body, alertPluginRef, phrasenPluginRef) {
await phrasenPluginRef.loadCategory('ui');
debugger
let splititem = ",";
let maillist = mails.join(splititem);
let useBcc = event?.ctrlKey || event?.metaKey;
+122
View File
@@ -0,0 +1,122 @@
// DatesManual.js: implemented custom input handling for tabulator date header filter,
// since primevue3 calendar manual input seems to be broken in this case. Main difference
// to normal Dates.js headerfilter is that this one does not automatically open the calendar
// overlay, as it tries to aggressively steal the focus away from the input field. try it out!
// Custom evaluation logic so Tabulator knows how to filter the array of Date objects
Tabulator.extendModule('filter', 'filters', {
"dates": (headerValue, rowValue) => {
if (!headerValue) return true;
let rowDate = new Date(rowValue);
if (Array.isArray(headerValue)) {
let startDate = new Date(headerValue[0]);
if (headerValue[1]) {
let endDate = new Date(headerValue[1]);
endDate.setHours(23, 59, 59, 999);
return rowDate >= startDate && rowDate <= endDate;
}
return rowDate.toDateString() === startDate.toDateString();
}
let singleDate = new Date(headerValue);
return rowDate.toDateString() === singleDate.toDateString();
}
});
export function dateFilter(cell, onRendered, success) {
let div = document.createElement('div');
let initialValue = null;
let val = cell.getValue();
if (Array.isArray(val)) {
const start = val[0] ? new Date(val[0]) : null;
const end = val[1] ? new Date(val[1]) : null;
initialValue = [start, end];
}
// Manual parser needed since we are bypassing PrimeVue's broken manualInput mode
function parseDMY(str) {
const m = str.trim().match(/^(\d{1,2})\.(\d{1,2})\.(\d{2,4})$/);
if (!m) return null;
let year = parseInt(m[3]);
if (year < 100) year += 2000;
const d = new Date(year, parseInt(m[2]) - 1, parseInt(m[1]));
return isNaN(d.getTime()) ? null : d;
}
// String formatter to sync the raw text field when dates are picked via the calendar UI
function formatRange(dates) {
if (!dates) return '';
const fmt = d => d
? `${String(d.getDate()).padStart(2,'0')}.${String(d.getMonth()+1).padStart(2,'0')}.${d.getFullYear()}`
: '';
if (Array.isArray(dates)) {
return fmt(dates[0]) + (dates[1] ? ` - ${fmt(dates[1])}` : '');
}
return fmt(dates);
}
Vue.createApp({
components: { PrimevueCalendar: primevue.calendar },
data() {
return {
// Split state into a proper Date object (calendar) and a raw string (text input)
calVal: initialValue,
textVal: formatRange(initialValue)
};
},
watch: {
calVal(n) {
// Centralized synchronization: updates text representation
// AND safely notifies Tabulator of the value change
this.textVal = formatRange(n);
success(n);
}
},
methods: {
// Translates the typed string back into Date objects for Tabulator's filter
onTextChange() {
if (!this.textVal) {
this.calVal = null; // Triggers watcher -> success(null)
return;
}
const parts = this.textVal.split(/\s*-\s*/);
const start = parseDMY(parts[0]);
const end = parts[1] ? parseDMY(parts[1]) : null;
if (start) {
// Changing calVal automatically triggers the watcher,
// which handles executing success() exactly once.
this.calVal = [start, end];
}
}
},
// Native HTML input handles typing, PrimeVue calendar input is hidden (icon-only)
// Placeholder removed to match the rest of the application's header filters
template: `
<div style="display:flex;align-items:center;width:100%">
<input
type="text"
v-model="textVal"
@change="onTextChange"
@keydown.stop
@keypress.stop
@keyup.stop
@mousedown.stop
class="p-inputtext p-component"
style="flex:1;min-width:0"
/>
<primevue-calendar
v-model="calVal"
selection-mode="range"
show-button-bar
:showIcon="true"
:input-style="{display:'none'}"
dateFormat="dd.mm.yy">
</primevue-calendar>
</div>`
}).use(primevue.config.default).mount(div);
return div;
}
+164 -64
View File
@@ -1036,7 +1036,7 @@ $phrases = array(
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'BewerberIn möglicherweise vorhanden',
'text' => 'Bewerber*In möglicherweise vorhanden',
'description' => '',
'insertvon' => 'system'
),
@@ -2134,6 +2134,26 @@ $phrases = array(
)
)
),
array(
'app' => 'core',
'category' => 'ui',
'phrase' => 'savedAtByV3',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Gespeichert am {0} von {1}',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Saved on {0} by {1}',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'anrechnung',
@@ -3771,7 +3791,7 @@ $phrases = array(
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'LektorIn',
'text' => 'Lektor*In',
'description' => '',
'insertvon' => 'system'
),
@@ -4400,7 +4420,7 @@ $phrases = array(
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'BewerberIn',
'text' => 'Bewerber*In',
'description' => '',
'insertvon' => 'system'
),
@@ -5300,7 +5320,7 @@ $phrases = array(
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'BewerberIn parken',
'text' => 'Bewerber*In parken',
'description' => '',
'insertvon' => 'system'
),
@@ -5320,7 +5340,7 @@ $phrases = array(
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'BewerberIn ausparken',
'text' => 'Bewerber*In ausparken',
'description' => '',
'insertvon' => 'system'
),
@@ -5400,7 +5420,7 @@ $phrases = array(
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'BewerberIn geparkt bis',
'text' => 'Bewerber*In geparkt bis',
'description' => '',
'insertvon' => 'system'
),
@@ -5460,7 +5480,7 @@ $phrases = array(
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'BewerberIn zurückstellen',
'text' => 'Bewerber*In zurückstellen',
'description' => '',
'insertvon' => 'system'
),
@@ -5500,7 +5520,7 @@ $phrases = array(
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'BewerberIn zurückgestellt bis',
'text' => 'Bewerber*In zurückgestellt bis',
'description' => '',
'insertvon' => 'system'
),
@@ -5580,8 +5600,8 @@ $phrases = array(
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Geparkte und zurückgestellte BewerberInnen werden von der Bearbeitung temporär ausgenommen.
Geparkte BewerberInnen werden zum angegebenen Datum automatisch entparkt, während zurückgestellte BewerberInnen nur manuell durch Drücken des Buttons den Zurückgestellt-Status verlieren.
'text' => 'Geparkte und zurückgestellte Bewerber*Innen werden von der Bearbeitung temporär ausgenommen.
Geparkte Bewerber*Innen werden zum angegebenen Datum automatisch entparkt, während zurückgestellte Bewerber*Innen nur manuell durch Drücken des Buttons den Zurückgestellt-Status verlieren.
Bei einer Zurückstellung dient das Datum nur der Erinnerung.',
'description' => '',
'insertvon' => 'system'
@@ -8811,7 +8831,7 @@ The invoice will be sent to you again. <u><strong>The amount is only to be trans
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'ANNEHMEN<br>(LektorIn)',
'text' => 'ANNEHMEN<br>(Lektor*In)',
'description' => '',
'insertvon' => 'system'
),
@@ -13682,7 +13702,7 @@ Any unusual occurrences
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'StudentIn',
'text' => 'Student*In',
'description' => '',
'insertvon' => 'system'
),
@@ -15858,7 +15878,7 @@ array(
'phrases' => array(
array(
'sprache' => 'German',
'text' => "Alle anzeigen, die auf Empfehlung von LektorIn warten.",
'text' => "Alle anzeigen, die auf Empfehlung von Lektor*In warten.",
'description' => '',
'insertvon' => 'system'
),
@@ -25145,7 +25165,7 @@ array(
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'StudentIn ({prestudent_id})',
'text' => 'Student*In ({prestudent_id})',
'description' => '',
'insertvon' => 'system'
),
@@ -28188,7 +28208,7 @@ array(
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'MitarbeiterIn',
'text' => 'Mitarbeiter*In',
'description' => '',
'insertvon' => 'system'
),
@@ -29191,7 +29211,7 @@ array(
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'MitarbeiterIn',
'text' => 'Mitarbeiter*In',
'description' => '',
'insertvon' => 'system'
),
@@ -29231,7 +29251,7 @@ array(
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Mitarbeiter Information',
'text' => 'Mitarbeiter*In Information',
'description' => '',
'insertvon' => 'system'
),
@@ -29251,7 +29271,7 @@ array(
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'StudentIn',
'text' => 'Student*In',
'description' => '',
'insertvon' => 'system'
),
@@ -29271,7 +29291,7 @@ array(
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Student Information',
'text' => 'Student*In Information',
'description' => '',
'insertvon' => 'system'
),
@@ -37852,7 +37872,7 @@ array(
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'In welches Semester sollen diese {count} PrestudentInnen ({status})verschoben werden?',
'text' => 'In welches Semester sollen diese {count} Prestudent*Innen ({status})verschoben werden?',
'description' => '',
'insertvon' => 'system'
),
@@ -40097,7 +40117,7 @@ array(
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'InteressentIn',
'text' => 'Interessent*In',
'description' => '',
'insertvon' => 'system'
),
@@ -40277,7 +40297,7 @@ array(
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'PreStudentIn',
'text' => 'PreStudent*In',
'description' => '',
'insertvon' => 'system'
),
@@ -41241,7 +41261,7 @@ array(
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'LektorIn',
'text' => 'Lektor*In',
'description' => '',
'insertvon' => 'system'
),
@@ -41381,7 +41401,7 @@ array(
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'StudentIn hat keinen Status in diesem Semester',
'text' => 'Student*In hat keinen Status in diesem Semester',
'description' => '',
'insertvon' => 'system'
),
@@ -42289,7 +42309,7 @@ array(
'phrases' => array(
array(
'sprache' => 'German',
'text' => "Dokumentabgabe - MitarbeiterInbereich",
'text' => "Dokumentabgabe - Mitarbeiter*Inbereich",
'description' => '',
'insertvon' => 'system'
),
@@ -44095,7 +44115,7 @@ array(
'phrases' => array(
array(
'sprache' => 'German',
'text' => "BetreuerIn",
'text' => "Betreuer*In",
'description' => '',
'insertvon' => 'system'
),
@@ -44170,7 +44190,7 @@ array(
array(
'app' => 'core',
'category' => 'abgabetool',
'phrase' => 'c4abgabeStudentenbereich',
'phrase' => 'c4abgabeStudentenbereichv2',
'insertvon' => 'system',
'phrases' => array(
array(
@@ -45135,7 +45155,7 @@ array(
'phrases' => array(
array(
'sprache' => 'German',
'text' => "Projektarbeit Detailansicht MitarbeiterIn",
'text' => "Projektarbeit Detailansicht Mitarbeiter*In",
'description' => '',
'insertvon' => 'system'
),
@@ -45691,7 +45711,7 @@ array(
'phrases' => array(
array(
'sprache' => 'German',
'text' => "E-Mail BetreuerIn",
'text' => "E-Mail Betreuer*In",
'description' => '',
'insertvon' => 'system'
),
@@ -45731,7 +45751,7 @@ array(
'phrases' => array(
array(
'sprache' => 'German',
'text' => "ErstbetreuerIn Beurteilung herunterladen",
'text' => "Erstbetreuer*In Beurteilung herunterladen",
'description' => '',
'insertvon' => 'system'
),
@@ -45751,7 +45771,7 @@ array(
'phrases' => array(
array(
'sprache' => 'German',
'text' => "ZweitbetreuerIn Beurteilung herunterladen",
'text' => "Zweitbetreuer*In Beurteilung herunterladen",
'description' => '',
'insertvon' => 'system'
),
@@ -45931,7 +45951,7 @@ array(
'phrases' => array(
array(
'sprache' => 'German',
'text' => "Fehler beim Laden der Projektarbeit - StudentIn Zuordnung",
'text' => "Fehler beim Laden der Projektarbeit - Student*In Zuordnung",
'description' => '',
'insertvon' => 'system'
),
@@ -45951,7 +45971,7 @@ array(
'phrases' => array(
array(
'sprache' => 'German',
'text' => "Kein zugeteilter StudentIn gefunden für Projektarbeit",
'text' => "Kein zugeteilter Student*In gefunden für Projektarbeit",
'description' => '',
'insertvon' => 'system'
),
@@ -46226,7 +46246,7 @@ array(
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'ErstbetreuerIn',
'text' => 'Erstbetreuer*In',
'description' => '',
'insertvon' => 'system'
),
@@ -46246,7 +46266,7 @@ array(
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'ZweitbetreuerIn',
'text' => 'Zweitbetreuer*In',
'description' => '',
'insertvon' => 'system'
),
@@ -46458,6 +46478,46 @@ array(
)
)
),
array(
'app' => 'core',
'category' => 'abgabetool',
'phrase' => 'c4zweitBegutachterTokenMailSenden',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Access Token E-Mail an Zweitbegutachter*In senden',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Send access token email to second reviewer',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'abgabetool',
'phrase' => 'c4zweitBegutachterTokenMailSuccess',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Access Token E-Mail an Zweitbegutachter*In wurde erfolgreich versandt.',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Access token email has been successfully sent to second reviewer.',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'abgabetool',
@@ -46911,7 +46971,7 @@ array(
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'BetreuerIn Berufspraktikum oder Projektarbeit',
'text' => 'Betreuer*In Berufspraktikum oder Projektarbeit',
'description' => '',
'insertvon' => 'system'
),
@@ -47192,7 +47252,7 @@ array(
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'ErstbetreuerIn Titel Pre',
'text' => 'Erstbetreuer*In Titel Pre',
'description' => '',
'insertvon' => 'system'
),
@@ -47212,7 +47272,7 @@ array(
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'ErstbetreuerIn Vorname',
'text' => 'Erstbetreuer*In Vorname',
'description' => '',
'insertvon' => 'system'
),
@@ -47232,7 +47292,7 @@ array(
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'ErstbetreuerIn Nachname',
'text' => 'Erstbetreuer*In Nachname',
'description' => '',
'insertvon' => 'system'
),
@@ -47252,7 +47312,7 @@ array(
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'ErstbetreuerIn Titel Post',
'text' => 'Erstbetreuer*In Titel Post',
'description' => '',
'insertvon' => 'system'
),
@@ -47272,7 +47332,7 @@ array(
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'ZweitbetreuerIn Titel Pre',
'text' => 'Zweitbetreuer*In Titel Pre',
'description' => '',
'insertvon' => 'system'
),
@@ -47292,7 +47352,7 @@ array(
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'ZweitbetreuerIn Vorname',
'text' => 'Zweitbetreuer*In Vorname',
'description' => '',
'insertvon' => 'system'
),
@@ -47312,7 +47372,7 @@ array(
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'ZweitbetreuerIn Nachname',
'text' => 'Zweitbetreuer*In Nachname',
'description' => '',
'insertvon' => 'system'
),
@@ -47332,7 +47392,7 @@ array(
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'ZweitbetreuerIn Titel Post',
'text' => 'Zweitbetreuer*In Titel Post',
'description' => '',
'insertvon' => 'system'
),
@@ -47424,6 +47484,46 @@ array(
)
)
),
array(
'app' => 'core',
'category' => 'abgabetool',
'phrase' => 'c4abgabetypAendernNichtErlaubt',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Ändern des Abgabetyps ist nach erfolgtem Upload oder einer erfolgten Benotung nicht erlaubt.',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'You are not allowed to change the submission type after the upload is complete or after grades have been assigned.',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'abgabetool',
'phrase' => 'c4datumAendernNichtErlaubt',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Ändern des Termin Zieldatums ist nach erfolgtem Upload oder einer erfolgten Benotung nicht erlaubt.',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'You are not allowed to change the deadline date after the upload is complete or after grades have been assigned.',
'description' => '',
'insertvon' => 'system'
)
)
),
// ABGABETOOL PHRASEN END
array(
'app' => 'core',
@@ -48099,7 +48199,7 @@ array(
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'PrüferIn 1',
'text' => 'Prüfer*In 1',
'description' => '',
'insertvon' => 'system'
),
@@ -48119,7 +48219,7 @@ array(
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'PrüferIn 2',
'text' => 'Prüfer*In 2',
'description' => '',
'insertvon' => 'system'
),
@@ -48139,7 +48239,7 @@ array(
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'PrüferIn 3',
'text' => 'Prüfer*In 3',
'description' => '',
'insertvon' => 'system'
),
@@ -49243,7 +49343,7 @@ array(
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'PrestudentIn ID',
'text' => 'Prestudent*In ID',
'description' => '',
'insertvon' => 'system'
),
@@ -51909,7 +52009,7 @@ and represent the current state of research on the topic. The prescribed citatio
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'LektorIn hinzufügen',
'text' => 'Lektor*In hinzufügen',
'description' => '',
'insertvon' => 'system'
),
@@ -52291,7 +52391,7 @@ and represent the current state of research on the topic. The prescribed citatio
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'SenderIn',
'text' => 'Sender*In',
'description' => '',
'insertvon' => 'system'
),
@@ -52311,7 +52411,7 @@ and represent the current state of research on the topic. The prescribed citatio
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'EmpfängerIn',
'text' => 'Empfänger*In',
'description' => '',
'insertvon' => 'system'
),
@@ -52351,7 +52451,7 @@ and represent the current state of research on the topic. The prescribed citatio
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'EmpfängerIn ID',
'text' => 'Empfänger*In ID',
'description' => '',
'insertvon' => 'system'
),
@@ -54388,7 +54488,7 @@ and represent the current state of research on the topic. The prescribed citatio
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Bitte StudentIn auswählen!',
'text' => 'Bitte Student*In auswählen!',
'description' => '',
'insertvon' => 'system'
),
@@ -54853,7 +54953,7 @@ and represent the current state of research on the topic. The prescribed citatio
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'MitarbeiterIn',
'text' => 'Mitarbeiter*In',
'description' => '',
'insertvon' => 'system'
),
@@ -56289,7 +56389,7 @@ I have been informed that I am under no obligation to consent to the transmissio
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'BetreuerIn',
'text' => 'Betreuer*In',
'description' => '',
'insertvon' => 'system'
),
@@ -56309,7 +56409,7 @@ I have been informed that I am under no obligation to consent to the transmissio
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'BetreuerIn',
'text' => 'Betreuer*In',
'description' => '',
'insertvon' => 'system'
),
@@ -56529,7 +56629,7 @@ I have been informed that I am under no obligation to consent to the transmissio
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Löschen nicht möglich, dieser Projektarbeit sind bereits BetreuerInnen zugewiesen',
'text' => 'Löschen nicht möglich, dieser Projektarbeit sind bereits Betreuer*Innen zugewiesen',
'description' => '',
'insertvon' => 'system'
),
@@ -56569,7 +56669,7 @@ I have been informed that I am under no obligation to consent to the transmissio
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'ProjektbetreuerIn ungültig',
'text' => 'Projektbetreuer*In ungültig',
'description' => '',
'insertvon' => 'system'
),
@@ -56589,7 +56689,7 @@ I have been informed that I am under no obligation to consent to the transmissio
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Löschen nicht möglich, ProjektbetreuerIn hat bereits einen Vertrag',
'text' => 'Löschen nicht möglich, Projektbetreuer*In hat bereits einen Vertrag',
'description' => '',
'insertvon' => 'system'
),
@@ -57029,7 +57129,7 @@ I have been informed that I am under no obligation to consent to the transmissio
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'BetreuerIn bearbeiten',
'text' => 'Betreuer*In bearbeiten',
'description' => '',
'insertvon' => 'system'
),
@@ -57044,12 +57144,12 @@ I have been informed that I am under no obligation to consent to the transmissio
array(
'app' => 'core',
'category' => 'projektarbeit',
'phrase' => 'betreuerSpeichern',
'phrase' => 'betreuerSpeichernv2',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'BetreuerIn speichern',
'text' => 'Betreuer*In speichern',
'description' => '',
'insertvon' => 'system'
),
@@ -58186,7 +58286,7 @@ I have been informed that I am under no obligation to consent to the transmissio
),
array(
'sprache' => 'English',
'text' => 'Saved on {date} von {name}',
'text' => 'Saved on {date} by {name}',
'description' => '',
'insertvon' => 'system'
)
@@ -58906,7 +59006,7 @@ I have been informed that I am under no obligation to consent to the transmissio
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'StudentIn ist in diesem Semester keinem Lehrverband zugeteilt',
'text' => 'Student*In ist in diesem Semester keinem Lehrverband zugeteilt',
'description' => '',
'insertvon' => 'system'
),