Merge branch 'feature-76671/moving-avg-grade-calc-to-backend' into cis40_2026-02_rc

This commit is contained in:
Harald Bamberger
2026-04-30 07:33:42 +02:00
5 changed files with 80 additions and 35 deletions
@@ -38,7 +38,8 @@ class Lehre extends FHCAPI_Controller
parent::__construct([
'lvStudentenMail' => self::PERM_LOGGED,
'LV' => self::PERM_LOGGED,
'Pruefungen' => self::PERM_LOGGED
'Pruefungen' => self::PERM_LOGGED,
'semesterAverageGrade' => self::PERM_LOGGED,
]);
}
@@ -100,5 +101,49 @@ class Lehre extends FHCAPI_Controller
$this->terminateWithSuccess($result);
}
/**
* calculates and returns the grade average and weighted average for a specific semester
* @param string $studiensemester_kurzbz
* @return void
*/
public function semesterAverageGrade($studiensemester_kurzbz)
{
$this->load->model('education/Lehrveranstaltung_model', 'LehrveranstaltungModel');
$semesterLvs = $this->LehrveranstaltungModel->getLvsByStudentWithGrades(getAuthUID(), $studiensemester_kurzbz, getUserLanguage());
if (isError($semesterLvs))
return $this->outputJsonError(getError($semesterLvs));
$semesterLvsData = getData($semesterLvs);
$doGradesExist = false;
$sum = 0;
$count = 0;
$sumWeighted = 0;
$sumEcts = 0;
foreach ($semesterLvsData as $lv) {
if (!$lv->znote || $lv->znote < 1 || $lv->znote > 5)
continue;
$doGradesExist = true;
$sum += $lv->znote;
$count++;
$sumWeighted += $lv->znote * floatval($lv->ects);
$sumEcts += floatval($lv->ects);
}
$averageGrade = null;
$weightedAverageGrade = null;
if ($doGradesExist) {
$averageGrade = $sum/$count;
$weightedAverageGrade = $sumWeighted/$sumEcts;
}
$this->terminateWithSuccess(['average_grade' => $averageGrade, 'weighted_average_grade' => $weightedAverageGrade]);
}
}
+6
View File
@@ -35,5 +35,11 @@ export default {
method: 'get',
url: `/api/frontend/v1/Lehre/Pruefungen/${lehrveranstaltung_id}`
};
},
getSemesterAverageGrade(semester) {
return {
method: 'get',
url: `/api/frontend/v1/Lehre/semesterAverageGrade/${semester}`
}
}
};
+3 -2
View File
@@ -36,8 +36,9 @@ export default {
return this.lvs.filter(lv => lv.studiengang_kz == studiengang.studiengang_kz && lv.semester == studiengang.semester);
}
},
template: `<div class="mylv-semester" v-if="ready">
<mylv-semester-studiengang v-for="studiengang in studiengaenge" :key="studiengang.studiengang_kz" v-bind="studiengang" :lvs="lvsForStudiengang(studiengang)"/>
template: `
<div class="mylv-semester" v-if="ready">
<mylv-semester-studiengang v-for="studiengang in studiengaenge" :key="studiengang.studiengang_kz" v-bind="studiengang" :lvs="lvsForStudiengang(studiengang)" :semesterInfo="$props.semester" />
</div>
<div class="mylv-semester text-center" v-else>
<i class="fa-solid fa-spinner fa-pulse fa-3x"></i>
@@ -14,6 +14,7 @@ export default {
bezeichnung: String,
kuerzel: String,
semester: [String,Number],
semesterInfo: [String,Number],
lvs: Array,
sg_bezeichnung_eng: String
},
@@ -44,7 +45,7 @@ export default {
<mylv-semester-studiengang-lv v-bind="lv" class="text-center h-100"></mylv-semester-studiengang-lv>
</div>
</div>
<mylv-semester-studiengang-average-grade :lvs="lvs" />
<mylv-semester-studiengang-average-grade :semesterInfo="$props.semesterInfo" />
</div>
</div>`
};
@@ -1,50 +1,42 @@
import Phrasen from "../../../../../mixins/Phrasen.js";
import ApiLehre from "../../../../../api/factory/lehre.js";
export default {
mixins: [
Phrasen
],
props: {
lvs: Array,
semesterInfo: String,
},
data: ( ) =>{
data() {
return {
gradeAverage: null,
gradeWeightedAverage: null,
existingGrades: false
}
},
methods: {
calculateAverages(){
let sum = 0;
let count = 0;
let sumWeighted = 0;
let sumEcts = 0;
async fetchAverageGrade() {
this.gradeAverage = null;
this.gradeWeightedAverage = null;
if (!this.$props.semesterInfo) return;
this.lvs.forEach((lv) => {
if ((lv.znote >= 1 && lv.znote <= 5) && lv.znote!= null) {
this.existingGrades = true;
sum+= lv.znote;
count++;
sumWeighted += lv.znote * Number(lv.ects);
sumEcts += Number(lv.ects);
}
});
this.gradeAverage = (sum/count).toFixed(2);
this.gradeWeightedAverage = (sumWeighted/sumEcts).toFixed(2);
}
let gradeAverageResponse = await this.$api.call(
ApiLehre.getSemesterAverageGrade(this.$props.semesterInfo),
);
const gradeAverageResponseData = gradeAverageResponse.data;
this.gradeAverage =
gradeAverageResponseData.average_grade?.toFixed(2);
this.gradeWeightedAverage =
gradeAverageResponseData.weighted_average_grade?.toFixed(2);
},
},
watch: {
lvs: {
handler() {
this.calculateAverages();
},
deep: true,
immediate: true
}
semesterInfo() {
this.fetchAverageGrade();
},
},
mounted(){
this.calculateAverages();
async created() {
await this.fetchAverageGrade();
},
template: /*html*/`
<div class="card mylv-semester-studiengang-grades">
@@ -53,7 +45,7 @@ export default {
<h6>{{$p.t('lehre/notenstatistik')}}</h6>
</div>
<div v-if="existingGrades">
<div v-if="gradeAverage && gradeWeightedAverage">
<table class="card-body table w-auto mx-auto">
<tbody>
<tr>
@@ -79,7 +71,7 @@ export default {
<p>{{$p.t('lehre/info_noGradesYet')}}</p>
</div>
<div v-if="existingGrades" class="card-footer d-flex align-items-start text-muted small">
<div v-if="gradeAverage && gradeWeightedAverage" class="card-footer d-flex align-items-start text-muted small">
<i class="fa fa-circle-info me-2 mt-1"></i>
<div>
<strong>{{$p.t('ui', 'hinweis')}}</strong><br>