added fixtermin variable to Paabgabe->update() statement; email logic for sancho emails towards betreuer: return $email[0]->uid ? $email[0]->uid.'@'.DOMAIN : $email[0]->private_email; phrasen wordings; reworked assistenz config api promises as allSettled to avoid race conditions; nachreichen möglich is always the default everywhere; WIP enabling the same status logic workflow everywhere;

This commit is contained in:
Johann Hoffmann
2026-01-13 18:20:05 +01:00
parent e89fcbab31
commit bbb90f6dc4
5 changed files with 104 additions and 80 deletions
@@ -531,6 +531,7 @@ class Abgabe extends FHCAPI_Controller
'datum' => $datum, 'datum' => $datum,
'kurzbz' => $kurzbz, 'kurzbz' => $kurzbz,
'note' => $note, 'note' => $note,
'fixtermin' => $fixtermin,
'beurteilungsnotiz' => $beurteilungsnotiz, 'beurteilungsnotiz' => $beurteilungsnotiz,
'upload_allowed' => $upload_allowed, 'upload_allowed' => $upload_allowed,
'updatevon' => getAuthUID(), 'updatevon' => getAuthUID(),
@@ -543,6 +544,7 @@ class Abgabe extends FHCAPI_Controller
'datum' => $datum, 'datum' => $datum,
'kurzbz' => $kurzbz, 'kurzbz' => $kurzbz,
'note' => $note, 'note' => $note,
'fixtermin' => $fixtermin,
'beurteilungsnotiz' => $beurteilungsnotiz, 'beurteilungsnotiz' => $beurteilungsnotiz,
'upload_allowed' => $upload_allowed, 'upload_allowed' => $upload_allowed,
'updatevon' => getAuthUID(), 'updatevon' => getAuthUID(),
@@ -734,7 +736,7 @@ class Abgabe extends FHCAPI_Controller
$result = $this->ProjektarbeitModel->getProjektbetreuerEmail($projektarbeit_id); $result = $this->ProjektarbeitModel->getProjektbetreuerEmail($projektarbeit_id);
$email = $this->getDataOrTerminateWithError($result); $email = $this->getDataOrTerminateWithError($result);
return $email[0]->private_email ?? $email[0]->uid.'@'.DOMAIN; return $email[0]->uid ? $email[0]->uid.'@'.DOMAIN : $email[0]->private_email;
} }
@@ -787,7 +789,7 @@ class Abgabe extends FHCAPI_Controller
$this->terminateWithError($this->p->t('abgabetool','c4userNichtGefunden'), 'general'); $this->terminateWithError($this->p->t('abgabetool','c4userNichtGefunden'), 'general');
} }
$subject = $this->p->t('abgabetool', 'c4qualgateNegativEmailSubject'); $subject = $this->p->t('abgabetool', 'c4qualgateNegativEmailSubjectv2');
$tomail = $student_uid.'@'.DOMAIN; $tomail = $student_uid.'@'.DOMAIN;
$datetime = new DateTime($paabgabe->datum); $datetime = new DateTime($paabgabe->datum);
@@ -299,6 +299,14 @@ export const AbgabeMitarbeiterDetail = {
const abgabedatum = new Date(termin.abgabedatum) const abgabedatum = new Date(termin.abgabedatum)
termin.diffindays = this.dateDiffInDays(termin.datum) 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(today > datum && termin.benotbar && !termin.note) return 'beurteilungerforderlich'
if (termin.abgabedatum === null && termin.upload_allowed) { if (termin.abgabedatum === null && termin.upload_allowed) {
@@ -532,7 +540,7 @@ export const AbgabeMitarbeiterDetail = {
}, },
getTooltipBeurteilungerforderlich() { getTooltipBeurteilungerforderlich() {
return { return {
value: this.$p.t('abgabetool/c4tooltipBeurteilungerfolderlich'), value: this.$p.t('abgabetool/c4tooltipBeurteilungerforderlich'),
class: "custom-tooltip" class: "custom-tooltip"
} }
}, },
@@ -72,6 +72,7 @@ export const AbgabetoolAssistenz = {
selectedStudiengangOption: null, selectedStudiengangOption: null,
studiengaengeOptions: null, studiengaengeOptions: null,
detailIsFullscreen: false, detailIsFullscreen: false,
allConfigPromise: null,
phrasenPromise: null, phrasenPromise: null,
phrasenResolved: false, phrasenResolved: false,
turnitin_link: null, turnitin_link: null,
@@ -90,6 +91,7 @@ export const AbgabetoolAssistenz = {
}, },
kurzbz: '', kurzbz: '',
fixtermin: false, fixtermin: false,
invertedFixtermin: true,
upload_allowed: false upload_allowed: false
}), }),
showAll: false, showAll: false,
@@ -285,7 +287,7 @@ export const AbgabetoolAssistenz = {
} else if(qgate.upload_allowed == true && qgate.abgabedatum == null && projekt.qgate1StatusRank <= 2) { } else if(qgate.upload_allowed == true && qgate.abgabedatum == null && projekt.qgate1StatusRank <= 2) {
projekt.qgate1Status = this.$p.t('abgabetool/c4notSubmitted') projekt.qgate1Status = this.$p.t('abgabetool/c4notSubmitted')
projekt.qgate1StatusRank = 2 projekt.qgate1StatusRank = 2
} else if (qgate.upload_allowed == false && diffMs <= 0 && projekt.qgate1StatusRank <= 1) { } else if (qgate.upload_allowed == false && qgate.diffMs <= 0 && projekt.qgate1StatusRank <= 1) {
projekt.qgate1Status = this.$p.t('abgabetool/c4notHappenedYet') projekt.qgate1Status = this.$p.t('abgabetool/c4notHappenedYet')
projekt.qgate1StatusRank = 1 projekt.qgate1StatusRank = 1
} }
@@ -307,7 +309,7 @@ export const AbgabetoolAssistenz = {
} else if(qgate.upload_allowed == true && qgate.abgabedatum == null && projekt.qgate2StatusRank <= 2) { } else if(qgate.upload_allowed == true && qgate.abgabedatum == null && projekt.qgate2StatusRank <= 2) {
projekt.qgate2Status = this.$p.t('abgabetool/c4notSubmitted') projekt.qgate2Status = this.$p.t('abgabetool/c4notSubmitted')
projekt.qgate2StatusRank = 2 projekt.qgate2StatusRank = 2
} else if (qgate.upload_allowed == false && diffMs <= 0 && projekt.qgate2StatusRank <= 1) { } else if (qgate.upload_allowed == false && qgate.diffMs <= 0 && projekt.qgate2StatusRank <= 1) {
projekt.qgate2Status = this.$p.t('abgabetool/c4notHappenedYet') projekt.qgate2Status = this.$p.t('abgabetool/c4notHappenedYet')
projekt.qgate2StatusRank = 1 projekt.qgate2StatusRank = 1
} }
@@ -582,6 +584,7 @@ export const AbgabetoolAssistenz = {
addSeries() { addSeries() {
const pids = this.selectedData?.map(projekt => projekt.projektarbeit_id) const pids = this.selectedData?.map(projekt => projekt.projektarbeit_id)
this.saving = true this.saving = true
this.serienTermin.fixtermin = !this.serienTermin.invertedFixtermin
this.$api.call(ApiAbgabe.postSerientermin( this.$api.call(ApiAbgabe.postSerientermin(
this.serienTermin.datum.toISOString(), this.serienTermin.datum.toISOString(),
this.serienTermin.bezeichnung.paabgabetyp_kurzbz, this.serienTermin.bezeichnung.paabgabetyp_kurzbz,
@@ -896,6 +899,8 @@ export const AbgabetoolAssistenz = {
this.tableBuiltPromise = new Promise(this.tableResolve) this.tableBuiltPromise = new Promise(this.tableResolve)
await this.tableBuiltPromise await this.tableBuiltPromise
await this.allConfigPromise
// called through notenOptionFilter/selectedStudiengangOption watcher on startup // called through notenOptionFilter/selectedStudiengangOption watcher on startup
// this.loadProjektarbeiten() // this.loadProjektarbeiten()
@@ -929,79 +934,74 @@ export const AbgabetoolAssistenz = {
this.loading = true this.loading = true
this.phrasenPromise = this.$p.loadCategory(['abgabetool', 'global']) this.phrasenPromise = this.$p.loadCategory(['abgabetool', 'global'])
this.phrasenPromise.then(()=> {this.phrasenResolved = true}) this.phrasenPromise.then(()=> {this.phrasenResolved = true})
// 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 => {
this.loading = false
})
// fetch studiengänge options
this.$api.call(ApiAbgabe.getStudiengaenge()).then(res => {
this.studiengaengeOptions = res.data
if(this.studiengaengeOptions?.length) {
// use this.stg_kz_prop as default selected in case of url param usage
this.selectedStudiengangOption = this.stg_kz_prop ? res.data.find(stgOpt => stgOpt.studiengang_kz == this.stg_kz_prop) : res.data[0]
}
}).catch(e => {
this.loading = false
})
this.$api.call(ApiStudiensemester.getAllStudiensemesterAndAktOrNext()).then((res) => {
this.allSem = res.data[0]
const all = {studiensemester_kurzbz: this.$p.t('abgabetool/c4all')}
this.curSem = all // res.data[1]
this.studiensemesterOptions = [all, ...this.allSem]
}).catch(e => {
this.loading = false
})
// fetch noten options
//TODO: SWITCH TO NOTEN API ONCE NOTENTOOL IS IN MASTER TO AVOID DUPLICATE API //TODO: SWITCH TO NOTEN API ONCE NOTENTOOL IS IN MASTER TO AVOID DUPLICATE API
this.$api.call(ApiAbgabe.getNoten()).then(res => { const requests = [
if(res.meta.status == 'success') { this.$api.call(ApiAbgabe.getConfig()),
this.notenOptions = res.data[0] this.$api.call(ApiAbgabe.getStudiengaenge()),
this.$api.call(ApiStudiensemester.getAllStudiensemesterAndAktOrNext()),
this.$api.call(ApiAbgabe.getNoten()),
this.$api.call(ApiAbgabe.getPaAbgabetypen())
];
this.allowedNotenOptions = this.notenOptions.filter( this.allConfigPromise = Promise.allSettled(requests)
opt => res.data[1].includes(opt.note) .then((results) => {
) // results is an array of { status: 'fulfilled'|'rejected', value?: any, reason?: any }
}
// allowedNotenOptions apply to quality gates abgabetermine
// this selection is about graded projektarbeiten, so take different options here
this.allowedNotenFilterOptions = [
{
bezeichnung: Vue.computed(() => this.$p.t('abgabetool/keineNoteEingetragen')),
benotet: 0,
},
{
bezeichnung: Vue.computed(() => this.$p.t('abgabetool/c4benotet')),
benotet: 1,
},
{
bezeichnung: Vue.computed(() => this.$p.t('abgabetool/showAll')),
benotet: -1,
},
]
this.notenOptionFilter = this.allowedNotenFilterOptions[0]
}).catch(e => {
this.loading = false
})
// fetch abgabetypen options // 1. Config
this.$api.call(ApiAbgabe.getPaAbgabetypen()).then(res => { if (results[0].status === 'fulfilled') {
this.abgabeTypeOptions = res.data const res = results[0].value;
}).catch(e => { this.turnitin_link = res.data?.turnitin_link;
this.loading = false this.old_abgabe_beurteilung_link = res.data?.old_abgabe_beurteilung_link;
}) }
// 2. Studiengänge
if (results[1].status === 'fulfilled') {
const res = results[1].value;
this.studiengaengeOptions = res.data;
if (this.studiengaengeOptions?.length) {
this.selectedStudiengangOption = this.stg_kz_prop
? res.data.find(stgOpt => stgOpt.studiengang_kz == this.stg_kz_prop)
: res.data[0];
}
}
// 3. Studiensemester
if (results[2].status === 'fulfilled') {
const res = results[2].value;
this.allSem = res.data[0];
const all = { studiensemester_kurzbz: this.$p.t('abgabetool/c4all') };
this.curSem = all;
this.studiensemesterOptions = [all, ...this.allSem];
}
// 4. Noten
if (results[3].status === 'fulfilled') {
const res = results[3].value;
if (res.meta?.status === 'success') {
this.notenOptions = res.data[0];
this.allowedNotenOptions = this.notenOptions.filter(
opt => res.data[1].includes(opt.note)
);
}
this.allowedNotenFilterOptions = [
{ bezeichnung: Vue.computed(() => this.$p.t('abgabetool/keineNoteEingetragen')), benotet: 0 },
{ bezeichnung: Vue.computed(() => this.$p.t('abgabetool/c4benotet')), benotet: 1 },
{ bezeichnung: Vue.computed(() => this.$p.t('abgabetool/showAll')), benotet: -1 }
];
this.notenOptionFilter = this.allowedNotenFilterOptions[0];
}
// 5. Abgabetypen
if (results[4].status === 'fulfilled') {
const res = results[4].value;
this.abgabeTypeOptions = res.data;
}
})
.finally(() => {
this.loading = false;
});
}, },
mounted() { mounted() {
this.setupMounted() this.setupMounted()
@@ -1023,7 +1023,7 @@ export const AbgabetoolAssistenz = {
<div class="col-12 col-md-3 fw-bold align-content-center">{{$capitalize( $p.t('abgabetool/c4fixterminv4') )}}</div> <div class="col-12 col-md-3 fw-bold align-content-center">{{$capitalize( $p.t('abgabetool/c4fixterminv4') )}}</div>
<div class="col-12 col-md-9"> <div class="col-12 col-md-9">
<Checkbox <Checkbox
v-model="serienTermin.fixtermin" v-model="serienTermin.invertedFixtermin"
:binary="true" :binary="true"
:pt="{ root: { class: 'ml-auto' }}" :pt="{ root: { class: 'ml-auto' }}"
> >
@@ -45,11 +45,19 @@ export const AbgabetoolStudent = {
}; };
}, },
methods: { methods: {
dateDiffInDays(datum){ dateDiffInDays(datumParam){
let datum = datumParam
if(datumParam instanceof Date && !isNaN(datum.getTime()))
{
const year = datumParam.getFullYear();
const month = datumParam.getMonth() + 1; // getMonth() is 0-indexed
const day = datumParam.getDate();
const pad = (num) => String(num).padStart(2, '0');
datum = `${year}-${pad(month)}-${pad(day)}`
}
const dateToday = luxon.DateTime.now().startOf('day'); const dateToday = luxon.DateTime.now().startOf('day');
const dateDatum = luxon.DateTime.fromISO(datum).startOf('day'); const dateDatum = luxon.DateTime.fromISO(datum).startOf('day');
const duration = dateDatum.diff(dateToday, 'days'); const duration = dateDatum.diff(dateToday, 'days');
return duration.values.days; return duration.values.days;
@@ -60,6 +68,12 @@ export const AbgabetoolStudent = {
termin.diffindays = this.dateDiffInDays(termin.datum) 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(today > datum && termin.benotbar && !termin.note) return 'beurteilungerforderlich'
if (termin.abgabedatum === null && termin.upload_allowed) { if (termin.abgabedatum === null && termin.upload_allowed) {
if(datum < today) { if(datum < today) {
+2 -2
View File
@@ -44855,12 +44855,12 @@ array(
array( array(
'app' => 'core', 'app' => 'core',
'category' => 'abgabetool', 'category' => 'abgabetool',
'phrase' => 'c4qualgateNegativEmailSubject', 'phrase' => 'c4qualgateNegativEmailSubjectv2',
'insertvon' => 'system', 'insertvon' => 'system',
'phrases' => array( 'phrases' => array(
array( array(
'sprache' => 'German', 'sprache' => 'German',
'text' => "Quality Gate Negativ beurteilt", 'text' => "Quality Gate negativ beurteilt",
'description' => '', 'description' => '',
'insertvon' => 'system' 'insertvon' => 'system'
), ),