mirror of
https://github.com/FH-Complete/FHC-Core.git
synced 2026-06-01 20:29:29 +00:00
Merge branch 'feature-61164/AbgabetoolQualityGates'
This commit is contained in:
@@ -41,3 +41,5 @@ $config['STG_MOODLE_LINK'] = 'https://moodle.technikum-wien.at/course/view.php?i
|
||||
|
||||
$config['ASSISTENZ_SAMMELMAIL_BUTTON_STUDENT'] = true;
|
||||
$config['ASSISTENZ_SAMMELMAIL_BUTTON_BETREUER'] = true;
|
||||
|
||||
$config['BETREUER_SAMMELMAIL_BUTTON_STUDENT'] = true;
|
||||
|
||||
@@ -89,13 +89,15 @@ class Abgabe extends FHCAPI_Controller
|
||||
$abgabetypenBetreuer = $this->config->item('ALLOWED_ABGABETYPEN_BETREUER');
|
||||
$ASSISTENZ_SAMMELMAIL_BUTTON_STUDENT = $this->config->item('ASSISTENZ_SAMMELMAIL_BUTTON_STUDENT');
|
||||
$ASSISTENZ_SAMMELMAIL_BUTTON_BETREUER = $this->config->item('ASSISTENZ_SAMMELMAIL_BUTTON_BETREUER');
|
||||
$BETREUER_SAMMELMAIL_BUTTON_STUDENT = $this->config->item('BETREUER_SAMMELMAIL_BUTTON_STUDENT');
|
||||
|
||||
$ret = array(
|
||||
'old_abgabe_beurteilung_link' => $old_abgabe_beurteilung_link,
|
||||
'turnitin_link' => $turnitin_link,
|
||||
'abgabetypenBetreuer' => $abgabetypenBetreuer,
|
||||
'ASSISTENZ_SAMMELMAIL_BUTTON_STUDENT' => $ASSISTENZ_SAMMELMAIL_BUTTON_STUDENT,
|
||||
'ASSISTENZ_SAMMELMAIL_BUTTON_BETREUER' => $ASSISTENZ_SAMMELMAIL_BUTTON_BETREUER
|
||||
'ASSISTENZ_SAMMELMAIL_BUTTON_BETREUER' => $ASSISTENZ_SAMMELMAIL_BUTTON_BETREUER,
|
||||
'BETREUER_SAMMELMAIL_BUTTON_STUDENT' => $BETREUER_SAMMELMAIL_BUTTON_STUDENT,
|
||||
);
|
||||
|
||||
$this->terminateWithSuccess($ret);
|
||||
|
||||
@@ -17,6 +17,7 @@ export const AbgabeMitarbeiterDetail = {
|
||||
Message: primevue.message,
|
||||
VueDatePicker
|
||||
},
|
||||
emits: ['paUpdated'],
|
||||
inject: [
|
||||
'abgabeTypeOptions',
|
||||
'abgabetypenBetreuer',
|
||||
@@ -132,8 +133,8 @@ export const AbgabeMitarbeiterDetail = {
|
||||
const noteOptExisting = this.allowedNotenOptions.find(opt => opt.note == existingTerminRes.note)
|
||||
existingTerminRes.note = noteOptExisting
|
||||
|
||||
const existingTerminResCurrObj = this.projektarbeit.abgabetermine.find(paa => paa.paabgabe_id == existingTerminRes.paabgabe_id)
|
||||
existingTerminResCurrObj.noteBackend = noteOpt // do NOT take noteOptExisting -> should reflect the "yes the qgate grade is confirmed in backend ux behaviour"
|
||||
termin.paabgabetyp_kurzbz = newTerminRes.paabgabetyp_kurzbz
|
||||
termin.noteBackend = noteOpt // do NOT take noteOptExisting -> should reflect the "yes the qgate grade is confirmed in backend ux behaviour"
|
||||
termin.dateStyle = getDateStyleClass(termin, this.notenOptions)
|
||||
}
|
||||
|
||||
@@ -174,6 +175,8 @@ export const AbgabeMitarbeiterDetail = {
|
||||
} else {
|
||||
this.showAutomagicModalPhrase = false
|
||||
}
|
||||
|
||||
this.$emit("paUpdated", this.projektarbeit)
|
||||
} else if(res?.meta?.status == 'error'){
|
||||
this.$fhcAlert.alertError()
|
||||
}
|
||||
@@ -257,6 +260,7 @@ 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)
|
||||
this.$emit("paUpdated", this.projektarbeit)
|
||||
} else if(res?.meta?.status == 'error'){
|
||||
this.$fhcAlert.alertError()
|
||||
}
|
||||
|
||||
@@ -210,12 +210,12 @@ export const AbgabetoolAssistenz = {
|
||||
headerFilter: dateFilter,
|
||||
headerFilterFunc: this.headerFilterTerminCol,
|
||||
sorter: this.sortFuncTerminCol,
|
||||
field: 'prevTermin', formatter: this.abgabterminFormatter, widthGrow: 1, width: 220, tooltip: false},
|
||||
field: 'prevTermin', formatter: this.abgabterminFormatter, widthGrow: 1, width: 250, tooltip: false},
|
||||
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4nextAbgabetermin'))), field: 'nextTermin',
|
||||
headerFilter: dateFilter,
|
||||
headerFilterFunc: this.headerFilterTerminCol,
|
||||
sorter: this.sortFuncTerminCol,
|
||||
formatter: this.abgabterminFormatter, widthGrow: 1, width: 220, tooltip: false},
|
||||
formatter: this.abgabterminFormatter, widthGrow: 1, width: 250, tooltip: false},
|
||||
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4qgate1Status'))),
|
||||
headerFilter: 'list',
|
||||
headerFilterParams: { valuesLookup: this.getQGateStatusList },
|
||||
@@ -226,7 +226,7 @@ export const AbgabetoolAssistenz = {
|
||||
field: 'qgate2Status', formatter: this.centeredTextFormatter, widthGrow: 1, width: 220, tooltip: false},
|
||||
],
|
||||
persistence: false,
|
||||
persistenceID: "abgabetool_2026_02"
|
||||
persistenceID: "abgabetool_2026_02_26"
|
||||
},
|
||||
abgabeTableEventHandlers: [
|
||||
{
|
||||
@@ -247,6 +247,10 @@ export const AbgabetoolAssistenz = {
|
||||
]};
|
||||
},
|
||||
methods: {
|
||||
handlePaUpdated(projektarbeit) {
|
||||
this.checkAbgabetermineProjektarbeit(projektarbeit)
|
||||
this.$refs.abgabeTable.tabulator.redraw(true)
|
||||
},
|
||||
getQGateStatusList() {
|
||||
return [
|
||||
this.$p.t('abgabetool/c4keinTerminVorhanden'),
|
||||
@@ -309,11 +313,13 @@ export const AbgabetoolAssistenz = {
|
||||
return false
|
||||
},
|
||||
sammelMailStudent(param) {
|
||||
|
||||
const emails = this.selectedData
|
||||
.map(row => `${row.student_uid}@${this.domain}`)
|
||||
.join(',');
|
||||
const uniqueRecipients = [...new Set(emails)];
|
||||
|
||||
const recipientList = [];
|
||||
this.selectedData.forEach(d => {
|
||||
recipientList.push(`${d.student_uid}@${this.domain}`)
|
||||
})
|
||||
|
||||
const uniqueRecipients = [...new Set(recipientList)];
|
||||
const subject = this.$p.t('abgabetool/c4sammelmailStudentBetreff', [this.selectedStudiengangOption?.bezeichnung]);
|
||||
splitMailsHelper(uniqueRecipients, param.originalEvent, subject, this.$fhcAlert, this.$p)
|
||||
},
|
||||
@@ -362,7 +368,6 @@ export const AbgabetoolAssistenz = {
|
||||
return false;
|
||||
},
|
||||
checkQualityGateStatus(projekt) {
|
||||
// TODO: might refine the representation of these states and maybe refactor code a little
|
||||
const qgate1Termine = []
|
||||
const qgate2Termine = []
|
||||
|
||||
@@ -382,7 +387,7 @@ export const AbgabetoolAssistenz = {
|
||||
// reuse luxon calculated diffMs (termin.datum in relation to today) from previous datestyle check
|
||||
qgate1Termine.forEach(qgate => {
|
||||
if(qgate.note != null && projekt.qgate1StatusRank <= 5) {
|
||||
const noteOpt = this.notenOptions.find(opt => opt.note == qgate.note)
|
||||
const noteOpt = typeof qgate.note !== 'object' ? this.notenOptions.find(opt => opt.note == qgate.note) : qgate.note
|
||||
if(noteOpt.positiv) {
|
||||
projekt.qgate1Status = this.$p.t('abgabetool/c4positivBenotet')
|
||||
projekt.qgate1StatusRank = 5
|
||||
@@ -404,7 +409,7 @@ export const AbgabetoolAssistenz = {
|
||||
|
||||
qgate2Termine.forEach(qgate => {
|
||||
if(qgate.note != null && projekt.qgate1StatusRank <= 5) {
|
||||
const noteOpt = this.notenOptions.find(opt => opt.note == qgate.note)
|
||||
const noteOpt = typeof qgate.note !== 'object' ? this.notenOptions.find(opt => opt.note == qgate.note) : qgate.note
|
||||
if(noteOpt.positiv) {
|
||||
projekt.qgate2Status = this.$p.t('abgabetool/c4positivBenotet')
|
||||
projekt.qgate2StatusRank = 5
|
||||
@@ -1287,7 +1292,13 @@ export const AbgabetoolAssistenz = {
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:default>
|
||||
<AbgabeDetail :projektarbeit="selectedProjektarbeit" :isFullscreen="detailIsFullscreen" :assistenzMode="true"></AbgabeDetail>
|
||||
<AbgabeDetail
|
||||
:projektarbeit="selectedProjektarbeit"
|
||||
:isFullscreen="detailIsFullscreen"
|
||||
:assistenzMode="true"
|
||||
@paUpdated="handlePaUpdated">
|
||||
|
||||
</AbgabeDetail>
|
||||
|
||||
</template>
|
||||
</bs-modal>
|
||||
|
||||
@@ -6,6 +6,7 @@ import ApiAbgabe from '../../../api/factory/abgabe.js'
|
||||
import FhcOverlay from "../../Overlay/FhcOverlay.js";
|
||||
import { getDateStyleClass } from "./getDateStyleClass.js";
|
||||
import { dateFilter } from '../../../tabulator/filters/Dates.js';
|
||||
import {splitMailsHelper} from "../../../helpers/EmailHelpers.js";
|
||||
|
||||
export const AbgabetoolMitarbeiter = {
|
||||
name: "AbgabetoolMitarbeiter",
|
||||
@@ -16,6 +17,7 @@ export const AbgabetoolMitarbeiter = {
|
||||
Checkbox: primevue.checkbox,
|
||||
Dropdown: primevue.dropdown,
|
||||
Textarea: primevue.textarea,
|
||||
TieredMenu: primevue.tieredmenu,
|
||||
VueDatePicker,
|
||||
FhcOverlay
|
||||
},
|
||||
@@ -48,6 +50,7 @@ export const AbgabetoolMitarbeiter = {
|
||||
phrasenResolved: false,
|
||||
turnitin_link: null,
|
||||
old_abgabe_beurteilung_link: null,
|
||||
BETREUER_SAMMELMAIL_BUTTON_STUDENT: null,
|
||||
saving: false,
|
||||
loading: false,
|
||||
abgabeTypeOptions: null,
|
||||
@@ -139,7 +142,6 @@ export const AbgabetoolMitarbeiter = {
|
||||
},
|
||||
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4details'))), field: 'details', formatter: this.detailFormatter, headerFilter: false, headerSort: false, widthGrow: 1, tooltip: false, cssClass: 'sticky-col'},
|
||||
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4personenkennzeichen'))), headerFilter: true, field: 'pkz', formatter: this.pkzTextFormatter, widthGrow: 1, tooltip: false},
|
||||
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4kontakt'))), field: 'mail', formatter: this.mailFormatter, widthGrow: 1, tooltip: false, visible: false},
|
||||
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4vorname'))), field: 'vorname', headerFilter: true, formatter: this.centeredTextFormatter,widthGrow: 1},
|
||||
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4nachname'))), field: 'nachname', headerFilter: true, formatter: this.centeredTextFormatter, widthGrow: 1},
|
||||
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4projekttyp'))), field: 'projekttyp_kurzbz', formatter: this.centeredTextFormatter, widthGrow: 1},
|
||||
@@ -151,12 +153,12 @@ export const AbgabetoolMitarbeiter = {
|
||||
headerFilter: dateFilter,
|
||||
headerFilterFunc: this.headerFilterTerminCol,
|
||||
sorter: this.sortFuncTerminCol,
|
||||
formatter: this.abgabterminFormatter, widthGrow: 1, width: 220, tooltip: false},
|
||||
formatter: this.abgabterminFormatter, widthGrow: 1, width: 250, tooltip: false},
|
||||
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4nextAbgabetermin'))), field: 'nextTermin',
|
||||
headerFilter: dateFilter,
|
||||
headerFilterFunc: this.headerFilterTerminCol,
|
||||
sorter: this.sortFuncTerminCol,
|
||||
formatter: this.abgabterminFormatter, widthGrow: 1, width: 220, tooltip: false},
|
||||
formatter: this.abgabterminFormatter, widthGrow: 1, width: 250, tooltip: false},
|
||||
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4qgate1Status'))),
|
||||
headerFilter: 'list',
|
||||
headerFilterParams: { valuesLookup: this.getQGateStatusList },
|
||||
@@ -167,7 +169,7 @@ export const AbgabetoolMitarbeiter = {
|
||||
field: 'qgate2Status', formatter: this.centeredTextFormatter, widthGrow: 1, width: 220, tooltip: false}
|
||||
],
|
||||
persistence: false,
|
||||
persistenceID: 'abgabeTableBetreuer2026-02-24'
|
||||
persistenceID: 'abgabeTableBetreuer2026-02-26'
|
||||
},
|
||||
abgabeTableEventHandlers: [{
|
||||
event: "tableBuilt",
|
||||
@@ -203,6 +205,20 @@ export const AbgabetoolMitarbeiter = {
|
||||
]};
|
||||
},
|
||||
methods: {
|
||||
handlePaUpdated(projektarbeit) {
|
||||
this.checkAbgabetermineProjektarbeit(projektarbeit)
|
||||
this.$refs.abgabeTable.tabulator.redraw(true)
|
||||
},
|
||||
sammelMailStudent(param) {
|
||||
|
||||
const recipientList = [];
|
||||
this.selectedData.forEach(d => {
|
||||
recipientList.push(`${d.student_uid}@${this.domain}`)
|
||||
})
|
||||
const uniqueRecipients = [...new Set(recipientList)];
|
||||
const subject = ""; // empty subject line
|
||||
splitMailsHelper(uniqueRecipients, param.originalEvent, subject, this.$fhcAlert, this.$p)
|
||||
},
|
||||
getQGateStatusList() {
|
||||
return [
|
||||
this.$p.t('abgabetool/c4keinTerminVorhanden'),
|
||||
@@ -375,7 +391,6 @@ export const AbgabetoolMitarbeiter = {
|
||||
});
|
||||
},
|
||||
checkQualityGateStatus(projekt) {
|
||||
// TODO: might refine the representation of these states and maybe refactor code a little
|
||||
const qgate1Termine = []
|
||||
const qgate2Termine = []
|
||||
|
||||
@@ -395,7 +410,7 @@ export const AbgabetoolMitarbeiter = {
|
||||
// reuse luxon calculated diffMs (termin.datum in relation to today) from previous datestyle check
|
||||
qgate1Termine.forEach(qgate => {
|
||||
if(qgate.note != null && projekt.qgate1StatusRank <= 5) {
|
||||
const noteOpt = this.notenOptions.find(opt => opt.note == qgate.note)
|
||||
const noteOpt = typeof qgate.note !== 'object' ? this.notenOptions.find(opt => opt.note == qgate.note) : qgate.note
|
||||
if(noteOpt.positiv) {
|
||||
projekt.qgate1Status = this.$p.t('abgabetool/c4positivBenotet')
|
||||
projekt.qgate1StatusRank = 5
|
||||
@@ -417,7 +432,7 @@ export const AbgabetoolMitarbeiter = {
|
||||
|
||||
qgate2Termine.forEach(qgate => {
|
||||
if(qgate.note != null && projekt.qgate1StatusRank <= 5) {
|
||||
const noteOpt = this.notenOptions.find(opt => opt.note == qgate.note)
|
||||
const noteOpt = typeof qgate.note !== 'object' ? this.notenOptions.find(opt => opt.note == qgate.note) : qgate.note
|
||||
if(noteOpt.positiv) {
|
||||
projekt.qgate2Status = this.$p.t('abgabetool/c4positivBenotet')
|
||||
projekt.qgate2StatusRank = 5
|
||||
@@ -677,12 +692,13 @@ export const AbgabetoolMitarbeiter = {
|
||||
}
|
||||
|
||||
pa.abgabetermine.forEach(termin => {
|
||||
termin.note = this.allowedNotenOptions.find(opt => opt.note == termin.note)
|
||||
const noteOpt = this.allowedNotenOptions.find(opt => opt.note == termin.note)
|
||||
if(noteOpt) termin.note = noteOpt
|
||||
termin.file = []
|
||||
|
||||
// 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
|
||||
if(!termin.noteBackend && noteOpt) {
|
||||
termin.noteBackend = noteOpt
|
||||
}
|
||||
|
||||
// update 08-01-2026: everybody is allowed to do everything in client, critical checks happen at backend level
|
||||
@@ -719,11 +735,6 @@ export const AbgabetoolMitarbeiter = {
|
||||
return '<div style="display: flex; justify-content: center; align-items: center; height: 100%">' +
|
||||
'<a><i class="fa fa-folder-open" style="color:#00649C"></i></a></div>'
|
||||
},
|
||||
mailFormatter(cell) {
|
||||
const val = cell.getValue()
|
||||
return '<div style="display: flex; justify-content: center; align-items: center; height: 100%">' +
|
||||
'<a href='+val+'><i class="fa fa-envelope" style="color:#00649C"></i></a></div>'
|
||||
},
|
||||
beurteilungFormatter(cell) {
|
||||
const val = cell.getValue()
|
||||
if(val) {
|
||||
@@ -828,6 +839,29 @@ export const AbgabetoolMitarbeiter = {
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
emailItems() {
|
||||
const menu = []
|
||||
|
||||
if(this.BETREUER_SAMMELMAIL_BUTTON_STUDENT){
|
||||
menu.push({
|
||||
label: this.$p.t('abgabetool/c4sendEmailStudierendev2', [this.uniqueStudentEmailCount]),
|
||||
command: this.sammelMailStudent
|
||||
})
|
||||
}
|
||||
|
||||
return menu
|
||||
},
|
||||
uniqueStudentEmailCount() {
|
||||
const emails = new Set();
|
||||
|
||||
this.selectedData.forEach(row => {
|
||||
if (row.student_uid) {
|
||||
emails.add(row.student_uid); // actually dont need domain for this
|
||||
}
|
||||
});
|
||||
|
||||
return emails.size;
|
||||
},
|
||||
getAllowedAbgabeTypeOptions() {
|
||||
return this.abgabeTypeOptions.filter(opt => this.abgabetypenBetreuer.includes(opt.paabgabetyp_kurzbz))
|
||||
}
|
||||
@@ -840,6 +874,7 @@ export const AbgabetoolMitarbeiter = {
|
||||
this.turnitin_link = res.data?.turnitin_link
|
||||
this.old_abgabe_beurteilung_link = res.data?.old_abgabe_beurteilung_link
|
||||
this.abgabetypenBetreuer = res.data?.abgabetypenBetreuer
|
||||
this.BETREUER_SAMMELMAIL_BUTTON_STUDENT = res.data?.BETREUER_SAMMELMAIL_BUTTON_STUDENT
|
||||
}).catch(e => {
|
||||
this.loading = false
|
||||
})
|
||||
@@ -952,7 +987,11 @@ export const AbgabetoolMitarbeiter = {
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:default>
|
||||
<AbgabeDetail :projektarbeit="selectedProjektarbeit" :isFullscreen="detailIsFullscreen"></AbgabeDetail>
|
||||
<AbgabeDetail
|
||||
:projektarbeit="selectedProjektarbeit"
|
||||
:isFullscreen="detailIsFullscreen"
|
||||
@paUpdated="handlePaUpdated">
|
||||
</AbgabeDetail>
|
||||
|
||||
</template>
|
||||
</bs-modal>
|
||||
@@ -988,7 +1027,17 @@ export const AbgabetoolMitarbeiter = {
|
||||
<i class="fa fa-hourglass-end"></i>
|
||||
{{ $p.t('abgabetool/showDeadlines') }}
|
||||
</button>
|
||||
|
||||
<button
|
||||
v-if="emailItems.length"
|
||||
role="button"
|
||||
@click="evt => $refs.menu.toggle(evt)"
|
||||
class="btn btn-outline-secondary dropdown-toggle"
|
||||
aria-haspopup="true"
|
||||
>
|
||||
<i class="fa fa-envelope"></i>
|
||||
</button>
|
||||
<tiered-menu ref="menu" :model="emailItems" popup :autoZIndex="false" />
|
||||
|
||||
</template>
|
||||
</core-filter-cmpt>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user