upload_required => allowed; hardcoded links => config; styling; endupload/qualgate logic; phrasen;

This commit is contained in:
Johann Hoffmann
2025-08-29 14:15:20 +02:00
parent 63390b192c
commit 1f0fe08b69
13 changed files with 634 additions and 308 deletions
+9
View File
@@ -0,0 +1,9 @@
<?php
if (!defined('BASEPATH')) exit('No direct script access allowed');
$config['turnitin_link'] = 'https://technikum-wien.turnitin.com/sso/sp/redwood/saml/5IyfmBr2OcSIaWQTKlFCGj/start';
$config['old_abgabe_beurteilung_link'] = 'https://moodle.technikum-wien.at/mod/page/view.php?id=1005052';
@@ -36,6 +36,7 @@ class Abgabe extends FHCAPI_Controller
public function __construct()
{
parent::__construct([
'getConfig' => self::PERM_LOGGED,
'getStudentProjektarbeiten' => self::PERM_LOGGED, // TODO: abgabetool berechtigung?
'getStudentProjektabgaben' => self::PERM_LOGGED,
'postStudentProjektarbeitZwischenabgabe' => self::PERM_LOGGED,
@@ -70,6 +71,22 @@ class Abgabe extends FHCAPI_Controller
//------------------------------------------------------------------------------------------------------------------
// Public methods
/**
* loads config related to abgabetool, found in application/config/abgabe
*/
public function getConfig() {
$this->load->config('abgabe');
$old_abgabe_beurteilung_link =$this->config->item('old_abgabe_beurteilung_link');
$turnitin_link =$this->config->item('turnitin_link');
$ret = array(
'old_abgabe_beurteilung_link' => $old_abgabe_beurteilung_link,
'turnitin_link' => $turnitin_link
);
$this->terminateWithSuccess($ret);
}
/**
* fetches all projektabgabetermine for a given projektarbeit_id used in cis4 student abgabetool
*/
@@ -116,9 +133,19 @@ class Abgabe extends FHCAPI_Controller
$isMitarbeiter = $this->MitarbeiterModel->isMitarbeiter(getAuthUID());
if ($isMitarbeiter && $isZugeteilterBetreuer){
$projektarbeiten = $this->ProjektarbeitModel->getStudentProjektarbeitenWithBetreuer($uid);
$result = $this->ProjektarbeitModel->getStudentProjektarbeitenWithBetreuer($uid);
} else {
$projektarbeiten = $this->ProjektarbeitModel->getStudentProjektarbeitenWithBetreuer(getAuthUID());
$result = $this->ProjektarbeitModel->getStudentProjektarbeitenWithBetreuer(getAuthUID());
}
$projektarbeiten = getData($result);
// TODO: save access to this, array could be empty
foreach($projektarbeiten as $pa) {
$result = $this->ProjektarbeitModel->getProjektbetreuerEmail($pa->projektarbeit_id);
// TODO: save access
$pa->email = getData($result)[0]->private_email;
}
$this->terminateWithSuccess(array($projektarbeiten, DOMAIN, $uid));
@@ -327,12 +354,8 @@ class Abgabe extends FHCAPI_Controller
$maildata['parbeituebersichtlink'] = "<p><a href='".APP_ROOT."cis/private/lehre/abgabe_lektor_frameset.html'>Zur Projektarbeitsübersicht</a></p>";
$maildata['bewertunglink'] = $num_rows_sem >= 1 && $paabgabetyp_kurzbz == 'end' ? "<p><a href='$mail_fulllink'>Zur Beurteilung der Arbeit</a></p>" : "";
$maildata['token'] = "";
// TODO: clarify if all betreuer are mitarbeiter and have benutzer entries
// or if uid = null has to be checked WITH
// 'CASE WHEN tbl_benutzer.uid IS NULL THEN kontakt ELSE tbl_benutzer.uid || '@".DOMAIN."' END AS email'
$email = $this->getBetreuerEmail($bperson_id);
$email = $this->getProjektbetreuerEmail($projektarbeit_id);
if(!$email) $this->terminateWithError($this->p->t('abgabetool', 'fehlerMailBegutachter'), 'general');
@@ -401,12 +424,7 @@ class Abgabe extends FHCAPI_Controller
$zweitbetmaildata['parbeituebersichtlink'] = $intern ? $maildata['parbeituebersichtlink'] : "";
$zweitbetmaildata['bewertunglink'] = $num_rows_sem >= 1 ? "<p><a href='$mail_link'>Zur Beurteilung der Arbeit</a></p>" : "";
$zweitbetmaildata['token'] = $num_rows_sem >= 1 && isset($begutachterMitToken->zugangstoken) && !$intern ? "<p>Zugangstoken: " . $begutachterMitToken->zugangstoken . "</p>" : "";
$email = $this->getBetreuerEmail($bperson_id);
if(!$email) $this->terminateWithError($this->p->t('abgabetool', 'fehlerMailBegutachter'), 'general');
$mailres = sendSanchoMail(
'ParbeitsbeurteilungEndupload',
@@ -464,12 +482,13 @@ class Abgabe extends FHCAPI_Controller
$fixtermin = $_POST['fixtermin'];
$kurzbz = $_POST['kurzbz'];
$note = $_POST['note'];
$upload_required = $_POST['upload_required'];
$upload_allowed = $_POST['upload_allowed'];
$betreuer_person_id = $_POST['betreuer_person_id'];
if (!isset($projektarbeit_id) || isEmptyString($projektarbeit_id)
|| !isset($paabgabe_id) || isEmptyString($paabgabe_id)
|| !isset($datum) || isEmptyString($datum)
|| !isset($datum) || isEmptyString($datum)
|| !isset($kurzbz)
|| !isset($paabgabetyp_kurzbz) || isEmptyString($paabgabetyp_kurzbz))
$this->terminateWithError($this->p->t('global', 'wrongParameters'), 'general');
@@ -484,7 +503,7 @@ class Abgabe extends FHCAPI_Controller
'datum' => $datum,
'kurzbz' => $kurzbz,
'note' => $note,
'upload_required' => $upload_required,
'upload_allowed' => $upload_allowed,
'insertvon' => getAuthUID(),
'insertamum' => date('Y-m-d H:i:s')
)
@@ -499,24 +518,37 @@ class Abgabe extends FHCAPI_Controller
'datum' => $datum,
'kurzbz' => $kurzbz,
'note' => $note,
'upload_required' => $upload_required,
'upload_allowed' => $upload_allowed,
'updatevon' => getAuthUID(),
'updateamum' => date('Y-m-d H:i:s')
)
);
}
$paabgabe_id = $this->getDataOrTerminateWithError($result);
$paabgabe = $this->PaabgabeModel->load($paabgabe_id);
// check if $paaabgabe is a qual gate and its note is deemed negative
// -> send email to student with that info
$paabgabe_id = $this->getDataOrTerminateWithError($result);
// TODO: DEFAULT NOTE 9 OR DEFAULT NOTE NULL? 9 COUNTS AS NEGATIV :/
$result = $this->PaabgabeModel->load($paabgabe_id);
$paabgabeArr = $this->getDataOrTerminateWithError($result);
$paabgabe = $paabgabeArr[0];
$this->addMeta('paabgabe', $paabgabeArr);
// check if abgabe even has note
if($paabgabe->note) {
$this->load->model('education/Note_model', 'NoteModel');
$result = $this->NoteModel->load($paabgabe->note);
$noteArr = $this->getDataOrTerminateWithError($result);
$note = $noteArr[0];
if($note->positiv === false) {
$this->addMeta('noteNegativ', true);
$this->sendQualGateNegativEmail($projektarbeit_id, $betreuer_person_id, $paabgabe);
}
}
$this->terminateWithSuccess($result);
$this->terminateWithSuccess($paabgabe);
}
public function deleteProjektarbeitAbgabe() {
@@ -556,7 +588,7 @@ class Abgabe extends FHCAPI_Controller
if (!isset($projektarbeit_ids) || !is_array($projektarbeit_ids) || empty($projektarbeit_ids)
|| !isset($datum) || isEmptyString($datum)
|| !isset($kurzbz) || isEmptyString($kurzbz)
|| !isset($kurzbz)
|| !isset($bezeichnung) || isEmptyString($bezeichnung)
|| !isset($paabgabetyp_kurzbz) || isEmptyString($paabgabetyp_kurzbz))
$this->terminateWithError($this->p->t('global', 'wrongParameters'), 'general');
@@ -664,11 +696,11 @@ class Abgabe extends FHCAPI_Controller
$this->terminateWithSuccess($paabgabetypen);
}
private function getBetreuerEmail($person_id) {
$this->load->model('education/Projektbetreuer_model', 'ProjektbetreuerModel');
$result = $this->ProjektbetreuerModel->getBetreuerEmail($person_id);
private function getProjektbetreuerEmail($projektarbeit_id) {
$this->load->model('education/Projektarbeit_model', 'ProjektarbeitModel');
$result = $this->ProjektarbeitModel->getProjektbetreuerEmail($projektarbeit_id);
$email = $this->getDataOrTerminateWithError($result);
return $email[0]->email;
return $email[0]->private_email;
}
//TODO: SWITCH TO NOTEN API ONCE NOTENTOOL IS IN MASTER TO AVOID DUPLICATE API
@@ -685,7 +717,24 @@ class Abgabe extends FHCAPI_Controller
$this->terminateWithSuccess($noten);
}
private function sendQualGateNegativEmail($student_uid) {
private function sendQualGateNegativEmail($projektarbeit_id, $betreuer_person_id, $paabgabe) {
$this->load->model('education/Projektarbeit_model', 'ProjektarbeitModel');
$result = $this->ProjektarbeitModel->load($projektarbeit_id);
$projektarbeitArr = $this->getDataOrTerminateWithError($result);
$projektarbeit = $projektarbeitArr[0];
$result = $this->ProjektarbeitModel->getProjektbetreuerAnrede($betreuer_person_id);
$anredeArr = $this->getDataOrTerminateWithError($result);
$anrede = $anredeArr[0];
$student_uid = $projektarbeit->student_uid;
$this->load->model('education/Paabgabetyp_model', 'PaabgabetypModel');
$result = $this->PaabgabetypModel->load($paabgabe->paabgabetyp_kurzbz);
$paabgabetyp_kurzbzArr = $this->getDataOrTerminateWithError($result);
$paabgabetyp_kurzbz = $paabgabetyp_kurzbzArr[0];
// Mail an Student wenn Qualgate negativ beurteilt wurde
$student = new student();
if(!$student->load($student_uid))
@@ -696,21 +745,21 @@ class Abgabe extends FHCAPI_Controller
$tomail = $student_uid.'@'.DOMAIN;
// TODO: emaildata
// TODO: datum format
$data = array(
'betreuerfullname' => $student->vorname,
'qualgatebezeichnung' => $student->nachname,
'datum' => $datum,
'projektarbeitname' => $name
'betreuerfullname' => $anrede->first,
'qualgatebezeichnung' => $paabgabetyp_kurzbz->bezeichnung,
'datum' => $paabgabe->datum,
'projektarbeitname' => $projektarbeit->titel
);
$this->addMeta('$emaildata', $data);
$mailres = sendSanchoMail(
'QualGateNegativ',
$data,
$tomail,
$subject
);
return $mailres;
}
}
@@ -168,7 +168,7 @@ class Projektarbeit_model extends DB_Model
campus.tbl_paabgabe.kurzbz,
campus.tbl_paabgabe.datum,
campus.tbl_paabgabe.note,
campus.tbl_paabgabe.upload_required,
campus.tbl_paabgabe.upload_allowed,
campus.tbl_paabgabetyp.paabgabetyp_kurzbz,
campus.tbl_paabgabetyp.bezeichnung,
campus.tbl_paabgabe.abgabedatum,
@@ -189,7 +189,28 @@ class Projektarbeit_model extends DB_Model
WHERE public.tbl_person.person_id= ?";
return $this->execReadOnlyQuery($qry_betr, [$bperson_id]);
}
public function getProjektbetreuerEmail($projektarbeit_id) {
$qry = "SELECT (
SELECT kontakt
FROM public.tbl_kontakt
WHERE kontakttyp = 'email'
AND person_id = pers.person_id
ORDER BY
CASE WHEN zustellung THEN 0 ELSE 1 END,
insertamum DESC NULLS LAST
LIMIT 1
) AS private_email
FROM lehre.tbl_projektarbeit pa
JOIN lehre.tbl_projektbetreuer USING (projektarbeit_id)
JOIN public.tbl_person pers USING (person_id)
LEFT JOIN public.tbl_benutzer ben USING (person_id)
LEFT JOIN public.tbl_mitarbeiter ma ON ben.uid = ma.mitarbeiter_uid
WHERE (ben.aktiv OR ben.aktiv IS NULL)
AND projektarbeit_id = ?";
return $this->execReadOnlyQuery($qry, [$projektarbeit_id]);
}
public function getProjektarbeitBenutzer($uid) {
@@ -225,7 +246,7 @@ class Projektarbeit_model extends DB_Model
FROM
(SELECT tbl_person.vorname, tbl_person.nachname, tbl_studiengang.typ, tbl_studiengang.kurzbz,
tbl_projektarbeit.projekttyp_kurzbz, tbl_projekttyp.bezeichnung, tbl_projektarbeit.titel, tbl_projektarbeit.projektarbeit_id,
tbl_projektbetreuer.betreuerart_kurzbz, tbl_betreuerart.beschreibung AS betreuerart_beschreibung,
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
FROM lehre.tbl_projektarbeit
LEFT JOIN lehre.tbl_projektbetreuer using(projektarbeit_id)
@@ -231,40 +231,4 @@ class Projektbetreuer_model extends DB_Model
return $this->execQuery($qry, array($projektarbeit_id, $betreuer_person_id));
}
/**
* Gets email of a Betreuer for a Projektarbeit.
* Projektarbeitbetreuer can be external employees, which should be contacted on their private
* email contact, if they have one. Internal Betreuer should be contacted on their "uid@DOMAIN" adress.
* @param int betreuer_person_id
* @return object success or error
*/
public function getBetreuerEmail($betreuer_person_id) {
// TODO: check if benutzer uid exists, not fixangestellt
$qry ="
SELECT uid,
CASE
WHEN public.tbl_mitarbeiter.fixangestellt = false
THEN (
SELECT kontakt AS email
FROM public.tbl_kontakt
WHERE
public.tbl_kontakt.person_id = ? AND
kontakttyp = 'email'
)
ELSE (
SELECT public.tbl_benutzer.uid || '@' || '".DOMAIN."' AS email
FROM public.tbl_benutzer
WHERE person_id = ?
)
END AS email
FROM public.tbl_benutzer
JOIN public.tbl_mitarbeiter ON (public.tbl_benutzer.uid = public.tbl_mitarbeiter.mitarbeiter_uid)
WHERE person_id = ?
";
return $this->execReadOnlyQuery($qry, array($betreuer_person_id, $betreuer_person_id, $betreuer_person_id));
}
}
+10 -3
View File
@@ -1,4 +1,10 @@
export default {
getConfig() {
return {
method: 'get',
url: '/api/frontend/v1/Abgabe/getConfig'
};
},
getStudentProjektarbeiten(uid) {
return {
method: 'get',
@@ -51,12 +57,13 @@ export default {
paabgabe_id: termin.paabgabe_id,
paabgabetyp_kurzbz: termin.bezeichnung.paabgabetyp_kurzbz,
datum: termin.datum,
note: termin.note,
upload_required: termin.upload_required,
note: termin.note_pk,
upload_allowed: termin.upload_allowed,
fixtermin: termin.fixtermin,
insertvon: termin.insertvon,
kurzbz: termin.kurzbz,
projektarbeit_id: termin.projektarbeit_id
projektarbeit_id: termin.projektarbeit_id,
betreuer_person_id: termin.betreuer_person_id
}
};
},
@@ -13,7 +13,7 @@ export const AbgabeMitarbeiterDetail = {
Textarea: primevue.textarea,
VueDatePicker
},
inject: ['abgabeTypeOptions', 'allowedNotenOptions'],
inject: ['abgabeTypeOptions', 'allowedNotenOptions', 'turnitin_link', 'old_abgabe_beurteilung_link'],
props: {
projektarbeit: {
type: Object,
@@ -22,7 +22,6 @@ export const AbgabeMitarbeiterDetail = {
},
data() {
return {
oldPaBeurteilungLink: 'https://moodle.technikum-wien.at/mod/page/view.php?id=1005052', // TODO: inject from app & app provide link from config
eidAkzeptiert: false,
enduploadTermin: null,
allActiveLanguages: FHC_JS_DATA_STORAGE_OBJECT.server_languages
@@ -34,18 +33,15 @@ export const AbgabeMitarbeiterDetail = {
},
saveTermin(termin) {
const paabgabe_id = termin.paabgabe_id
// 9 => magic number for "noch nicht eingetragen"
termin.note = termin.note?.note ?? 9
termin.note_pk = termin.note?.note ?? null
termin.betreuer_person_id = this.projektarbeit.betreuer_person_id
this.$api.call(ApiAbgabe.postProjektarbeitAbgabe(termin)).then( (res) => {
if(res?.meta?.status == 'success') {
this.$fhcAlert.alertSuccess(this.$p.t('ui/gespeichert'))
if(paabgabe_id === -1) { // new abgabe has been inserted
termin.paabgabe_id = res?.data?.retval
termin.paabgabe_id = res?.data?.paabgabe_id
this.projektarbeit.abgabetermine.push({ // new abgatermin row
'paabgabe_id': -1,
'projektarbeit_id': this.projektarbeit.projektarbeit_id,
'fixtermin': false,
@@ -53,7 +49,7 @@ export const AbgabeMitarbeiterDetail = {
'datum': new Date().toISOString().split('T')[0],
'paabgabetyp_kurzbz': termin.paabgabetyp_kurzbz,
'note': this.allowedNotenOptions.find(opt => opt.note == termin.note?.note),
'upload_required': termin.upload_required,
'upload_allowed': termin.upload_allowed,
'bezeichnung': this.abgabeTypeOptions.find(opt => opt.paabgabetyp_kurzbz === termin.paabgabetyp_kurzbz),
'abgabedatum': null,
'insertvon': this.viewData?.uid ?? '',
@@ -61,12 +57,9 @@ export const AbgabeMitarbeiterDetail = {
'allowedToDelete': true
})
}
} else if(res?.meta?.status == 'error'){
this.$fhcAlert.alertError()
}
})
},
deleteTermin(termin) {
@@ -76,8 +69,6 @@ export const AbgabeMitarbeiterDetail = {
// this.$p.t('global/tooltipLektorDeleteKontrolle', [this.$entryParams.permissions.kontrolleDeleteMaxReach ])
const deletedTerminIndex = this.projektarbeit.abgabetermine.findIndex(t => t.paabgabe_id === termin.paabgabe_id)
this.projektarbeit.abgabetermine.splice(deletedTerminIndex, 1)
} else if(res?.meta?.status == 'error'){
this.$fhcAlert.alertError()
}
@@ -92,15 +83,13 @@ export const AbgabeMitarbeiterDetail = {
return true;
},
downloadAbgabe(termin) {
// TODO: test
this.$api.call(ApiAbgabe.getStudentProjektarbeitAbgabeFile(termin.paabgabe_id, this.projektarbeit.student_uid))
// this.$fhcApi.factory.lehre.getStudentProjektarbeitAbgabeFile(termin.paabgabe_id, this.projektarbeit.student_uid)
},
dateDiffInDays(datum, today){
const oneDayMs = 1000 * 60 * 60 * 24
return Math.round((new Date(datum) - new Date(today)) / oneDayMs)
},
getDateStyle(termin) {
getDateStyle(termin, mode) {
const datum = new Date(termin.datum)
const abgabedatum = new Date(termin.abgabedatum)
@@ -108,21 +97,31 @@ export const AbgabeMitarbeiterDetail = {
// https://wiki.fhcomplete.info/doku.php?id=cis:abgabetool_fuer_studierende
let color = 'white'
let fontColor = 'black'
let icon = '';
if (termin.abgabedatum === null) {
if(datum < today) {
color = 'red'
fontColor = 'white'
icon = 'fa-triangle-exclamation'
} else if (datum > today && this.dateDiffInDays(datum, today) <= 12) {
color = 'yellow'
icon = 'fa-circle-exclamation'
}
} else if(abgabedatum > datum) {
color = 'pink' // aka "hellrot"
fontColor = 'white'
icon = 'fa-circle-question'
} else {
color = 'green'
icon = 'fa-square-check'
}
return `font-color: ${fontColor} ; background-color: ${color}; border-radius: 50%;`
//return `font-color: ${fontColor} ; background-color: ${color}; border-radius: 50%;`
if( typeof mode !== 'undefined' || mode === 'icon') {
return icon;
} else {
return 'abgabe-zieldatum-border-' + color;
}
},
openBeurteilungLink(link) {
window.open(link, '_blank')
@@ -153,8 +152,7 @@ export const AbgabeMitarbeiterDetail = {
window.open(link, '_blank')
},
openPlagiatcheck() {
// todo: hardcoded turnitin link?
const link = "https://technikum-wien.turnitin.com/sso/sp/redwood/saml/5IyfmBr2OcSIaWQTKlFCGj/start"
const link = this.turnitin_link
window.open(link, '_blank')
},
openBenotung() {
@@ -175,8 +173,17 @@ export const AbgabeMitarbeiterDetail = {
},
endUploadVorhanden(){
return this.projektarbeit?.abgabetermine.find(abgabe => abgabe.paabgabetyp_kurzbz === 'end' && abgabe.abgabedatum !== null)
},
qualityGateTerminAvailable() {
let qgatefound = false
this.projektarbeit?.abgabetermine.forEach(abgabe => {
if(abgabe.bezeichnung?.paabgabetyp_kurzbz == 'qualgate1'
|| abgabe?.bezeichnung?.paabgabetyp_kurzbz == 'qualgate2') {
qgatefound = true
}
})
return qgatefound
}
},
created() {
@@ -185,7 +192,7 @@ export const AbgabeMitarbeiterDetail = {
},
template: `
<div v-if="projektarbeit">
<div v-if="projektarbeit" style="max-width: 95%">
<h5>{{$p.t('abgabetool/c4abgabeMitarbeiterbereich')}}</h5>
@@ -199,20 +206,20 @@ export const AbgabeMitarbeiterDetail = {
<div class="col">
<div class="row">
<button :disabled="!getSemesterBenotbar || !endUploadVorhanden" class="btn btn-secondary border-0" @click="openBenotung" style="width: 80%;">
benoten
<i style="margin-left: 8px" class="fa-solid fa-user-check"></i>
{{ $p.t('abgabetool/c4benoten') }}
<i class="fa-solid fa-user-check"></i>
</button>
</div>
<div class="row" style="width: 90%;">
<span v-if="!getSemesterBenotbar" v-html="$p.t('abgabetool/c4aeltereParbeitBenoten', oldPaBeurteilungLink)"></span>
<span v-else-if="!endUploadVorhanden">Kein Endupload vorhanden!</span>
<span v-else-if="!endUploadVorhanden">{{ $p.t('abgabetool/c4noEnduploadFound') }}</span>
</div>
</div>
<div class="col">
<div class="row">
<button v-if="projektarbeit?.betreuerart_kurzbz !== 'Zweitbegutachter'" class="btn btn-secondary border-0" @click="openPlagiatcheck" style="width: 80%;">
zur Plagiatsprüfung
<i style="margin-left: 8px" class="fa-solid fa-user-check"></i>
{{ $p.t('abgabetool/c4plagiatcheck_link')}}
<i class="fa-solid fa-user-check"></i>
</button>
</div>
@@ -220,8 +227,8 @@ export const AbgabeMitarbeiterDetail = {
<div class="col">
<div class="row">
<button class="btn btn-secondary border-0" @click="openStudentPage" style="width: 80%;">
Studentenansicht
<i style="margin-left: 8px" class="fa-solid fa-eye"></i>
{{ $p.t('abgabetool/c4student_perspective')}}
<i class="fa-solid fa-eye"></i>
</button>
</div>
@@ -230,86 +237,87 @@ export const AbgabeMitarbeiterDetail = {
</div>
<div id="uploadWrapper">
<div class="row" style="margin-bottom: 12px;">
<div style="width: 100px">{{$p.t('abgabetool/c4fixtermin')}}</div>
<div class="col-2">{{$p.t('abgabetool/c4zieldatum')}}</div>
<div class="col-2">{{$p.t('abgabetool/c4abgabetyp')}}</div>
<div class="col-2">{{$p.t('abgabetool/c4note')}}</div>
<div class="col-1">{{$p.t('abgabetool/c4upload_required')}}</div>
<div class="col-2">{{$p.t('abgabetool/c4abgabekurzbz')}}</div>
<div class="col-1">{{$p.t('abgabetool/c4abgabedatum')}}</div>
<div class="col">
<div class="fw-bold" style="width: 100px">{{$p.t('abgabetool/c4fixtermin')}}</div>
<div class="row" style="max-width: calc(100% - 100px)">
<div class="col-2 fw-bold">{{$p.t('abgabetool/c4zieldatum')}}</div>
<div class="col-2 fw-bold">{{$p.t('abgabetool/c4abgabetyp')}}</div>
<div v-show="qualityGateTerminAvailable" class="col-2 fw-bold">{{$p.t('abgabetool/c4note')}}</div>
<div v-show="qualityGateTerminAvailable" class="col-1 fw-bold">{{$p.t('abgabetool/c4upload_allowed')}}</div>
<div class="col-2 fw-bold">{{$p.t('abgabetool/c4abgabekurzbz')}}</div>
<div class="col-1 fw-bold">{{$p.t('abgabetool/c4abgabedatum')}}</div>
<div class="col">
</div>
</div>
</div>
<div v-if="!projektarbeit?.abgabetermine?.length">keine Termine gefunden!</div>
<div class="row" v-for="termin in projektarbeit.abgabetermine">
<div style="width: 100px" class="d-flex justify-content-center align-items-center">
<p class="fhc-bullet" :class="{ 'fhc-bullet-red': termin.fixtermin, 'fhc-bullet-green': !termin.fixtermin }"></p>
<i v-if="termin.fixtermin" class="fa-solid fa-2x fa-circle-check fhc-bullet-red"></i>
<i v-else="" class="fa-solid fa-2x fa-circle-xmark fhc-bullet-green"></i>
<!-- <p class="fhc-bullet" :class="{ 'fhc-bullet-red': termin.fixtermin, 'fhc-bullet-green': !termin.fixtermin }"></p>-->
</div>
<div class="col-2 d-flex justify-content-center align-items-center">
<div :style="getDateStyle(termin)">
<VueDatePicker
style="width: 95%;"
v-model="termin.datum"
:clearable="false"
<div class="row" style="max-width: calc(100% - 100px)">
<div class="col-2 d-flex justify-content-center align-items-center">
<div class="position-relative" :class="getDateStyle(termin)">
<VueDatePicker
style="width: 95%;"
v-model="termin.datum"
:clearable="false"
:disabled="!termin.allowedToSave"
:enable-time-picker="false"
:format="formatDate"
:text-input="true"
auto-apply>
</VueDatePicker>
<i class="position-absolute abgabe-zieldatum-overlay fa-solid fa-2x" :class="getDateStyle(termin, 'icon')"></i>
</div>
</div>
<div class="col-2 d-flex justify-content-center align-items-center">
<Dropdown
:style="{'width': '100%'}"
:disabled="!termin.allowedToSave"
:enable-time-picker="false"
:format="formatDate"
:text-input="true"
auto-apply>
</VueDatePicker>
</div>
</div>
<div class="col-2 d-flex justify-content-center align-items-center">
<Dropdown
:style="{'width': '100%'}"
:disabled="!termin.allowedToSave"
v-model="termin.bezeichnung"
:options="abgabeTypeOptions"
:optionLabel="getOptionLabelAbgabetyp">
</Dropdown>
</div>
<div class="col-2 d-flex justify-content-center align-items-center">
<Dropdown
v-if="termin.bezeichnung?.paabgabetyp_kurzbz === 'qualgate1' || termin.bezeichnung?.paabgabetyp_kurzbz === 'qualgate2'"
:style="{'width': '100%'}"
v-model="termin.note"
:options="allowedNotenOptions"
:optionLabel="getNotenOptionLabel">
</Dropdown>
</div>
<div class="col-1 d-flex justify-content-center align-items-center">
<Checkbox
v-if="termin.bezeichnung?.paabgabetyp_kurzbz === 'qualgate1' || termin.bezeichnung?.paabgabetyp_kurzbz === 'qualgate2'"
v-model="termin.upload_required"
:binary="true"
:pt="{ root: { class: 'ml-auto' }}"
>
</Checkbox>
</div>
<div class="col-2 d-flex justify-content-center align-items-center">
<Textarea style="margin-bottom: 4px;" v-model="termin.kurzbz" rows="3" cols="30" :disabled="!termin.allowedToSave"></Textarea>
</div>
<div class="col-1 d-flex justify-content-center align-items-center">
{{ termin.abgabedatum?.split("-").reverse().join(".") }}
<a v-if="termin?.abgabedatum" @click="downloadAbgabe(termin)" style="margin-left:4px; cursor: pointer;">
<i class="fa-solid fa-file-pdf"></i>
</a>
</div>
<div class="col-2 align-content-center">
<div class="row">
<div class="col-6">
<button v-if="termin.allowedToSave" class="btn btn-primary border-0" @click="saveTermin(termin)">
Speichern
<i style="margin-left: 8px" class="fa-solid fa-floppy-disk"></i>
</button>
</div>
<div class="col-6">
<button v-if="termin.allowedToDelete && termin.paabgabe_id > 0" class="btn btn-primary border-0" @click="deleteTermin(termin)">
Löschen
<i style="margin-left: 8px" class="fa-solid fa-trash"></i>
</button>
</div>
v-model="termin.bezeichnung"
:options="abgabeTypeOptions"
:optionLabel="getOptionLabelAbgabetyp">
</Dropdown>
</div>
<div v-if="qualityGateTerminAvailable || termin.bezeichnung?.paabgabetyp_kurzbz === 'qualgate1' || termin.bezeichnung?.paabgabetyp_kurzbz === 'qualgate2'" class="col-2 d-flex justify-content-center align-items-center">
<Dropdown
v-if="termin.bezeichnung?.paabgabetyp_kurzbz === 'qualgate1' || termin.bezeichnung?.paabgabetyp_kurzbz === 'qualgate2'"
:style="{'width': '100%'}"
v-model="termin.note"
:options="allowedNotenOptions"
:optionLabel="getNotenOptionLabel">
</Dropdown>
</div>
<div v-if="qualityGateTerminAvailable || termin.bezeichnung?.paabgabetyp_kurzbz === 'qualgate1' || termin.bezeichnung?.paabgabetyp_kurzbz === 'qualgate2'" class="col-1 d-flex justify-content-center align-items-center">
<Checkbox
v-if="termin.bezeichnung?.paabgabetyp_kurzbz === 'qualgate1' || termin.bezeichnung?.paabgabetyp_kurzbz === 'qualgate2'"
v-model="termin.upload_allowed"
:binary="true"
:pt="{ root: { class: 'ml-auto' }}"
>
</Checkbox>
</div>
<div class="col-2 d-flex justify-content-center align-items-center">
<Textarea style="margin-bottom: 4px;" v-model="termin.kurzbz" rows="1" cols="20" :disabled="!termin.allowedToSave"></Textarea>
</div>
<div class="col-1 d-flex justify-content-center align-items-center">
{{ termin.abgabedatum?.split("-").reverse().join(".") }}
<a v-if="termin?.abgabedatum" @click="downloadAbgabe(termin)" style="margin-left:4px; cursor: pointer;">
<i class="fa-solid fa-2x fa-file-pdf"></i>
</a>
</div>
<div class="col-2 align-content-center">
<button v-if="termin.allowedToSave" style="max-height: 40px;" class="btn btn-primary border-0" @click="saveTermin(termin)">
{{ $p.t('abgabetool/c4save') }}
<i class="fa-solid fa-floppy-disk"></i>
</button>
<button v-if="termin.allowedToDelete && termin.paabgabe_id > 0" style="max-height: 40px;" class="btn btn-primary border-0" @click="deleteTermin(termin)">
{{ $p.t('abgabetool/c4delete') }}
<i class="fa-solid fa-trash"></i>
</button>
</div>
</div>
</div>
@@ -15,6 +15,7 @@ export const AbgabeStudentDetail = {
Textarea: primevue.textarea,
VueDatePicker
},
inject: ['notenOptions'],
props: {
projektarbeit: {
type: Object,
@@ -83,9 +84,7 @@ export const AbgabeStudentDetail = {
this.$refs.modalContainerEnduploadZusatzdaten.hide()
},
downloadAbgabe(termin) {
// TODO: test
this.$api.call(ApiAbgabe.getStudentProjektarbeitAbgabeFile(termin.paabgabe_id, this.projektarbeit.student_uid))
// this.$fhcApi.factory.lehre.getStudentProjektarbeitAbgabeFile(termin.paabgabe_id, this.projektarbeit.student_uid)
},
formatDate(dateParam) {
const date = new Date(dateParam)
@@ -181,6 +180,10 @@ export const AbgabeStudentDetail = {
},
getOptionLabel(option) {
return option.sprache
},
getTerminNoteBezeichnung(termin) {
const noteOpt = this.notenOptions.find(opt => opt.note == termin.note)
return noteOpt ? noteOpt.bezeichnung : ''
}
},
watch: {
@@ -201,6 +204,16 @@ export const AbgabeStudentDetail = {
},
getAllowedToSendEndupload() {
return !this.eidAkzeptiert
},
qualityGateTerminAvailable() {
let qgatefound = false
this.projektarbeit?.abgabetermine.forEach(abgabe => {
if(abgabe.paabgabetyp_kurzbz == 'qualgate1'
|| abgabe.paabgabetyp_kurzbz == 'qualgate2') {
qgatefound = true
}
})
return qgatefound
}
},
created() {
@@ -221,8 +234,10 @@ export const AbgabeStudentDetail = {
<div class="row" style="margin-bottom: 12px;">
<div class="col-1 fw-bold text-center">{{$p.t('abgabetool/c4fixtermin')}}</div>
<div class="col-2 fw-bold">{{$p.t('abgabetool/c4zieldatum')}}</div>
<div class="col-2 fw-bold">{{$p.t('abgabetool/c4abgabetyp')}}</div>
<div class="col-3 fw-bold">{{$p.t('abgabetool/c4abgabekurzbz')}}</div>
<div class="col-1 fw-bold">{{$p.t('abgabetool/c4abgabetyp')}}</div>
<div v-show="qualityGateTerminAvailable" class="col-1 fw-bold">{{$p.t('abgabetool/c4note')}}</div>
<div v-show="qualityGateTerminAvailable" class="col-1 fw-bold">{{$p.t('abgabetool/c4upload_allowed')}}</div>
<div class="col-2 fw-bold">{{$p.t('abgabetool/c4abgabekurzbz')}}</div>
<div class="col-1 fw-bold text-center">{{$p.t('abgabetool/c4abgabedatum')}}</div>
<div class="col-3 fw-bold">
{{$p.t('abgabetool/c4fileupload')}}
@@ -250,14 +265,27 @@ export const AbgabeStudentDetail = {
<i class="position-absolute abgabe-zieldatum-overlay fa-solid fa-2x" :class="getDateStyle(termin, 'icon')"></i>
</div>
</div>
<div class="col-2 d-flex justify-content-start align-items-start">{{ termin.bezeichnung }}</div>
<div class="col-3 d-flex justify-content-start align-items-start">
<Textarea style="margin-bottom: 4px;" v-model="termin.kurzbz" rows="3" cols="45" :disabled="true"></Textarea>
<div class="col-1 d-flex justify-content-start align-items-start">{{ termin.bezeichnung }}</div>
<div v-if="qualityGateTerminAvailable || termin.bezeichnung?.paabgabetyp_kurzbz === 'qualgate1' || termin.bezeichnung?.paabgabetyp_kurzbz === 'qualgate2'" class="col-1 d-flex justify-content-start align-items-start">
{{ getTerminNoteBezeichnung(termin) }}
</div>
<div class="col-1 d-flex flex-column justify-content-start align-items-center">
<div v-if="qualityGateTerminAvailable || termin.paabgabetyp_kurzbz === 'qualgate1' || termin.paabgabetyp_kurzbz === 'qualgate2'" class="col-1 d-flex justify-content-center align-items-start">
<Checkbox
v-if="termin.paabgabetyp_kurzbz === 'qualgate1' || termin.paabgabetyp_kurzbz === 'qualgate2'"
disabled
v-model="termin.upload_allowed"
:binary="true"
:pt="{ root: { class: 'ml-auto' }}"
>
</Checkbox>
</div>
<div class="col-2 d-flex justify-content-start align-items-start">
<Textarea style="margin-bottom: 4px;" v-model="termin.kurzbz" rows="1" cols="45" :disabled="true"></Textarea>
</div>
<div class="col-1 d-flex justify-content-start align-items-center">
{{ termin.abgabedatum?.split("-").reverse().join(".") }}
<a v-if="termin?.abgabedatum" @click="downloadAbgabe(termin)" style="margin-left:4px; cursor: pointer;">
<i class="fa-solid fa-3x fa-file-pdf"></i>
<i class="fa-solid fa-2x fa-file-pdf"></i>
</a>
</div>
<div class="col-3" v-if="!viewMode">
@@ -267,8 +295,8 @@ export const AbgabeStudentDetail = {
</div>
<div class="col-4">
<button class="btn btn-primary border-0" @click="upload(termin)" :disabled="!termin.allowedToUpload">
Upload
<i style="margin-left: 8px" class="fa-solid fa-upload"></i>
{{$p.t('abgabetool/c4upload')}}
<i class="fa-solid fa-upload"></i>
</button>
</div>
</div>
@@ -289,7 +317,7 @@ export const AbgabeStudentDetail = {
</div>
<div class="row mb-3 align-items-start">
<p class="ml-4 mr-4">Titel: {{ projektarbeit?.titel }}</p>
<p class="ml-4 mr-4">{{$p.t('abgabetool/c4titel')}}: {{ projektarbeit?.titel }}</p>
</div>
</template>
@@ -19,7 +19,9 @@ export const AbgabetoolMitarbeiter = {
provide() {
return {
abgabeTypeOptions: Vue.computed(() => this.abgabeTypeOptions),
allowedNotenOptions: Vue.computed(() => this.allowedNotenOptions)
allowedNotenOptions: Vue.computed(() => this.allowedNotenOptions),
turnitin_link: Vue.computed(() => this.turnitin_link),
old_abgabe_beurteilung_link: Vue.computed(() => this.old_abgabe_beurteilung_link)
}
},
props: {
@@ -34,6 +36,8 @@ export const AbgabetoolMitarbeiter = {
},
data() {
return {
turnitin_link: null,
old_abgabe_beurteilung_link: null,
saving: false,
loading: false,
abgabeTypeOptions: null,
@@ -59,9 +63,9 @@ export const AbgabetoolMitarbeiter = {
tableBuiltResolve: null,
tableBuiltPromise: null,
abgabeTableOptions: {
height: 700,
minHeight: 250,
index: 'projektarbeit_id',
layout: 'fitDataStretch',
layout: 'fitColumns',
placeholder: this.$p.t('global/noDataAvailable'),
selectable: true,
selectableCheck: this.selectionCheck,
@@ -80,13 +84,13 @@ export const AbgabetoolMitarbeiter = {
{title: Vue.computed(() => this.$p.t('abgabetool/c4details')), field: 'details', formatter: this.detailFormatter, widthGrow: 1, tooltip: false},
{title: Vue.computed(() => this.$p.t('abgabetool/c4personenkennzeichen')), field: 'pkz', formatter: this.pkzTextFormatter, widthGrow: 1, tooltip: false},
{title: Vue.computed(() => this.$p.t('abgabetool/c4kontakt')), field: 'mail', formatter: this.mailFormatter, widthGrow: 1, tooltip: false},
{title: Vue.computed(() => this.$p.t('abgabetool/c4vorname')), field: 'vorname', formatter: this.centeredTextFormatter, widthGrow: 1},
{title: Vue.computed(() => this.$p.t('abgabetool/c4vorname')), field: 'vorname', formatter: this.centeredTextFormatter,widthGrow: 1},
{title: Vue.computed(() => this.$p.t('abgabetool/c4nachname')), field: 'nachname', formatter: this.centeredTextFormatter, widthGrow: 1},
{title: Vue.computed(() => this.$p.t('abgabetool/c4projekttyp')), field: 'projekttyp_kurzbz', formatter: this.centeredTextFormatter, widthGrow: 1},
{title: Vue.computed(() => this.$p.t('abgabetool/c4stg')), field: 'stg', formatter: this.centeredTextFormatter, widthGrow: 2},
{title: Vue.computed(() => this.$p.t('abgabetool/c4stg')), field: 'stg', formatter: this.centeredTextFormatter, widthGrow: 1},
{title: Vue.computed(() => this.$p.t('abgabetool/c4sem')), field: 'studiensemester_kurzbz', formatter: this.centeredTextFormatter, widthGrow: 1},
{title: Vue.computed(() => this.$p.t('abgabetool/c4titel')), field: 'titel', formatter: this.centeredTextFormatter, maxWidth: 500, widthGrow: 8},
{title: Vue.computed(() => this.$p.t('abgabetool/c4betreuerart')), field: 'betreuerart_beschreibung',formatter: this.centeredTextFormatter, widthGrow: 8}
{title: Vue.computed(() => this.$p.t('abgabetool/c4betreuerart')), field: 'betreuerart_beschreibung',formatter: this.centeredTextFormatter, widthGrow: 1}
],
persistence: false,
},
@@ -209,7 +213,7 @@ export const AbgabetoolMitarbeiter = {
'kurzbz': '',
'datum': new Date().toISOString().split('T')[0],
'note': this.allowedNotenOptions.find(opt => opt.note == 9),
'upload_required': false,
'upload_allowed': false,
'paabgabetyp_kurzbz': '',
'bezeichnung': '',
'abgabedatum': null,
@@ -227,7 +231,6 @@ export const AbgabetoolMitarbeiter = {
paabgabetyp_kurzbz: termin.paabgabetyp_kurzbz
}
})
pa.betreuer = this.buildBetreuer(pa)
pa.student_uid = details.student_uid
pa.student = `${pa.vorname} ${pa.nachname}`
@@ -280,10 +283,6 @@ export const AbgabetoolMitarbeiter = {
buildStg(projekt) {
return (projekt.typ + projekt.kurzbz)?.toUpperCase()
},
buildBetreuer(abgabe) {
// TODO: preload and insert own titled name of betreuer somehow
return abgabe.betreuerart_beschreibung + ': ' + (abgabe.btitelpre ? abgabe.btitelpre + ' ' : '') + abgabe.bvorname + ' ' + abgabe.bnachname + (abgabe.btitelpost ? ' ' + abgabe.btitelpost : '')
},
setupData(data){
this.projektarbeiten = data[0]
this.domain = data[1]
@@ -337,7 +336,7 @@ export const AbgabetoolMitarbeiter = {
if(!tableDataSet) return
const rect = tableDataSet.getBoundingClientRect();
this.abgabeTableOptions.height = window.visualViewport.height - rect.top
this.abgabeTableOptions.height = window.visualViewport.height - rect.top - 80
this.$refs.abgabeTable.tabulator.setHeight(this.abgabeTableOptions.height)
},
async setupMounted() {
@@ -346,7 +345,6 @@ export const AbgabetoolMitarbeiter = {
this.loadProjektarbeiten()
this.$refs.verticalsplit.collapseBottom()
this.calcMaxTableHeight()
@@ -359,6 +357,15 @@ export const AbgabetoolMitarbeiter = {
},
created() {
// fetch config to avoid hard coded links
this.$api.call(ApiAbgabe.getConfig()).then(res => {
this.turnitin_link = res.data?.turnitin_link
this.old_abgabe_beurteilung_link = res.data?.old_abgabe_beurteilung_link
}).catch(e => {
console.log(e)
this.loading = false
})
// fetch noten options
//TODO: SWITCH TO NOTEN API ONCE NOTENTOOL IS IN MASTER TO AVOID DUPLICATE API
this.$api.call(ApiAbgabe.getNoten()).then(res => {
@@ -367,7 +374,6 @@ export const AbgabetoolMitarbeiter = {
this.allowedNotenOptions = this.notenOptions.filter(
opt => opt.bezeichnung === 'Bestanden'
|| opt.bezeichnung === 'Nicht bestanden'
|| opt.bezeichnung === 'Noch nicht eingetragen'
)
}).catch(e => {
this.loading = false
@@ -385,7 +391,7 @@ export const AbgabetoolMitarbeiter = {
},
template: `
<bs-modal ref="modalContainerAddSeries" class="bootstrap-prompt"
dialogClass="modal-lg">
dialogClass="modal-lg">
<template v-slot:title>
<div>
{{ $p.t('abgabetool/neueTerminserie') }}
@@ -435,57 +441,59 @@ export const AbgabetoolMitarbeiter = {
<button type="button" class="btn btn-primary" @click="addSeries">{{ $p.t('global/speichern') }}</button>
</template>
</bs-modal>
<!-- low max height on this vsplit wrapper to avoid padding scrolls, elements have their inherent height anyways -->
<div style="max-height:40vw;">
<vertical-split ref="verticalsplit">
<template #top>
<h2>{{$p.t('abgabetool/abgabetoolTitle')}}</h2>
<hr>
<core-filter-cmpt
:title="''"
@uuidDefined="handleUuidDefined"
ref="abgabeTable"
:newBtnShow="true"
:newBtnLabel="$p.t('abgabetool/neueTerminserie')"
:newBtnDisabled="!selectedData.length"
@click:new=openAddSeriesModal
:tabulator-options="abgabeTableOptions"
:tabulator-events="abgabeTableEventHandlers"
tableOnly
:sideMenu="false"
:useSelectionSpan="false"
>
<template #actions>
<button @click="toggleShowAll(!showAll)" role="button" class="btn btn-secondary ml-2">
<i v-show="!showAll" class="fa fa-eye"></i>
<i v-show="showAll" class="fa fa-eye-slash"></i>
{{ $p.t('abgabetool/showAll') }}
</button>
<button @click="showDeadlines" role="button" class="btn btn-secondary ml-2">
<i class="fa fa-hourglass-end"></i>
{{ $p.t('abgabetool/showDeadlines') }}
</button>
<div v-show="saving">
{{ $p.t('abgabetool/currentlySaving') }} <i class="fa-solid fa-spinner fa-pulse fa-3x"></i>
</div>
<div v-show="loading">
{{ $p.t('abgabetool/currentlyLoading') }} <i class="fa-solid fa-spinner fa-pulse fa-3x"></i>
</div>
</template>
</core-filter-cmpt>
</template>
<template #bottom>
<div v-show="selectedProjektarbeit" ref="selProj">
<AbgabeDetail :projektarbeit="selectedProjektarbeit"></AbgabeDetail>
</div>
</template>
</vertical-split>
<vertical-split ref="verticalsplit">
<template #top>
<h2>{{$p.t('abgabetool/abgabetoolTitle')}}</h2>
<hr>
<core-filter-cmpt
:title="''"
@uuidDefined="handleUuidDefined"
ref="abgabeTable"
:newBtnShow="true"
:newBtnLabel="$p.t('abgabetool/neueTerminserie')"
:newBtnDisabled="!selectedData.length"
@click:new=openAddSeriesModal
:tabulator-options="abgabeTableOptions"
:tabulator-events="abgabeTableEventHandlers"
tableOnly
:sideMenu="false"
:useSelectionSpan="false"
>
<template #actions>
<button @click="toggleShowAll(!showAll)" role="button" class="btn btn-secondary ml-2">
<i v-show="!showAll" class="fa fa-eye"></i>
<i v-show="showAll" class="fa fa-eye-slash"></i>
{{ $p.t('abgabetool/showAll') }}
</button>
<button @click="showDeadlines" role="button" class="btn btn-secondary ml-2">
<i class="fa fa-hourglass-end"></i>
{{ $p.t('abgabetool/showDeadlines') }}
</button>
<div v-show="saving">
{{ $p.t('abgabetool/currentlySaving') }} <i class="fa-solid fa-spinner fa-pulse fa-3x"></i>
</div>
<div v-show="loading">
{{ $p.t('abgabetool/currentlyLoading') }} <i class="fa-solid fa-spinner fa-pulse fa-3x"></i>
</div>
</template>
</core-filter-cmpt>
</template>
<template #bottom>
<div v-show="selectedProjektarbeit" ref="selProj">
<AbgabeDetail :projektarbeit="selectedProjektarbeit"></AbgabeDetail>
</div>
</template>
</vertical-split>
</div>
`,
};
@@ -10,6 +10,11 @@ export const AbgabetoolStudent = {
AbgabeDetail,
VerticalSplit
},
provide() {
return {
notenOptions: Vue.computed(() => this.notenOptions)
}
},
props: {
student_uid_prop: {
default: null
@@ -25,6 +30,7 @@ export const AbgabetoolStudent = {
},
data() {
return {
notenOptions: null,
tabulatorUuid: Vue.ref(0),
domain: '',
student_uid: null,
@@ -83,27 +89,42 @@ export const AbgabetoolStudent = {
]};
},
methods: {
checkQualityGates(termine, enduploadtermin) {
return true
checkQualityGates(termine) {
let qgate1Passed = false
let qgate2Passed = false
termine.forEach(t => {
const noteOption = this.notenOptions.find(opt => opt.note == t.note)
if(noteOption && noteOption.positiv) {
if(t.paabgabetyp_kurzbz == 'qualgate1') {
qgate1Passed = true
} else if(t.paabgabetyp_kurzbz == 'qualgate2') {
qgate2Passed = true
}
}
})
return qgate1Passed && qgate2Passed
},
isPastDate(date) {
return new Date(date) < new Date(Date.now())
},
setDetailComponent(details){
this.loadAbgaben(details).then((res)=> {
const pa = this.projektarbeiten?.retval?.find(projekarbeit => projekarbeit.projektarbeit_id == details.projektarbeit_id)
const pa = this.projektarbeiten?.find(projekarbeit => projekarbeit.projektarbeit_id == details.projektarbeit_id)
pa.abgabetermine = res.data[0].retval
pa.abgabetermine.forEach(termin => {
termin.file = []
// termin.allowedToUpload = true
termin.allowedToUpload = false
// termin.datum = '2025-10-16'
// TODO: fixtermin logic?
if(termin.bezeichnung == 'Endupload' &&
(this.isPastDate(termin.datum) || this.checkQualityGates(pa.abgabetermine, termin))) {
if(termin.paabgabetyp_kurzbz == 'enda') {
// termin.allowedToUpload = false
termin.allowedToUpload = !this.isPastDate(termin.datum) && this.checkQualityGates(pa.abgabetermine)
} else if(termin.paabgabetyp_kurzbz == 'qualgate1' || termin.paabgabetyp_kurzbz == 'qualgate2') {
termin.allowedToUpload = termin.upload_allowed
} else {
// termin.allowedToUpload = true
termin.allowedToUpload = true
}
})
@@ -111,7 +132,6 @@ export const AbgabetoolStudent = {
pa.student_uid = this.student_uid
this.selectedProjektarbeit = pa
this.$refs.verticalsplit.showBoth()
@@ -150,8 +170,12 @@ export const AbgabetoolStudent = {
tableResolve(resolve) {
this.tableBuiltResolve = resolve
},
buildMailToLink(abgabe) {
return 'mailto:' + abgabe.mitarbeiter_uid +'@'+ this.domain
buildMailToLink(projekt) {
if(projekt.mitarbeiter_uid) { // standard
return 'mailto:' + projekt.mitarbeiter_uid +'@'+ this.domain
} else { // private
return 'mailto:' + projekt.email
}
},
buildBetreuer(abgabe) {
return abgabe.betreuerart_beschreibung + ': ' + (abgabe.btitelpre ? abgabe.btitelpre + ' ' : '') + abgabe.bvorname + ' ' + abgabe.bnachname + (abgabe.btitelpost ? ' ' + abgabe.btitelpost : '')
@@ -160,7 +184,7 @@ export const AbgabetoolStudent = {
this.projektarbeiten = data[0]
this.domain = data[1]
this.student_uid = data[2]
const d = data[0]?.retval?.map(projekt => {
const d = data[0]?.map(projekt => {
let mode = 'detailTermine'
if (projekt.babgeschickt || projekt.zweitbetreuer_abgeschickt) {
@@ -213,7 +237,7 @@ export const AbgabetoolStudent = {
if(!tableDataSet) return
const rect = tableDataSet.getBoundingClientRect();
this.abgabeTableOptions.height = window.visualViewport.height - rect.top
this.abgabeTableOptions.height = window.visualViewport.height - rect.top - 100
this.$refs.abgabeTable.tabulator.setHeight(this.abgabeTableOptions.height)
},
async setupMounted() {
@@ -223,7 +247,7 @@ export const AbgabetoolStudent = {
this.loadProjektarbeiten()
this.$refs.verticalsplit.collapseBottom()
//this.calcMaxTableHeight()
this.calcMaxTableHeight()
}
},
watch: {
@@ -235,34 +259,43 @@ export const AbgabetoolStudent = {
}
},
created() {
//TODO: SWITCH TO NOTEN API ONCE NOTENTOOL IS IN MASTER TO AVOID DUPLICATE API
this.$api.call(ApiAbgabe.getNoten()).then(res => {
this.notenOptions = res.data
}).catch(e => {
this.loading = false
})
},
mounted() {
this.setupMounted()
},
template: `
<vertical-split ref="verticalsplit">
<template #top>
<h2>{{$p.t('abgabetool/abgabetoolTitle')}}</h2>
<hr>
<core-filter-cmpt
@uuidDefined="handleUuidDefined"
:title="''"
ref="abgabeTable"
:tabulator-options="abgabeTableOptions"
:tabulator-events="abgabeTableEventHandlers"
tableOnly
:sideMenu="false"
/>
</template>
<template #bottom>
<div v-show="selectedProjektarbeit">
<AbgabeDetail :viewMode="isViewMode" :projektarbeit="selectedProjektarbeit"></AbgabeDetail>
</div>
</template>
</vertical-split>
<!-- low max height on this vsplit wrapper to avoid padding scrolls, elements have their inherent height anyways -->
<div style="max-height:40vw;">
<vertical-split ref="verticalsplit">
<template #top>
<h2>{{$p.t('abgabetool/abgabetoolTitle')}}</h2>
<hr>
<core-filter-cmpt
@uuidDefined="handleUuidDefined"
:title="''"
ref="abgabeTable"
:tabulator-options="abgabeTableOptions"
:tabulator-events="abgabeTableEventHandlers"
tableOnly
:sideMenu="false"
/>
</template>
<template #bottom>
<div v-show="selectedProjektarbeit" style="max-width: 95%">
<AbgabeDetail :viewMode="isViewMode" :projektarbeit="selectedProjektarbeit"></AbgabeDetail>
</div>
</template>
</vertical-split>
</div>
`,
};
@@ -110,7 +110,7 @@ export const DeadlineOverview = {
if(!tableDataSet) return
const rect = tableDataSet.getBoundingClientRect();
this.deadlineTableOptions.height = window.visualViewport.height - rect.top
this.deadlineTableOptions.height = window.visualViewport.height - rect.top - 30
this.$refs.deadlineTable.tabulator.setHeight(this.deadlineTableOptions.height)
},
async setupMounted() {
-1
View File
@@ -164,7 +164,6 @@ export default {
return fhcApiAxios.post(uri, data, config);
},
call(factory, configoverwrite, form) {
debugger
if (Array.isArray(factory)) {
const $fhcAlert = app.config.globalProperties.$fhcAlert;
const $api = app.config.globalProperties.$api;
@@ -34,7 +34,7 @@ if($result = $db->db_query("SELECT 1 FROM information_schema.columns WHERE table
{
$qry = "ALTER TABLE campus.tbl_paabgabe
ADD COLUMN note SMALLINT NOT NULL DEFAULT 9,
ADD COLUMN note SMALLINT DEFAULT NULL,
ADD CONSTRAINT tbl_paabgabe_note_fkey
FOREIGN KEY (note)
REFERENCES lehre.tbl_note(note)
@@ -47,16 +47,16 @@ if($result = $db->db_query("SELECT 1 FROM information_schema.columns WHERE table
}
}
if($result = $db->db_query("SELECT 1 FROM information_schema.columns WHERE table_schema = 'campus' AND table_name = 'tbl_paabgabe' AND column_name = 'upload_required'"))
if($result = $db->db_query("SELECT 1 FROM information_schema.columns WHERE table_schema = 'campus' AND table_name = 'tbl_paabgabe' AND column_name = 'upload_allowed'"))
{
if($db->db_num_rows($result) === 0)
{
$qry = "ALTER TABLE campus.tbl_paabgabe
ADD COLUMN IF NOT EXISTS upload_required boolean DEFAULT false;";
ADD COLUMN IF NOT EXISTS upload_allowed boolean DEFAULT false;";
if(!$db->db_query($qry))
echo '<strong>campus.tbl_paabgabe: '.$db->db_last_error().'</strong><br>';
else
echo '<br>paabgabe column upload_required default false hinzugefuegt';
echo '<br>paabgabe column upload_allowed default false hinzugefuegt';
}
}
+208 -8
View File
@@ -41490,7 +41490,7 @@ array(
),
array(
'sprache' => 'English',
'text' => "details",
'text' => "Details",
'description' => '',
'insertvon' => 'system'
)
@@ -41510,7 +41510,7 @@ array(
),
array(
'sprache' => 'English',
'text' => "semester",
'text' => "Semester",
'description' => '',
'insertvon' => 'system'
)
@@ -41530,7 +41530,7 @@ array(
),
array(
'sprache' => 'English',
'text' => "degree program",
'text' => "Degree program",
'description' => '',
'insertvon' => 'system'
)
@@ -41550,7 +41550,7 @@ array(
),
array(
'sprache' => 'English',
'text' => "contact",
'text' => "Contact",
'description' => '',
'insertvon' => 'system'
)
@@ -41590,7 +41590,7 @@ array(
),
array(
'sprache' => 'English',
'text' => "project type",
'text' => "Project type",
'description' => '',
'insertvon' => 'system'
)
@@ -41610,7 +41610,7 @@ array(
),
array(
'sprache' => 'English',
'text' => "title",
'text' => "Title",
'description' => '',
'insertvon' => 'system'
)
@@ -41670,7 +41670,7 @@ array(
),
array(
'sprache' => 'English',
'text' => "target date",
'text' => "Target date",
'description' => '',
'insertvon' => 'system'
)
@@ -41730,7 +41730,7 @@ array(
),
array(
'sprache' => 'English',
'text' => "submission date",
'text' => "Submission date",
'description' => '',
'insertvon' => 'system'
)
@@ -41776,6 +41776,26 @@ array(
)
)
),
array(
'app' => 'core',
'category' => 'abgabetool',
'phrase' => 'c4benoten',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => "Benoten",
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => "Grade",
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'abgabetool',
@@ -41796,6 +41816,46 @@ array(
)
)
),
array(
'app' => 'core',
'category' => 'abgabetool',
'phrase' => 'c4save',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => "Speichern",
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => "Save",
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'abgabetool',
'phrase' => 'c4delete',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => "Löschen",
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => "Delete",
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'abgabetool',
@@ -42176,6 +42236,146 @@ array(
)
)
),
array(
'app' => 'core',
'category' => 'abgabetool',
'phrase' => 'c4note',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => "Note",
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Grade',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'abgabetool',
'phrase' => 'c4upload_allowed',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => "Upload erlaubt",
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Upload allowed',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'abgabetool',
'phrase' => 'c4student_perspective',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => "Studentenansicht",
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Student perspective',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'abgabetool',
'phrase' => 'c4upload',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => "Hochladen",
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Upload',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'abgabetool',
'phrase' => 'c4plagiatcheck_link',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => "zur Plagiatsprüfung",
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Plagiarism check',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'abgabetool',
'phrase' => 'c4plagiatcheck_link',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => "zur Plagiatsprüfung",
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Plagiarism check',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'abgabetool',
'phrase' => 'c4noEnduploadFound',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => "Kein Endupload vorhanden!",
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'No final submission found!',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'abgabetool',