getStudentConfig Api Method; moodle link config entry; write abgabetool termin noten into header; uniform getDateStyleClass logic in all 3 views; WIP refining AbgabetoolJob;

This commit is contained in:
Johann Hoffmann
2026-01-14 16:57:57 +01:00
parent bbb90f6dc4
commit eb15d6b841
10 changed files with 165 additions and 50 deletions
+4 -1
View File
@@ -24,4 +24,7 @@ $config['beurteilung_link_fallback'] = 'addons/fhtw/content/projektbeurteilung/p
$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';
$config['SIGNATUR_CHECK_PAABGABETYPEN'] = ['end'];
$config['SIGNATUR_CHECK_PAABGABETYPEN'] = ['end'];
// to be used as "https://moodle.technikum-wien.at/course/view.php?idnumber=dl{$stg_kz}" for stg specific moodle routing
$config['STG_MOODLE_LINK'] = 'https://moodle.technikum-wien.at/course/view.php?idnumber=dl';
@@ -30,6 +30,7 @@ class Abgabe extends FHCAPI_Controller
{
parent::__construct([
'getConfig' => array('basis/abgabe_assistenz:rw', 'basis/abgabe_lektor:rw'),
'getConfigStudent' => array('basis/abgabe_assistenz:rw', 'basis/abgabe_student:rw', 'basis/abgabe_lektor:rw'),
'getStudentProjektarbeiten' => array('basis/abgabe_assistenz:rw', 'basis/abgabe_student:rw', 'basis/abgabe_lektor:rw'),
'getStudentProjektabgaben' => array('basis/abgabe_assistenz:rw', 'basis/abgabe_student:rw', 'basis/abgabe_lektor:rw'),
'postStudentProjektarbeitZwischenabgabe' => array('basis/abgabe_assistenz:rw', 'basis/abgabe_student:rw'),
@@ -94,6 +95,19 @@ class Abgabe extends FHCAPI_Controller
$this->terminateWithSuccess($ret);
}
/**
* loads config related to abgabetool for students to avoid handing out links reserved for employees
*/
public function getConfigStudent() {
$moodle_link =$this->config->item('STG_MOODLE_LINK');
$ret = array(
'moodle_link' => $moodle_link,
);
$this->terminateWithSuccess($ret);
}
/**
* fetches all projektabgabetermine for a given projektarbeit_id used in cis4 student abgabetool & lektor abgabetool
+16 -8
View File
@@ -96,14 +96,14 @@ class AbgabetoolJob extends JOB_Controller
// filter for abgaben which where not inserted by the current betreuer iteration if there is no updateamum
// or not changed by the betreuer if there is updateamum
$relevantAbgaben = array_filter($changedAbgaben, function($abgabetermin) use ($betreuerRow) {
$relevantAbgaben = array_values(array_filter($changedAbgaben, function($abgabetermin) use ($betreuerRow) {
// new termin not created by that betreuer
if($abgabetermin->updatevon == null && $abgabetermin->insertvon != $betreuerRow->uid) {
return $abgabetermin;
} else if($abgabetermin->updatevon != null && $abgabetermin->updatevon != $betreuerRow->uid) {
return $abgabetermin;
}
});
}));
if(count($relevantAbgaben) == 0) {
break; // skip that projektarbeit if only changes originate from the betreuer in question
@@ -143,15 +143,17 @@ class AbgabetoolJob extends JOB_Controller
'abgabenString' => $abgabenString,
'linkAbgabetool' => $url
);
$email = $betreuerRow->uid ? $betreuerRow->uid."@".DOMAIN : $betreuerRow->private_email;
// send email with bundled info
sendSanchoMail(
'PAAChangesBetSM',
$body_fields,
$betreuerRow->private_email,
$email,
$this->p->t('abgabetool', 'changedAbgabeterminev2')
);
$count++;
}
@@ -170,6 +172,8 @@ class AbgabetoolJob extends JOB_Controller
$result = $this->_ci->PaabgabeModel->findAbgabenNewOrUpdatedSinceByAbgabedatum($interval);
$retval = getData($result);
var_dump($retval);
// retval are paabgaben joined with projektarbeit and betreuer
if(count($retval) == 0) {
@@ -186,14 +190,14 @@ class AbgabetoolJob extends JOB_Controller
$betreuer_uids[$paabgabe->person_id][] = $paabgabe;
}
$count = 0;
forEach ($betreuer_uids as $person_id => $abgaben) {
// $person_id is from betreuer
$result = $this->_ci->ProjektarbeitModel->getProjektbetreuerAnrede($person_id);
$data = getData($result)[0];
$anrede = $data->anrede;
$anredeFillString = $data->anrede == "Herr" ? "r" : "";
$fullFormattedNameString = $data->first;
@@ -233,11 +237,15 @@ class AbgabetoolJob extends JOB_Controller
'linkAbgabetool' => $url
);
// TODO: get this guys uid safely
// $email = $betreuerRow->uid ? $betreuerRow->uid."@".DOMAIN : $betreuerRow->private_email;
// send email with bundled info
sendSanchoMail(
'PaabgabeUpdatesBetSM',
$body_fields,
$data->private_email,
$email,
$this->p->t('abgabetool', 'changedAbgabeterminev2')
);
+6
View File
@@ -5,6 +5,12 @@ export default {
url: '/api/frontend/v1/Abgabe/getConfig'
};
},
getConfigStudent() {
return {
method: 'get',
url: '/api/frontend/v1/Abgabe/getConfigStudent'
};
},
getStudentProjektarbeiten(uid) {
return {
method: 'get',
@@ -78,6 +78,15 @@ export const AbgabeMitarbeiterDetail = {
}
},
methods: {
getNoteBezeichnung(termin){
if(termin.note?.bezeichnung) {
return termin.note?.positiv ? this.$capitalize(this.$p.t('abgabetool/c4positivBenotet')) + ' ✅' : this.$capitalize(this.$p.t('abgabetool/c4negativBenotet')) + ' ❌'
} else if(termin.bezeichnung?.benotbar === true && !termin.note) {
return this.$capitalize(this.$p.t('abgabetool/c4notYetGraded'));
} else {
return ''
}
},
getPlaceholderTermin(termin) {
return termin?.bezeichnung ? this.$p.t('abgabetool/c4paatyp' + termin.paabgabetyp_kurzbz) : this.$p.t('abgabetool/abgabetypPlaceholder')
},
@@ -299,16 +308,8 @@ export const AbgabeMitarbeiterDetail = {
const abgabedatum = new Date(termin.abgabedatum)
termin.diffindays = this.dateDiffInDays(termin.datum)
// TODO: load benotbar in every view or change status logic all together!
// console.log('\n\n')
// console.log(termin)
// console.log(today)
// console.log(datum)
// console.log('\n\n')
if(today > datum && termin.benotbar && !termin.note) return 'beurteilungerforderlich'
if(termin.bezeichnung?.benotbar && !termin.note) return 'beurteilungerforderlich'
if (termin.abgabedatum === null && termin.upload_allowed) {
if(datum < today) {
return 'verpasst' // needs upload, missed it and has not submitted anything
@@ -737,8 +738,9 @@ export const AbgabeMitarbeiterDetail = {
<template v-for="termin in this.projektarbeit?.abgabetermine">
<AccordionTab :headerClass="getDateStyleClass(termin) + '-header'">
<template #header>
<div class="d-flex row w-100 flex-nowrap align-items-center">
<div class="col-auto" style="transform: translateX(-58px); height: 36px; width:36px; padding: 0px; display: flex; align-items: center; justify-content: center;">
<div class="d-flex flex-nowrap align-items-center w-100">
<div class="flex-shrink-0 d-flex align-items-center justify-content-center" style="width: 36px; height: 36px; margin-left: -66px;">
<i v-if="getDateStyleClass(termin) == 'verspaetet'" v-tooltip.right="getTooltipVerspaetet" class="fa-solid fa-triangle-exclamation"></i>
<i v-else-if="getDateStyleClass(termin) == 'verpasst'" v-tooltip.right="getTooltipVerpasst" class="fa-solid fa-calendar-xmark"></i>
<i v-else-if="getDateStyleClass(termin) == 'abzugeben'" v-tooltip.right="getTooltipAbzugeben" class="fa-solid fa-hourglass-half"></i>
@@ -746,19 +748,26 @@ export const AbgabeMitarbeiterDetail = {
<i v-else-if="getDateStyleClass(termin) == 'abgegeben'" v-tooltip.right="getTooltipAbgegeben" class="fa-solid fa-check"></i>
<i v-else-if="getDateStyleClass(termin) == 'beurteilungerforderlich'" v-tooltip.right="getTooltipBeurteilungerforderlich" class="fa-solid fa-list-check"></i>
</div>
<div class="col-auto text-start" style="min-width: max(150px, 20%); max-width: min(300px, 30%); transform: translateX(-30px)">
<span>{{ termin ? $p.t('abgabetool/c4paatyp' + termin.paabgabetyp_kurzbz) : '' }}</span>
<div class="text-start px-2" style="min-width: 150px; max-width: 300px; margin-left: 40px">
<span class="text-truncate d-block">{{ termin ? $p.t('abgabetool/c4paatyp' + termin.paabgabetyp_kurzbz) : '' }}</span>
</div>
<div class="col-auto text-start" style="min-width: 100px; transform: translateX(-30px)">
<div class="text-start px-2" style="min-width: 100px;">
<span>{{ formatDate(termin.datum) }}</span>
</div>
<div v-if="termin?.fixtermin" class="col-auto" style="transform: translateX(-30px)">
<i v-tooltip.right="getTooltipFixtermin" class="fa-solid fa-lock"></i>
<div v-if="termin?.fixtermin" class="px-1">
<i v-tooltip.right="getTooltipFixtermin" class="fa-solid fa-lock"></i>
</div>
<div v-if="termin?.abgabedatum" class="col-auto" style="transform: translateX(-30px)">
<div v-if="termin?.abgabedatum" class="px-1">
<i v-tooltip.right="getTooltipAbgabeDetected" class="fa-solid fa-file"></i>
</div>
</div>
<div class="flex-grow-1 text-end pe-2">
<span class="fw-bold">{{getNoteBezeichnung(termin)}}</span>
</div>
</div>
</template>
<div class="row mt-2" v-if="assistenzMode">
<div class="col-12 col-md-3 fw-bold align-content-center">{{$capitalize( $p.t('abgabetool/c4fixterminv4') )}}</div>
@@ -20,7 +20,7 @@ export const AbgabeStudentDetail = {
VueDatePicker,
FhcOverlay
},
inject: ['notenOptions', 'isMobile', 'isViewMode'],
inject: ['notenOptions', 'isMobile', 'isViewMode', 'moodle_link'],
props: {
projektarbeit: {
type: Object,
@@ -49,6 +49,17 @@ export const AbgabeStudentDetail = {
}
},
methods: {
getNoteBezeichnung(termin){
const noteOpt = this.notenOptions.find(opt => opt.note == termin.note)
if(noteOpt?.bezeichnung) {
return noteOpt?.positiv ? this.$capitalize(this.$p.t('abgabetool/c4positivBenotet')) + ' ✅' : this.$capitalize(this.$p.t('abgabetool/c4negativBenotet')) + ' ❌'
} else if(noteOpt?.benotbar === true && !termin.note) {
return this.$capitalize(this.$p.t('abgabetool/c4notYetGraded'));
} else {
return ''
}
},
async validate(termin, endupload = false) {
if(!termin.file.length) {
this.$fhcAlert.alertWarning(this.$capitalize(this.$p.t('global/warningChooseFile')));
@@ -196,6 +207,9 @@ export const AbgabeStudentDetail = {
}
},
computed: {
getMoodleLink() {
return this.moodle_link + this.projektarbeit.studiengang_kz
},
getMessagePtStyle() {
// adjust outer spacing and internal padding to appear similar to doenload button in size
return {
@@ -305,16 +319,23 @@ export const AbgabeStudentDetail = {
<h5>{{$capitalize( $p.t('abgabetool/c4abgabeStudentenbereich') )}}</h5>
<div class="row">
<p> {{projektarbeit ? $p.t('abgabetool/c4betrart' + projektarbeit.betreuerart_kurzbz) + ' ' + projektarbeit.betreuer : ''}}</p>
<p> {{projektarbeit?.titel}}</p>
<div class="col-8">
<p> {{projektarbeit ? $p.t('abgabetool/c4betrart' + projektarbeit.betreuerart_kurzbz) + ' ' + projektarbeit.betreuer : ''}}</p>
<p> {{projektarbeit?.titel}}</p>
</div>
<div class="col-4">
<p>{{ $p.t('abgabetool/c4checkoutStgMoodleInfos') }}
<a :href="getMoodleLink">Moodle</a>
</p>
</div>
</div>
<Accordion :multiple="true">
<template v-for="termin in this.projektarbeit?.abgabetermine">
<AccordionTab :headerClass="termin.dateStyle + '-header'">
<template #header>
<div class="d-flex row w-100 flex-nowrap align-items-center">
<div class="col-auto" style="transform: translateX(-58px); height: 36px; width:36px; padding: 0px; display: flex; align-items: center; justify-content: center;">
<div class="d-flex flex-nowrap align-items-center w-100">
<div class="flex-shrink-0 d-flex align-items-center justify-content-center" style="width: 36px; height: 36px; margin-left: -66px;">
<i v-if="termin.dateStyle == 'verspaetet'" v-tooltip.right="getTooltipVerspaetet" class="fa-solid fa-triangle-exclamation"></i>
<i v-else-if="termin.dateStyle == 'verpasst'" v-tooltip.right="getTooltipVerpasst" class="fa-solid fa-calendar-xmark"></i>
<i v-else-if="termin.dateStyle == 'abzugeben'" v-tooltip.right="getTooltipAbzugeben" class="fa-solid fa-hourglass-half"></i>
@@ -322,18 +343,21 @@ export const AbgabeStudentDetail = {
<i v-else-if="termin.dateStyle == 'abgegeben'" v-tooltip.right="getTooltipAbgegeben" class="fa-solid fa-check"></i>
<i v-else-if="termin.dateStyle == 'beurteilungerforderlich'" v-tooltip.right="getTooltipBeurteilungerforderlich" class="fa-solid fa-list-check"></i>
</div>
<div class="col-auto text-start pl-2 pr-0 pt-0 pb-0" style="min-width: min(300px, 30%); max-width: min(300px, 30%); transform: translateX(-30px)">
<div class="text-start px-2" style="min-width: 150px; max-width: 300px; margin-left: 40px">
<span>{{ termin ? $p.t('abgabetool/c4paatyp' + termin.paabgabetyp_kurzbz) : '' }}</span>
</div>
<div class="col-auto text-start p-0" style="min-width: max(80px, 15%); transform: translateX(-30px)">
<div class="text-start px-2" style="min-width: 100px;">
<span>{{ formatDate(termin.datum) }}</span>
</div>
<div class="col-auto" style="transform: translateX(-30px); min-width: 42px;">
<div class="px-1">
<i v-if="termin?.fixtermin" v-tooltip.right="getTooltipFixtermin" class="fa-solid fa-lock"></i>
<i v-if="termin?.abgabedatum && isMobile" v-tooltip.right="getTooltipAbgabeDetected" class="fa-solid fa-file"></i>
</div>
<div v-if="termin?.abgabedatum && !isMobile" class="col-auto" style="transform: translateX(-30px); min-width: 42px;">
<i v-tooltip.right="getTooltipAbgabeDetected" class="fa-solid fa-file"></i>
<div v-if="termin?.abgabedatum && !isMobile" class="px-1">
<i v-tooltip.right="getTooltipAbgabeDetected" class="fa-solid fa-file"></i>
</div>
<div class="flex-grow-1 text-end pe-2">
<span class="fw-bold">{{getNoteBezeichnung(termin)}}</span>
</div>
</div>
</template>
@@ -359,7 +359,8 @@ export const AbgabetoolAssistenz = {
checkAbgabetermineProjektarbeit(projekt) {
// calculate Abgabetermin time diff to now and assign last and next to projekt
projekt.abgabetermine.forEach(termin => {
// while already looping through each termin, calculate datestyle beforehand
termin.dateStyle = this.getDateStyleClass(termin)
@@ -723,7 +724,7 @@ export const AbgabetoolAssistenz = {
termin.diffindays = this.dateDiffInDays(termin.datum)
// seperate status if termin is in the past, it needs a note but doesnt have one yet
if(today > datum && termin.benotbar && !termin.note) return 'beurteilungerforderlich'
if(termin.bezeichnung?.benotbar && !termin.note) return 'beurteilungerforderlich'
if (termin.abgabedatum === null && termin.upload_allowed) {
if(datum < today) {
return 'verpasst' // needs upload, missed it and has not submitted anything
@@ -454,7 +454,6 @@ export const AbgabetoolMitarbeiter = {
this.old_abgabe_beurteilung_link = res.data?.old_abgabe_beurteilung_link
this.abgabetypenBetreuer = res.data?.abgabetypenBetreuer
}).catch(e => {
console.log(e)
this.loading = false
})
@@ -16,7 +16,8 @@ export const AbgabetoolStudent = {
provide() {
return {
notenOptions: Vue.computed(() => this.notenOptions),
isViewMode: Vue.computed(() => this.isViewMode)
isViewMode: Vue.computed(() => this.isViewMode),
moodle_link: Vue.computed(() => this.moodle_link)
}
},
props: {
@@ -35,17 +36,19 @@ export const AbgabetoolStudent = {
data() {
return {
activeTabIndex: [0],
abgabeTypeOptions: null,
phrasenPromise: null,
phrasenResolved: false,
loading: false,
notenOptions: null,
detail: null,
projektarbeiten: null,
selectedProjektarbeit: null
selectedProjektarbeit: null,
moodle_link: null
};
},
methods: {
dateDiffInDays(datumParam){
dateDiffInDays(datumParam) {
let datum = datumParam
if(datumParam instanceof Date && !isNaN(datum.getTime()))
{
@@ -67,14 +70,8 @@ export const AbgabetoolStudent = {
const abgabedatum = new Date(termin.abgabedatum)
termin.diffindays = this.dateDiffInDays(termin.datum)
// console.log('\n\n')
// console.log(termin)
// console.log(today)
// console.log(datum)
// console.log('\n\n')
if(today > datum && termin.benotbar && !termin.note) return 'beurteilungerforderlich'
if (termin.bezeichnung?.benotbar && !termin.note) return 'beurteilungerforderlich'
if (termin.abgabedatum === null && termin.upload_allowed) {
if(datum < today) {
return 'verpasst' // needs upload, missed it and has not submitted anything
@@ -175,6 +172,7 @@ export const AbgabetoolStudent = {
termin.allowedToUpload = termin.upload_allowed
}
termin.bezeichnung = this.abgabeTypeOptions.find(opt => opt.paabgabetyp_kurzbz === termin.paabgabetyp_kurzbz)
termin.dateStyle = this.getDateStyleClass(termin)
})
@@ -324,6 +322,19 @@ export const AbgabetoolStudent = {
this.loading = false
})
// fetch abgabetypen options
this.$api.call(ApiAbgabe.getPaAbgabetypen()).then(res => {
this.abgabeTypeOptions = res.data
}).catch(e => {
this.loading = false
})
// fetch config to avoid hard coded links
this.$api.call(ApiAbgabe.getConfigStudent()).then(res => {
this.moodle_link = res.data?.moodle_link
}).catch(e => {
this.loading = false
})
},
mounted() {
this.setupMounted()
+40
View File
@@ -44872,6 +44872,26 @@ array(
)
)
),
array(
'app' => 'core',
'category' => 'abgabetool',
'phrase' => 'c4noGradeAssignedYet',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => "Keine Note eingetragen",
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'no grade assigned yet',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'abgabetool',
@@ -46153,6 +46173,26 @@ array(
)
)
),
array(
'app' => 'core',
'category' => 'abgabetool',
'phrase' => 'c4checkoutStgMoodleInfos',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Für weitere Information zu den Abgabemodalitäten Ihres Studienganges lesen Sie bitte die Unterlagen in Moodle: ',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => "For further information on the submission procedures for your degree program, please refer to the documents in Moodle: ",
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'abgabetool',