From 5bbf05ac8abf4346802a5109f389af5b3442ac59 Mon Sep 17 00:00:00 2001 From: Johann Hoffmann Date: Mon, 12 May 2025 12:50:56 +0200 Subject: [PATCH 001/262] WIP Gesamtnoteneingabe Notenberechnung endpoint --- application/config/routes.php | 1 + application/controllers/Cis/Benotungstool.php | 36 ++ .../controllers/api/frontend/v1/Lehre.php | 37 +- .../controllers/api/frontend/v1/Noten.php | 94 ++++ .../api/frontend/v1/Studiensemester.php | 67 +++ .../education/Lehrveranstaltung_model.php | 20 + application/models/education/Note_model.php | 9 + .../models/education/Pruefung_model.php | 15 + .../views/CisRouterView/CisRouterView.php | 3 +- public/js/api/factory/lehre.js | 20 + public/js/api/factory/noten.js | 39 ++ public/js/api/factory/studiensemester.js | 25 + public/js/apps/Dashboard/Fhc.js | 7 + .../Cis/Benotungstool/Benotungstool.js | 337 ++++++++++++++ system/phrasesupdate.php | 427 +++++++++++++++++- 15 files changed, 1134 insertions(+), 3 deletions(-) create mode 100644 application/controllers/Cis/Benotungstool.php create mode 100644 application/controllers/api/frontend/v1/Noten.php create mode 100644 application/controllers/api/frontend/v1/Studiensemester.php create mode 100644 public/js/api/factory/noten.js create mode 100644 public/js/api/factory/studiensemester.js create mode 100644 public/js/components/Cis/Benotungstool/Benotungstool.js diff --git a/application/config/routes.php b/application/config/routes.php index eb4c267ce..e031305ee 100644 --- a/application/config/routes.php +++ b/application/config/routes.php @@ -62,6 +62,7 @@ $route['api/v1/ressource/[B|b]etriebsmittelperson/(:any)'] = 'api/v1/ressource/b $route['api/v1/system/[S|s]prache/(:any)'] = 'api/v1/system/sprache2/$1'; $route['Cis/Stundenplan/.*'] = 'Cis/Stundenplan/index/$1'; +$route['Cis/Benotungstool/.*'] = 'Cis/Benotungstool/index/$1'; // load routes from extensions $subdir = 'application/config/extensions'; diff --git a/application/controllers/Cis/Benotungstool.php b/application/controllers/Cis/Benotungstool.php new file mode 100644 index 000000000..30d4ed0c1 --- /dev/null +++ b/application/controllers/Cis/Benotungstool.php @@ -0,0 +1,36 @@ + self::PERM_LOGGED + ]); + } + + // ----------------------------------------------------------------------------------------------------------------- + // Public methods + + /** + * @return void + */ + public function index() + { + + $viewData = array( + 'uid'=>getAuthUID(), + ); + + $this->load->view('CisRouterView/CisRouterView.php', ['viewData' => $viewData, 'route' => 'Benotungstool']); + } + +} \ No newline at end of file diff --git a/application/controllers/api/frontend/v1/Lehre.php b/application/controllers/api/frontend/v1/Lehre.php index f079a5b37..237f97090 100644 --- a/application/controllers/api/frontend/v1/Lehre.php +++ b/application/controllers/api/frontend/v1/Lehre.php @@ -31,10 +31,15 @@ class Lehre extends FHCAPI_Controller 'lvStudentenMail' => self::PERM_LOGGED, 'LV' => self::PERM_LOGGED, 'Pruefungen' => self::PERM_LOGGED, + 'getLvViewData' => self::PERM_LOGGED, + 'getZugewieseneLv' => self::PERM_LOGGED ]); + // Loads phrases system + $this->loadPhrases([ + 'global' + ]); - } //------------------------------------------------------------------------------------------------------------------ @@ -94,6 +99,36 @@ class Lehre extends FHCAPI_Controller $this->terminateWithSuccess($result); } + + /** + * fetches all assigned lehrveranstaltungen of a mitarbeiter for a given semester + * @param mixed $uid + * @param mixed $sem_kurzbz + * @return void + */ + public function getZugewieseneLv() { + $uid = $this->input->get("uid",TRUE); + $sem_kurzbz = $this->input->get("sem_kurzbz",TRUE); + + if(!isset($sem_kurzbz) || isEmptyString($sem_kurzbz)) + $this->terminateWithError($this->p->t('global', 'wrongParameters'), 'general'); + + if (!isset($uid) || isEmptyString($uid)) + $uid = getAuthUID(); + + // querying other ma_uids data requires admin permission + if($uid !== getAuthUID()) { + $this->load->library('PermissionLib'); + $isAdmin = $this->permissionlib->isBerechtigt('admin'); + if(!$isAdmin) $this->terminateWithError($this->p->t('ui', 'keineBerechtigung'), 'general'); + } + + $this->load->model('education/Lehrveranstaltung_model', 'LehrveranstaltungModel'); + $result = $this->LehrveranstaltungModel->getLvForLektorInSemester($sem_kurzbz, $uid); + $data = $this->getDataOrTerminateWithError($result); + $this->terminateWithSuccess($data); + } + diff --git a/application/controllers/api/frontend/v1/Noten.php b/application/controllers/api/frontend/v1/Noten.php new file mode 100644 index 000000000..50447786c --- /dev/null +++ b/application/controllers/api/frontend/v1/Noten.php @@ -0,0 +1,94 @@ +. + */ + +if (! defined('BASEPATH')) exit('No direct script access allowed'); + +require_once(__DIR__.'/../../../../../include/lehreinheit.class.php'); +require_once(__DIR__.'/../../../../../include/legesamtnote.class.php'); +class Noten extends FHCAPI_Controller +{ + + /** + * Object initialization + */ + public function __construct() + { + parent::__construct([ + 'getStudentenNoten' => self::PERM_LOGGED, // todo: berechtigung + 'getNoten' => self::PERM_LOGGED, + 'saveStudentenNoten' => self::PERM_LOGGED // todo: berechtigungen! + ]); + + $this->load->library('AuthLib', null, 'AuthLib'); + + // Loads phrases system + $this->loadPhrases([ + 'global' + ]); + + } + + public function getStudentenNoten() { + $lv_id = $this->input->get("lv_id",TRUE); + $sem_kurzbz = $this->input->get("sem_kurzbz",TRUE); + $active = true; // todo: check this param + + if (!isset($lv_id) || isEmptyString($lv_id) + || !isset($sem_kurzbz) || isEmptyString($sem_kurzbz)) + $this->terminateWithError($this->p->t('global', 'wrongParameters'), 'general'); + + // todo: check various other berechtigungen if its mitarbeiter/lektor/zugeteilterLektor? + + $this->load->model('education/Pruefung_model', 'PruefungModel'); + $this->load->model('education/Lehrveranstaltung_model', 'LehrveranstaltungModel'); + + // get studenten for lva & sem with zeugnisnote if available + $studenten = $this->LehrveranstaltungModel->getStudentsByLv($sem_kurzbz, $lv_id, $active); + $studentenData = $this->getDataOrTerminateWithError($studenten); + + + + // get all prüfungen with noten held in that semester in that lva + $pruefungen = $this->PruefungModel->getPruefungenByLvStudiensemester($lv_id, $sem_kurzbz); + $pruefungenData = $this->getDataOrTerminateWithError($pruefungen); + + $this->terminateWithSuccess(array($studentenData, $pruefungenData, DOMAIN)); + } + + public function getNoten() { + $this->load->model('education/Note_model', 'NoteModel'); + + $result = $this->NoteModel->getAll(); + $noten = $this->getDataOrTerminateWithError($result); + $this->terminateWithSuccess($noten); + } + + public function saveStudentenNoten() { + $result = $this->getPostJSON(); + + if(!property_exists($result, 'password')) { + $this->terminateWithError($this->p->t('global', 'missingParameters'), 'general'); + } + + // TODO: send & save noten + + $this.$this->terminateWithSuccess($this->AuthLib->checkUserAuthByUsernamePassword(getAuthUID(), $result->password)); + } + +} + diff --git a/application/controllers/api/frontend/v1/Studiensemester.php b/application/controllers/api/frontend/v1/Studiensemester.php new file mode 100644 index 000000000..44f236a3c --- /dev/null +++ b/application/controllers/api/frontend/v1/Studiensemester.php @@ -0,0 +1,67 @@ +. + */ + +if (! defined('BASEPATH')) exit('No direct script access allowed'); +class Studiensemester extends FHCAPI_Controller +{ + + private $_ci; + + /** + * Object initialization + */ + public function __construct() + { + parent::__construct([ + 'getStudiensemester'=> self::PERM_LOGGED, + + ]); + + $this->_ci =& get_instance(); + + $this->_ci->load->model('organisation/Studiensemester_model', 'StudiensemesterModel'); + + + } + + //------------------------------------------------------------------------------------------------------------------ + // Public methods + + /** + * GET METHOD + * returns List of all studiensemester as well as current one + */ + public function getStudiensemester() + { + $this->_ci->StudiensemesterModel->addOrder("start", "DESC"); + $result = $this->_ci->StudiensemesterModel->load(); + + $studiensemester = getData($result); + $result = $this->_ci->StudiensemesterModel->getAkt(); + $aktuell = getData($result); + + $this->terminateWithSuccess(array($studiensemester, $aktuell)); + } + + //------------------------------------------------------------------------------------------------------------------ + // Private methods + + + +} + diff --git a/application/models/education/Lehrveranstaltung_model.php b/application/models/education/Lehrveranstaltung_model.php index 056fb45d7..883c6d9ca 100644 --- a/application/models/education/Lehrveranstaltung_model.php +++ b/application/models/education/Lehrveranstaltung_model.php @@ -988,4 +988,24 @@ class Lehrveranstaltung_model extends DB_Model return $this->execQuery($qry, $params); } + + public function getLvForLektorInSemester($sem_kurzbz, $uid) { + $qry = "SELECT DISTINCT (tbl_lehrveranstaltung.lehrveranstaltung_id), + UPPER(tbl_studiengang.typ::varchar(1) || tbl_studiengang.kurzbz) as stg_kurzbz, + tbl_lehrveranstaltung.semester as lv_semester, + tbl_lehrveranstaltung.bezeichnung as lv_bezeichnung, + (SELECT kurzbz FROM public.tbl_mitarbeiter + WHERE mitarbeiter_uid=tbl_lehreinheitmitarbeiter.mitarbeiter_uid) as lektor + FROM + lehre.tbl_lehreinheit JOIN lehre.tbl_lehreinheitmitarbeiter USING(lehreinheit_id) + JOIN lehre.tbl_lehrveranstaltung USING(lehrveranstaltung_id) + JOIN public.tbl_studiengang USING(studiengang_kz) + JOIN lehre.tbl_lehrveranstaltung as lehrfach ON(tbl_lehreinheit.lehrfach_id=lehrfach.lehrveranstaltung_id) + WHERE + tbl_lehreinheit.studiensemester_kurzbz = ? + AND mitarbeiter_uid = ? + ORDER BY stg_kurzbz,lv_semester,lv_bezeichnung"; + + return $this->execReadOnlyQuery($qry, array($sem_kurzbz, $uid)); + } } diff --git a/application/models/education/Note_model.php b/application/models/education/Note_model.php index 80b454398..fe601b1f9 100644 --- a/application/models/education/Note_model.php +++ b/application/models/education/Note_model.php @@ -11,4 +11,13 @@ class Note_model extends DB_Model $this->dbTable = 'lehre.tbl_note'; $this->pk = 'note'; } + + public function getAll() { + $qry ="SELECT * + FROM lehre.tbl_note"; + + return $this->execReadOnlyQuery($qry); + } + + } \ No newline at end of file diff --git a/application/models/education/Pruefung_model.php b/application/models/education/Pruefung_model.php index 927d83c82..d3cc72d69 100644 --- a/application/models/education/Pruefung_model.php +++ b/application/models/education/Pruefung_model.php @@ -306,4 +306,19 @@ class Pruefung_model extends DB_Model return $this->loadWhereCommitteeExamsFailed(); } + + public function getPruefungenByLvStudiensemester($lv_id, $sem_kurzbz) { + $qry = "SELECT tbl_pruefung.*, tbl_lehrveranstaltung.bezeichnung as lehrveranstaltung_bezeichnung, tbl_lehrveranstaltung.lehrveranstaltung_id, + tbl_note.bezeichnung as note_bezeichnung, tbl_pruefungstyp.beschreibung as typ_beschreibung, tbl_lehreinheit.studiensemester_kurzbz as studiensemester_kurzbz + FROM lehre.tbl_pruefung, lehre.tbl_lehreinheit, lehre.tbl_lehrveranstaltung, lehre.tbl_note, lehre.tbl_pruefungstyp + WHERE tbl_pruefung.lehreinheit_id=tbl_lehreinheit.lehreinheit_id + AND tbl_lehreinheit.lehrveranstaltung_id=tbl_lehrveranstaltung.lehrveranstaltung_id + AND tbl_pruefung.note = tbl_note.note + AND tbl_pruefung.pruefungstyp_kurzbz=tbl_pruefungstyp.pruefungstyp_kurzbz + AND tbl_lehrveranstaltung.lehrveranstaltung_id = ? + AND tbl_lehreinheit.studiensemester_kurzbz = ? + ORDER BY datum DESC;"; + + return $this->execReadOnlyQuery($qry, array($lv_id, $sem_kurzbz)); + } } diff --git a/application/views/CisRouterView/CisRouterView.php b/application/views/CisRouterView/CisRouterView.php index 4590b210e..9d24d9a68 100644 --- a/application/views/CisRouterView/CisRouterView.php +++ b/application/views/CisRouterView/CisRouterView.php @@ -23,7 +23,8 @@ $includesArray = array( ), 'customJSs' => array( 'vendor/npm-asset/primevue/accordion/accordion.js', - 'vendor/npm-asset/primevue/accordiontab/accordiontab.js' + 'vendor/npm-asset/primevue/accordiontab/accordiontab.js', + 'vendor/npm-asset/primevue/password/password.js' ), 'customJSModules' => array( 'public/js/apps/Dashboard/Fhc.js' diff --git a/public/js/api/factory/lehre.js b/public/js/api/factory/lehre.js index 84c8e8662..f9306839e 100644 --- a/public/js/api/factory/lehre.js +++ b/public/js/api/factory/lehre.js @@ -35,5 +35,25 @@ export default { method: 'get', url: `/api/frontend/v1/Lehre/Pruefungen/${lehrveranstaltung_id}` }; + }, + getStudentenNoten(lv_id, sem_kurzbz) { + return { + method: 'get', + url: '/api/frontend/v1/Noten/getStudentenNoten', + params: { lv_id, sem_kurzbz } + }; + }, + getNoten(){ + return { + method: 'get', + url: '/api/frontend/v1/Noten/getNoten' + }; + }, + getZugewieseneLv(uid, sem_kurzbz){ + return { + method: 'get', + url: '/api/frontend/v1/Lehre/getZugewieseneLv', + params: { uid, sem_kurzbz} + }; } }; \ No newline at end of file diff --git a/public/js/api/factory/noten.js b/public/js/api/factory/noten.js new file mode 100644 index 000000000..e0343cb4b --- /dev/null +++ b/public/js/api/factory/noten.js @@ -0,0 +1,39 @@ +/** + * Copyright (C) 2025 fhcomplete.org + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +export default { + getStudentenNoten(lv_id, sem_kurzbz) { + return { + method: 'get', + url: '/api/frontend/v1/Noten/getStudentenNoten', + params: { lv_id, sem_kurzbz } + }; + }, + getNoten(){ + return { + method: 'get', + url: '/api/frontend/v1/Noten/getNoten' + }; + }, + saveStudentenNoten(password) { + return { + method: 'post', + url: '/api/frontend/v1/Noten/saveStudentenNoten', + params: { password } + }; + } +}; \ No newline at end of file diff --git a/public/js/api/factory/studiensemester.js b/public/js/api/factory/studiensemester.js new file mode 100644 index 000000000..ac4819e98 --- /dev/null +++ b/public/js/api/factory/studiensemester.js @@ -0,0 +1,25 @@ +/** + * Copyright (C) 2025 fhcomplete.org + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +export default { + getStudiensemester() { + return { + method: 'get', + url: '/api/frontend/v1/Studiensemester/getStudiensemester' + }; + }, +}; \ No newline at end of file diff --git a/public/js/apps/Dashboard/Fhc.js b/public/js/apps/Dashboard/Fhc.js index 3b6bb5683..707961078 100644 --- a/public/js/apps/Dashboard/Fhc.js +++ b/public/js/apps/Dashboard/Fhc.js @@ -9,6 +9,7 @@ import CmsNews from "../../components/Cis/Cms/News.js"; import CmsContent from "../../components/Cis/Cms/Content.js"; import Info from "../../components/Cis/Mylv/Semester/Studiengang/Lv/Info.js"; import RoomInformation, {DEFAULT_MODE_RAUMINFO} from "../../components/Cis/Mylv/RoomInformation.js"; +import Benotungstool from "../../components/Cis/Benotungstool/Benotungstool.js"; const ciPath = FHC_JS_DATA_STORAGE_OBJECT.app_root.replace(/(https:|)(^|\/\/)(.*?\/)/g, '') + FHC_JS_DATA_STORAGE_OBJECT.ci_router; @@ -27,6 +28,12 @@ const router = VueRouter.createRouter({ component: Profil, props: true }, + { + path: `/Cis/Benotungstool/:lv_id/:sem_kurzbz`, + name: 'Benotungstool', + component: Benotungstool, + props: true + }, // Redirect old links to new format { diff --git a/public/js/components/Cis/Benotungstool/Benotungstool.js b/public/js/components/Cis/Benotungstool/Benotungstool.js new file mode 100644 index 000000000..6dd968316 --- /dev/null +++ b/public/js/components/Cis/Benotungstool/Benotungstool.js @@ -0,0 +1,337 @@ +import {CoreFilterCmpt} from "../../filter/Filter.js"; +import ApiLehre from "../../../api/factory/lehre.js"; +import ApiNoten from "../../../api/factory/noten.js"; +import ApiStudiensemester from "../../../api/factory/studiensemester.js"; +import BsModal from '../../Bootstrap/Modal.js'; + +export const Benotungstool = { + name: "Benotungstool", + components: { + BsModal, + CoreFilterCmpt, + Dropdown: primevue.dropdown, + Password: primevue.password + }, + props: { + lv_id: { + default: null, + required: true + }, + sem_kurzbz: { + default: null, + required: true + }, + viewData: { + type: Object, + required: true, + default: () => ({name: '', uid: ''}), + validator(value) { + return value && value.name && value.uid + } + } + }, + data() { + return { + password: '', + tabulatorUuid: Vue.ref(0), + domain: '', + lv: null, + studenten: null, + pruefungen: null, + studiensemester: null, + selectedSemester: null, + lehrveranstaltungen: null, + selectedLehrveranstaltung: null, + tableBuiltResolve: null, + notenOptions: null, + tableBuiltPromise: null, + notenTableOptions: { + height: 700, + index: 'student_uid', + layout: 'fitColumns', + placeholder: this.$p.t('global/noDataAvailable'), + columns: [ + {title: Vue.computed(() => this.$p.t('benotungstool/c4mail')), field: 'email', formatter: this.mailFormatter, tooltip: false, widthGrow: 1}, + {title: 'UID', field: 'uid', tooltip: false, widthGrow: 1}, + {title: Vue.computed(() => this.$p.t('benotungstool/c4vorname')), field: 'vorname', tooltip: false, widthGrow: 1}, + {title: Vue.computed(() => this.$p.t('benotungstool/c4nachname')), field: 'nachname', widthGrow: 1}, + {title: Vue.computed(() => this.$p.t('benotungstool/c4teilnoten')), field: 'teilnoten', widthGrow: 1}, + {title: Vue.computed(() => this.$p.t('benotungstool/c4note')), field: 'note_vorschlag', + editor: 'list', + editorParams: { + values: Vue.computed(()=>this.notenOptions.map(opt => { + return { + label: opt.bezeichnung, + value: opt.note + } + })) + }, + formatter: (cell) => { + const value = cell.getValue() + const match = this.notenOptions.find(opt => opt.note === value) + return match ? match.bezeichnung : value + }, + widthGrow: 1}, + {title: '', width: 50, hozAlign: 'center', formatter: this.arrowFormatter, cellClick: this.saveNote}, + {title: Vue.computed(() => this.$p.t('benotungstool/c4lvnote')), field: 'lv_note', + formatter: (cell) => { + const value = cell.getValue() + const match = this.notenOptions.find(opt => opt.note === value) + return match ? match.bezeichnung : value + }, + widthGrow: 1}, + {title: Vue.computed(() => this.$p.t('benotungstool/c4freigabe')), field: 'freigegeben', widthGrow: 1}, + {title: Vue.computed(() => this.$p.t('benotungstool/c4zeugnisnote')), field: 'note', widthGrow: 1} + // {title: Vue.computed(() => this.$p.t('benotungstool/c4termin1')), field: 'termin1', widthGrow: 1}, + // {title: Vue.computed(() => this.$p.t('benotungstool/c4termin2')), field: 'termin2', widthGrow: 1}, + // {title: Vue.computed(() => this.$p.t('benotungstool/c4termin3')), field: 'termin3', widthGrow: 1} + ], + persistence: false, + }, + notenTableEventHandlers: [{ + event: "tableBuilt", + handler: async () => { + this.tableBuiltResolve() + } + }, + { + event: "cellClick", + handler: async (e, cell) => { + + + + } + } + ]}; + }, + methods: { + handlePasswordChanged(pw) { + console.log('pw:', pw) + }, + saveNote(e, cell) { + const row = cell.getRow() + const data = row.getData() + + row.update({ note: data.note_vorschlag }) + }, + arrowFormatter(cell) { + return '
' + + '
' + }, + mailFormatter(cell) { + const val = cell.getValue() + return '
' + + '
' + }, + buildMailToLink(student){ + return 'mailto:' + student.uid +'@'+ this.domain + }, + tableResolve(resolve) { + this.tableBuiltResolve = resolve + }, + setupData(data){ + this.studenten = data[0] + this.pruefungen = data[1] + this.domain = data[2] + + this.pruefungen.forEach(p => { + const student = this.studenten.find(s => s.uid === p.student_uid) + + if(!student) return + // TODO: fetch typen and remove hardcoded strings + // TODO: dynamic amount of termin columns! + // if(p.pruefungstyp_kurzbz == "Termin1") { + // student.termin1 = p + // } else if (p.pruefungstyp_kurzbz == "Termin2") { + // student.termin2 = p + // } else if (p.pruefungstyp_kurzbz == "Termin3") { + // student.termin3 = p + // } + + // TODO: LE TEILNOTEN IN BACKEND LADEN, BERECHNEN UND VORSCHLAG PASTEN + // if(p.negativ) + student.teilnoten = '' + }) + + this.studenten.forEach(s => { + s.email = this.buildMailToLink(s) + }) + + this.$refs.notenTable.tabulator.clearSort() + this.$refs.notenTable.tabulator.setColumns(this.notenTableOptions.columns) + this.$refs.notenTable.tabulator.setData(this.studenten); + }, + loadNoten(lv_id, sem_kurzbz) { + this.$api.call(ApiNoten.getStudentenNoten(lv_id, sem_kurzbz)) + .then(res => { + if(res?.data) this.setupData(res.data) + }) + }, + handleUuidDefined(uuid) { + this.tabulatorUuid = uuid + }, + calcMaxTableHeight() { + const tableID = this.tabulatorUuid ? ('-' + this.tabulatorUuid) : '' + const tableDataSet = document.getElementById('filterTableDataset' + tableID); + if(!tableDataSet) return + const rect = tableDataSet.getBoundingClientRect(); + + this.notenTableOptions.height = window.visualViewport.height - rect.top + this.$refs.notenTable.tabulator.setHeight(this.notenTableOptions.height) + }, + async setupCreated() { + // fetch lva dropdown + this.$api.call(ApiLehre.getZugewieseneLv(this.viewData?.uid, this.sem_kurzbz)).then(res => { + console.log(res) + this.lehrveranstaltungen = res.data + + // build dropdown option string + this.lehrveranstaltungen.forEach(lva => { + lva.fullString = `${lva.stg_kurzbz} - ${lva.lv_semester}: ${lva.lv_bezeichnung}` + }) + + this.selectedLehrveranstaltung = this.lehrveranstaltungen.find(lva => lva.lehrveranstaltung_id == this.lv_id) + }) + + //fetch sem_kurzbz dropdown + this.$api.call(ApiStudiensemester.getStudiensemester()).then(res => { + this.studiensemester = res.data[0] + this.selectedSemester = this.studiensemester.find(sem => sem.studiensemester_kurzbz === this.sem_kurzbz) + }) + + // fetch noten dropdown + await this.$api.call(ApiNoten.getNoten()).then(res => { + this.notenOptions = res.data + }) + + }, + async setupMounted() { + this.tableBuiltPromise = new Promise(this.tableResolve) + await this.tableBuiltPromise + + this.loadNoten(this.lv_id, this.sem_kurzbz) + + this.calcMaxTableHeight() + + }, + lvChanged(e) { + this.$router.push({ + name: "Benotungstool", + params: { + sem_kurzbz: this.sem_kurzbz, + lv_id: e.value.lehrveranstaltung_id + } + }) + + // reload data + this.loadNoten(e.value.lehrveranstaltung_id, this.sem_kurzbz) + }, + ssChanged(e) { + // change url params & write history + this.$router.push({ + name: "Benotungstool", + params: { + sem_kurzbz: e.value.studiensemester_kurzbz, + lv_id: this.lv_id + } + }) + + // reload data + this.loadNoten(this.lv_id, e.value.studiensemester_kurzbz) + + }, + getOptionLabel(option) { + return option.studiensemester_kurzbz + }, + getOptionLabelLv(option) { + return option.fullString + }, + saveNoteneingabe() { + + this.$api.call(ApiNoten.saveStudentenNoten(this.password)) + + this.$refs.modalContainerNotenSpeichern.hide() + }, + openSaveModal() { + this.$refs.modalContainerNotenSpeichern.show() + } + }, + watch: { + + }, + computed: { + getSaveBtnClass() { + return "btn btn-primary ml-2" + // return !this.changedData.length ? "btn btn-secondary ml-2" : "btn btn-primary ml-2" + } + }, + created() { + this.setupCreated() + }, + mounted() { + this.setupMounted() + }, + template: ` + + + + + + +
+
+

{{$p.t('benotungstool/benotungstoolTitle')}}

+

{{ lv?.bezeichnung }}

+
+
+
+ + + +
+
+
+
+ + + +
+
+
+
+ + + + + + + `, +}; + +export default Benotungstool; \ No newline at end of file diff --git a/system/phrasesupdate.php b/system/phrasesupdate.php index 1afe4a196..d15a9f266 100644 --- a/system/phrasesupdate.php +++ b/system/phrasesupdate.php @@ -41453,8 +41453,433 @@ and represent the current state of research on the topic. The prescribed citatio 'insertvon' => 'system' ) ) - ) + ), // PROJEKTARBEITSBEURTEILUNG SS2025 ENDE --------------------------------------------------------------------------- + // CIS4 GESAMTNOTENEINGABE START ----------------------------------------------------------------------------------- + array( + 'app' => 'core', + 'category' => 'benotungstool', + 'phrase' => 'benotungstoolTitle', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Gesamtnote', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Final Grade', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'benotungstool', + 'phrase' => 'c4mail', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Kontakt', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Email', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'benotungstool', + 'phrase' => 'c4vorname', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Vorname', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'First Name', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'benotungstool', + 'phrase' => 'c4nachname', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Nachname', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Last Name', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'benotungstool', + 'phrase' => 'c4teilnoten', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Teilnoten', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Partial Grades', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'benotungstool', + 'phrase' => 'c4note', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Punkte/Note', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Points/Grade', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'benotungstool', + 'phrase' => 'c4lvnote', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'LV-Note', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Subject grade', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'benotungstool', + 'phrase' => 'c4freigabe', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Freigabe', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Approval', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'benotungstool', + 'phrase' => 'c4password', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Passwort', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Password', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'benotungstool', + 'phrase' => 'c4zeugnisnote', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Zeugnisnote', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Transcript Grade', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'benotungstool', + 'phrase' => 'c4date', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Datum', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Date', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'benotungstool', + 'phrase' => 'c4grade', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Note', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Grade', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'benotungstool', + 'phrase' => 'c4termin1', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Nachprüfung', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Re-examination', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'benotungstool', + 'phrase' => 'c4termin2', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => '2. Nebenprüfungstermin', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => '2nd subsidiary examination date', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'benotungstool', + 'phrase' => 'c4termin3', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Kommissionelle Prüfung', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Oral Examination', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'benotungstool', + 'phrase' => 'c4anlegen', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Anlegen', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Create', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'benotungstool', + 'phrase' => 'c4change', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Ändern', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Change', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'benotungstool', + 'phrase' => 'c4import', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Importieren', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Import', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'benotungstool', + 'phrase' => 'c4changedGradesAvailable', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Es sind geänderte Noten vorhanden. Geben Sie diese frei, um die Assistenz zu informieren', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'There are changed grades. Please send the changes to the assistant by clicking "Approval"', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'benotungstool', + 'phrase' => 'c4gradeListExcel', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Notenliste für den LV-Noten-Import (Excel)', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Grade list for the subject grade import (Excel)', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'benotungstool', + 'phrase' => 'passwort', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Passwort', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Password', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ) + + // CIS4 GESAMTNOTENEINGABE ENDE ------------------------------------------------------------------------------------ + + ); From 658fe79ad7246cd60f31265d4343e1b5d6ba9a43 Mon Sep 17 00:00:00 2001 From: Johann Hoffmann Date: Mon, 12 May 2025 16:45:33 +0200 Subject: [PATCH 002/262] load Teilnoten via moodle Event trigger; WIP further noten/punkte logic; --- .../controllers/api/frontend/v1/Noten.php | 30 +++++++++++++++---- .../Cis/Benotungstool/Benotungstool.js | 25 +++++++++------- 2 files changed, 40 insertions(+), 15 deletions(-) diff --git a/application/controllers/api/frontend/v1/Noten.php b/application/controllers/api/frontend/v1/Noten.php index 50447786c..9a5030d82 100644 --- a/application/controllers/api/frontend/v1/Noten.php +++ b/application/controllers/api/frontend/v1/Noten.php @@ -18,8 +18,8 @@ if (! defined('BASEPATH')) exit('No direct script access allowed'); -require_once(__DIR__.'/../../../../../include/lehreinheit.class.php'); -require_once(__DIR__.'/../../../../../include/legesamtnote.class.php'); +use CI3_Events as Events; + class Noten extends FHCAPI_Controller { @@ -46,7 +46,6 @@ class Noten extends FHCAPI_Controller public function getStudentenNoten() { $lv_id = $this->input->get("lv_id",TRUE); $sem_kurzbz = $this->input->get("sem_kurzbz",TRUE); - $active = true; // todo: check this param if (!isset($lv_id) || isEmptyString($lv_id) || !isset($sem_kurzbz) || isEmptyString($sem_kurzbz)) @@ -58,16 +57,37 @@ class Noten extends FHCAPI_Controller $this->load->model('education/Lehrveranstaltung_model', 'LehrveranstaltungModel'); // get studenten for lva & sem with zeugnisnote if available - $studenten = $this->LehrveranstaltungModel->getStudentsByLv($sem_kurzbz, $lv_id, $active); + $studenten = $this->LehrveranstaltungModel->getStudentsByLv($sem_kurzbz, $lv_id); $studentenData = $this->getDataOrTerminateWithError($studenten); + $func = function ($value) { + return $value->uid; + }; + + $grades = array(); + $student_uids = array_map($func, $studentenData); + foreach($student_uids as $uid) { + $grades[$uid]['grades'] = []; + } + // send $grades reference to moodle addon + Events::trigger( + 'moodleGrades', + function & () use (&$grades) + { + return $grades; + }, + [ + 'lvid' => $lv_id, + 'stsem' => $sem_kurzbz + ] + ); // get all prüfungen with noten held in that semester in that lva $pruefungen = $this->PruefungModel->getPruefungenByLvStudiensemester($lv_id, $sem_kurzbz); $pruefungenData = $this->getDataOrTerminateWithError($pruefungen); - $this->terminateWithSuccess(array($studentenData, $pruefungenData, DOMAIN)); + $this->terminateWithSuccess(array($studentenData, $pruefungenData, DOMAIN, $grades)); } public function getNoten() { diff --git a/public/js/components/Cis/Benotungstool/Benotungstool.js b/public/js/components/Cis/Benotungstool/Benotungstool.js index 6dd968316..2f3dfe3be 100644 --- a/public/js/components/Cis/Benotungstool/Benotungstool.js +++ b/public/js/components/Cis/Benotungstool/Benotungstool.js @@ -48,14 +48,14 @@ export const Benotungstool = { notenTableOptions: { height: 700, index: 'student_uid', - layout: 'fitColumns', + layout: 'fitDataStretch', placeholder: this.$p.t('global/noDataAvailable'), columns: [ {title: Vue.computed(() => this.$p.t('benotungstool/c4mail')), field: 'email', formatter: this.mailFormatter, tooltip: false, widthGrow: 1}, {title: 'UID', field: 'uid', tooltip: false, widthGrow: 1}, {title: Vue.computed(() => this.$p.t('benotungstool/c4vorname')), field: 'vorname', tooltip: false, widthGrow: 1}, {title: Vue.computed(() => this.$p.t('benotungstool/c4nachname')), field: 'nachname', widthGrow: 1}, - {title: Vue.computed(() => this.$p.t('benotungstool/c4teilnoten')), field: 'teilnoten', widthGrow: 1}, + {title: Vue.computed(() => this.$p.t('benotungstool/c4teilnoten')), field: 'teilnote', widthGrow: 1}, {title: Vue.computed(() => this.$p.t('benotungstool/c4note')), field: 'note_vorschlag', editor: 'list', editorParams: { @@ -74,14 +74,10 @@ export const Benotungstool = { widthGrow: 1}, {title: '', width: 50, hozAlign: 'center', formatter: this.arrowFormatter, cellClick: this.saveNote}, {title: Vue.computed(() => this.$p.t('benotungstool/c4lvnote')), field: 'lv_note', - formatter: (cell) => { - const value = cell.getValue() - const match = this.notenOptions.find(opt => opt.note === value) - return match ? match.bezeichnung : value - }, + formatter: this.notenFormatter, widthGrow: 1}, {title: Vue.computed(() => this.$p.t('benotungstool/c4freigabe')), field: 'freigegeben', widthGrow: 1}, - {title: Vue.computed(() => this.$p.t('benotungstool/c4zeugnisnote')), field: 'note', widthGrow: 1} + {title: Vue.computed(() => this.$p.t('benotungstool/c4zeugnisnote')), field: 'note', formatter: this.notenFormatter, widthGrow: 1} // {title: Vue.computed(() => this.$p.t('benotungstool/c4termin1')), field: 'termin1', widthGrow: 1}, // {title: Vue.computed(() => this.$p.t('benotungstool/c4termin2')), field: 'termin2', widthGrow: 1}, // {title: Vue.computed(() => this.$p.t('benotungstool/c4termin3')), field: 'termin3', widthGrow: 1} @@ -105,6 +101,11 @@ export const Benotungstool = { ]}; }, methods: { + notenFormatter(cell) { + const value = cell.getValue() + const match = this.notenOptions.find(opt => opt.note === value) + return match ? match.bezeichnung : value + }, handlePasswordChanged(pw) { console.log('pw:', pw) }, @@ -133,6 +134,7 @@ export const Benotungstool = { this.studenten = data[0] this.pruefungen = data[1] this.domain = data[2] + this.teilnoten = data[3] this.pruefungen.forEach(p => { const student = this.studenten.find(s => s.uid === p.student_uid) @@ -149,12 +151,15 @@ export const Benotungstool = { // } // TODO: LE TEILNOTEN IN BACKEND LADEN, BERECHNEN UND VORSCHLAG PASTEN - // if(p.negativ) - student.teilnoten = '' + // if(p.negativ) }) this.studenten.forEach(s => { s.email = this.buildMailToLink(s) + + const grades = this.teilnoten[s.uid].grades + s.teilnote = '' + grades.forEach(g => s.teilnote += g.text + ' ') }) this.$refs.notenTable.tabulator.clearSort() From b752b475d92f3b893dfb4976f5c398329b99370f Mon Sep 17 00:00:00 2001 From: SimonGschnell Date: Wed, 4 Jun 2025 11:18:02 +0200 Subject: [PATCH 003/262] update(Lehrauftrag Mitarbeiter View): adds the new CIS4-Header/Footer if in CIS4 context and updates minor layout problems --- .../views/lehre/lehrauftrag/Dashboard.php | 33 ++- .../lehre/lehrauftrag/LehrendeUebersicht.php | 31 ++- .../lehre/lehrauftrag/approveLehrauftrag.php | 212 ++++++++++-------- .../lehre/lehrauftrag/orderLehrauftrag.php | 211 +++++++++-------- application/views/widgets/navigationMenu.php | 2 +- 5 files changed, 288 insertions(+), 201 deletions(-) diff --git a/application/views/lehre/lehrauftrag/Dashboard.php b/application/views/lehre/lehrauftrag/Dashboard.php index 3da01700c..8fa364618 100644 --- a/application/views/lehre/lehrauftrag/Dashboard.php +++ b/application/views/lehre/lehrauftrag/Dashboard.php @@ -1,7 +1,5 @@ load->view( - 'templates/FHC-Header', - array( +$includesArray = array( 'title' => 'Lehrauftrag bestellen', 'jquery3' => true, 'jqueryui1' => true, @@ -12,8 +10,20 @@ $this->load->view( 'dialoglib' => true, 'navigationwidget' => true, 'addons' => true, - ) ); + +if(defined('CIS4')){ + $this->load->view( + 'templates/CISVUE-Header', + $includesArray + ); +}else{ + $this->load->view( + 'templates/FHC-Header', + $includesArray + ); +} + ?> widgetlib->widget('NavigationWidget'); ?> @@ -33,4 +43,17 @@ $this->load->view( -load->view('templates/FHC-Footer'); ?> +load->view( + 'templates/CISVUE-Footer', + $includesArray + ); +}else{ + $this->load->view( + 'templates/FHC-Footer', + $includesArray + ); +} + ?> diff --git a/application/views/lehre/lehrauftrag/LehrendeUebersicht.php b/application/views/lehre/lehrauftrag/LehrendeUebersicht.php index 4f49a2518..721dc8d44 100644 --- a/application/views/lehre/lehrauftrag/LehrendeUebersicht.php +++ b/application/views/lehre/lehrauftrag/LehrendeUebersicht.php @@ -1,7 +1,6 @@ load->view( - 'templates/FHC-Header', - array( + +$includesArray = array( 'title' => 'Lehrauftrag bestellen', 'jquery3' => true, 'bootstrap3' => true, @@ -9,8 +8,18 @@ $this->load->view( 'sbadmintemplate3' => true, 'ajaxlib' => true, 'navigationwidget' => true, - ) ); +if(defined('CIS4')){ + $this->load->view( + 'templates/CISVUE-Header', + $includesArray + ); +}else{ + $this->load->view( + 'templates/FHC-Header', + $includesArray + ); +} ?> widgetlib->widget('NavigationWidget'); ?> @@ -34,4 +43,16 @@ $this->load->view( -load->view('templates/FHC-Footer'); ?> +load->view( + 'templates/CISVUE-Footer', + $includesArray + ); +}else{ + $this->load->view( + 'templates/FHC-Footer', + $includesArray + ); +} +?> diff --git a/application/views/lehre/lehrauftrag/approveLehrauftrag.php b/application/views/lehre/lehrauftrag/approveLehrauftrag.php index a598be015..1634d992f 100644 --- a/application/views/lehre/lehrauftrag/approveLehrauftrag.php +++ b/application/views/lehre/lehrauftrag/approveLehrauftrag.php @@ -1,99 +1,108 @@ load->view( - 'templates/FHC-Header', - array( - 'title' => 'Lehrauftrag erteilen', - 'jquery3' => true, - 'jqueryui1' => true, - 'jquerycheckboxes1' => true, - 'bootstrap3' => true, - 'fontawesome6' => true, - 'sbadmintemplate3' => true, - 'tabulator5' => true, - 'tabulator5JQuery' => true, - 'momentjs2' => true, - 'ajaxlib' => true, - 'dialoglib' => true, - 'tablewidget' => true, - 'navigationwidget' => true, - 'phrases' => array( - 'global' => array( - 'lehrauftraegeErteilen', - 'mehrHilfe', - 'weitereInformationenUnter' - ), - 'ui' => array( - 'anzeigen', - 'alleAnzeigen', - 'nurNeueAnzeigen', - 'nurBestellteAnzeigen', - 'nurErteilteAnzeigen', - 'nurAngenommeneAnzeigen', - 'nurGeaenderteAnzeigen', - 'nurDummiesAnzeigen', - 'hilfeZuDieserSeite', - 'alleAuswaehlen', - 'alleAbwaehlen', - 'ausgewaehlteZeilen', - 'hilfe', - 'tabelleneinstellungen', - 'keineDatenVorhanden', - 'spaltenEinstellen', - 'bestelltVon', - 'erteiltVon', - 'angenommenVon', - 'stundenStundensatzGeaendert', - 'neuerLehrauftragOhneLektorVerplant', - 'wartetAufBestellung', - 'wartetAufErneuteBestellung', - 'neuerLehrauftragWartetAufBestellung', - 'letzterStatusBestellt', - 'letzterStatusErteilt', - 'letzterStatusAngenommen', - ), - 'table' => array( - 'spaltenEinAusblenden', - 'spaltenEinAusblendenMitKlickOeffnen', - 'spaltenEinAusblendenAufEinstellungenKlicken', - 'spaltenEinAusblendenMitKlickAktivieren', - 'spaltenEinAusblendenMitKlickSchliessen', - 'spaltenbreiteVeraendern', - 'spaltenbreiteVeraendernText', - 'spaltenbreiteVeraendernInfotext', - 'zeilenAuswaehlen', - 'zeilenAuswaehlenEinzeln', - 'zeilenAuswaehlenBereich', - 'zeilenAuswaehlenAlle' - ), - 'lehre' => array( - 'lehrauftragStandardBestellprozess', - 'lehrauftragStandardBestellprozessBestellen', - 'lehrauftragStandardBestellprozessErteilen', - 'lehrauftragStandardBestellprozessAnnehmen', - 'lehrauftraegeErteilen', - 'lehrauftraegeErteilenText', - 'lehrauftraegeErteilenKlickStatusicon', - 'lehrauftraegeErteilenLehrauftraegeWaehlen', - 'lehrauftraegeErteilenMitKlickErteilen', - 'geaenderteLehrauftraege', - 'geaenderteLehrauftraegeTextBeiErteilung', - 'lehrauftraegeNichtAuswaehlbar', - 'lehrauftraegeNichtAuswaehlbarTextBeiErteilung', - 'filterAlle', - 'filterNeu', - 'filterBestellt', - 'filterErteilt', - 'filterAngenommen', - 'filterGeaendert', - 'filterDummies' - ) - ), - 'customJSs' => array( - 'public/js/bootstrapper.js', - 'public/js/lehre/lehrauftrag/approveLehrauftrag.js' - ) - ) +$includesArray = array( + 'title' => 'Lehrauftrag erteilen', + 'jquery3' => true, + 'jqueryui1' => true, + 'jquerycheckboxes1' => true, + 'bootstrap3' => true, + 'fontawesome6' => true, + 'sbadmintemplate3' => true, + 'tabulator5' => true, + 'tabulator5JQuery' => true, + 'momentjs2' => true, + 'ajaxlib' => true, + 'dialoglib' => true, + 'tablewidget' => true, + 'navigationwidget' => true, + 'phrases' => array( + 'global' => array( + 'lehrauftraegeErteilen', + 'mehrHilfe', + 'weitereInformationenUnter' + ), + 'ui' => array( + 'anzeigen', + 'alleAnzeigen', + 'nurNeueAnzeigen', + 'nurBestellteAnzeigen', + 'nurErteilteAnzeigen', + 'nurAngenommeneAnzeigen', + 'nurGeaenderteAnzeigen', + 'nurDummiesAnzeigen', + 'hilfeZuDieserSeite', + 'alleAuswaehlen', + 'alleAbwaehlen', + 'ausgewaehlteZeilen', + 'hilfe', + 'tabelleneinstellungen', + 'keineDatenVorhanden', + 'spaltenEinstellen', + 'bestelltVon', + 'erteiltVon', + 'angenommenVon', + 'stundenStundensatzGeaendert', + 'neuerLehrauftragOhneLektorVerplant', + 'wartetAufBestellung', + 'wartetAufErneuteBestellung', + 'neuerLehrauftragWartetAufBestellung', + 'letzterStatusBestellt', + 'letzterStatusErteilt', + 'letzterStatusAngenommen', + ), + 'table' => array( + 'spaltenEinAusblenden', + 'spaltenEinAusblendenMitKlickOeffnen', + 'spaltenEinAusblendenAufEinstellungenKlicken', + 'spaltenEinAusblendenMitKlickAktivieren', + 'spaltenEinAusblendenMitKlickSchliessen', + 'spaltenbreiteVeraendern', + 'spaltenbreiteVeraendernText', + 'spaltenbreiteVeraendernInfotext', + 'zeilenAuswaehlen', + 'zeilenAuswaehlenEinzeln', + 'zeilenAuswaehlenBereich', + 'zeilenAuswaehlenAlle' + ), + 'lehre' => array( + 'lehrauftragStandardBestellprozess', + 'lehrauftragStandardBestellprozessBestellen', + 'lehrauftragStandardBestellprozessErteilen', + 'lehrauftragStandardBestellprozessAnnehmen', + 'lehrauftraegeErteilen', + 'lehrauftraegeErteilenText', + 'lehrauftraegeErteilenKlickStatusicon', + 'lehrauftraegeErteilenLehrauftraegeWaehlen', + 'lehrauftraegeErteilenMitKlickErteilen', + 'geaenderteLehrauftraege', + 'geaenderteLehrauftraegeTextBeiErteilung', + 'lehrauftraegeNichtAuswaehlbar', + 'lehrauftraegeNichtAuswaehlbarTextBeiErteilung', + 'filterAlle', + 'filterNeu', + 'filterBestellt', + 'filterErteilt', + 'filterAngenommen', + 'filterGeaendert', + 'filterDummies' + ) + ), + 'customJSs' => array( + 'public/js/bootstrapper.js', + 'public/js/lehre/lehrauftrag/approveLehrauftrag.js' + ) ); + +if(defined('CIS4')){ + $this->load->view( + 'templates/CISVUE-Header', + $includesArray + ); +}else{ + $this->load->view( + 'templates/FHC-Header', + $includesArray + ); +} ?> widgetlib->widget('NavigationWidget'); ?> @@ -208,5 +217,18 @@ $this->load->view(
-load->view('templates/FHC-Footer'); ?> +load->view( + 'templates/CISVUE-Footer', + $includesArray + ); +}else{ + $this->load->view( + 'templates/FHC-Footer', + $includesArray + ); +} + ?> diff --git a/application/views/lehre/lehrauftrag/orderLehrauftrag.php b/application/views/lehre/lehrauftrag/orderLehrauftrag.php index 6806aa401..ab66f998f 100644 --- a/application/views/lehre/lehrauftrag/orderLehrauftrag.php +++ b/application/views/lehre/lehrauftrag/orderLehrauftrag.php @@ -1,99 +1,108 @@ load->view( - 'templates/FHC-Header', - array( - 'title' => 'Lehrauftrag bestellen', - 'jquery3' => true, - 'jqueryui1' => true, - 'jquerycheckboxes1' => true, - 'bootstrap3' => true, - 'fontawesome6' => true, - 'sbadmintemplate3' => true, - 'tabulator5' => true, - 'tabulator5JQuery' => true, - 'momentjs2' => true, - 'ajaxlib' => true, - 'dialoglib' => true, - 'tablewidget' => true, - 'navigationwidget' => true, - 'phrases' => array( - 'global' => array( - 'lehrauftraegeBestellen', - 'mehrHilfe', - 'weitereInformationenUnter' - ), - 'ui' => array( - 'anzeigen', - 'alleAnzeigen', - 'nurNeueAnzeigen', - 'nurBestellteAnzeigen', - 'nurErteilteAnzeigen', - 'nurAngenommeneAnzeigen', - 'nurGeaenderteAnzeigen', - 'nurDummiesAnzeigen', - 'hilfeZuDieserSeite', - 'alleAuswaehlen', - 'alleAbwaehlen', - 'ausgewaehlteZeilen', - 'hilfe', - 'tabelleneinstellungen', - 'keineDatenVorhanden', - 'spaltenEinstellen', - 'bestelltVon', - 'erteiltVon', - 'angenommenVon', - 'neuerLehrauftragOhneLektorVerplant', - 'neuerLehrauftragWartetAufBestellung', - 'letzterStatusBestellt', - 'letzterStatusErteilt', - 'letzterStatusAngenommen', - 'nachAenderungStundensatzStunden', - 'vorAenderungStundensatzStunden' - ), - 'table' => array( - 'spaltenEinAusblenden', - 'spaltenEinAusblendenMitKlickOeffnen', - 'spaltenEinAusblendenAufEinstellungenKlicken', - 'spaltenEinAusblendenMitKlickAktivieren', - 'spaltenEinAusblendenMitKlickSchliessen', - 'spaltenbreiteVeraendern', - 'spaltenbreiteVeraendernText', - 'spaltenbreiteVeraendernInfotext', - 'zeilenAuswaehlen', - 'zeilenAuswaehlenEinzeln', - 'zeilenAuswaehlenBereich', - 'zeilenAuswaehlenAlle' - ), - 'lehre' => array( - 'lehrauftragStandardBestellprozess', - 'lehrauftragStandardBestellprozessBestellen', - 'lehrauftragStandardBestellprozessErteilen', - 'lehrauftragStandardBestellprozessAnnehmen', - 'lehrauftraegeBestellen', - 'lehrauftraegeBestellenText', - 'lehrauftraegeBestellenKlickStatusicon', - 'lehrauftraegeBestellenLehrauftraegeWaehlen', - 'lehrauftraegeBestellenMitKlickBestellen', - 'lehrauftraegeBestellenVertragWirdAngelegt', - 'geaenderteLehrauftraege', - 'geaenderteLehrauftraegeText', - 'lehrauftraegeNichtAuswaehlbar', - 'lehrauftraegeNichtAuswaehlbarText', - 'filterAlle', - 'filterNeu', - 'filterBestellt', - 'filterErteilt', - 'filterAngenommen', - 'filterGeaendert', - 'filterDummies' - ) - ), - 'customJSs' => array( - 'public/js/bootstrapper.js', - 'public/js/lehre/lehrauftrag/orderLehrauftrag.js' - ) - ) +$includesArray = array( + 'title' => 'Lehrauftrag bestellen', + 'jquery3' => true, + 'jqueryui1' => true, + 'jquerycheckboxes1' => true, + 'bootstrap3' => true, + 'fontawesome6' => true, + 'sbadmintemplate3' => true, + 'tabulator5' => true, + 'tabulator5JQuery' => true, + 'momentjs2' => true, + 'ajaxlib' => true, + 'dialoglib' => true, + 'tablewidget' => true, + 'navigationwidget' => true, + 'phrases' => array( + 'global' => array( + 'lehrauftraegeBestellen', + 'mehrHilfe', + 'weitereInformationenUnter' + ), + 'ui' => array( + 'anzeigen', + 'alleAnzeigen', + 'nurNeueAnzeigen', + 'nurBestellteAnzeigen', + 'nurErteilteAnzeigen', + 'nurAngenommeneAnzeigen', + 'nurGeaenderteAnzeigen', + 'nurDummiesAnzeigen', + 'hilfeZuDieserSeite', + 'alleAuswaehlen', + 'alleAbwaehlen', + 'ausgewaehlteZeilen', + 'hilfe', + 'tabelleneinstellungen', + 'keineDatenVorhanden', + 'spaltenEinstellen', + 'bestelltVon', + 'erteiltVon', + 'angenommenVon', + 'neuerLehrauftragOhneLektorVerplant', + 'neuerLehrauftragWartetAufBestellung', + 'letzterStatusBestellt', + 'letzterStatusErteilt', + 'letzterStatusAngenommen', + 'nachAenderungStundensatzStunden', + 'vorAenderungStundensatzStunden' + ), + 'table' => array( + 'spaltenEinAusblenden', + 'spaltenEinAusblendenMitKlickOeffnen', + 'spaltenEinAusblendenAufEinstellungenKlicken', + 'spaltenEinAusblendenMitKlickAktivieren', + 'spaltenEinAusblendenMitKlickSchliessen', + 'spaltenbreiteVeraendern', + 'spaltenbreiteVeraendernText', + 'spaltenbreiteVeraendernInfotext', + 'zeilenAuswaehlen', + 'zeilenAuswaehlenEinzeln', + 'zeilenAuswaehlenBereich', + 'zeilenAuswaehlenAlle' + ), + 'lehre' => array( + 'lehrauftragStandardBestellprozess', + 'lehrauftragStandardBestellprozessBestellen', + 'lehrauftragStandardBestellprozessErteilen', + 'lehrauftragStandardBestellprozessAnnehmen', + 'lehrauftraegeBestellen', + 'lehrauftraegeBestellenText', + 'lehrauftraegeBestellenKlickStatusicon', + 'lehrauftraegeBestellenLehrauftraegeWaehlen', + 'lehrauftraegeBestellenMitKlickBestellen', + 'lehrauftraegeBestellenVertragWirdAngelegt', + 'geaenderteLehrauftraege', + 'geaenderteLehrauftraegeText', + 'lehrauftraegeNichtAuswaehlbar', + 'lehrauftraegeNichtAuswaehlbarText', + 'filterAlle', + 'filterNeu', + 'filterBestellt', + 'filterErteilt', + 'filterAngenommen', + 'filterGeaendert', + 'filterDummies' + ) + ), + 'customJSs' => array( + 'public/js/bootstrapper.js', + 'public/js/lehre/lehrauftrag/orderLehrauftrag.js' + ) ); + +if(defined('CIS4')){ + $this->load->view( + 'templates/CISVUE-Header', + $includesArray + ); +}else{ + $this->load->view( + 'templates/FHC-Header', + $includesArray + ); +} ?> @@ -209,5 +218,17 @@ $this->load->view(
-load->view('templates/FHC-Footer'); ?> +load->view( + 'templates/CISVUE-Footer', + $includesArray + ); +}else{ + $this->load->view( + 'templates/FHC-Footer', + $includesArray + ); +} + ?> diff --git a/application/views/widgets/navigationMenu.php b/application/views/widgets/navigationMenu.php index 42dd901fb..974128343 100644 --- a/application/views/widgets/navigationMenu.php +++ b/application/views/widgets/navigationMenu.php @@ -1,4 +1,4 @@ -
-load->view('templates/FHC-Footer'); ?> +load->view( + 'templates/CISVUE-Footer', + $includesArray + ); +} else { + $this->load->view( + 'templates/FHC-Footer', + $includesArray + ); +} +?> diff --git a/application/views/lehre/lehrauftrag/approveLehrauftrag.php b/application/views/lehre/lehrauftrag/approveLehrauftrag.php index 1634d992f..822cd396e 100644 --- a/application/views/lehre/lehrauftrag/approveLehrauftrag.php +++ b/application/views/lehre/lehrauftrag/approveLehrauftrag.php @@ -92,17 +92,11 @@ $includesArray = array( ) ); -if(defined('CIS4')){ - $this->load->view( - 'templates/CISVUE-Header', - $includesArray - ); -}else{ - $this->load->view( - 'templates/FHC-Header', - $includesArray - ); -} + +$this->load->view( + 'templates/FHC-Header', + $includesArray +); ?> widgetlib->widget('NavigationWidget'); ?> @@ -219,16 +213,11 @@ if(defined('CIS4')){ load->view( - 'templates/CISVUE-Footer', - $includesArray - ); -}else{ - $this->load->view( - 'templates/FHC-Footer', - $includesArray - ); -} + +$this->load->view( + 'templates/FHC-Footer', + $includesArray +); + ?> diff --git a/application/views/lehre/lehrauftrag/orderLehrauftrag.php b/application/views/lehre/lehrauftrag/orderLehrauftrag.php index ab66f998f..107db2871 100644 --- a/application/views/lehre/lehrauftrag/orderLehrauftrag.php +++ b/application/views/lehre/lehrauftrag/orderLehrauftrag.php @@ -92,17 +92,11 @@ $includesArray = array( ) ); -if(defined('CIS4')){ - $this->load->view( - 'templates/CISVUE-Header', - $includesArray - ); -}else{ - $this->load->view( - 'templates/FHC-Header', - $includesArray - ); -} + +$this->load->view( + 'templates/FHC-Header', + $includesArray +); ?> @@ -219,16 +213,10 @@ if(defined('CIS4')){
load->view( - 'templates/CISVUE-Footer', - $includesArray - ); -}else{ - $this->load->view( - 'templates/FHC-Footer', - $includesArray - ); -} + +$this->load->view( + 'templates/FHC-Footer', + $includesArray +); ?> From 6a3982347b8fff5b1938bbb07532a5cc62f4c8e4 Mon Sep 17 00:00:00 2001 From: Johann Hoffmann Date: Thu, 24 Jul 2025 17:02:57 +0200 Subject: [PATCH 005/262] =?UTF-8?q?teilnoten/punkte=20berechnen=20f=C3=BCr?= =?UTF-8?q?=20notenvorschl=C3=A4ge;=20vorschl=C3=A4ge=20=C3=BCbernehmen;?= =?UTF-8?q?=20noten=20freigabe=20mit=20passwort;=20pr=C3=BCfungen=20generi?= =?UTF-8?q?sch=20anzeigen=20nach=20datum=20gruppiert;=20pr=C3=BCfungen=20a?= =?UTF-8?q?nlegen/bearbeiten=20auf=20mapping=20termin1/2/3=20m=C3=B6glich.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- application/controllers/Cis/Benotungstool.php | 6 + .../controllers/api/frontend/v1/Lehre.php | 14 +- .../controllers/api/frontend/v1/Noten.php | 455 +++++++++++- .../models/education/Lehreinheit_model.php | 22 + .../education/Lehrveranstaltung_model.php | 5 +- application/models/education/Note_model.php | 5 +- .../views/CisRouterView/CisRouterView.php | 1 + public/js/api/factory/lehre.js | 20 +- public/js/api/factory/noten.js | 18 +- .../Cis/Benotungstool/Benotungstool.js | 698 +++++++++++++++--- system/phrasesupdate.php | 144 +++- 11 files changed, 1270 insertions(+), 118 deletions(-) diff --git a/application/controllers/Cis/Benotungstool.php b/application/controllers/Cis/Benotungstool.php index 30d4ed0c1..81379b587 100644 --- a/application/controllers/Cis/Benotungstool.php +++ b/application/controllers/Cis/Benotungstool.php @@ -15,6 +15,8 @@ class Benotungstool extends Auth_Controller parent::__construct([ 'index' => self::PERM_LOGGED ]); + + $this->_ci =& get_instance(); } // ----------------------------------------------------------------------------------------------------------------- @@ -26,8 +28,12 @@ class Benotungstool extends Auth_Controller public function index() { + + // TODO: check if related CIS config is also loaded when being routed in Cis4 by vuerouter + // TODO: check if new benotungstool should be configurable the exact same way? $viewData = array( 'uid'=>getAuthUID(), + 'CIS_GESAMTNOTE_UEBERSCHREIBEN' => CIS_GESAMTNOTE_UEBERSCHREIBEN ); $this->load->view('CisRouterView/CisRouterView.php', ['viewData' => $viewData, 'route' => 'Benotungstool']); diff --git a/application/controllers/api/frontend/v1/Lehre.php b/application/controllers/api/frontend/v1/Lehre.php index 237f97090..5c821296f 100644 --- a/application/controllers/api/frontend/v1/Lehre.php +++ b/application/controllers/api/frontend/v1/Lehre.php @@ -32,7 +32,8 @@ class Lehre extends FHCAPI_Controller 'LV' => self::PERM_LOGGED, 'Pruefungen' => self::PERM_LOGGED, 'getLvViewData' => self::PERM_LOGGED, - 'getZugewieseneLv' => self::PERM_LOGGED + 'getZugewieseneLv' => self::PERM_LOGGED, + 'getLeForLv' => self::PERM_LOGGED ]); // Loads phrases system @@ -129,6 +130,17 @@ class Lehre extends FHCAPI_Controller $this->terminateWithSuccess($data); } + + public function getLeForLv() { + $lv_id = $this->input->get("lv_id",TRUE); + $sem_kurzbz = $this->input->get("sem_kurzbz",TRUE); + + $this->load->model('education/Lehreinheit_model', 'LehreinheitModel'); + +// $this->terminateWithSuccess($this->LehreinheitModel->getLesForLv($lv_id, $sem_kurzbz)); + $this->terminateWithSuccess($this->LehreinheitModel->getAllLehreinheitenForLvaAndMaUid($lv_id, getAuthUID(), $sem_kurzbz)); + } + diff --git a/application/controllers/api/frontend/v1/Noten.php b/application/controllers/api/frontend/v1/Noten.php index 9a5030d82..86c56d5ad 100644 --- a/application/controllers/api/frontend/v1/Noten.php +++ b/application/controllers/api/frontend/v1/Noten.php @@ -31,7 +31,10 @@ class Noten extends FHCAPI_Controller parent::__construct([ 'getStudentenNoten' => self::PERM_LOGGED, // todo: berechtigung 'getNoten' => self::PERM_LOGGED, - 'saveStudentenNoten' => self::PERM_LOGGED // todo: berechtigungen! + 'saveStudentenNoten' => self::PERM_LOGGED, // todo: berechtigungen! + 'getNotenvorschlagStudent' => self::PERM_LOGGED, + 'saveNotenvorschlag' => self::PERM_LOGGED, + 'saveStudentPruefung' => self::PERM_LOGGED ]); $this->load->library('AuthLib', null, 'AuthLib'); @@ -40,9 +43,16 @@ class Noten extends FHCAPI_Controller $this->loadPhrases([ 'global' ]); - + require_once(FHCPATH . 'include/mobilitaet.class.php'); + require_once(FHCPATH . 'include/student.class.php'); + require_once(FHCPATH . 'include/lvgesamtnote.class.php'); + require_once(FHCPATH . 'include/lehrveranstaltung.class.php'); + require_once(FHCPATH . 'include/lehreinheit.class.php'); + require_once(FHCPATH . 'include/studiengang.class.php'); + require_once(FHCPATH . 'include/pruefung.class.php'); + } - + public function getStudentenNoten() { $lv_id = $this->input->get("lv_id",TRUE); $sem_kurzbz = $this->input->get("sem_kurzbz",TRUE); @@ -58,18 +68,44 @@ class Noten extends FHCAPI_Controller // get studenten for lva & sem with zeugnisnote if available $studenten = $this->LehrveranstaltungModel->getStudentsByLv($sem_kurzbz, $lv_id); - $studentenData = $this->getDataOrTerminateWithError($studenten); +// $studentenData = $this->getDataOrTerminateWithError($studenten); + $studentenData = getData($studenten); + $this->addMeta('$studentenData', $studentenData); $func = function ($value) { return $value->uid; }; - + $grades = array(); $student_uids = array_map($func, $studentenData); foreach($student_uids as $uid) { $grades[$uid]['grades'] = []; + + $student = new student(); + $student->load($uid); + $student->result[]= $student; + $prestudent_id = $student->prestudent_id; + + $mobility = new mobilitaet(); + $mobility->loadPrestudent($prestudent_id); + $output = $mobility->result; + $eintrag = ''; + foreach ($output as $k) + { + if(($k->mobilitaetstyp_kurzbz == 'GS') && ($k->studiensemester_kurzbz == $sem_kurzbz)) + $eintrag = ' (d.d.)'; + } + $grades[$uid]['mobility'] = $eintrag; + + if ($lvgesamtnote = new lvgesamtnote($lv_id, $uid, $sem_kurzbz)) + { + $grades[$uid]['note_lv'] = $lvgesamtnote->note; + $grades[$uid]['freigabedatum'] = $lvgesamtnote->freigabedatum; + $grades[$uid]['benotungsdatum'] = $lvgesamtnote->benotungsdatum; + $grades[$uid]['punkte_lv'] = $lvgesamtnote->punkte; + } } - + // send $grades reference to moodle addon Events::trigger( 'moodleGrades', @@ -82,18 +118,81 @@ class Noten extends FHCAPI_Controller 'stsem' => $sem_kurzbz ] ); + $this->addMeta('$grades', $grades); + + // calculate notenvorschläge from teilnoten, TODO: seperate function + own endpoint + foreach($studentenData as $student) { + $g = $grades[$student->uid]['grades']; + $note_lv = $grades[$student->uid]['note_lv']; + + // overwrite any calculation with lv note once available + if(!is_null($note_lv)) { + $student->note_vorschlag = $note_lv; + } else if(count($g) > 0) { + + $notensumme = 0; + $notensumme_gewichtet = 0; + $gewichtsumme = 0; + $punktesumme = 0; + $punktesumme_gewichtet = 0; + $anzahlnoten = 0; + foreach($g as $teilnote) { + if (is_numeric($teilnote['grade']) || (is_null($teilnote['grade']) && is_numeric($teilnote['points']))) + { + $notensumme += $teilnote['grade']; + $punktesumme += $teilnote['points']; + $notensumme_gewichtet += $teilnote['grade'] * $teilnote['weight']; + $punktesumme_gewichtet += $teilnote['points'] * $teilnote['weight']; + $gewichtsumme += $teilnote['weight']; + $anzahlnoten += 1; + } + } + + // calculate grades points from notenschlüssel + if (CIS_GESAMTNOTE_PUNKTE) + { + if (defined('CIS_GESAMTNOTE_GEWICHTUNG') && CIS_GESAMTNOTE_GEWICHTUNG) + { + // Lehreinheitsgewichtung + $punkte_vorschlag = round($punktesumme_gewichtet / $gewichtsumme, 2); + $notenschluessel = new notenschluessel(); + $note_vorschlag = $notenschluessel->getNote($punkte_vorschlag, $lv_id, $sem_kurzbz); + } + else + { + $punkte_vorschlag = round($punktesumme / $anzahlnoten, 2); + $notenschluessel = new notenschluessel(); + $note_vorschlag = $notenschluessel->getNote($punkte_vorschlag, $lv_id, $sem_kurzbz); + } + } + else + { + if (defined('CIS_GESAMTNOTE_GEWICHTUNG') && CIS_GESAMTNOTE_GEWICHTUNG) + { + $note_vorschlag = round($notensumme_gewichtet / $gewichtsumme); + } + else + { + $note_vorschlag = round($notensumme / $anzahlnoten); + } + } + + $student->note_vorschlag = $note_vorschlag; + } + } // get all prüfungen with noten held in that semester in that lva $pruefungen = $this->PruefungModel->getPruefungenByLvStudiensemester($lv_id, $sem_kurzbz); - $pruefungenData = $this->getDataOrTerminateWithError($pruefungen); - + $pruefungenData = getData($pruefungen); +// $pruefungenData = $this->getDataOrTerminateWithError($pruefungen); + $this->addMeta('$pruefungenData', $pruefungenData); $this->terminateWithSuccess(array($studentenData, $pruefungenData, DOMAIN, $grades)); } public function getNoten() { $this->load->model('education/Note_model', 'NoteModel'); - $result = $this->NoteModel->getAll(); + $result = $this->NoteModel->getAllActive(); $noten = $this->getDataOrTerminateWithError($result); $this->terminateWithSuccess($noten); } @@ -101,13 +200,345 @@ class Noten extends FHCAPI_Controller public function saveStudentenNoten() { $result = $this->getPostJSON(); - if(!property_exists($result, 'password')) { + if(!property_exists($result, 'sem_kurzbz') || !property_exists($result, 'lv_id') || + !property_exists($result, 'password') || !property_exists($result, 'noten')) { + $this->terminateWithError($this->p->t('global', 'missingParameters'), 'general'); + } + + if(!$this->AuthLib->checkUserAuthByUsernamePassword(getAuthUID(), $result->password)->retval) { + $this->terminateWithError($this->p->t('global', 'wrongPassword'), 'general'); + } + + $lv_id = $result->lv_id; + $sem_kurzbz = $result->sem_kurzbz; + + $ret = []; + + // TODO: also do something similar when updating/creating a pruefung! + + foreach($result->noten as $note) { + $lvgesamtnote = new lvgesamtnote(); + if ($lvgesamtnote->load($lv_id, $note->uid, $sem_kurzbz)) + { + if ($lvgesamtnote->benotungsdatum > $lvgesamtnote->freigabedatum) + { + $lvgesamtnote->freigabedatum = date("Y-m-d H:i:s"); + $lvgesamtnote->freigabevon_uid = getAuthUID(); + if($lvgesamtnote->save()) { + $ret[] = array('uid' => $note->uid, 'freigabedatum' => $lvgesamtnote->freigabedatum, 'benotungsdatum' => $lvgesamtnote->benotungsdatum); + } + + if (defined('CIS_GESAMTNOTE_FREIGABEMAIL_NOTE') && CIS_GESAMTNOTE_FREIGABEMAIL_NOTE) + { + // TODO: infomail an studiengangsassistenz + // Enthalten sind MatrikelNr., Vorname, Nachname und Note der neuen oder geänderten Einträge. + } + + } + } + + } + + + $this->terminateWithSuccess($ret); + } + + public function getNotenvorschlagStudent() { + // TODO: Notenvorschlag laden allgemeiner Endpunkt, der im Backend mit Logik (z.B. Moodle) angepasst werden kann. + + $this->terminateWithSuccess(); + } + + public function saveStudentPruefung() { // einzelne pruefung speichern + $result = $this->getPostJSON(); + + if(!property_exists($result, 'datum') || !property_exists($result, 'lva_id') || + !property_exists($result, 'student_uid') || !property_exists($result, 'note')) { $this->terminateWithError($this->p->t('global', 'missingParameters'), 'general'); } - // TODO: send & save noten + $student_uid = $result->student_uid; + $note = $result->note; + $punkte = $result->punkte; + $datum = $result->datum; + $lva_id = $result->lva_id; + $lehreinheit_id = $result->lehreinheit_id; - $this.$this->terminateWithSuccess($this->AuthLib->checkUserAuthByUsernamePassword(getAuthUID(), $result->password)); +// $lehreinheit_id_pr = $result->lehreinheit_id_pr; // todo: very rare, refactor? + $stsem = $result->sem_kurzbz; + $typ = $result->typ; + + $jetzt = date("Y-m-d H:i:s"); + + // nachpruefungeintragen.php script calls query on campus.student_lehrveranstaltung to find a + // lehreinheit_id for lva_id -> lehreinheit should be determined prior to that in new benotungstool + // by retrieving it from students row in campus.vw_student_lehrveranstaltung earlier on + +// $lehreinheit_id = getLehreinheit($db, $lvid, $student_uid, $stsem); + $lehreinheit_id = $result->lehreinheit_id; + + $punkte = str_replace(',', '.', $punkte); + + if($punkte!='') + { + // Bei Punkteeingabe wird die Note nochmals geprueft und ggf korrigiert + $notenschluessel = new notenschluessel(); + $note_pruef = $notenschluessel->getNote($punkte, $lva_id, $stsem); + if($note_pruef!=$note) + { + $note = $note_pruef; + $note_dirty=true; + } + } + + if($note=='') + $note = 9; + + $old_note = $note; + + //Laden der Lehrveranstaltung + $lv_obj = new lehrveranstaltung(); + if(!$lv_obj->load($lva_id)) + die($lv_obj->errormsg); + + //Studiengang laden + $stg_obj = new studiengang($lv_obj->studiengang_kz); + +// $response = savePruefung($lvid, $student_uid, $stsem, $lehreinheit_id, $datum, $typ, $note, $punkte); + $savedPruefung = null; + $extraPruefung = null; + if($typ == "Termin2") { + + $pr = new Pruefung(); + + // Wenn eine Pruefung angelegt wird, wird zuerst eine Pruefung mit 1. Termin angelegt + // und dort die Zeugnisnote gespeichert + if($pr->getPruefungen($student_uid, "Termin1", $lva_id, $stsem)) + { + if ($pr->result) + { + // TODO: is this filler if branch really necessary? + $termin1 = 1; + } + else + { + $lvnote = new lvgesamtnote(); + // update Termin1 note + if ($lvnote->load($lva_id, $student_uid, $stsem)) + { + $pr_note = $lvnote->note; + $pr_punkte = $lvnote->punkte; + $benotungsdatum = $lvnote->benotungsdatum; + } + else // set Termin1 note to "noch nicht eingetragen" + { + $pr_note = 9; + $pr_punkte = ''; + $benotungsdatum = $jetzt; + } + + $pr_1 = new Pruefung(); + $pr_1->lehreinheit_id = $lehreinheit_id; + $pr_1->student_uid = $student_uid; + $pr_1->mitarbeiter_uid = getAuthUID(); + $pr_1->note = $pr_note; + $pr_1->punkte = $pr_punkte; + $pr_1->pruefungstyp_kurzbz = "Termin1"; + $pr_1->datum = $benotungsdatum; + $pr_1->anmerkung = ""; + $pr_1->insertamum = $jetzt; + $pr_1->insertvon = getAuthUID(); + $pr_1->updateamum = null; + $pr_1->updatevon = null; + $pr_1->ext_id = null; + $pr_1->new = true; + $pr_1->save(); + $extraPruefung = $pr_1; //"neu T1"; + } + + $prTermin2 = new Pruefung(); + $pr_2 = new Pruefung(); + + // Die Pruefung wird als Termin2 eingetragen + if ($prTermin2->getPruefungen($student_uid, 'Termin2', $lva_id, $stsem)) + { + if ($prTermin2->result) + { + $pr_2->load($prTermin2->result[0]->pruefung_id); + $pr_2->new = null; + $pr_2->updateamum = $jetzt; + $pr_2->updatevon = getAuthUID(); + $old_note = $pr_2->note; + $pr_2->note = $note; + $pr_2->punkte = $punkte; + $pr_2->datum = $datum; + $pr_2->anmerkung = ""; + $savedPruefung = $pr_2;//"update T2"; + } + else + { + $pr_2->lehreinheit_id = $lehreinheit_id; + $pr_2->student_uid = $student_uid; + $pr_2->mitarbeiter_uid = getAuthUID(); + $pr_2->note = $note; + $pr_2->punkte = $punkte; + $pr_2->pruefungstyp_kurzbz = $typ; + $pr_2->datum = $datum; + $pr_2->anmerkung = ""; + $pr_2->insertamum = $jetzt; + $pr_2->insertvon = getAuthUID(); + $pr_2->updateamum = null; + $pr_2->updatevon = null; + $pr_2->ext_id = null; + $pr_2->new = true; + $old_note = -1; + $savedPruefung = $pr_2;//"new T2"; + } + $pr_2->save(); + } + } + + } else if($typ == "Termin3") { + + $prTermin3 = new Pruefung(); + $pr_3 = new Pruefung(); + + if ($prTermin3->getPruefungen($student_uid, 'Termin3', $lva_id, $stsem)) + { + if ($prTermin3->result) + { + $pr_3->load($prTermin3->result[0]->pruefung_id); + $pr_3->new = null; + $pr_3->updateamum = $jetzt; + $pr_3->updatevon = getAuthUID(); + $old_note = $pr_3->note; + $pr_3->note = $note; + $pr_3->punkte = $punkte; + $pr_3->datum = $datum; + $pr_3->anmerkung = ""; + $savedPruefung = $pr_3; //"update T3"; + } + else + { + $pr_3->lehreinheit_id = $lehreinheit_id; + $pr_3->student_uid = $student_uid; + $pr_3->mitarbeiter_uid = getAuthUID(); + $pr_3->note = $note; + $pr_3->punkte = $punkte; + $pr_3->pruefungstyp_kurzbz = $typ; + $pr_3->datum = $datum; + $pr_3->anmerkung = ""; + $pr_3->insertamum = $jetzt; + $pr_3->insertvon = getAuthUID(); + $pr_3->updateamum = null; + $pr_3->updatevon = null; + $pr_3->ext_id = null; + $pr_3->new = true; + $old_note = -1; + $savedPruefung = $pr_3; //"new T3"; + } + $pr_3->save(); + } + } else { + $this->terminateWithError("typ is not termin2 or termin3", 'general'); + } + + + + //Gesamtnote updaten + $lvgesamtnote = new lvgesamtnote(); + if (!$lvgesamtnote->load($lva_id, $student_uid, $stsem)) + { + $lvgesamtnote->student_uid = $student_uid; + $lvgesamtnote->lehrveranstaltung_id = $lva_id; + $lvgesamtnote->studiensemester_kurzbz = $stsem; + $lvgesamtnote->note = $note; + $lvgesamtnote->punkte = $punkte; + $lvgesamtnote->mitarbeiter_uid = getAuthUID(); + $lvgesamtnote->benotungsdatum = $jetzt; + $lvgesamtnote->freigabedatum = null; + $lvgesamtnote->freigabevon_uid = null; + $lvgesamtnote->bemerkung = null; + $lvgesamtnote->updateamum = null; + $lvgesamtnote->updatevon = null; + $lvgesamtnote->insertamum = $jetzt; + $lvgesamtnote->insertvon = getAuthUID(); + $new = true; +// $response = "neu"; + } + else + { + $lvgesamtnote->note = $note; + $lvgesamtnote->punkte = $punkte; + $lvgesamtnote->benotungsdatum = $jetzt; + $lvgesamtnote->updateamum = $jetzt; + $lvgesamtnote->updatevon = getAuthUID(); + $new = false; +// if ($lvgesamtnote->freigabedatum) +// $response = "update_f"; +// else +// $response = "update"; + } + + $saved = $lvgesamtnote->save($new); + + $this->terminateWithSuccess(array($savedPruefung, $lvgesamtnote, $saved, $extraPruefung)); + } + + public function saveNotenvorschlag() { + $result = $this->getPostJSON(); + + if(!property_exists($result, 'lv_id') || !property_exists($result, 'sem_kurzbz') || + !property_exists($result, 'student_uid') || !property_exists($result, 'note')) { + $this->terminateWithError($this->p->t('global', 'missingParameters'), 'general'); + } + + $lv_id = $result->lv_id; + $student_uid = $result->student_uid; + $sem_kurzbz = $result->sem_kurzbz; + $note = $result->note; + + $lvgesamtnote = new lvgesamtnote(); + if (!$lvgesamtnote->load($lv_id, $student_uid, $sem_kurzbz)) + { + $lvgesamtnote->student_uid = $student_uid; + $lvgesamtnote->lehrveranstaltung_id = $lv_id; + $lvgesamtnote->studiensemester_kurzbz = $sem_kurzbz; + $lvgesamtnote->note = trim($note); + $lvgesamtnote->mitarbeiter_uid = getAuthUID(); + $lvgesamtnote->benotungsdatum = date("Y-m-d H:i:s"); + $lvgesamtnote->freigabedatum = null; + $lvgesamtnote->freigabevon_uid = null; + $lvgesamtnote->bemerkung = null; + $lvgesamtnote->updateamum = null; + $lvgesamtnote->updatevon = null; + $lvgesamtnote->insertamum = date("Y-m-d H:i:s"); + $lvgesamtnote->insertvon = getAuthUID(); + $lvgesamtnote->punkte =// TODO: deprecated? + $new = true; + $response = "neu"; + } + else + { + $lvgesamtnote->note = trim($note); + $lvgesamtnote->punkte = null; // TODO: deprecated? + $lvgesamtnote->benotungsdatum = date("Y-m-d H:i:s"); + $lvgesamtnote->updateamum = date("Y-m-d H:i:s"); + $lvgesamtnote->updatevon = getAuthUID(); + $new = false; + if ($lvgesamtnote->freigabedatum) + $response = "update_f"; + else + $response = "update"; + } + if (!$lvgesamtnote->save($new)) + $ret = $lvgesamtnote->errormsg; + else + $ret = $response; + + $lvgesamtnote->load($lv_id, $student_uid, $sem_kurzbz); + + $this->terminateWithSuccess(array($ret, $lvgesamtnote)); } } diff --git a/application/models/education/Lehreinheit_model.php b/application/models/education/Lehreinheit_model.php index d4bc7a22f..15024c023 100644 --- a/application/models/education/Lehreinheit_model.php +++ b/application/models/education/Lehreinheit_model.php @@ -303,4 +303,26 @@ EOSQL; return $this->execQuery($query, $params); } + + public function getAllLehreinheitenForLvaAndMaUid($lva_id, $ma_uid, $sem_kurzbz) + { + $query = "SELECT DISTINCT tbl_lehreinheitmitarbeiter.lehreinheit_id, tbl_lehreinheit.lehrveranstaltung_id, tbl_lehreinheit.lehrform_kurzbz, + tbl_lehreinheitmitarbeiter.mitarbeiter_uid, + tbl_lehreinheitgruppe.semester, + tbl_lehreinheitgruppe.verband, + tbl_lehreinheitgruppe.gruppe, + tbl_lehreinheitgruppe.gruppe_kurzbz, + tbl_lehrveranstaltung.kurzbz, + tbl_studiengang.kurzbzlang, + (SELECT COUNT(DISTINCT datum) FROM campus.vw_stundenplan WHERE lehreinheit_id = lehre.tbl_lehreinheit.lehreinheit_id) as termincount, + (SELECT COUNT(*) FROM campus.vw_student_lehrveranstaltung WHERE lehreinheit_id = lehre.tbl_lehreinheit.lehreinheit_id) as studentcount + FROM lehre.tbl_lehreinheit JOIN lehre.tbl_lehreinheitmitarbeiter USING(lehreinheit_id) + JOIN lehre.tbl_lehreinheitgruppe USING(lehreinheit_id) + JOIN lehre.tbl_lehrveranstaltung USING(lehrveranstaltung_id) + JOIN public.tbl_studiengang ON (tbl_lehreinheitgruppe.studiengang_kz = tbl_studiengang.studiengang_kz) + WHERE lehrveranstaltung_id = ? AND studiensemester_kurzbz = ? AND mitarbeiter_uid = ? + ORDER BY tbl_lehreinheitgruppe.gruppe_kurzbz"; + + return $this->execQuery($query, [$lva_id, $sem_kurzbz, $ma_uid]); + } } diff --git a/application/models/education/Lehrveranstaltung_model.php b/application/models/education/Lehrveranstaltung_model.php index 883c6d9ca..6fa38b36c 100644 --- a/application/models/education/Lehrveranstaltung_model.php +++ b/application/models/education/Lehrveranstaltung_model.php @@ -316,7 +316,8 @@ class Lehrveranstaltung_model extends DB_Model (SELECT status_kurzbz FROM public.tbl_prestudentstatus WHERE prestudent_id=tbl_student.prestudent_id ORDER BY datum DESC, insertamum DESC, ext_id DESC LIMIT 1) as status, tbl_bisio.bisio_id, tbl_bisio.von, tbl_bisio.bis, tbl_student.studiengang_kz AS stg_kz_student, tbl_zeugnisnote.note, tbl_mitarbeiter.mitarbeiter_uid, tbl_person.matr_nr, tbl_benutzer.uid, - UPPER(tbl_studiengang.typ::varchar(1) || tbl_studiengang.kurzbz) as kuerzel, tbl_studiengang.orgform_kurzbz, vw_student_lehrveranstaltung.semester, vw_student_lehrveranstaltung.studiensemester_kurzbz, vw_student_lehrveranstaltung.bezeichnung + UPPER(tbl_studiengang.typ::varchar(1) || tbl_studiengang.kurzbz) as kuerzel, tbl_studiengang.orgform_kurzbz, vw_student_lehrveranstaltung.semester, vw_student_lehrveranstaltung.studiensemester_kurzbz, vw_student_lehrveranstaltung.bezeichnung, + campus.vw_student_lehrveranstaltung.lehreinheit_id FROM campus.vw_student_lehrveranstaltung @@ -343,7 +344,7 @@ class Lehrveranstaltung_model extends DB_Model $query .= " ORDER BY nachname, vorname, person_id, tbl_bisio.bis DESC"; - + return $this->execQuery($query, array($studiensemester_kurzbz, $lehrveranstaltung_id)); } diff --git a/application/models/education/Note_model.php b/application/models/education/Note_model.php index fe601b1f9..f5ffa2cd3 100644 --- a/application/models/education/Note_model.php +++ b/application/models/education/Note_model.php @@ -12,9 +12,10 @@ class Note_model extends DB_Model $this->pk = 'note'; } - public function getAll() { + public function getAllActive() { $qry ="SELECT * - FROM lehre.tbl_note"; + FROM lehre.tbl_note + WHERE aktiv = true"; return $this->execReadOnlyQuery($qry); } diff --git a/application/views/CisRouterView/CisRouterView.php b/application/views/CisRouterView/CisRouterView.php index 9d24d9a68..b433ab9bd 100644 --- a/application/views/CisRouterView/CisRouterView.php +++ b/application/views/CisRouterView/CisRouterView.php @@ -9,6 +9,7 @@ $includesArray = array( 'vue3' => true, 'primevue3' => true, 'customCSSs' => array( + 'vendor/vuejs/vuedatepicker_css/main.css', 'public/css/components/verticalsplit.css', 'public/css/components/searchbar/searchbar.css', 'public/css/Fhc.css', diff --git a/public/js/api/factory/lehre.js b/public/js/api/factory/lehre.js index f9306839e..05edd02ca 100644 --- a/public/js/api/factory/lehre.js +++ b/public/js/api/factory/lehre.js @@ -36,24 +36,18 @@ export default { url: `/api/frontend/v1/Lehre/Pruefungen/${lehrveranstaltung_id}` }; }, - getStudentenNoten(lv_id, sem_kurzbz) { - return { - method: 'get', - url: '/api/frontend/v1/Noten/getStudentenNoten', - params: { lv_id, sem_kurzbz } - }; - }, - getNoten(){ - return { - method: 'get', - url: '/api/frontend/v1/Noten/getNoten' - }; - }, getZugewieseneLv(uid, sem_kurzbz){ return { method: 'get', url: '/api/frontend/v1/Lehre/getZugewieseneLv', params: { uid, sem_kurzbz} }; + }, + getLeForLv(lv_id, sem_kurzbz) { + return { + method: 'get', + url: '/api/frontend/v1/Lehre/getLeForLv', + params: { lv_id, sem_kurzbz } + }; } }; \ No newline at end of file diff --git a/public/js/api/factory/noten.js b/public/js/api/factory/noten.js index e0343cb4b..d26a1a884 100644 --- a/public/js/api/factory/noten.js +++ b/public/js/api/factory/noten.js @@ -29,11 +29,25 @@ export default { url: '/api/frontend/v1/Noten/getNoten' }; }, - saveStudentenNoten(password) { + saveStudentenNoten(password, noten, lv_id, sem_kurzbz) { return { method: 'post', url: '/api/frontend/v1/Noten/saveStudentenNoten', - params: { password } + params: { password, noten, lv_id, sem_kurzbz } + }; + }, + saveNotenvorschlag(lv_id, sem_kurzbz, student_uid, note) { + return { + method: 'post', + url: '/api/frontend/v1/Noten/saveNotenvorschlag', + params: { lv_id, sem_kurzbz, student_uid, note } + }; + }, + saveStudentPruefung(student_uid, note, punkte, datum, lva_id, lehreinheit_id, sem_kurzbz, typ){ + return { + method: 'post', + url: '/api/frontend/v1/Noten/saveStudentPruefung', + params: { student_uid, note, punkte, datum, lva_id, lehreinheit_id, sem_kurzbz, typ } }; } }; \ No newline at end of file diff --git a/public/js/components/Cis/Benotungstool/Benotungstool.js b/public/js/components/Cis/Benotungstool/Benotungstool.js index 2f3dfe3be..2075082cb 100644 --- a/public/js/components/Cis/Benotungstool/Benotungstool.js +++ b/public/js/components/Cis/Benotungstool/Benotungstool.js @@ -3,6 +3,7 @@ import ApiLehre from "../../../api/factory/lehre.js"; import ApiNoten from "../../../api/factory/noten.js"; import ApiStudiensemester from "../../../api/factory/studiensemester.js"; import BsModal from '../../Bootstrap/Modal.js'; +import VueDatePicker from '../../vueDatepicker.js.php'; export const Benotungstool = { name: "Benotungstool", @@ -10,7 +11,8 @@ export const Benotungstool = { BsModal, CoreFilterCmpt, Dropdown: primevue.dropdown, - Password: primevue.password + Password: primevue.password, + Datepicker: VueDatePicker }, props: { lv_id: { @@ -32,9 +34,18 @@ export const Benotungstool = { }, data() { return { + selectedLehreinheit: null, + lehreinheiten: null, + tabulatorCanBeBuilt: false, + selectedPruefungNote: null, + selectedPruefungDate: new Date(), // v-model for pruefung edit datepicker + pruefungStudent: null, + pruefung: null, password: '', + changedNotenCounter: 0, tabulatorUuid: Vue.ref(0), domain: '', + teilnoten: null, lv: null, studenten: null, pruefungen: null, @@ -44,78 +55,311 @@ export const Benotungstool = { selectedLehrveranstaltung: null, tableBuiltResolve: null, notenOptions: null, + notenOptionsLehre: null, + notenOptionsPromise: null, tableBuiltPromise: null, - notenTableOptions: { - height: 700, - index: 'student_uid', - layout: 'fitDataStretch', - placeholder: this.$p.t('global/noDataAvailable'), - columns: [ - {title: Vue.computed(() => this.$p.t('benotungstool/c4mail')), field: 'email', formatter: this.mailFormatter, tooltip: false, widthGrow: 1}, - {title: 'UID', field: 'uid', tooltip: false, widthGrow: 1}, - {title: Vue.computed(() => this.$p.t('benotungstool/c4vorname')), field: 'vorname', tooltip: false, widthGrow: 1}, - {title: Vue.computed(() => this.$p.t('benotungstool/c4nachname')), field: 'nachname', widthGrow: 1}, - {title: Vue.computed(() => this.$p.t('benotungstool/c4teilnoten')), field: 'teilnote', widthGrow: 1}, - {title: Vue.computed(() => this.$p.t('benotungstool/c4note')), field: 'note_vorschlag', - editor: 'list', - editorParams: { - values: Vue.computed(()=>this.notenOptions.map(opt => { - return { - label: opt.bezeichnung, - value: opt.note - } - })) - }, - formatter: (cell) => { - const value = cell.getValue() - const match = this.notenOptions.find(opt => opt.note === value) - return match ? match.bezeichnung : value - }, - widthGrow: 1}, - {title: '', width: 50, hozAlign: 'center', formatter: this.arrowFormatter, cellClick: this.saveNote}, - {title: Vue.computed(() => this.$p.t('benotungstool/c4lvnote')), field: 'lv_note', - formatter: this.notenFormatter, - widthGrow: 1}, - {title: Vue.computed(() => this.$p.t('benotungstool/c4freigabe')), field: 'freigegeben', widthGrow: 1}, - {title: Vue.computed(() => this.$p.t('benotungstool/c4zeugnisnote')), field: 'note', formatter: this.notenFormatter, widthGrow: 1} - // {title: Vue.computed(() => this.$p.t('benotungstool/c4termin1')), field: 'termin1', widthGrow: 1}, - // {title: Vue.computed(() => this.$p.t('benotungstool/c4termin2')), field: 'termin2', widthGrow: 1}, - // {title: Vue.computed(() => this.$p.t('benotungstool/c4termin3')), field: 'termin3', widthGrow: 1} - ], - persistence: false, - }, + notenTableOptions: null, // built later when noten are available notenTableEventHandlers: [{ event: "tableBuilt", handler: async () => { this.tableBuiltResolve() } }, - { - event: "cellClick", - handler: async (e, cell) => { - - + { + event: "cellClick", + handler: async (e, cell) => { + + } + }, + { + event: "cellEdited", + handler: async (cell) => { + const field = cell.getField() + + if(field === 'note_vorschlag') { + const rowData = cell.getRow().getData(); + const newValue = cell.getValue(); + const original = rowData._originalNoteVorschlag; + // If nothing was selected, restore + if (newValue == null || newValue === "" || newValue === original) { + // revert value + cell.setValue(original, true); + } + + delete rowData._originalNoteVorschlag; // Clean up + + const row = cell.getRow() + row.reformat() // trigger reformat of arrow } } + } ]}; }, methods: { + getNotenTableOptions() { + return { + height: 700, + index: 'uid', + layout: 'fitDataStretch', + placeholder: this.$p.t('global/noDataAvailable'), + columns: [ + {title: Vue.computed(() => this.$p.t('benotungstool/c4mail')), field: 'email', formatter: this.mailFormatter, tooltip: false, widthGrow: 1}, + {title: 'UID', field: 'uid', tooltip: false, widthGrow: 1}, + {title: Vue.computed(() => this.$p.t('benotungstool/c4vorname')), field: 'vorname', tooltip: false, widthGrow: 1}, + {title: Vue.computed(() => this.$p.t('benotungstool/c4nachname')), field: 'nachname', widthGrow: 1}, + {title: Vue.computed(() => this.$p.t('benotungstool/c4teilnoten')), field: 'teilnote', widthGrow: 1, formatter: this.teilnotenFormatter}, + {title: Vue.computed(() => this.$p.t('benotungstool/c4note')), field: 'note_vorschlag', + editor: 'list', + editorParams: (cell) => { + // write original cell value into row to it can be retrieved if edit is cancelled without selection + const rowData = cell.getRow().getData(); + rowData._originalNoteVorschlag = cell.getValue(); + + return { + values: this.notenOptionsLehre.map(opt => ({ + label: opt.bezeichnung, + value: opt.note + })) + }; + }, + editable: (cell) => { + const rowData = cell.getRow().getData(); + const noteOption = this.notenOptions.find(opt => opt.note == rowData.note) + if(!noteOption) return true + + // also if student has any pruefungsnote disable noten selection + if(this.pruefungen.find(p => p.student_uid == rowData.uid)) return false + + return noteOption.lkt_ueberschreibbar + }, + formatter: (cell) => { + const rowData = cell.getRow().getData(); + const value = cell.getValue() + const match = this.notenOptions?.find(opt => opt.note == value) + const val = match ? match.bezeichnung : value + const p = this.pruefungen.find(p => p.student_uid == rowData.uid) + let style = '' + + if(val === undefined) return '' + if(p || !match?.lkt_ueberschreibbar) style = 'color: gray;font-style: italic; background-color: #f0f0f0;pointer-events: none;opacity: 0.6;user-select: none;cursor: not-allowed;' + return '
' + val + '
' + }, + widthGrow: 1}, + {title: '', width: 50, hozAlign: 'center', formatter: this.arrowFormatter, cellClick: this.saveNote}, + {title: Vue.computed(() => this.$p.t('benotungstool/c4lvnote')), field: 'lv_note', + formatter: this.notenFormatter, + widthGrow: 1}, + {title: Vue.computed(() => this.$p.t('benotungstool/c4freigabe')), field: 'freigegeben', widthGrow: 1, formatter: this.freigabeFormatter}, + {title: Vue.computed(() => this.$p.t('benotungstool/c4zeugnisnote')), field: 'note', formatter: this.notenFormatter, widthGrow: 1}, + {title: Vue.computed(() => this.$p.t('benotungstool/c4kommPruef')), field: 'kommPruef', widthGrow: 1, formatter: this.pruefungFormatter, hozAlign:"center", minWidth: 150} + // {title: Vue.computed(() => this.$p.t('benotungstool/c4termin1')), field: 'termin1', widthGrow: 1}, + // {title: Vue.computed(() => this.$p.t('benotungstool/c4termin2')), field: 'termin2', widthGrow: 1}, + // {title: Vue.computed(() => this.$p.t('benotungstool/c4termin3')), field: 'termin3', widthGrow: 1} + ], + persistence: false, + } + }, + parseDate(timestamp) { + if(!timestamp) return null + const [datePart, timePart] = timestamp.split(" "); + const [year, month, day] = datePart.split("-").map(Number); + const [hour, minute, second] = timePart.split(":").map(Number); + return new Date(year, month - 1, day, hour, minute, second); + }, + checkFreigabe(freigabedatum, benotungsdatum, uid) { + if(!freigabedatum) { + // check for change -> set freigabe to 'changed' on change + return 'offen' + } else if(benotungsdatum > freigabedatum) { + return 'changed' + } else { + return 'ok' + } + }, notenFormatter(cell) { const value = cell.getValue() - const match = this.notenOptions.find(opt => opt.note === value) - return match ? match.bezeichnung : value + const field = cell.getField() + let style = 'display: flex; justify-content: center; align-items: center; height: 100%;'; + // Wenn sich die Zeugnisnote von der von Ihnen freigegebenen Note unterscheidet, + // wird erstere rot umrandet markiert. + + + const data = cell.getData() + if(field == 'note' && data.note && data.note != data.lv_note) { + style += 'color:red; border-color:red; border-style:solid; border-width:1px;' + } + + const match = this.notenOptions.find(opt => opt.note == value) + const val = match ? match.bezeichnung : value + if(val) return '
' + val + '
' + else return '' + + }, + freigabeFormatter(cell) { + const value = cell.getValue() + + if(value === 'ok') { + return '
' + + '
' + } else if (value === 'offen') { + return '
' + + '
' + } else if (value === 'changed') { + return '
' + + '
' + } + + return value }, handlePasswordChanged(pw) { - console.log('pw:', pw) + // console.log('pw:', pw) }, - saveNote(e, cell) { + saveNote(e, cell) { // Notenvorschlag freigeben const row = cell.getRow() const data = row.getData() - row.update({ note: data.note_vorschlag }) + if(!data.note_vorschlag) return + this.$api.call(ApiNoten.saveNotenvorschlag(this.lv_id, this.sem_kurzbz, data.uid, data.note_vorschlag)) + .then((res) => { + if (res.meta.status === 'success') { + const s = this.studenten.find(s => s.uid === data.uid) + this.teilnoten[s.uid].note_lv = data.note_vorschlag + s.freigabedatum = this.parseDate(res.data[1]['freigabedatum']) + s.benotungsdatum = this.parseDate(res.data[1]['benotungsdatum']) + + s.freigegeben = this.checkFreigabe(s.freigabedatum, s.benotungsdatum, s.uid); + + row.update({ lv_note: data.note_vorschlag }) + row.update({ freigegeben: 'changed' }) + row.reformat() // trigger reformat of arrow + this.changedNotenCounter++; + } + }) + + + }, + teilnotenFormatter(cell) { + const val = cell.getValue() + return '
'+val+'
' + }, + pruefungFormatter(cell) { + const data = cell.getData() + + const noteDef = data.note ? this.notenOptions.find(n => n.note == data.note) : null + if(!data.note || !noteDef?.lkt_ueberschreibbar) { + return '' + } + + // TODO: check for some time limit maybe? old pruefungen can be changed/created + + // Create root row div + const rowDiv = document.createElement('div'); + rowDiv.className = 'row'; + rowDiv.style.display = 'flex'; + rowDiv.style.justifyContent = 'center'; + rowDiv.style.alignItems = 'center'; + rowDiv.style.height = '100%'; + + function createCol(content) { + const colDiv = document.createElement('div'); + colDiv.className = 'col-4'; + colDiv.style.justifyContent = 'center'; + colDiv.style.alignItems = 'center'; + colDiv.style.height = '100%'; + + if (typeof content === 'string') { + colDiv.textContent = content; + } else if (content instanceof HTMLElement) { + colDiv.appendChild(content); + } + + return colDiv; + } + + const field = cell.getColumn().getField() + if(data[field]) { + const dateParts = data[field].datum.split('-') + const date = `${dateParts[2]}.${dateParts[1]}.${dateParts[0]}` + + // First column (date) + rowDiv.appendChild(createCol(date)); + + // Second column (note_bezeichnung) + rowDiv.appendChild(createCol(data[field].note_bezeichnung || '')); + + if(field === 'kommPruef' || field === 'pruefungNr0') {// no actions on kommPruef allowed + rowDiv.appendChild(createCol('')); // append empty col4 to have formatting similar + return rowDiv + } + + // Third column (button) + const button = document.createElement('button'); + button.className = 'btn btn-outline-secondary'; + button.textContent = 'Change'; // TODO: phrase + button.addEventListener('click', () => { + this.openPruefungModal(data, data[field]); + }); + + rowDiv.appendChild(createCol(button)); + + return rowDiv; + + } else if (field !== 'kommPruef' && field !== 'pruefungNr0') { // return new btn action + const button = document.createElement('button'); + button.className = 'btn btn-outline-secondary'; + button.textContent = 'Add'; // TODO: phrase + button.addEventListener('click', () => { + this.openPruefungModal(data) + }); + + rowDiv.appendChild(createCol(button)); + + return rowDiv; + } else return '' + }, + openPruefungModal(student, pruefung = null) { + this.pruefungStudent = student + this.pruefung = pruefung + debugger + if(this.pruefung?.datum) { + const pruefungDateParts = this.pruefung?.datum?.split('-') + + // new date obj so datepicker picks ob the change by ref + const newDate = new Date() + newDate.setFullYear(pruefungDateParts[0]) + newDate.setMonth(pruefungDateParts[1]) + newDate.setDate(pruefungDateParts[2]) + this.selectedPruefungDate = newDate + } else { + const newDate = new Date() + newDate.setTime(Date.now()) + this.selectedPruefungDate = newDate + } + + if(this.pruefung?.note) { + this.selectedPruefungNote = this.notenOptions.find(n => n.note == this.pruefung.note) + } else { + this.selectedPruefungNote = null + } + + this.$refs.modalContainerPruefung.show() + }, + pruefungTitleFormatter(cell) { + const col = cell.getColumn() + if(col.getField() === "pruefungNr0") return this.$p.t('benotungstool/c4originalZnote') + return col.getDefinition().title; }, arrowFormatter(cell) { + const row = cell.getRow() + const data = row.getData() + + if(!data.note_vorschlag || (data.note_vorschlag == data.lv_note)) { // uncolored arrow + return '
' + + '
' + } + + // can save a notenvorschlag -> colored return '
' + '
' }, @@ -130,41 +374,103 @@ export const Benotungstool = { tableResolve(resolve) { this.tableBuiltResolve = resolve }, + notenOptionsResolve(resolve) { + this.notenOptionsResolve = resolve + }, setupData(data){ - this.studenten = data[0] - this.pruefungen = data[1] + this.studenten = data[0] ?? [] + this.studenten.forEach(s => s.pruefungen = []) + this.pruefungen = data[1] ?? [] this.domain = data[2] - this.teilnoten = data[3] - this.pruefungen.forEach(p => { + // contains notenvorschläge from moodle, lv_note + this.teilnoten = data[3] ?? [] + + // let pruefungenRegularColCount = 0; + const distinctPruefungsDates = [] + const cols = [...this.notenTableOptions.columns.slice(0, -1)]; + const kommCol = this.notenTableOptions.columns[this.notenTableOptions.columns.length - 1]; + + this.pruefungen?.forEach(p => { const student = this.studenten.find(s => s.uid === p.student_uid) if(!student) return - // TODO: fetch typen and remove hardcoded strings - // TODO: dynamic amount of termin columns! - // if(p.pruefungstyp_kurzbz == "Termin1") { - // student.termin1 = p - // } else if (p.pruefungstyp_kurzbz == "Termin2") { - // student.termin2 = p - // } else if (p.pruefungstyp_kurzbz == "Termin3") { - // student.termin3 = p - // } - // TODO: LE TEILNOTEN IN BACKEND LADEN, BERECHNEN UND VORSCHLAG PASTEN - // if(p.negativ) + if(!distinctPruefungsDates.includes(p.datum)) distinctPruefungsDates.push(p.datum) + + // seperate kommPruefungen from previous pruefungen counts since the column count variability always ends with this + if(p.pruefungstyp_kurzbz == 'kommPruef') { + student['kommPruef'] = p + } else { + student.pruefungen.push(p) + } + + // if(student.pruefungen.length > pruefungenRegularColCount) pruefungenRegularColCount = student.pruefungen.length }) - - this.studenten.forEach(s => { - s.email = this.buildMailToLink(s) + // TODO: check if note is überschreibbar! + this.studenten?.forEach(s => { + // sort students regular pruefungen by datum + s.pruefungen.sort((p1, p2) => { + if(p1.datum > p2.datum) { + return 1 + } else if (p1.datum < p2.datum) { + return -1 + } else { + return 0 + } + }) + // set the sorted pruefungen to their respective column fields + s.pruefungen.forEach((p, i) => { + s[p.datum] = p + }) + + s.email = this.buildMailToLink(s) + s.lv_note = this.teilnoten[s.uid].note_lv + s.freigabedatum = this.parseDate(this.teilnoten[s.uid]['freigabedatum']) + s.benotungsdatum = this.parseDate(this.teilnoten[s.uid]['benotungsdatum']) + s.freigegeben = this.checkFreigabe(s.freigabedatum, s.benotungsdatum, s.uid); + const grades = this.teilnoten[s.uid].grades s.teilnote = '' - grades.forEach(g => s.teilnote += g.text + ' ') + + grades.forEach(g => { + const notenOption = this.notenOptions.find(n=>n.note == g.grade) + if(notenOption.positiv) s.teilnote += (''+g.text +''+ '
') + else s.teilnote += (''+g.text +''+ '
') + }) + }) + + distinctPruefungsDates.sort((d1, d2) => { + if(d1 > d2) { + return 1 + } else if (d1 < d2) { + return -1 + } else { + return 0 + } + }) + distinctPruefungsDates.forEach((date, index)=>{ + // TODO date format dd.mm.yyyy + cols.push({ + title: date,//this.$p.t('benotungstool/pruefungNr', [index+1]), + field: date, + formatter: this.pruefungFormatter, + titleFormatter: this.pruefungTitleFormatter, + hozAlign:"center", + widthGrow: 1, + minWidth: 150 + }) + }) + + + cols.push(kommCol) // keep kommPruef Col as last this.$refs.notenTable.tabulator.clearSort() - this.$refs.notenTable.tabulator.setColumns(this.notenTableOptions.columns) + this.$refs.notenTable.tabulator.setColumns(cols) this.$refs.notenTable.tabulator.setData(this.studenten); + this.$refs.notenTable.tabulator.redraw(true); }, loadNoten(lv_id, sem_kurzbz) { this.$api.call(ApiNoten.getStudentenNoten(lv_id, sem_kurzbz)) @@ -184,10 +490,9 @@ export const Benotungstool = { this.notenTableOptions.height = window.visualViewport.height - rect.top this.$refs.notenTable.tabulator.setHeight(this.notenTableOptions.height) }, - async setupCreated() { + setupCreated() { // fetch lva dropdown this.$api.call(ApiLehre.getZugewieseneLv(this.viewData?.uid, this.sem_kurzbz)).then(res => { - console.log(res) this.lehrveranstaltungen = res.data // build dropdown option string @@ -198,24 +503,66 @@ export const Benotungstool = { this.selectedLehrveranstaltung = this.lehrveranstaltungen.find(lva => lva.lehrveranstaltung_id == this.lv_id) }) - //fetch sem_kurzbz dropdown + this.$api.call(ApiLehre.getLeForLv(this.lv_id, this.sem_kurzbz)).then(res => { + + const data = [] + // TODO: could be done on server in some shared function, copied from anw extension for now + res.data?.retval?.forEach(entry => { + + const existing = data.find(e => e.lehreinheit_id === entry.lehreinheit_id) + if (existing) { + // supplement info + existing.infoString += ', ' + if (entry.gruppe_kurzbz !== null) { + existing.infoString += entry.gruppe_kurzbz + } else { + existing.infoString += entry.kurzbzlang + '-' + entry.semester + + (entry.verband ? entry.verband : '') + + (entry.gruppe ? entry.gruppe : '') + } + } else { + // entries are supposed to be fetched ordered by non null gruppe_kurzbz first + // so a new entry will always start with those groups, others are appended afterwards + entry.infoString = entry.kurzbz + ' - ' + entry.lehrform_kurzbz + ' - ' + if (entry.gruppe_kurzbz !== null) { + entry.infoString += entry.gruppe_kurzbz + } else { + entry.infoString += entry.kurzbzlang + '-' + entry.semester + + (entry.verband ? entry.verband : '') + + (entry.gruppe ? entry.gruppe : '') + } + + data.push(entry) + } + }) + + data.forEach(entry => { + entry.infoString += ' | 👥' + entry.studentcount + ' | 📅' + entry.termincount + }) + + this.lehreinheiten = [...data] + + }) + + // fetch sem_kurzbz dropdown this.$api.call(ApiStudiensemester.getStudiensemester()).then(res => { this.studiensemester = res.data[0] this.selectedSemester = this.studiensemester.find(sem => sem.studiensemester_kurzbz === this.sem_kurzbz) }) // fetch noten dropdown - await this.$api.call(ApiNoten.getNoten()).then(res => { + this.$api.call(ApiNoten.getNoten()).then(res => { this.notenOptions = res.data + this.notenOptionsLehre = res.data.filter(n => n.lehre === true) + this.notenTableOptions = this.getNotenTableOptions() + this.tabulatorCanBeBuilt = true // because promises would be more work and not much better here }) }, async setupMounted() { this.tableBuiltPromise = new Promise(this.tableResolve) await this.tableBuiltPromise - this.loadNoten(this.lv_id, this.sem_kurzbz) - this.calcMaxTableHeight() }, @@ -241,8 +588,20 @@ export const Benotungstool = { } }) - // reload data - this.loadNoten(this.lv_id, e.value.studiensemester_kurzbz) + // diff lv_id -> reload zugewiesene lv + this.$api.call(ApiLehre.getZugewieseneLv(this.viewData?.uid, this.sem_kurzbz)).then(res => { + this.lehrveranstaltungen = res.data + + // build dropdown option string + this.lehrveranstaltungen.forEach(lva => { + lva.fullString = `${lva.stg_kurzbz} - ${lva.lv_semester}: ${lva.lv_bezeichnung}` + }) + + this.selectedLehrveranstaltung = this.lehrveranstaltungen.find(lva => lva.lehrveranstaltung_id == this.lv_id) + }).then(()=>{ + // reload data + this.loadNoten(this.lv_id, e.value.studiensemester_kurzbz) + }) }, getOptionLabel(option) { @@ -251,23 +610,146 @@ export const Benotungstool = { getOptionLabelLv(option) { return option.fullString }, - saveNoteneingabe() { + getOptionLabelLe(option) { + return option.infoString + }, + savePruefungEingabe() { + const year = this.selectedPruefungDate.getFullYear(); + const month = String(this.selectedPruefungDate.getMonth() + 1).padStart(2, '0'); // Months are 0-based + const day = String(this.selectedPruefungDate.getDate()).padStart(2, '0'); + const dateStr = `${year}-${month}-${day}`; - this.$api.call(ApiNoten.saveStudentenNoten(this.password)) + // TODO: test this hypothesis + // first pruefung is always "Termin2" since normal note counts as Termin1 + const pOffset = this.pruefung === null && this.pruefungStudent.pruefungen.length === 0 ? 2 : 1 + const typ = this.pruefung ? this.pruefung.pruefungstyp_kurzbz : ('Termin'+(this.pruefungStudent.pruefungen.length + pOffset)) + + this.$api.call(ApiNoten.saveStudentPruefung( + this.pruefungStudent.uid, + this.selectedPruefungNote.note, + this.pruefung?.punkte ?? '', + dateStr, + this.lv_id, + this.pruefungStudent.lehreinheit_id, + this.sem_kurzbz, + typ + )).then(res => { + if(res.meta.status === 'success') { + const s = this.studenten.find(s => s.uid === res.data[1]?.student_uid) + console.log('old student freigabedatum', s.freigabedatum) + console.log('old student benotungsdatum', s.benotungsdatum) + + s.freigabedatum = this.parseDate(res.data[1]?.['freigabedatum']) + s.benotungsdatum = this.parseDate(res.data[1]?.['benotungsdatum']) + + console.log('new student freigabedatum', s.freigabedatum) + console.log('new student benotungsdatum', s.benotungsdatum) + s.freigegeben = this.checkFreigabe(s.freigabedatum, s.benotungsdatum, s.uid); + + // todo: update student subject grade/lv_note + s.lv_note = res.data[1]?.note + + // find the exact student pruefung and update that + + // add new pruefung to row + if(res.data[0]?.new) { + // TODO: if new pruefung was of typ "Termin2", "Termin1" shadow pruefung is not available + this.handleAddNewTermin(res.data, s) + } else { // update existing + const oldIndex = s.pruefungen.findIndex(p => p.pruefung_id == res.data[0]?.pruefung_id) + if(oldIndex !== -1) { + s.pruefungen.splice(oldIndex, 1, res.data[0]) + s['pruefungNr'+oldIndex] = res.data[0] + } + } + + // // TODO: manual row update! + // const row = this.$refs.notenTable.tabulator.getRow(s.uid) + // row.update() + + this.$refs.notenTable.tabulator.redraw(true) + + this.$fhcAlert.alertSuccess('Prüfung gespeichert') // TODO: phrase + } + }) + + this.$refs.modalContainerPruefung.hide() + }, + handleAddNewTermin(data, student){ + + }, + saveNoteneingabe() { + this.$api.call(ApiNoten.saveStudentenNoten(this.password, this.changedNoten, this.lv_id, this.sem_kurzbz)) + .then((res) => { + if(res.meta.status === 'success') { + this.$fhcAlert.alertSuccess('Noten gespeichert') + } + + res.data.forEach(d => { + const s = this.studenten.find(s => s.uid === d.uid) + s.freigabedatum = this.parseDate(d.freigabedatum) + s.benotungsdatum = this.parseDate(d.benotungsdatum) + s.freigegeben = this.checkFreigabe(s.freigabedatum, s.benotungsdatum, s.uid); + }) + this.changedNotenCounter++; + + this.$refs.notenTable.tabulator.redraw(true) + }) this.$refs.modalContainerNotenSpeichern.hide() }, openSaveModal() { this.$refs.modalContainerNotenSpeichern.show() + }, + handleChangePruefungDatum(e) { + // console.log('handleChangePruefungDatum', e) + }, + handleChangePruefungNote(e) { + // console.log(e) + }, + getOptionLabelNotePruefung(option) { + return option.bezeichnung + }, + leChanged(e) { + this.selectedLehreinheit = e.value } }, watch: { - + selectedLehreinheit(newVal) { + if(!this.$refs.notenTable) return + this.$refs.notenTable.tabulator.clearFilter(); + if(newVal) this.$refs.notenTable.tabulator.setFilter("lehreinheit_id", "=", newVal.lehreinheit_id); + }, + getKommPruefCount(newVal) { + if(this.$refs.notenTable?.tabulator && newVal > 0) { + const kommPruefCol = this.$refs.notenTable?.tabulator.getColumn("kommPruef") + kommPruefCol.show() + } else if(this.$refs.notenTable?.tabulator && newVal == 0) { + const kommPruefCol = this.$refs.notenTable?.tabulator.getColumn("kommPruef") + kommPruefCol.hide() + } + } }, computed: { + getKommPruefCount(){ + let counter = 0 + this.studenten?.forEach(s => {if(s['kommPruef']){counter++}}) + return counter + }, getSaveBtnClass() { return "btn btn-primary ml-2" // return !this.changedData.length ? "btn btn-secondary ml-2" : "btn btn-primary ml-2" + }, + changedNoten() { + const v = this.changedNotenCounter // hack to trigger computed + const cs = this.studenten.reduce((acc, cur) => { + const teilnote = this.teilnoten[cur.uid] + if(teilnote.note_lv && (cur.benotungsdatum > cur.freigabedatum)) { + acc.push(cur) + } + return acc + }, []) + return cs } }, created() { @@ -287,12 +769,47 @@ export const Benotungstool = { + + + + + +
-
+

{{$p.t('benotungstool/benotungstoolTitle')}}

{{ lv?.bezeichnung }}

@@ -306,6 +823,18 @@ export const Benotungstool = {
+ +
+
+ + + +
+
+
diff --git a/system/phrasesupdate.php b/system/phrasesupdate.php index 367df502f..8f94090db 100644 --- a/system/phrasesupdate.php +++ b/system/phrasesupdate.php @@ -42159,7 +42159,7 @@ and represent the current state of research on the topic. The prescribed citatio array( 'app' => 'core', 'category' => 'benotungstool', - 'phrase' => 'c4termin3', + 'phrase' => 'c4kommPruef', 'insertvon' => 'system', 'phrases' => array( array( @@ -42295,7 +42295,147 @@ and represent the current state of research on the topic. The prescribed citatio 'insertvon' => 'system' ) ) - ) + ), + array( + 'app' => 'core', + 'category' => 'benotungstool', + 'phrase' => 'pruefungNr', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Prüfung Nr. {0}', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Examination No. {0}', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'benotungstool', + 'phrase' => 'noteneingabeSpeichern', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Noteneingabe speichern', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Save note entry', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'benotungstool', + 'phrase' => 'noteneingabeSpeichern', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Noteneingabe speichern', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Save note entry', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'benotungstool', + 'phrase' => 'noteneingabeBestätigen', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Noten bestätigen', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Confirm grades', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'benotungstool', + 'phrase' => 'createPruefungFor', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Prüfung erstellen für', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Create examination for', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'benotungstool', + 'phrase' => 'approveGrades', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Noten freigeben', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Approve Grades', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'benotungstool', + 'phrase' => 'c4originalZnote', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Ursprüngliche Zeugnisnote', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Original Transcript Grade', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), // CIS4 GESAMTNOTENEINGABE ENDE ------------------------------------------------------------------------------------ From 52d9e0a1956f9593e836380f4f3461cfd4688977 Mon Sep 17 00:00:00 2001 From: Johann Hoffmann Date: Fri, 25 Jul 2025 12:50:51 +0200 Subject: [PATCH 006/262] pruefungen columns pro datum nicht pro termintyp; row Selection & modal newPruefungForSelectedStudents mit linked multiselect dropdown; WIP antritte berechnen --- .../views/CisRouterView/CisRouterView.php | 3 +- .../Cis/Benotungstool/Benotungstool.js | 123 ++++++++++++++++-- system/phrasesupdate.php | 40 ++++++ 3 files changed, 156 insertions(+), 10 deletions(-) diff --git a/application/views/CisRouterView/CisRouterView.php b/application/views/CisRouterView/CisRouterView.php index b433ab9bd..b58490094 100644 --- a/application/views/CisRouterView/CisRouterView.php +++ b/application/views/CisRouterView/CisRouterView.php @@ -25,7 +25,8 @@ $includesArray = array( 'customJSs' => array( 'vendor/npm-asset/primevue/accordion/accordion.js', 'vendor/npm-asset/primevue/accordiontab/accordiontab.js', - 'vendor/npm-asset/primevue/password/password.js' + 'vendor/npm-asset/primevue/password/password.js', + 'vendor/npm-asset/primevue/multiselect/multiselect.js' ), 'customJSModules' => array( 'public/js/apps/Dashboard/Fhc.js' diff --git a/public/js/components/Cis/Benotungstool/Benotungstool.js b/public/js/components/Cis/Benotungstool/Benotungstool.js index 2075082cb..83144f4d7 100644 --- a/public/js/components/Cis/Benotungstool/Benotungstool.js +++ b/public/js/components/Cis/Benotungstool/Benotungstool.js @@ -12,7 +12,8 @@ export const Benotungstool = { CoreFilterCmpt, Dropdown: primevue.dropdown, Password: primevue.password, - Datepicker: VueDatePicker + Datepicker: VueDatePicker, + Multiselect: primevue.multiselect }, props: { lv_id: { @@ -34,6 +35,7 @@ export const Benotungstool = { }, data() { return { + selectedUids: [], // shared selection state selectedLehreinheit: null, lehreinheiten: null, tabulatorCanBeBuilt: false, @@ -72,6 +74,14 @@ export const Benotungstool = { } }, { + event: "rowSelectionChanged", + handler: async (data, rows) => { + console.log("Selected Data:", data); + console.log("Selected Rows:", rows); + this.selectedUids = data; + } + }, + { event: "cellEdited", handler: async (cell) => { const field = cell.getField() @@ -103,7 +113,20 @@ export const Benotungstool = { index: 'uid', layout: 'fitDataStretch', placeholder: this.$p.t('global/noDataAvailable'), + selectable: true, + selectableRangeMode: "click", // shift+click + selectablePersistence: false, // reset selection on table reload columns: [ + { + formatter: "rowSelection", + titleFormatter: "rowSelection", // Adds "select all" checkbox in header + hozAlign: "center", + headerSort: false, + cellClick: function (e, cell) { + cell.getRow().toggleSelect(); + }, + width: 50, + }, {title: Vue.computed(() => this.$p.t('benotungstool/c4mail')), field: 'email', formatter: this.mailFormatter, tooltip: false, widthGrow: 1}, {title: 'UID', field: 'uid', tooltip: false, widthGrow: 1}, {title: Vue.computed(() => this.$p.t('benotungstool/c4vorname')), field: 'vorname', tooltip: false, widthGrow: 1}, @@ -153,9 +176,6 @@ export const Benotungstool = { {title: Vue.computed(() => this.$p.t('benotungstool/c4freigabe')), field: 'freigegeben', widthGrow: 1, formatter: this.freigabeFormatter}, {title: Vue.computed(() => this.$p.t('benotungstool/c4zeugnisnote')), field: 'note', formatter: this.notenFormatter, widthGrow: 1}, {title: Vue.computed(() => this.$p.t('benotungstool/c4kommPruef')), field: 'kommPruef', widthGrow: 1, formatter: this.pruefungFormatter, hozAlign:"center", minWidth: 150} - // {title: Vue.computed(() => this.$p.t('benotungstool/c4termin1')), field: 'termin1', widthGrow: 1}, - // {title: Vue.computed(() => this.$p.t('benotungstool/c4termin2')), field: 'termin2', widthGrow: 1}, - // {title: Vue.computed(() => this.$p.t('benotungstool/c4termin3')), field: 'termin3', widthGrow: 1} ], persistence: false, } @@ -379,7 +399,10 @@ export const Benotungstool = { }, setupData(data){ this.studenten = data[0] ?? [] - this.studenten.forEach(s => s.pruefungen = []) + this.studenten.forEach(s => { + s.pruefungen = [] + s.infoString = `${s.vorname} ${s.nachname}`// (${s.semester}${s.verband}${s.gruppe}) Mat.: ${s.matrikelnr}`// used for multiselect + }) this.pruefungen = data[1] ?? [] this.domain = data[2] @@ -396,6 +419,8 @@ export const Benotungstool = { if(!student) return + // TODO: filter kommPruef here? or change kommProf ColDefinition + if(!distinctPruefungsDates.includes(p.datum)) distinctPruefungsDates.push(p.datum) // seperate kommPruefungen from previous pruefungen counts since the column count variability always ends with this @@ -453,6 +478,9 @@ export const Benotungstool = { }) distinctPruefungsDates.forEach((date, index)=>{ // TODO date format dd.mm.yyyy + + // const title = + cols.push({ title: date,//this.$p.t('benotungstool/pruefungNr', [index+1]), field: date, @@ -463,8 +491,9 @@ export const Benotungstool = { minWidth: 150 }) }) - - + console.log('distinctPruefungsDates', distinctPruefungsDates) + console.log('cols', cols) + cols.push(kommCol) // keep kommPruef Col as last this.$refs.notenTable.tabulator.clearSort() @@ -701,6 +730,9 @@ export const Benotungstool = { openSaveModal() { this.$refs.modalContainerNotenSpeichern.show() }, + openNewPruefungsdatumModal() { + this.$refs.modalContainerNeuesPruefungsdatum.show() + }, handleChangePruefungDatum(e) { // console.log('handleChangePruefungDatum', e) }, @@ -712,9 +744,30 @@ export const Benotungstool = { }, leChanged(e) { this.selectedLehreinheit = e.value + }, + addPruefung(){ + // TODO: save new pruefungs entry for all selected students on selected date with default note "noch nicht eingetragen" aka 9 } }, watch: { + selectedUids(newVal, oldVal) { + const table = this.$refs.notenTable?.tabulator + + if (!table) return; + console.log('selectedUids watcher newVal',newVal) + // Get all current rows + const allRows = table.getRows(); + console.log('table allRows',allRows) + // allRows.forEach(row => { + // const rowData = row.getData(); + // + // if (newVal.includes(rowData.uid)) { + // row.select(); // ensure row is selected + // } else { + // row.deselect(); // ensure row is deselected + // } + // }); + }, selectedLehreinheit(newVal) { if(!this.$refs.notenTable) return this.$refs.notenTable.tabulator.clearFilter(); @@ -731,24 +784,31 @@ export const Benotungstool = { } }, computed: { + getStudentenOptions() { + return this.studenten ? this.studenten : [] + }, getKommPruefCount(){ let counter = 0 this.studenten?.forEach(s => {if(s['kommPruef']){counter++}}) return counter }, getSaveBtnClass() { + // return "btn btn-primary ml-2" + return !this.changedNoten?.length ? "btn btn-primary ml-2" : "btn btn-secondary ml-2" + }, + getNewBtnClass() { return "btn btn-primary ml-2" // return !this.changedData.length ? "btn btn-secondary ml-2" : "btn btn-primary ml-2" }, changedNoten() { const v = this.changedNotenCounter // hack to trigger computed - const cs = this.studenten.reduce((acc, cur) => { + const cs = this.studenten ? this.studenten.reduce((acc, cur) => { const teilnote = this.teilnoten[cur.uid] if(teilnote.note_lv && (cur.benotungsdatum > cur.freigabedatum)) { acc.push(cur) } return acc - }, []) + }, []) : [] return cs } }, @@ -759,6 +819,48 @@ export const Benotungstool = { this.setupMounted() }, template: ` + + + + + + + diff --git a/system/phrasesupdate.php b/system/phrasesupdate.php index 8f94090db..d3ea1322f 100644 --- a/system/phrasesupdate.php +++ b/system/phrasesupdate.php @@ -42436,6 +42436,46 @@ and represent the current state of research on the topic. The prescribed citatio ) ) ), + array( + 'app' => 'core', + 'category' => 'benotungstool', + 'phrase' => 'c4addNewPruefung', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Neues Prüfungsdatum anlegen', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Create new exam date', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'benotungstool', + 'phrase' => 'prueflingSelection', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Prüflinge', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Examinees', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), // CIS4 GESAMTNOTENEINGABE ENDE ------------------------------------------------------------------------------------ From 6ccbc956972ad9d30099e0bd75041d4c399867a7 Mon Sep 17 00:00:00 2001 From: Johann Hoffmann Date: Tue, 29 Jul 2025 17:32:07 +0200 Subject: [PATCH 007/262] createPruefung entry with "noch nicht eingetragen" note as default for a selection of students at given date; filterHeaders in noten cols; antrittCount col; pruefungsformatter with antritt highlighting; --- .../controllers/api/frontend/v1/Noten.php | 348 ++++++----- public/js/api/factory/noten.js | 9 +- .../Cis/Benotungstool/Benotungstool.js | 559 ++++++++++++++---- system/phrasesupdate.php | 40 ++ 4 files changed, 681 insertions(+), 275 deletions(-) diff --git a/application/controllers/api/frontend/v1/Noten.php b/application/controllers/api/frontend/v1/Noten.php index 86c56d5ad..68bd3f4fd 100644 --- a/application/controllers/api/frontend/v1/Noten.php +++ b/application/controllers/api/frontend/v1/Noten.php @@ -34,7 +34,8 @@ class Noten extends FHCAPI_Controller 'saveStudentenNoten' => self::PERM_LOGGED, // todo: berechtigungen! 'getNotenvorschlagStudent' => self::PERM_LOGGED, 'saveNotenvorschlag' => self::PERM_LOGGED, - 'saveStudentPruefung' => self::PERM_LOGGED + 'saveStudentPruefung' => self::PERM_LOGGED, + 'createPruefungen' => self::PERM_LOGGED ]); $this->load->library('AuthLib', null, 'AuthLib'); @@ -264,7 +265,6 @@ class Noten extends FHCAPI_Controller $lva_id = $result->lva_id; $lehreinheit_id = $result->lehreinheit_id; -// $lehreinheit_id_pr = $result->lehreinheit_id_pr; // todo: very rare, refactor? $stsem = $result->sem_kurzbz; $typ = $result->typ; @@ -275,7 +275,7 @@ class Noten extends FHCAPI_Controller // by retrieving it from students row in campus.vw_student_lehrveranstaltung earlier on // $lehreinheit_id = getLehreinheit($db, $lvid, $student_uid, $stsem); - $lehreinheit_id = $result->lehreinheit_id; +// $lehreinheit_id = $result->lehreinheit_id; $punkte = str_replace(',', '.', $punkte); @@ -296,155 +296,18 @@ class Noten extends FHCAPI_Controller $old_note = $note; - //Laden der Lehrveranstaltung - $lv_obj = new lehrveranstaltung(); - if(!$lv_obj->load($lva_id)) - die($lv_obj->errormsg); - - //Studiengang laden - $stg_obj = new studiengang($lv_obj->studiengang_kz); + // TODO: notwendiger check? +// //Laden der Lehrveranstaltung +// $lv_obj = new lehrveranstaltung(); +// if(!$lv_obj->load($lva_id)) +// die($lv_obj->errormsg); +// +// //Studiengang laden +// $stg_obj = new studiengang($lv_obj->studiengang_kz); +// -// $response = savePruefung($lvid, $student_uid, $stsem, $lehreinheit_id, $datum, $typ, $note, $punkte); - $savedPruefung = null; - $extraPruefung = null; - if($typ == "Termin2") { - - $pr = new Pruefung(); - - // Wenn eine Pruefung angelegt wird, wird zuerst eine Pruefung mit 1. Termin angelegt - // und dort die Zeugnisnote gespeichert - if($pr->getPruefungen($student_uid, "Termin1", $lva_id, $stsem)) - { - if ($pr->result) - { - // TODO: is this filler if branch really necessary? - $termin1 = 1; - } - else - { - $lvnote = new lvgesamtnote(); - // update Termin1 note - if ($lvnote->load($lva_id, $student_uid, $stsem)) - { - $pr_note = $lvnote->note; - $pr_punkte = $lvnote->punkte; - $benotungsdatum = $lvnote->benotungsdatum; - } - else // set Termin1 note to "noch nicht eingetragen" - { - $pr_note = 9; - $pr_punkte = ''; - $benotungsdatum = $jetzt; - } - - $pr_1 = new Pruefung(); - $pr_1->lehreinheit_id = $lehreinheit_id; - $pr_1->student_uid = $student_uid; - $pr_1->mitarbeiter_uid = getAuthUID(); - $pr_1->note = $pr_note; - $pr_1->punkte = $pr_punkte; - $pr_1->pruefungstyp_kurzbz = "Termin1"; - $pr_1->datum = $benotungsdatum; - $pr_1->anmerkung = ""; - $pr_1->insertamum = $jetzt; - $pr_1->insertvon = getAuthUID(); - $pr_1->updateamum = null; - $pr_1->updatevon = null; - $pr_1->ext_id = null; - $pr_1->new = true; - $pr_1->save(); - $extraPruefung = $pr_1; //"neu T1"; - } - - $prTermin2 = new Pruefung(); - $pr_2 = new Pruefung(); - - // Die Pruefung wird als Termin2 eingetragen - if ($prTermin2->getPruefungen($student_uid, 'Termin2', $lva_id, $stsem)) - { - if ($prTermin2->result) - { - $pr_2->load($prTermin2->result[0]->pruefung_id); - $pr_2->new = null; - $pr_2->updateamum = $jetzt; - $pr_2->updatevon = getAuthUID(); - $old_note = $pr_2->note; - $pr_2->note = $note; - $pr_2->punkte = $punkte; - $pr_2->datum = $datum; - $pr_2->anmerkung = ""; - $savedPruefung = $pr_2;//"update T2"; - } - else - { - $pr_2->lehreinheit_id = $lehreinheit_id; - $pr_2->student_uid = $student_uid; - $pr_2->mitarbeiter_uid = getAuthUID(); - $pr_2->note = $note; - $pr_2->punkte = $punkte; - $pr_2->pruefungstyp_kurzbz = $typ; - $pr_2->datum = $datum; - $pr_2->anmerkung = ""; - $pr_2->insertamum = $jetzt; - $pr_2->insertvon = getAuthUID(); - $pr_2->updateamum = null; - $pr_2->updatevon = null; - $pr_2->ext_id = null; - $pr_2->new = true; - $old_note = -1; - $savedPruefung = $pr_2;//"new T2"; - } - $pr_2->save(); - } - } - - } else if($typ == "Termin3") { - - $prTermin3 = new Pruefung(); - $pr_3 = new Pruefung(); - - if ($prTermin3->getPruefungen($student_uid, 'Termin3', $lva_id, $stsem)) - { - if ($prTermin3->result) - { - $pr_3->load($prTermin3->result[0]->pruefung_id); - $pr_3->new = null; - $pr_3->updateamum = $jetzt; - $pr_3->updatevon = getAuthUID(); - $old_note = $pr_3->note; - $pr_3->note = $note; - $pr_3->punkte = $punkte; - $pr_3->datum = $datum; - $pr_3->anmerkung = ""; - $savedPruefung = $pr_3; //"update T3"; - } - else - { - $pr_3->lehreinheit_id = $lehreinheit_id; - $pr_3->student_uid = $student_uid; - $pr_3->mitarbeiter_uid = getAuthUID(); - $pr_3->note = $note; - $pr_3->punkte = $punkte; - $pr_3->pruefungstyp_kurzbz = $typ; - $pr_3->datum = $datum; - $pr_3->anmerkung = ""; - $pr_3->insertamum = $jetzt; - $pr_3->insertvon = getAuthUID(); - $pr_3->updateamum = null; - $pr_3->updatevon = null; - $pr_3->ext_id = null; - $pr_3->new = true; - $old_note = -1; - $savedPruefung = $pr_3; //"new T3"; - } - $pr_3->save(); - } - } else { - $this->terminateWithError("typ is not termin2 or termin3", 'general'); - } - + $pruefungenChanged = $this->savePruefungstermin($typ, $student_uid, $lva_id, $stsem, $lehreinheit_id, $note, $punkte, $datum); - //Gesamtnote updaten $lvgesamtnote = new lvgesamtnote(); if (!$lvgesamtnote->load($lva_id, $student_uid, $stsem)) @@ -480,9 +343,162 @@ class Noten extends FHCAPI_Controller // $response = "update"; } - $saved = $lvgesamtnote->save($new); + $lvgesamtnote->save($new); + + $savedPruefung = $pruefungenChanged['savedPruefung'] ?? null; + $extraPruefung = $pruefungenChanged['extraPruefung'] ?? null; // TODO: test + + $this->terminateWithSuccess(array($savedPruefung, $lvgesamtnote, $extraPruefung)); + } + + private function savePruefungstermin($typ, $student_uid, $lva_id, $stsem, $lehreinheit_id, $note, $punkte, $datum) + { + $jetzt = date("Y-m-d H:i:s"); - $this->terminateWithSuccess(array($savedPruefung, $lvgesamtnote, $saved, $extraPruefung)); + $pruefungenChanged = []; + + if($typ == "Termin2") { + + $pr = new Pruefung(); + + // Wenn eine Pruefung angelegt wird, wird zuerst eine Pruefung mit 1. Termin angelegt + // und dort die Zeugnisnote gespeichert + if($pr->getPruefungen($student_uid, "Termin1", $lva_id, $stsem)) + { + if ($pr->result) + { + // TODO: is this filler if branch really necessary? + $termin1 = 1; + } + else + { + $lvnote = new lvgesamtnote(); + // update Termin1 note + if ($lvnote->load($lva_id, $student_uid, $stsem)) + { + $pr_note = $lvnote->note; + $pr_punkte = $lvnote->punkte; + $benotungsdatum = $lvnote->benotungsdatum; + } + else // set Termin1 note to "noch nicht eingetragen" + { + $pr_note = 9; + $pr_punkte = ''; + $benotungsdatum = $jetzt; + } + + $pr_1 = new Pruefung(); + $pr_1->lehreinheit_id = $lehreinheit_id; + $pr_1->student_uid = $student_uid; + $pr_1->mitarbeiter_uid = getAuthUID(); + $pr_1->note = $pr_note; + $pr_1->punkte = $pr_punkte; + $pr_1->pruefungstyp_kurzbz = "Termin1"; + $pr_1->datum = $benotungsdatum; + $pr_1->anmerkung = ""; + $pr_1->insertamum = $jetzt; + $pr_1->insertvon = getAuthUID(); + $pr_1->updateamum = null; + $pr_1->updatevon = null; + $pr_1->ext_id = null; + $pr_1->new = true; + $pr_1->save(); + $pruefungenChanged['extraPruefung'] = $pr_1; //"neu T1"; + } + + $prTermin2 = new Pruefung(); + $pr_2 = new Pruefung(); + + // Die Pruefung wird als Termin2 eingetragen + if ($prTermin2->getPruefungen($student_uid, 'Termin2', $lva_id, $stsem)) + { + if ($prTermin2->result) + { + $pr_2->load($prTermin2->result[0]->pruefung_id); + $pr_2->new = null; + $pr_2->updateamum = $jetzt; + $pr_2->updatevon = getAuthUID(); + $old_note = $pr_2->note; + $pr_2->note = $note; + $pr_2->punkte = $punkte; + $pr_2->datum = $datum; + $pr_2->anmerkung = ""; + $pruefungenChanged['savedPruefung'] = $pr_2; +// $savedPruefung = $pr_2;//"update T2"; + } + else + { + $pr_2->lehreinheit_id = $lehreinheit_id; + $pr_2->student_uid = $student_uid; + $pr_2->mitarbeiter_uid = getAuthUID(); + $pr_2->note = $note; + $pr_2->punkte = $punkte; + $pr_2->pruefungstyp_kurzbz = $typ; + $pr_2->datum = $datum; + $pr_2->anmerkung = ""; + $pr_2->insertamum = $jetzt; + $pr_2->insertvon = getAuthUID(); + $pr_2->updateamum = null; + $pr_2->updatevon = null; + $pr_2->ext_id = null; + $pr_2->new = true; + $old_note = -1; + $pruefungenChanged['savedPruefung'] = $pr_2; +// $savedPruefung = $pr_2;//"new T2"; + } + $pr_2->save(); + } + } + + } else if($typ == "Termin3") { + + $prTermin3 = new Pruefung(); + $pr_3 = new Pruefung(); + + if ($prTermin3->getPruefungen($student_uid, 'Termin3', $lva_id, $stsem)) + { + if ($prTermin3->result) + { + $pr_3->load($prTermin3->result[0]->pruefung_id); + $pr_3->new = null; + $pr_3->updateamum = $jetzt; + $pr_3->updatevon = getAuthUID(); + $old_note = $pr_3->note; + $pr_3->note = $note; + $pr_3->punkte = $punkte; + $pr_3->datum = $datum; + $pr_3->anmerkung = ""; + $pruefungenChanged['savedPruefung'] = $pr_3; +// $savedPruefung = $pr_3; //"update T3"; + } + else + { + $pr_3->lehreinheit_id = $lehreinheit_id; + $pr_3->student_uid = $student_uid; + $pr_3->mitarbeiter_uid = getAuthUID(); + $pr_3->note = $note; + $pr_3->punkte = $punkte; + $pr_3->pruefungstyp_kurzbz = $typ; + $pr_3->datum = $datum; + $pr_3->anmerkung = ""; + $pr_3->insertamum = $jetzt; + $pr_3->insertvon = getAuthUID(); + $pr_3->updateamum = null; + $pr_3->updatevon = null; + $pr_3->ext_id = null; + $pr_3->new = true; + $old_note = -1; + $pruefungenChanged['savedPruefung'] = $pr_3; +// $savedPruefung = $pr_3; //"new T3"; + } + $pr_3->save(); + } + } else { + // TODO: proper error phrase + $this->terminateWithError("Typ is not termin2 or termin3.", 'general'); + } + + return $pruefungenChanged; } public function saveNotenvorschlag() { @@ -540,6 +556,34 @@ class Noten extends FHCAPI_Controller $this->terminateWithSuccess(array($ret, $lvgesamtnote)); } + + public function createPruefungen() { + $result = $this->getPostJSON(); + + if(!property_exists($result, 'uids') || !property_exists($result, 'datum')) { + $this->terminateWithError($this->p->t('global', 'missingParameters'), 'general'); + } + + $uids = $result->uids; + $datum = $result->datum; + $lva_id = $result->lva_id; + + $stsem = $result->sem_kurzbz; + + $ret = []; + + foreach ($uids as $student) { + $student_uid = $student->uid; + $typ = $student->typ; + $note = 9; //$result->note; // TODO: parameterize for import maybe + $punkte = ''; // TODO: check punkte feature + + $lehreinheit_id = $student->lehreinheit_id; + $ret[$student->uid] = $this->savePruefungstermin($typ, $student_uid, $lva_id, $stsem, $lehreinheit_id, $note, $punkte, $datum); + } + + $this->terminateWithSuccess($ret); + } } diff --git a/public/js/api/factory/noten.js b/public/js/api/factory/noten.js index d26a1a884..b62ba1fe1 100644 --- a/public/js/api/factory/noten.js +++ b/public/js/api/factory/noten.js @@ -49,5 +49,12 @@ export default { url: '/api/frontend/v1/Noten/saveStudentPruefung', params: { student_uid, note, punkte, datum, lva_id, lehreinheit_id, sem_kurzbz, typ } }; - } + }, + createPruefungen(uids, datum, lva_id, sem_kurzbz){ + return { + method: 'post', + url: '/api/frontend/v1/Noten/createPruefungen', + params: { uids, datum, lva_id, sem_kurzbz } + }; + }, }; \ No newline at end of file diff --git a/public/js/components/Cis/Benotungstool/Benotungstool.js b/public/js/components/Cis/Benotungstool/Benotungstool.js index 83144f4d7..fdbcf098d 100644 --- a/public/js/components/Cis/Benotungstool/Benotungstool.js +++ b/public/js/components/Cis/Benotungstool/Benotungstool.js @@ -35,12 +35,14 @@ export const Benotungstool = { }, data() { return { + loading: false, selectedUids: [], // shared selection state selectedLehreinheit: null, lehreinheiten: null, tabulatorCanBeBuilt: false, selectedPruefungNote: null, selectedPruefungDate: new Date(), // v-model for pruefung edit datepicker + distinctPruefungsDates: null, pruefungStudent: null, pruefung: null, password: '', @@ -76,8 +78,6 @@ export const Benotungstool = { { event: "rowSelectionChanged", handler: async (data, rows) => { - console.log("Selected Data:", data); - console.log("Selected Rows:", rows); this.selectedUids = data; } }, @@ -107,6 +107,25 @@ export const Benotungstool = { ]}; }, methods: { + selectionArraysAreEqual(arr1, arr2) { + if(arr1.length !== arr2.length) return false + + const sortFunc = (s1, s2) => { + if(s1.nachname > s2.nachname) { + return 1 + } else if (s1.nachname < s2.nachname) { + return -1 + } else { + return 0 + } + } + const sortedArr1 = arr1.sort(sortFunc) + const sortedArr2 = arr2.sort(sortFunc) + + const arrsREqual = sortedArr1.every((val, index) => val === sortedArr2[index]); + + return arrsREqual + }, getNotenTableOptions() { return { height: 700, @@ -116,6 +135,15 @@ export const Benotungstool = { selectable: true, selectableRangeMode: "click", // shift+click selectablePersistence: false, // reset selection on table reload + selectableCheck: function(row){ + const data = row.getData(); + + if(data['kommPruef']) return false + else if(data.hoechsterAntritt >= 3) return false // 3 pruefungen counted + + return true; // student can be selected to add pruefung + }, + rowFormatter: this.unselectableFormatter, columns: [ { formatter: "rowSelection", @@ -128,6 +156,7 @@ export const Benotungstool = { width: 50, }, {title: Vue.computed(() => this.$p.t('benotungstool/c4mail')), field: 'email', formatter: this.mailFormatter, tooltip: false, widthGrow: 1}, + {title: Vue.computed(() => this.$p.t('benotungstool/c4antrittCount')), field: 'hoechsterAntritt', tooltip: false, widthGrow: 1}, {title: 'UID', field: 'uid', tooltip: false, widthGrow: 1}, {title: Vue.computed(() => this.$p.t('benotungstool/c4vorname')), field: 'vorname', tooltip: false, widthGrow: 1}, {title: Vue.computed(() => this.$p.t('benotungstool/c4nachname')), field: 'nachname', widthGrow: 1}, @@ -172,14 +201,30 @@ export const Benotungstool = { {title: '', width: 50, hozAlign: 'center', formatter: this.arrowFormatter, cellClick: this.saveNote}, {title: Vue.computed(() => this.$p.t('benotungstool/c4lvnote')), field: 'lv_note', formatter: this.notenFormatter, + headerFilter: 'list', + headerFilterParams: () => { + return { values: ["\u00A0",...this.notenOptions.map(opt => opt.bezeichnung)] } // TODO: fix option render height lmao... + }, + headerFilterFunc: this.notenFilterFunc, widthGrow: 1}, {title: Vue.computed(() => this.$p.t('benotungstool/c4freigabe')), field: 'freigegeben', widthGrow: 1, formatter: this.freigabeFormatter}, - {title: Vue.computed(() => this.$p.t('benotungstool/c4zeugnisnote')), field: 'note', formatter: this.notenFormatter, widthGrow: 1}, + {title: Vue.computed(() => this.$p.t('benotungstool/c4zeugnisnote')), + headerFilter: true, + field: 'note', + formatter: this.notenFormatter, + widthGrow: 1}, {title: Vue.computed(() => this.$p.t('benotungstool/c4kommPruef')), field: 'kommPruef', widthGrow: 1, formatter: this.pruefungFormatter, hozAlign:"center", minWidth: 150} ], persistence: false, } }, + notenFilterFunc(filterVal, rowVal) { + const opt = this.notenOptions.find(opt => opt.bezeichnung === filterVal) + if(opt.note == rowVal) return true + if(filterVal === "" || filterVal === null) return true + + return false + }, parseDate(timestamp) { if(!timestamp) return null const [datePart, timePart] = timestamp.split(" "); @@ -196,6 +241,9 @@ export const Benotungstool = { } else { return 'ok' } + }, + unselectableFormatter(row) { + }, notenFormatter(cell) { const value = cell.getValue() @@ -232,9 +280,6 @@ export const Benotungstool = { return value }, - handlePasswordChanged(pw) { - // console.log('pw:', pw) - }, saveNote(e, cell) { // Notenvorschlag freigeben const row = cell.getRow() const data = row.getData() @@ -271,19 +316,53 @@ export const Benotungstool = { return '' } + const colDef = cell.getColumn().getDefinition() + + // column is just a date, student can have any of his antritte on this date, so we need to get + // student.pruefungen and look for a pruefung with this cols title as date + + const field = cell.getColumn().getField() + const studentPruefung = field != 'kommPruef' ? data.pruefungen.find(p => p.datum === field) : data['kommPruef'] + + // is this column/cell allowed to have an add pruefung action + const canAdd = field !== 'kommPruef' && data.hoechsterAntritt < 4 && !colDef.originalNote + // TODO: check for some time limit maybe? old pruefungen can be changed/created + // TODO: it also looks ugly and unprofessional, should at some peoplt disable/hide the change action // Create root row div const rowDiv = document.createElement('div'); - rowDiv.className = 'row'; + rowDiv.className = 'row flex-nowrap'; rowDiv.style.display = 'flex'; rowDiv.style.justifyContent = 'center'; rowDiv.style.alignItems = 'center'; rowDiv.style.height = '100%'; - function createCol(content) { + if(studentPruefung) { + let color = '' + switch(studentPruefung.pruefungstyp_kurzbz) { + case 'Termin1': + color = 'green' + break + case 'Termin2': + color = 'yellow' + break + case 'Termin3': + color = 'orange' + break + case 'kommPruef': + color = 'red' + break + } + + rowDiv.style.borderLeft = `4px solid ${color}`; + rowDiv.style.marginLeft = "6px"; // small indent so text doesn't overlap + rowDiv.style.boxSizing = "border-box"; + } + + function createCol(content, classParam) { const colDiv = document.createElement('div'); - colDiv.className = 'col-4'; + colDiv.className = classParam ?? 'col-4'; colDiv.style.justifyContent = 'center'; colDiv.style.alignItems = 'center'; colDiv.style.height = '100%'; @@ -297,19 +376,22 @@ export const Benotungstool = { return colDiv; } - const field = cell.getColumn().getField() if(data[field]) { const dateParts = data[field].datum.split('-') const date = `${dateParts[2]}.${dateParts[1]}.${dateParts[0]}` - + // First column (date) - rowDiv.appendChild(createCol(date)); + rowDiv.appendChild(createCol(date, 'col-4 d-flex align-items-center')); + + const noteDefEntry = data.note ? this.notenOptions.find(n => n.note == data[field].note) : null // Second column (note_bezeichnung) - rowDiv.appendChild(createCol(data[field].note_bezeichnung || '')); - - if(field === 'kommPruef' || field === 'pruefungNr0') {// no actions on kommPruef allowed - rowDiv.appendChild(createCol('')); // append empty col4 to have formatting similar + rowDiv.appendChild(createCol(noteDefEntry.bezeichnung || '', 'col-auto ms-auto d-flex justify-content-center align-items-center')); + + // no actions on kommPruef allowed + // no actions on termin1 aka pruefung 0 aka ursprüngliche note erlaubt + if(field === 'kommPruef' || colDef.originalNote) { + rowDiv.appendChild(createCol('', 'col-4 d-flex justify-content-center align-items-center')); // append empty col4 to have formatting similar return rowDiv } @@ -318,44 +400,42 @@ export const Benotungstool = { button.className = 'btn btn-outline-secondary'; button.textContent = 'Change'; // TODO: phrase button.addEventListener('click', () => { - this.openPruefungModal(data, data[field]); + this.openPruefungModal(data, data[field], field); }); - rowDiv.appendChild(createCol(button)); + rowDiv.appendChild(createCol(button, 'col-4 d-flex justify-content-center align-items-center')); return rowDiv; - } else if (field !== 'kommPruef' && field !== 'pruefungNr0') { // return new btn action + } else if (canAdd) { // return new btn action const button = document.createElement('button'); button.className = 'btn btn-outline-secondary'; button.textContent = 'Add'; // TODO: phrase button.addEventListener('click', () => { - this.openPruefungModal(data) + this.openPruefungModal(data, null, field) }); - rowDiv.appendChild(createCol(button)); + rowDiv.appendChild(createCol(button), 'col-4 d-flex justify-content-center align-items-center'); return rowDiv; } else return '' }, - openPruefungModal(student, pruefung = null) { + openPruefungModal(student, pruefung = null, field) { this.pruefungStudent = student this.pruefung = pruefung - debugger - if(this.pruefung?.datum) { - const pruefungDateParts = this.pruefung?.datum?.split('-') + + const dateStr = this.pruefung?.datum ?? field + + const pruefungDateParts = dateStr.split('-') - // new date obj so datepicker picks ob the change by ref - const newDate = new Date() - newDate.setFullYear(pruefungDateParts[0]) - newDate.setMonth(pruefungDateParts[1]) - newDate.setDate(pruefungDateParts[2]) - this.selectedPruefungDate = newDate - } else { - const newDate = new Date() - newDate.setTime(Date.now()) - this.selectedPruefungDate = newDate - } + // new date obj so datepicker picks ob the change by ref + const newDate = new Date() + newDate.setFullYear(pruefungDateParts[0]) + newDate.setMonth(pruefungDateParts[1]) + newDate.setMonth(newDate.getMonth() - 1) // acount for js date month offset + newDate.setDate(pruefungDateParts[2]) + this.selectedPruefungDate = newDate + if(this.pruefung?.note) { this.selectedPruefungNote = this.notenOptions.find(n => n.note == this.pruefung.note) @@ -366,9 +446,9 @@ export const Benotungstool = { this.$refs.modalContainerPruefung.show() }, pruefungTitleFormatter(cell) { - const col = cell.getColumn() - if(col.getField() === "pruefungNr0") return this.$p.t('benotungstool/c4originalZnote') - return col.getDefinition().title; + const def = cell.getColumn().getDefinition() + if(def.originalNote) return this.$p.t('benotungstool/c4originalZnote') + return def.title; }, arrowFormatter(cell) { const row = cell.getRow() @@ -391,6 +471,17 @@ export const Benotungstool = { buildMailToLink(student){ return 'mailto:' + student.uid +'@'+ this.domain }, + insertSortedDate(arr, dateStr) { + // Binary search to find insertion index + let left = 0, right = arr.length; + while (left < right) { + const mid = (left + right) >> 1; + if (arr[mid] < dateStr) left = mid + 1; + else right = mid; + } + arr.splice(left, 0, dateStr); // insert at index + return arr; + }, tableResolve(resolve) { this.tableBuiltResolve = resolve }, @@ -410,7 +501,7 @@ export const Benotungstool = { this.teilnoten = data[3] ?? [] // let pruefungenRegularColCount = 0; - const distinctPruefungsDates = [] + this.distinctPruefungsDates = [] const cols = [...this.notenTableOptions.columns.slice(0, -1)]; const kommCol = this.notenTableOptions.columns[this.notenTableOptions.columns.length - 1]; @@ -421,7 +512,7 @@ export const Benotungstool = { // TODO: filter kommPruef here? or change kommProf ColDefinition - if(!distinctPruefungsDates.includes(p.datum)) distinctPruefungsDates.push(p.datum) + if(p.pruefungstyp_kurzbz !== 'kommPruef' && !this.distinctPruefungsDates.includes(p.datum)) this.distinctPruefungsDates.push(p.datum) // seperate kommPruefungen from previous pruefungen counts since the column count variability always ends with this if(p.pruefungstyp_kurzbz == 'kommPruef') { @@ -433,7 +524,6 @@ export const Benotungstool = { // if(student.pruefungen.length > pruefungenRegularColCount) pruefungenRegularColCount = student.pruefungen.length }) - // TODO: check if note is überschreibbar! this.studenten?.forEach(s => { // sort students regular pruefungen by datum s.pruefungen.sort((p1, p2) => { @@ -449,7 +539,8 @@ export const Benotungstool = { s.pruefungen.forEach((p, i) => { s[p.datum] = p }) - + + s.hoechsterAntritt = this.getAntrittCountStudent(s) s.email = this.buildMailToLink(s) s.lv_note = this.teilnoten[s.uid].note_lv s.freigabedatum = this.parseDate(this.teilnoten[s.uid]['freigabedatum']) @@ -466,8 +557,8 @@ export const Benotungstool = { }) }) - - distinctPruefungsDates.sort((d1, d2) => { + + this.distinctPruefungsDates.sort((d1, d2) => { if(d1 > d2) { return 1 } else if (d1 < d2) { @@ -476,32 +567,38 @@ export const Benotungstool = { return 0 } }) - distinctPruefungsDates.forEach((date, index)=>{ - // TODO date format dd.mm.yyyy - - // const title = + this.distinctPruefungsDates.forEach((date, index)=>{ + const dateparts = date.split('-') + const titledate = `${dateparts[2]}.${dateparts[1]}.${dateparts[0]}` + // TODO: should studenten without shadow pruefung Termin have their "ursprüngliche Zeugnisnote" + // col filled for consistency reasons? + + // TODO: test if this holds true + const originalNote = index === 0 cols.push({ - title: date,//this.$p.t('benotungstool/pruefungNr', [index+1]), + title: titledate,//this.$p.t('benotungstool/pruefungNr', [index+1]), field: date, formatter: this.pruefungFormatter, titleFormatter: this.pruefungTitleFormatter, hozAlign:"center", widthGrow: 1, - minWidth: 150 + minWidth: 200, + originalNote }) }) - console.log('distinctPruefungsDates', distinctPruefungsDates) - console.log('cols', cols) cols.push(kommCol) // keep kommPruef Col as last + this.loading = false + this.$refs.notenTable.tabulator.clearSort() this.$refs.notenTable.tabulator.setColumns(cols) this.$refs.notenTable.tabulator.setData(this.studenten); this.$refs.notenTable.tabulator.redraw(true); }, loadNoten(lv_id, sem_kurzbz) { + this.loading = true this.$api.call(ApiNoten.getStudentenNoten(lv_id, sem_kurzbz)) .then(res => { if(res?.data) this.setupData(res.data) @@ -520,6 +617,7 @@ export const Benotungstool = { this.$refs.notenTable.tabulator.setHeight(this.notenTableOptions.height) }, setupCreated() { + this.loading = true // fetch lva dropdown this.$api.call(ApiLehre.getZugewieseneLv(this.viewData?.uid, this.sem_kurzbz)).then(res => { this.lehrveranstaltungen = res.data @@ -642,16 +740,32 @@ export const Benotungstool = { getOptionLabelLe(option) { return option.infoString }, + getPruefungstypForStudentByAntritt(student) { + // when adding new pruefungen, determine the next pruefungstyp by using the antritt counter + switch (student.hoechsterAntritt) { + case 0: + return "Termin2" + break + case 1: + return "Termin2" + break + case 2: + return "Termin3" + break + default: + return "" + } + }, savePruefungEingabe() { const year = this.selectedPruefungDate.getFullYear(); const month = String(this.selectedPruefungDate.getMonth() + 1).padStart(2, '0'); // Months are 0-based const day = String(this.selectedPruefungDate.getDate()).padStart(2, '0'); const dateStr = `${year}-${month}-${day}`; - // TODO: test this hypothesis // first pruefung is always "Termin2" since normal note counts as Termin1 - const pOffset = this.pruefung === null && this.pruefungStudent.pruefungen.length === 0 ? 2 : 1 - const typ = this.pruefung ? this.pruefung.pruefungstyp_kurzbz : ('Termin'+(this.pruefungStudent.pruefungen.length + pOffset)) + // const pOffset = this.pruefung === null && this.pruefungStudent.pruefungen.length === 0 ? 2 : 1 + + const typ = this.pruefung ? this.pruefung.pruefungstyp_kurzbz : this.getPruefungstypForStudentByAntritt(this.pruefungStudent) this.$api.call(ApiNoten.saveStudentPruefung( this.pruefungStudent.uid, @@ -664,54 +778,116 @@ export const Benotungstool = { typ )).then(res => { if(res.meta.status === 'success') { + this.$fhcAlert.alertInfo('Prüfung für Student ' + this.pruefungStudent.uid + ' bearbeitet oder angelegt') // TODO: phrase + const s = this.studenten.find(s => s.uid === res.data[1]?.student_uid) - console.log('old student freigabedatum', s.freigabedatum) - console.log('old student benotungsdatum', s.benotungsdatum) s.freigabedatum = this.parseDate(res.data[1]?.['freigabedatum']) s.benotungsdatum = this.parseDate(res.data[1]?.['benotungsdatum']) - - console.log('new student freigabedatum', s.freigabedatum) - console.log('new student benotungsdatum', s.benotungsdatum) + s.freigegeben = this.checkFreigabe(s.freigabedatum, s.benotungsdatum, s.uid); - // todo: update student subject grade/lv_note s.lv_note = res.data[1]?.note - // find the exact student pruefung and update that - // add new pruefung to row - if(res.data[0]?.new) { - // TODO: if new pruefung was of typ "Termin2", "Termin1" shadow pruefung is not available + if(!this.pruefung) { this.handleAddNewTermin(res.data, s) } else { // update existing - const oldIndex = s.pruefungen.findIndex(p => p.pruefung_id == res.data[0]?.pruefung_id) + const oldIndex = s.pruefungen.findIndex(p => p.pruefung_id == this.pruefung.pruefung_id) if(oldIndex !== -1) { s.pruefungen.splice(oldIndex, 1, res.data[0]) - s['pruefungNr'+oldIndex] = res.data[0] + s[res.data[0].datum] = res.data[0] } + + // antritte might have changed due to different benotung + s.hoechsterAntritt = this.getAntrittCountStudent(s) } - - // // TODO: manual row update! - // const row = this.$refs.notenTable.tabulator.getRow(s.uid) - // row.update() this.$refs.notenTable.tabulator.redraw(true) - this.$fhcAlert.alertSuccess('Prüfung gespeichert') // TODO: phrase + this.$fhcAlert.alertInfo('Prüfung gespeichert') // TODO: phrase } }) this.$refs.modalContainerPruefung.hide() }, handleAddNewTermin(data, student){ + const savedPruefung = data[0] + const extra = data[2] + + // check for extra pruefung (termin1) to add before + if(extra) { + extra.datum = extra.datum.split(' ')[0] + if(!this.distinctPruefungsDates.includes(extra.datum)) { + this.insertSortedDate(this.distinctPruefungsDates, extra.datum) + } + + student.pruefungen.push(extra) + student[extra.datum] = extra + } + + if(!this.distinctPruefungsDates.includes(savedPruefung.datum)) { + this.insertSortedDate(this.distinctPruefungsDates, savedPruefung.datum) + } + + // add pruefung to pruefungen array + student.pruefungen.push(savedPruefung) + + // add pruefung to student via its datum as a field + student[savedPruefung.datum] = savedPruefung + + // usually should be in order naturally, just to be save + student.pruefungen.sort((p1, p2) => { + if(p1.datum > p2.datum) { + return 1 + } else if (p1.datum < p2.datum) { + return -1 + } else { + return 0 + } + }) + + // recalculate student antritte + student.hoechsterAntritt = this.getAntrittCountStudent(student) + + // add col to table + const cols = [...this.notenTableOptions.columns.slice(0, -1)]; + const kommCol = this.notenTableOptions.columns[this.notenTableOptions.columns.length - 1]; + + + // TODO: could reuse cols instead of recreating all from a variable maybe + this.distinctPruefungsDates.forEach((date, index)=>{ + const dateparts = date.split('-') + const titledate = `${dateparts[2]}.${dateparts[1]}.${dateparts[0]}` + + // TODO: should studenten without shadow pruefung Termin have their "ursprüngliche Zeugnisnote" + // col filled for consistency reasons? + + // TODO: test if this holds true + const originalNote = index === 0 + cols.push({ + title: titledate,//this.$p.t('benotungstool/pruefungNr', [index+1]), + field: date, + formatter: this.pruefungFormatter, + titleFormatter: this.pruefungTitleFormatter, + hozAlign:"center", + widthGrow: 1, + minWidth: 200, + originalNote + }) + }) + + cols.push(kommCol) // keep kommPruef Col as last + // redraw table + + }, saveNoteneingabe() { this.$api.call(ApiNoten.saveStudentenNoten(this.password, this.changedNoten, this.lv_id, this.sem_kurzbz)) .then((res) => { if(res.meta.status === 'success') { - this.$fhcAlert.alertSuccess('Noten gespeichert') + this.$fhcAlert.alertInfo('Noten gespeichert') } res.data.forEach(d => { @@ -733,12 +909,6 @@ export const Benotungstool = { openNewPruefungsdatumModal() { this.$refs.modalContainerNeuesPruefungsdatum.show() }, - handleChangePruefungDatum(e) { - // console.log('handleChangePruefungDatum', e) - }, - handleChangePruefungNote(e) { - // console.log(e) - }, getOptionLabelNotePruefung(option) { return option.bezeichnung }, @@ -746,7 +916,150 @@ export const Benotungstool = { this.selectedLehreinheit = e.value }, addPruefung(){ + + this.$refs.modalContainerNeuesPruefungsdatum.hide() + + // filter students that already have a pruefung on datum + // TODO: save new pruefungs entry for all selected students on selected date with default note "noch nicht eingetragen" aka 9 + + const year = this.selectedPruefungDate.getFullYear(); + const month = String(this.selectedPruefungDate.getMonth() + 1).padStart(2, '0'); // Months are 0-based + const day = String(this.selectedPruefungDate.getDate()).padStart(2, '0'); + const dateStr = `${year}-${month}-${day}`; + + const uids = this.selectedUids.map(student => { + return { + uid: student.uid, + lehreinheit_id: student.lehreinheit_id, + typ: this.getPruefungstypForStudentByAntritt(student)//student.hoechsterAntritt + } + }) + + this.loading = true; + this.$api.call(ApiNoten.createPruefungen( + uids, + dateStr, + this.lv_id, + this.sem_kurzbz, + )).then(res => { + if(res.meta.status === "success") { + this.$fhcAlert.alertInfo('Prüfung an ' + dateStr + ' angelegt') // TODO: phrase + + const pruefungen = res.data + uids.forEach(entry => { + const saved = pruefungen[entry.uid].savedPruefung + const extra = pruefungen[entry.uid].extraPruefung + + const student = this.studenten.find(s => s.uid == entry.uid) + if(!student) return + + // check for extra pruefung (termin1) to add before + if(extra) { + extra.datum = extra.datum.split(' ')[0] + if(!this.distinctPruefungsDates.includes(extra.datum)) { + this.insertSortedDate(this.distinctPruefungsDates, extra.datum) + } + + student.pruefungen.push(extra) + student[extra.datum] = extra + } + + if(!this.distinctPruefungsDates.includes(saved.datum)) { + this.insertSortedDate(this.distinctPruefungsDates, saved.datum) + } + + // add pruefung to pruefungen array + student.pruefungen.push(saved) + + // add pruefung to student via its datum as a field + student[saved.datum] = saved + + // usually should be in order naturally, just to be save + student.pruefungen.sort((p1, p2) => { + if(p1.datum > p2.datum) { + return 1 + } else if (p1.datum < p2.datum) { + return -1 + } else { + return 0 + } + }) + + // recalculate student antritte + student.hoechsterAntritt = this.getAntrittCountStudent(student) + + }) + + // add col to table + const cols = [...this.notenTableOptions.columns.slice(0, -1)]; + const kommCol = this.notenTableOptions.columns[this.notenTableOptions.columns.length - 1]; + + // TODO: could reuse cols instead of recreating all from a variable maybe + this.distinctPruefungsDates.forEach((date, index)=>{ + const dateparts = date.split('-') + const titledate = `${dateparts[2]}.${dateparts[1]}.${dateparts[0]}` + + // TODO: should studenten without shadow pruefung Termin have their "ursprüngliche Zeugnisnote" + // col filled for consistency reasons? + + // TODO: test if this holds true + const originalNote = index === 0 + cols.push({ + title: titledate,//this.$p.t('benotungstool/pruefungNr', [index+1]), + field: date, + formatter: this.pruefungFormatter, + titleFormatter: this.pruefungTitleFormatter, + hozAlign:"center", + widthGrow: 1, + minWidth: 150, + originalNote + }) + }) + + cols.push(kommCol) // keep kommPruef Col as last + // redraw table + + this.loading = false + + this.$refs.notenTable.tabulator.clearSort() + this.$refs.notenTable.tabulator.setColumns(cols) + this.$refs.notenTable.tabulator.setData(this.studenten); + this.$refs.notenTable.tabulator.redraw(true); + } + }) + }, + getAntrittCountStudent(student) { + // checks for existence of a prüfung with a note that resolves to a + // "angetretene Prüfung" -> anything except "entschuldigt" & "noch nicht eingetragen" + // and returns the next allowed pruefungstyp from the number of taken pruefungen + + // 1 -> reguläre note + // 2 -> erste Nachprüfung / Termin2 + // 3 -> 2te Nachprüfung / Termin3 + // 4 -> kommPruef + if(student['kommPruef']) return 4 + + let pruefungsAntrittCount = 0 + const pLen = student.pruefungen.length + for(let i = 0; i < pLen; i++) { + const p = student.pruefungen[i] + + if(p.note != 9 && p.note != 17) pruefungsAntrittCount++ + } + + // when student never had to take an exam beyond the original benotung + // aka pruefungsantritt (even though it does not have to have pruefungscharacter) + // it still counts as an antritt, except it is coming from a notenOption like "angerechnet" + // which indicates no participation at all + if(pruefungsAntrittCount === 0 && student.note){ + const noteOption = this.notenOptions.find(note => note.note == student.note) + + if(noteOption.lehre) return 1 + else return 0 + } + + return pruefungsAntrittCount } }, watch: { @@ -754,19 +1067,18 @@ export const Benotungstool = { const table = this.$refs.notenTable?.tabulator if (!table) return; - console.log('selectedUids watcher newVal',newVal) - // Get all current rows + const allRows = table.getRows(); - console.log('table allRows',allRows) - // allRows.forEach(row => { - // const rowData = row.getData(); - // - // if (newVal.includes(rowData.uid)) { - // row.select(); // ensure row is selected - // } else { - // row.deselect(); // ensure row is deselected - // } - // }); + + allRows.forEach(row => { + const rowData = row.getData(); + const found = newVal.find(stud => stud.uid == rowData.uid) + if (found) { + row.select(); // ensure row is selected + } else { + row.deselect(); // ensure row is deselected + } + }); }, selectedLehreinheit(newVal) { if(!this.$refs.notenTable) return @@ -794,7 +1106,7 @@ export const Benotungstool = { }, getSaveBtnClass() { // return "btn btn-primary ml-2" - return !this.changedNoten?.length ? "btn btn-primary ml-2" : "btn btn-secondary ml-2" + return this.changedNoten?.length ? "btn btn-primary ml-2" : "btn btn-secondary ml-2" }, getNewBtnClass() { return "btn btn-primary ml-2" @@ -824,34 +1136,32 @@ export const Benotungstool = { - +
diff --git a/system/phrasesupdate.php b/system/phrasesupdate.php index 5cfa302cd..f85bded48 100644 --- a/system/phrasesupdate.php +++ b/system/phrasesupdate.php @@ -42036,6 +42036,46 @@ and represent the current state of research on the topic. The prescribed citatio ) ) ), + array( + 'app' => 'core', + 'category' => 'benotungstool', + 'phrase' => 'c4notenImportieren', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Noten Import', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Import Grades', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'benotungstool', + 'phrase' => 'c4import', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Importieren', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Import', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), array( 'app' => 'core', 'category' => 'benotungstool', From bbe55a75ea46038e356c120b98a3c9964dfc731d Mon Sep 17 00:00:00 2001 From: Johann Hoffmann Date: Mon, 4 Aug 2025 14:27:33 +0200 Subject: [PATCH 010/262] noten/pruefungen import; import validation for nr of antritte and date/antritt chronological order; --- application/controllers/Cis/Benotungstool.php | 3 +- .../controllers/api/frontend/v1/Noten.php | 34 +- public/js/api/factory/noten.js | 6 +- .../Cis/Benotungstool/Benotungstool.js | 366 ++++++++++++------ 4 files changed, 284 insertions(+), 125 deletions(-) diff --git a/application/controllers/Cis/Benotungstool.php b/application/controllers/Cis/Benotungstool.php index 18038bb39..3ca3c62ef 100644 --- a/application/controllers/Cis/Benotungstool.php +++ b/application/controllers/Cis/Benotungstool.php @@ -41,7 +41,8 @@ class Benotungstool extends Auth_Controller 'CIS_GESAMTNOTE_PRUEFUNG_TERMIN2' => CIS_GESAMTNOTE_PRUEFUNG_TERMIN2, 'CIS_GESAMTNOTE_PRUEFUNG_MOODLE_LE_NOTE' => CIS_GESAMTNOTE_PRUEFUNG_MOODLE_LE_NOTE, 'CIS_GESAMTNOTE_PUNKTE' => CIS_GESAMTNOTE_PUNKTE, - 'CIS_GESAMTNOTE_GEWICHTUNG' => CIS_GESAMTNOTE_GEWICHTUNG + 'CIS_GESAMTNOTE_GEWICHTUNG' => CIS_GESAMTNOTE_GEWICHTUNG, + 'CIS_ANWESENHEITSLISTE_NOTENLISTE_ANZEIGEN' => CIS_ANWESENHEITSLISTE_NOTENLISTE_ANZEIGEN ); $this->load->view('CisRouterView/CisRouterView.php', ['viewData' => $viewData, 'route' => 'Benotungstool']); diff --git a/application/controllers/api/frontend/v1/Noten.php b/application/controllers/api/frontend/v1/Noten.php index c106b204f..3b48e1442 100644 --- a/application/controllers/api/frontend/v1/Noten.php +++ b/application/controllers/api/frontend/v1/Noten.php @@ -36,7 +36,8 @@ class Noten extends FHCAPI_Controller 'saveNotenvorschlag' => self::PERM_LOGGED, 'saveStudentPruefung' => self::PERM_LOGGED, 'createPruefungen' => self::PERM_LOGGED, - 'saveNotenvorschlagBulk' => self::PERM_LOGGED + 'saveNotenvorschlagBulk' => self::PERM_LOGGED, + 'savePruefungenBulk' => self::PERM_LOGGED ]); $this->load->library('AuthLib', null, 'AuthLib'); @@ -497,7 +498,7 @@ class Noten extends FHCAPI_Controller $pr_3->save(); } } else { - // TODO: proper error phrase + // TODO: proper error phrase that explains better why we terminated with error $this->terminateWithError("Typ is not termin2 or termin3.", 'general'); } @@ -654,5 +655,34 @@ class Noten extends FHCAPI_Controller $this->terminateWithSuccess($ret); } + public function savePruefungenBulk() { + $result = $this->getPostJSON(); + + if(!property_exists($result, 'lv_id') || !property_exists($result, 'sem_kurzbz') || + !property_exists($result, 'pruefungen')) { + $this->terminateWithError($this->p->t('global', 'missingParameters'), 'general'); + } + + $lv_id = $result->lv_id; + $sem_kurzbz = $result->sem_kurzbz; + $pruefungen = $result->pruefungen; + + $ret = []; + + foreach ($pruefungen as $pruefung) { + $student_uid = $pruefung->uid; + $typ = $pruefung->typ; + $note = $pruefung->note; // TODO: parameterize for import maybe + $datum = $pruefung->datum; + $punkte = ''; // TODO: check punkte feature + + $lehreinheit_id = $pruefung->lehreinheit_id; + $ret[$student_uid] = $this->savePruefungstermin($typ, $student_uid, $lv_id, $sem_kurzbz, $lehreinheit_id, $note, $punkte, $datum); + } + + + $this->terminateWithSuccess($ret); + } + } diff --git a/public/js/api/factory/noten.js b/public/js/api/factory/noten.js index e472f9a98..2ac91ad21 100644 --- a/public/js/api/factory/noten.js +++ b/public/js/api/factory/noten.js @@ -65,6 +65,10 @@ export default { }; }, saveStudentPruefungBulk(lv_id, sem_kurzbz, pruefungen) { - + return { + method: 'post', + url: '/api/frontend/v1/Noten/savePruefungenBulk', + params: { lv_id, sem_kurzbz, pruefungen } + }; } }; \ No newline at end of file diff --git a/public/js/components/Cis/Benotungstool/Benotungstool.js b/public/js/components/Cis/Benotungstool/Benotungstool.js index 50b7ec676..0b61cfb2d 100644 --- a/public/js/components/Cis/Benotungstool/Benotungstool.js +++ b/public/js/components/Cis/Benotungstool/Benotungstool.js @@ -109,52 +109,157 @@ export const Benotungstool = { ]}; }, methods: { - parseNote(rowParts, notenbulk) { - const uid = rowParts[0] + isValidDate_ddmmyyyy(str) { + if (typeof str !== 'string') return false; + + // Check format: dd.mm.yyyy + const regex = /^(\d{2})\.(\d{2})\.(\d{4})$/; + const match = str.match(regex); + if (!match) return false; + + // Extract date parts + const day = parseInt(match[1], 10); + const month = parseInt(match[2], 10); + const year = parseInt(match[3], 10); + + // Check valid ranges + if (month < 1 || month > 12 || day < 1 || day > 31) return false; + + // Handle months with different days and leap years + const date = new Date(year, month - 1, day); + return ( + date.getFullYear() === year && + date.getMonth() === month - 1 && + date.getDate() === day + ); + }, + identifyUid(str) { + if (typeof str !== 'string') return null; + const firstChar = str.charAt(0); + + if (/^[0-9]$/.test(firstChar)) { + return 'matrikelnr'; + } else if (/^[a-zA-Z]$/.test(firstChar)) { + return 'uid'; + } else { + return null; + } + }, + validatePruefungBulk(pruefungen) { + // need to check pruefungen for validity in respect to the students nr of antritte + // pruefungsdatum will be validated aswell so we dont get a termin 3 chronologically before + // a termin 2 which is totally possible in the old tool + const validatedPruefungen = [] + pruefungen.forEach( p => { + const student = this.studenten.find(s => s.uid === p.uid) + // check if student antrittCount is too high already + if(student.hoechsterAntritt >= 3) { + this.$fhcAlert.alertWarning('Student ' + student.uid + ' hat bereits ' + student.hoechsterAntritt + ' Prüfungsantritte abgelegt. Die Zeile wurde übersprungen.') + return + } - const student = this.studenten.find(s => s.uid === uid) - if(!student) return + // get student for pruefung and check if proposed datum does not conflict (no new pruefungen before existing ones) + const youngerPruefung = student.pruefungen.find(pr => { + return pr.dateObj >= p.dateObj + }) + if(youngerPruefung) { + this.$fhcAlert.alertWarning('Student ' + student.uid + ' hat bereits eine Prüfung am '+ youngerPruefung.datum +' eingetragen. Die Zeile wurde übersprungen.') + return + } + + validatedPruefungen.push(p) + }) + + pruefungen.splice(0, pruefungen.length, ...validatedPruefungen); + }, + validateNotenBulk(noten) { + // in case we need to further validate noten, currently parser does all + }, + parseNote(rowParts, notenbulk, rowNum) { + const id = this.identifyUid(rowParts[0]) + let student = null + if(id === 'matrikelnr') { // find student by matrnr and use uid later on + student = this.studenten.find(s => s.matrikelnr === rowParts[0]) + } else if(id === 'uid') { + student = this.studenten.find(s => s.uid === uid) + } + if(!student) { + this.$fhcAlert.alertWarning('Kein Student gefunden für ID ' + rowParts[0] + ' in Zeile Nr. ' + rowNum + ' Die Zeile wurde übersprungen.') + return + } const note = rowParts[1] // find notenoption and check if its allowed to use in lehre const notenOption = this.notenOptions.find(n => n.note == note) - if(!notenOption.lehre) return + if(!notenOption.lehre) { + this.$fhcAlert.alertWarning('Keine gültige Note gefunden für ID ' + rowParts[0] + ' in Zeile Nr. ' + rowNum + ' Die Zeile wurde übersprungen.') + return + } - notenbulk.push({uid, note}) + notenbulk.push({uid: student.uid, note}) }, - parsePruefung(rowParts, notenbulk) { - const uid = rowParts[0] + parsePruefung(rowParts, pruefungbulk, rowNum) { + const id = this.identifyUid(rowParts[0]) + let student = null + if(id === 'matrikelnr') { // find student by matrnr and use uid later on + student = this.studenten.find(s => s.matrikelnr === rowParts[0]) + } else if(id === 'uid') { + student = this.studenten.find(s => s.uid === rowParts[0]) + } + if(!student) { + this.$fhcAlert.alertWarning('Kein Student gefunden für ID ' + rowParts[0] + ' in Zeile Nr. ' + rowNum + ' Die Zeile wurde übersprungen.') + return + } - const student = this.studenten.find(s => s.uid === uid) - if(!student) return - - const datum = rowParts[1] // should be in 'YYYY.MM.DD' - const datumObj = datum - - const year = datumObj.getFullYear(); - const month = String(datumObj.getMonth() + 1).padStart(2, '0'); // Months are 0-based - const day = String(datumObj.getDate()).padStart(2, '0'); + const datum = rowParts[1] // should be in 'dd.MM.yyyy' + if(!this.isValidDate_ddmmyyyy(datum)) { + this.$fhcAlert.alertWarning('Ungültiges Datumformat für ID ' + rowParts[0] + ' in Zeile Nr. ' + rowNum + '. Bitte verwenden Sie das Format "DD.MM.YYYY". Die Zeile wurde übersprungen.') + return + } + const datumParts = datum.split('.') + const day = datumParts[0] + const month = datumParts[1].padStart(2, '0') + const year = datumParts[2].padStart(2, '0') const dateStr = `${year}-${month}-${day}` + // build date obj for validation later on + let monthInt = parseInt(month, 10) + monthInt -= 1 + const dateObj = new Date(year, monthInt, day) + const note = rowParts[2] // find notenoption and check if its allowed to use in lehre const notenOption = this.notenOptions.find(n => n.note == note) - if(!notenOption.lehre) return + if(!notenOption.lehre) { + + + this.$fhcAlert.alertWarning('Keine gültige Note gefunden für ID ' + rowParts[0] + ' in Zeile Nr. ' + rowNum + ' Die Zeile wurde übersprungen.') + return + } + + const typ = this.getPruefungstypForStudentByAntritt(student) + + pruefungbulk.push({uid: student.uid, datum: dateStr, note, typ, lehreinheit_id: student.lehreinheit_id, dateObj}) }, saveNotenBulk(notenbulk) { + this.loading = true this.$api.call(ApiNoten.saveNotenvorschlagBulk(this.lv_id, this.sem_kurzbz, notenbulk)).then(res => { - console.log(res) if(res.meta.status === 'success') { + this.$fhcAlert.alertWarning('Noten erfolgreich importiert') // TODO: phrase const lvNoten = res.data[0] + lvNoten.forEach(lvn => { // 1.) get relevant student row by uid - const s = this.studenten.find(s => s.uid === lvn.uid) + const s = this.studenten.find(s => s.uid === lvn.student_uid) s.note_vorschlag = lvn.note // TODO: check if note_vorschlag should be changed by import + s.lv_note = lvn.note + this.teilnoten[s.uid].note_lv = lvn.note + // recalculate freigabestatus s.freigabedatum = this.parseDate(lvn['freigabedatum']) s.benotungsdatum = this.parseDate(lvn['benotungsdatum']) @@ -163,45 +268,130 @@ export const Benotungstool = { } - // 2.) set note_vorschlag field - - // 4.) update rows with note_lv = note_vorschlag & recalculate freigabestatus + this.$refs.notenTable.tabulator.redraw(true) + }).finally(()=>{ + this.loading = false }) }, savePruefungBulk(pruefungenbulk) { + this.loading = false this.$api.call(ApiNoten.saveStudentPruefungBulk(this.lv_id, this.sem_kurzbz, pruefungenbulk)) .then((res)=> { if(res.meta.status === 'success') { + this.$fhcAlert.alertWarning('Prüfungen erfolgreich importiert und gespeichert') // TODO: phrase + this.handleAddNewPruefungenResponse(res, pruefungenbulk) + } + }).finally(()=>{this.loading = false}) + }, + handleAddNewPruefungenResponse(res, uids) { + const pruefungen = res.data + uids.forEach(entry => { + const saved = pruefungen[entry.uid].savedPruefung + const extra = pruefungen[entry.uid].extraPruefung + const student = this.studenten.find(s => s.uid == entry.uid) + if(!student) return + // check for extra pruefung (termin1) to add before + if(extra) { + extra.datum = extra.datum.split(' ')[0] + if(!this.distinctPruefungsDates.includes(extra.datum)) { + this.insertSortedDate(this.distinctPruefungsDates, extra.datum) + } + student.pruefungen.push(extra) + student[extra.datum] = extra + } - this.$fhcAlert.alertInfo('Prüfungen gespeichert') // TODO: phrase + if(!this.distinctPruefungsDates.includes(saved.datum)) { + this.insertSortedDate(this.distinctPruefungsDates, saved.datum) + } + + // add pruefung to pruefungen array + student.pruefungen.push(saved) + + // add pruefung to student via its datum as a field + student[saved.datum] = saved + + // usually should be in order naturally, just to be save + student.pruefungen.sort((p1, p2) => { + if(p1.datum > p2.datum) { + return 1 + } else if (p1.datum < p2.datum) { + return -1 + } else { + return 0 } }) + + // recalculate student antritte + student.hoechsterAntritt = this.getAntrittCountStudent(student) + }) + + // add col to table + const cols = [...this.notenTableOptions.columns.slice(0, -1)]; + const kommCol = this.notenTableOptions.columns[this.notenTableOptions.columns.length - 1]; + + // TODO: could reuse cols instead of recreating all from a variable maybe + this.distinctPruefungsDates.forEach((date, index)=>{ + const dateparts = date.split('-') + const titledate = `${dateparts[2]}.${dateparts[1]}.${dateparts[0]}` + + // TODO: should studenten without shadow pruefung Termin have their "ursprüngliche Zeugnisnote" + // col filled for consistency reasons? + + // TODO: test if this holds true + const originalNote = index === 0 + cols.push({ + title: titledate,//this.$p.t('benotungstool/pruefungNr', [index+1]), + field: date, + formatter: this.pruefungFormatter, + titleFormatter: this.pruefungTitleFormatter, + hozAlign:"center", + widthGrow: 1, + minWidth: 150, + originalNote + }) + }) + + cols.push(kommCol) // keep kommPruef Col as last + // redraw table + + this.loading = false + + this.$refs.notenTable.tabulator.clearSort() + this.$refs.notenTable.tabulator.setColumns(cols) + this.$refs.notenTable.tabulator.setData(this.studenten); + this.$refs.notenTable.tabulator.redraw(true); }, importNoten() { - console.log('importNoten', this.importString) - - // TODO: check for signs of notenimport or pruefung import - const rows = this.importString.split('\n') const bulk = [] let mode = '' // read the lines - rows.forEach(r => { + rows.forEach((r,i) => { const rowParts = r.split('\t') if(rowParts.length === 3) { - this.parsePruefung(rowParts, bulk) + this.parsePruefung(rowParts, bulk, i) mode = 'pruefung' // if line parts are not uniform we are in trouble } else if(rowParts.length === 2) { - this.parseNote(rowParts, bulk) + this.parseNote(rowParts, bulk, i) mode = 'note' } }) - if(mode === 'note') this.saveNotenBulk(bulk) - else if (mode === 'pruefung') this.savePruefungBulk(bulk) + // parsers check for notenOption.lehre === true and if student uid/matrikelnr matches + + // pruefungen check for younger pruefungen, so there are no further antritte with + // previous dates from automatic imports + if(mode === 'note') { + this.validateNotenBulk(bulk) + this.saveNotenBulk(bulk) + } + else if (mode === 'pruefung') { + this.validatePruefungBulk(bulk) + this.savePruefungBulk(bulk) + } this.$refs.modalContainerNotenImport.hide() }, @@ -502,16 +692,18 @@ export const Benotungstool = { } if(data[field]) { - const dateParts = data[field].datum.split('-') - const date = `${dateParts[2]}.${dateParts[1]}.${dateParts[0]}` + // showing date in - // First column (date) - rowDiv.appendChild(createCol(date, 'col-4 d-flex justify-content-center align-items-center')); + // const dateParts = data[field].datum.split('-') + // const date = `${dateParts[2]}.${dateParts[1]}.${dateParts[0]}` + // + // // First column (date) + // rowDiv.appendChild(createCol(date, 'col-4 d-flex justify-content-center align-items-center')); const noteDefEntry = data.note ? this.notenOptions.find(n => n.note == data[field].note) : null // Second column (note_bezeichnung) - rowDiv.appendChild(createCol(noteDefEntry.bezeichnung || '', 'col-auto ms-auto d-flex justify-content-center align-items-center')); + rowDiv.appendChild(createCol(noteDefEntry.bezeichnung || '', 'col-auto d-flex justify-content-center align-items-center')); // no actions on kommPruef allowed // no actions on termin1 aka pruefung 0 aka ursprüngliche note erlaubt @@ -533,6 +725,11 @@ export const Benotungstool = { return rowDiv; } else if (canAdd) { // return new btn action + + // dont render the add button in cells where a younger pruefung exists for the students + const youngerPruefung = data.pruefungen.find(p => p.datum > field) + if(youngerPruefung) return rowDiv + const button = document.createElement('button'); button.className = 'btn btn-outline-secondary'; button.textContent = 'Add'; // TODO: phrase @@ -636,6 +833,9 @@ export const Benotungstool = { const kommCol = this.notenTableOptions.columns[this.notenTableOptions.columns.length - 1]; this.pruefungen?.forEach(p => { + const dateParts = p.datum.split('-') + p.dateObj = new Date(dateParts[0], +(dateParts[1]) - 1, dateParts[2]) + const student = this.studenten.find(s => s.uid === p.student_uid) if(!student) return @@ -995,7 +1195,7 @@ export const Benotungstool = { typ )).then(res => { if(res.meta.status === 'success') { - this.$fhcAlert.alertInfo('Prüfung für Student ' + this.pruefungStudent.uid + ' bearbeitet oder angelegt') // TODO: phrase + this.$fhcAlert.alertWarning('Prüfung für Student ' + this.pruefungStudent.uid + ' bearbeitet oder angelegt') // TODO: phrase const s = this.studenten.find(s => s.uid === res.data[1]?.student_uid) @@ -1031,7 +1231,7 @@ export const Benotungstool = { // row.reformat() this.$refs.notenTable.tabulator.redraw(true) - this.$fhcAlert.alertInfo('Prüfung gespeichert') // TODO: phrase + this.$fhcAlert.alertWarning('Prüfung gespeichert') // TODO: phrase } }).finally(()=> { this.pruefungStudent = null @@ -1118,7 +1318,7 @@ export const Benotungstool = { this.$api.call(ApiNoten.saveStudentenNoten(this.password, this.changedNoten, this.lv_id, this.sem_kurzbz)) .then((res) => { if(res.meta.status === 'success') { - this.$fhcAlert.alertInfo('Noten gespeichert') + this.$fhcAlert.alertWarning('Noten gespeichert') } res.data.forEach(d => { @@ -1178,88 +1378,11 @@ export const Benotungstool = { this.sem_kurzbz, )).then(res => { if(res.meta.status === "success") { - this.$fhcAlert.alertInfo('Prüfung an ' + dateStr + ' angelegt') // TODO: phrase + this.$fhcAlert.alertWarning('Prüfung an ' + dateStr + ' angelegt') // TODO: phrase - const pruefungen = res.data - uids.forEach(entry => { - const saved = pruefungen[entry.uid].savedPruefung - const extra = pruefungen[entry.uid].extraPruefung - - const student = this.studenten.find(s => s.uid == entry.uid) - if(!student) return - - // check for extra pruefung (termin1) to add before - if(extra) { - extra.datum = extra.datum.split(' ')[0] - if(!this.distinctPruefungsDates.includes(extra.datum)) { - this.insertSortedDate(this.distinctPruefungsDates, extra.datum) - } - - student.pruefungen.push(extra) - student[extra.datum] = extra - } - - if(!this.distinctPruefungsDates.includes(saved.datum)) { - this.insertSortedDate(this.distinctPruefungsDates, saved.datum) - } - - // add pruefung to pruefungen array - student.pruefungen.push(saved) - - // add pruefung to student via its datum as a field - student[saved.datum] = saved - - // usually should be in order naturally, just to be save - student.pruefungen.sort((p1, p2) => { - if(p1.datum > p2.datum) { - return 1 - } else if (p1.datum < p2.datum) { - return -1 - } else { - return 0 - } - }) - - // recalculate student antritte - student.hoechsterAntritt = this.getAntrittCountStudent(student) - - }) - // add col to table - const cols = [...this.notenTableOptions.columns.slice(0, -1)]; - const kommCol = this.notenTableOptions.columns[this.notenTableOptions.columns.length - 1]; - - // TODO: could reuse cols instead of recreating all from a variable maybe - this.distinctPruefungsDates.forEach((date, index)=>{ - const dateparts = date.split('-') - const titledate = `${dateparts[2]}.${dateparts[1]}.${dateparts[0]}` - - // TODO: should studenten without shadow pruefung Termin have their "ursprüngliche Zeugnisnote" - // col filled for consistency reasons? - - // TODO: test if this holds true - const originalNote = index === 0 - cols.push({ - title: titledate,//this.$p.t('benotungstool/pruefungNr', [index+1]), - field: date, - formatter: this.pruefungFormatter, - titleFormatter: this.pruefungTitleFormatter, - hozAlign:"center", - widthGrow: 1, - minWidth: 150, - originalNote - }) - }) - - cols.push(kommCol) // keep kommPruef Col as last - // redraw table - - this.loading = false - - this.$refs.notenTable.tabulator.clearSort() - this.$refs.notenTable.tabulator.setColumns(cols) - this.$refs.notenTable.tabulator.setData(this.studenten); - this.$refs.notenTable.tabulator.redraw(true); + this.handleAddNewPruefungenResponse(res, uids) + } }) }, @@ -1442,6 +1565,7 @@ export const Benotungstool = { v-model="selectedPruefungDate" :clearable="false" :enableTimePicker="false" + format="dd.MM.yyyy" :text-input="true" :auto-apply="true"> From 367204a1eeaf65148aac7b9f7eed4e7ac6c973f3 Mon Sep 17 00:00:00 2001 From: Johann Hoffmann Date: Thu, 7 Aug 2025 14:54:41 +0200 Subject: [PATCH 011/262] removed legacy classes (except mobility) and moved crud functionality to LePruefungModel, LVgesamtnoteModel & LehrveranstaltungModel; --- .../controllers/api/frontend/v1/Noten.php | 624 ++++++++++-------- application/models/crm/Prestudent_model.php | 4 + .../models/education/LePruefung_model.php | 49 ++ .../models/education/Pruefung_model.php | 14 - .../Cis/Benotungstool/Benotungstool.js | 28 +- 5 files changed, 413 insertions(+), 306 deletions(-) diff --git a/application/controllers/api/frontend/v1/Noten.php b/application/controllers/api/frontend/v1/Noten.php index 3b48e1442..dcf57f9f1 100644 --- a/application/controllers/api/frontend/v1/Noten.php +++ b/application/controllers/api/frontend/v1/Noten.php @@ -47,13 +47,11 @@ class Noten extends FHCAPI_Controller 'global' ]); require_once(FHCPATH . 'include/mobilitaet.class.php'); - require_once(FHCPATH . 'include/student.class.php'); - require_once(FHCPATH . 'include/lvgesamtnote.class.php'); - require_once(FHCPATH . 'include/lehrveranstaltung.class.php'); - require_once(FHCPATH . 'include/lehreinheit.class.php'); - require_once(FHCPATH . 'include/studiengang.class.php'); - require_once(FHCPATH . 'include/pruefung.class.php'); + $this->load->model('education/LePruefung_model', 'LePruefungModel'); + $this->load->model('education/Lvgesamtnote_model', 'LvgesamtnoteModel'); + $this->load->model('education/Lehrveranstaltung_model', 'LehrveranstaltungModel'); + $this->load->model('crm/Student_model', 'StudentModel'); } public function getStudentenNoten() { @@ -66,8 +64,7 @@ class Noten extends FHCAPI_Controller // todo: check various other berechtigungen if its mitarbeiter/lektor/zugeteilterLektor? - $this->load->model('education/Pruefung_model', 'PruefungModel'); - $this->load->model('education/Lehrveranstaltung_model', 'LehrveranstaltungModel'); + // get studenten for lva & sem with zeugnisnote if available $studenten = $this->LehrveranstaltungModel->getStudentsByLv($sem_kurzbz, $lv_id); @@ -81,14 +78,25 @@ class Noten extends FHCAPI_Controller $grades = array(); $student_uids = array_map($func, $studentenData); + + $res = $this->StudentModel->load(['be21b081']); + $this->addMeta('be21b081', $res); + foreach($student_uids as $uid) { $grades[$uid]['grades'] = []; - $student = new student(); - $student->load($uid); - $student->result[]= $student; +// $student = new student(); +// $student->load($uid); +// $student->result[]= $student; + + $res = $this->StudentModel->load([$uid]); + $this->addMeta($uid, $res); + if(!isError($res) && hasData($res)) $student = getData($res)[0]; + $prestudent_id = $student->prestudent_id; + + // TODO: last class to get rid of but this one is complicated $mobility = new mobilitaet(); $mobility->loadPrestudent($prestudent_id); $output = $mobility->result; @@ -99,13 +107,20 @@ class Noten extends FHCAPI_Controller $eintrag = ' (d.d.)'; } $grades[$uid]['mobility'] = $eintrag; - - if ($lvgesamtnote = new lvgesamtnote($lv_id, $uid, $sem_kurzbz)) - { + + $result = $this->LvgesamtnoteModel->getLvGesamtNoten($lv_id, $uid, $sem_kurzbz); + $this->addMeta('getLvGesamtNoten', $result); + if(!isError($result) && hasData($result)) { + $lvgesamtnote = getData($result)[0]; $grades[$uid]['note_lv'] = $lvgesamtnote->note; $grades[$uid]['freigabedatum'] = $lvgesamtnote->freigabedatum; $grades[$uid]['benotungsdatum'] = $lvgesamtnote->benotungsdatum; $grades[$uid]['punkte_lv'] = $lvgesamtnote->punkte; + } else { + $grades[$uid]['note_lv'] = null; + $grades[$uid]['freigabedatum'] = null; + $grades[$uid]['benotungsdatum'] = null; + $grades[$uid]['punkte_lv'] = null; } } @@ -153,6 +168,8 @@ class Noten extends FHCAPI_Controller } } + + // TODO: develop the punkte feature with models // calculate grades points from notenschlüssel if (CIS_GESAMTNOTE_PUNKTE) { @@ -181,13 +198,14 @@ class Noten extends FHCAPI_Controller $note_vorschlag = round($notensumme / $anzahlnoten); } } + $student->note_vorschlag = $note_vorschlag; } } // get all prüfungen with noten held in that semester in that lva - $pruefungen = $this->PruefungModel->getPruefungenByLvStudiensemester($lv_id, $sem_kurzbz); + $pruefungen = $this->LePruefungModel->getPruefungenByLvStudiensemester($lv_id, $sem_kurzbz); $pruefungenData = getData($pruefungen); // $pruefungenData = $this->getDataOrTerminateWithError($pruefungen); $this->addMeta('$pruefungenData', $pruefungenData); @@ -222,9 +240,12 @@ class Noten extends FHCAPI_Controller // TODO: also do something similar when updating/creating a pruefung! foreach($result->noten as $note) { - $lvgesamtnote = new lvgesamtnote(); - if ($lvgesamtnote->load($lv_id, $note->uid, $sem_kurzbz)) + + $result = $this->LvgesamtnoteModel->getLvGesamtNoten($lv_id, $note->uid, $sem_kurzbz); + + if (!isError($result) && hasData($result)) { + $lvgesamtnote = getData($result)[0]; if ($lvgesamtnote->benotungsdatum > $lvgesamtnote->freigabedatum) { $lvgesamtnote->freigabedatum = date("Y-m-d H:i:s"); @@ -264,7 +285,7 @@ class Noten extends FHCAPI_Controller $student_uid = $result->student_uid; $note = $result->note; - $punkte = $result->punkte; + $punkte = null; $datum = $result->datum; $lva_id = $result->lva_id; $lehreinheit_id = $result->lehreinheit_id; @@ -280,79 +301,100 @@ class Noten extends FHCAPI_Controller // $lehreinheit_id = getLehreinheit($db, $lvid, $student_uid, $stsem); // $lehreinheit_id = $result->lehreinheit_id; + + $punkte = null; - $punkte = str_replace(',', '.', $punkte); - - if($punkte!='') - { - // Bei Punkteeingabe wird die Note nochmals geprueft und ggf korrigiert - $notenschluessel = new notenschluessel(); - $note_pruef = $notenschluessel->getNote($punkte, $lva_id, $stsem); - if($note_pruef!=$note) - { - $note = $note_pruef; - $note_dirty=true; - } - } +// if($punkte!='') +// { +// // Bei Punkteeingabe wird die Note nochmals geprueft und ggf korrigiert +// $notenschluessel = new notenschluessel(); +// $note_pruef = $notenschluessel->getNote($punkte, $lva_id, $stsem); +// if($note_pruef!=$note) +// { +// $note = $note_pruef; +// $note_dirty=true; +// } +// } + // TODO: more sophisticated empty check if($note=='') $note = 9; - - $old_note = $note; - // TODO: notwendiger check? -// //Laden der Lehrveranstaltung -// $lv_obj = new lehrveranstaltung(); -// if(!$lv_obj->load($lva_id)) -// die($lv_obj->errormsg); -// -// //Studiengang laden -// $stg_obj = new studiengang($lv_obj->studiengang_kz); -// + + $this->load->model('education/Lehrveranstaltung_model', 'LehrveranstaltungModel'); + $this->load->model('organisation/Studiengang_model', 'StudiengangModel'); + + $res = $this->LehrveranstaltungModel->load($lva_id); + if(isError($res) || !hasData($res)) { + $this->terminateWithError('Keine gültige Lehrveranstaltung gefunden für ID: '.$lva_id); + } + + $studiengang_kz = getData($res)[0]->studiengang_kz; + $res = $this->StudiengangModel->load($studiengang_kz); + if(isError($res) || !hasData($res)) { + $this->terminateWithError('Kein gültiger Studiengang gefunden für ID: '.$studiengang_kz); + } $pruefungenChanged = $this->savePruefungstermin($typ, $student_uid, $lva_id, $stsem, $lehreinheit_id, $note, $punkte, $datum); //Gesamtnote updaten - $lvgesamtnote = new lvgesamtnote(); - if (!$lvgesamtnote->load($lva_id, $student_uid, $stsem)) - { - $lvgesamtnote->student_uid = $student_uid; - $lvgesamtnote->lehrveranstaltung_id = $lva_id; - $lvgesamtnote->studiensemester_kurzbz = $stsem; - $lvgesamtnote->note = $note; - $lvgesamtnote->punkte = $punkte; - $lvgesamtnote->mitarbeiter_uid = getAuthUID(); - $lvgesamtnote->benotungsdatum = $jetzt; - $lvgesamtnote->freigabedatum = null; - $lvgesamtnote->freigabevon_uid = null; - $lvgesamtnote->bemerkung = null; - $lvgesamtnote->updateamum = null; - $lvgesamtnote->updatevon = null; - $lvgesamtnote->insertamum = $jetzt; - $lvgesamtnote->insertvon = getAuthUID(); - $new = true; -// $response = "neu"; + $result = $this->LvgesamtnoteModel->getLvGesamtNoten($lva_id, $student_uid, $stsem); + if(!isError($result) && !hasData($result)) { + + $id = $this->LvgesamtnoteModel->insert( + array( + 'student_uid' => $student_uid, + 'lehrveranstaltung_id' => $lva_id, + 'studiensemester_kurzbz' => $stsem, + 'note' => $note, + 'punkte' => $punkte, + 'mitarbeiter_uid' => getAuthUID(), + 'benotungsdatum' => $jetzt, + 'freigabedatum' => null, + 'freigabevon_uid' => null, + 'bemerkung' => null, + 'updateamum' => null, + 'updatevon' => null, + 'insertamum' => $jetzt, + 'insertvon' => getAuthUID() + ) + ); + if($id) { + $res = $this->LvgesamtnoteModel->load($id->retval); + if(hasData($res)) $lvgesamtnote = getData($res)[0]; + } } - else + else if(!isError($result) && hasData($result)) { - $lvgesamtnote->note = $note; - $lvgesamtnote->punkte = $punkte; - $lvgesamtnote->benotungsdatum = $jetzt; - $lvgesamtnote->updateamum = $jetzt; - $lvgesamtnote->updatevon = getAuthUID(); - $new = false; -// if ($lvgesamtnote->freigabedatum) -// $response = "update_f"; -// else -// $response = "update"; + $lvgesamtnote = getData($result)[0]; + + $id = $this->LvgesamtnoteModel->update( + [$lvgesamtnote->student_uid, $lvgesamtnote->studiensemester_kurzbz, $lvgesamtnote->lehrveranstaltung_id], + array( + 'note' => $note, + 'punkte' => $punkte, + 'benotungsdatum' => $jetzt, + 'updateamum' => $jetzt, + 'updatevon' => getAuthUID() + ) + ); + + if($id) { + $res = $this->LvgesamtnoteModel->load($id->retval); + if(hasData($res)) $lvgesamtnote = getData($res)[0]; + } + } - $lvgesamtnote->save($new); - + $this->addMeta('$pruefungenChanged', $pruefungenChanged); + $this->addMeta('$lvgesamtnote', $lvgesamtnote); $savedPruefung = $pruefungenChanged['savedPruefung'] ?? null; $extraPruefung = $pruefungenChanged['extraPruefung'] ?? null; // TODO: test - $this->terminateWithSuccess(array($savedPruefung, $lvgesamtnote, $extraPruefung)); + $savedPruefungData = count($savedPruefung) > 0 ? $savedPruefung[0] : null; + $extraPruefungData = count($extraPruefung) > 0 ? $extraPruefung[0] : null; + + $this->terminateWithSuccess(array($savedPruefungData, $lvgesamtnote, $extraPruefungData)); } private function savePruefungstermin($typ, $student_uid, $lva_id, $stsem, $lehreinheit_id, $note, $punkte, $datum) @@ -361,141 +403,165 @@ class Noten extends FHCAPI_Controller $pruefungenChanged = []; + $this->load->model('education/Lvgesamtnote_model', 'LvgesamtnoteModel'); + + if($typ == "Termin2") { + + // Wenn eine Nachprüfung angelegt wird, wird zuerst eine Pruefung mit 1. Termin angelegt welche für die ursprüngliche Note + // vor den Prüfungsantritten zählt + + $result1 = $this->LePruefungModel->getPruefungenByUidTypLvStudiensemester($student_uid, "Termin1", $lva_id, $stsem); + + // if there is a termin 1 entry already do nothing + if(!isError($result1) && hasData($result1)) { - $pr = new Pruefung(); + } else if(!isError($result1) && !hasData($result1)) { + // new entry termin1 - // Wenn eine Pruefung angelegt wird, wird zuerst eine Pruefung mit 1. Termin angelegt - // und dort die Zeugnisnote gespeichert - if($pr->getPruefungen($student_uid, "Termin1", $lva_id, $stsem)) - { - if ($pr->result) + $resultLV = $this->LvgesamtnoteModel->getLvGesamtNoten($lva_id, $student_uid, $stsem); + + $this->addMeta('$resultLV', $resultLV); + + // update Termin1 note + if (hasData($resultLV)) { - // TODO: is this filler if branch really necessary? - $termin1 = 1; + $lvgesamtnote = getData($resultLV)[0]; + $pr_note = $lvgesamtnote->note; + $pr_punkte = $lvgesamtnote->punkte; + $benotungsdatum = $lvgesamtnote->benotungsdatum; } - else + else if(!hasData($resultLV))// set Termin1 note to "noch nicht eingetragen" { - $lvnote = new lvgesamtnote(); - // update Termin1 note - if ($lvnote->load($lva_id, $student_uid, $stsem)) - { - $pr_note = $lvnote->note; - $pr_punkte = $lvnote->punkte; - $benotungsdatum = $lvnote->benotungsdatum; - } - else // set Termin1 note to "noch nicht eingetragen" - { - $pr_note = 9; - $pr_punkte = ''; - $benotungsdatum = $jetzt; - } - - $pr_1 = new Pruefung(); - $pr_1->lehreinheit_id = $lehreinheit_id; - $pr_1->student_uid = $student_uid; - $pr_1->mitarbeiter_uid = getAuthUID(); - $pr_1->note = $pr_note; - $pr_1->punkte = $pr_punkte; - $pr_1->pruefungstyp_kurzbz = "Termin1"; - $pr_1->datum = $benotungsdatum; - $pr_1->anmerkung = ""; - $pr_1->insertamum = $jetzt; - $pr_1->insertvon = getAuthUID(); - $pr_1->updateamum = null; - $pr_1->updatevon = null; - $pr_1->ext_id = null; - $pr_1->new = true; - $pr_1->save(); - $pruefungenChanged['extraPruefung'] = $pr_1; //"neu T1"; + $pr_note = 9; + $pr_punkte = null; + $benotungsdatum = $jetzt; } - $prTermin2 = new Pruefung(); - $pr_2 = new Pruefung(); + $this->addMeta('$pr_punkte', $pr_punkte); + + $id = $this->LePruefungModel->insert( + array( + 'lehreinheit_id' => $lehreinheit_id, + 'student_uid' => $student_uid, + 'mitarbeiter_uid' => getAuthUID(), + 'note' => $pr_note, + 'punkte' => $pr_punkte, + 'pruefungstyp_kurzbz' => "Termin1", + 'datum' => $benotungsdatum, + 'anmerkung' => "", + 'insertamum' => $jetzt, + 'insertvon' => getAuthUID(), + 'updateamum' => null, + 'updatevon' => null, + 'ext_id' => null + ) + ); + if($id) { + $this->addMeta('idTermin1', $id); + $res = $this->LePruefungModel->load($id->retval); + if(hasData($res)) $pruefungenChanged['extraPruefung'] = getData($res); + } + } + + + // Die Pruefung wird als Termin2 eingetragen + $result2 = $this->LePruefungModel->getPruefungenByUidTypLvStudiensemester($student_uid, "Termin2", $lva_id, $stsem); + // if there is a termin 2 entry already update it + if(!isError($result2) && hasData($result2)) { + // update + $termin2 = getData($result2)[0]; + $id = $this->LePruefungModel->update( + $termin2->pruefung_id, + array( + 'updateamum' => $jetzt, + 'updatevon' => getAuthUID(), + 'note' => $note, + 'punkte' => $punkte, + 'datum' => $datum, + 'anmerkung' => "" + ) + ); + if($id) { + $res = $this->LePruefungModel->load($id->retval); + if(hasData($res)) $pruefungenChanged['savedPruefung'] = getData($res); + } - // Die Pruefung wird als Termin2 eingetragen - if ($prTermin2->getPruefungen($student_uid, 'Termin2', $lva_id, $stsem)) - { - if ($prTermin2->result) - { - $pr_2->load($prTermin2->result[0]->pruefung_id); - $pr_2->new = null; - $pr_2->updateamum = $jetzt; - $pr_2->updatevon = getAuthUID(); - $old_note = $pr_2->note; - $pr_2->note = $note; - $pr_2->punkte = $punkte; - $pr_2->datum = $datum; - $pr_2->anmerkung = ""; - $pruefungenChanged['savedPruefung'] = $pr_2; -// $savedPruefung = $pr_2;//"update T2"; - } - else - { - $pr_2->lehreinheit_id = $lehreinheit_id; - $pr_2->student_uid = $student_uid; - $pr_2->mitarbeiter_uid = getAuthUID(); - $pr_2->note = $note; - $pr_2->punkte = $punkte; - $pr_2->pruefungstyp_kurzbz = $typ; - $pr_2->datum = $datum; - $pr_2->anmerkung = ""; - $pr_2->insertamum = $jetzt; - $pr_2->insertvon = getAuthUID(); - $pr_2->updateamum = null; - $pr_2->updatevon = null; - $pr_2->ext_id = null; - $pr_2->new = true; - $old_note = -1; - $pruefungenChanged['savedPruefung'] = $pr_2; -// $savedPruefung = $pr_2;//"new T2"; - } - $pr_2->save(); + } else if(!isError($result2) && !hasData($result2)) { + // new entry termin 2 + + $id = $this->LePruefungModel->insert( + array( + 'lehreinheit_id' => $lehreinheit_id, + 'student_uid' => $student_uid, + 'mitarbeiter_uid' => getAuthUID(), + 'note' => $note, + 'punkte' => null,//$punkte, + 'pruefungstyp_kurzbz' => $typ, + 'datum' => $datum, + 'anmerkung' => "", + 'insertamum' => $jetzt, + 'insertvon' => getAuthUID(), + 'updateamum' => null, + 'updatevon' => null, + 'ext_id' => null + ) + ); + if($id) { + $res = $this->LePruefungModel->load($id->retval); + if(hasData($res)) $pruefungenChanged['savedPruefung'] = getData($res); } } } else if($typ == "Termin3") { - $prTermin3 = new Pruefung(); - $pr_3 = new Pruefung(); + $result3 = $this->LePruefungModel->getPruefungenByUidTypLvStudiensemester($student_uid, "Termin3", $lva_id, $stsem); - if ($prTermin3->getPruefungen($student_uid, 'Termin3', $lva_id, $stsem)) - { - if ($prTermin3->result) - { - $pr_3->load($prTermin3->result[0]->pruefung_id); - $pr_3->new = null; - $pr_3->updateamum = $jetzt; - $pr_3->updatevon = getAuthUID(); - $old_note = $pr_3->note; - $pr_3->note = $note; - $pr_3->punkte = $punkte; - $pr_3->datum = $datum; - $pr_3->anmerkung = ""; - $pruefungenChanged['savedPruefung'] = $pr_3; -// $savedPruefung = $pr_3; //"update T3"; + if(!isError($result3) && hasData($result3)) { + // update + $termin3 = getData($result3)[0]; + + $id = $this->LePruefungModel->update( + $termin3->pruefung_id, + array( + 'updateamum' => $jetzt, + 'updatevon' => getAuthUID(), + 'note' => $note, + 'punkte' => $punkte, + 'datum' => $datum, + 'anmerkung' => "" + ) + ); + if($id) { + $res = $this->LePruefungModel->load($id->retval); + if(hasData($res)) $pruefungenChanged['savedPruefung'] = getData($res); } - else - { - $pr_3->lehreinheit_id = $lehreinheit_id; - $pr_3->student_uid = $student_uid; - $pr_3->mitarbeiter_uid = getAuthUID(); - $pr_3->note = $note; - $pr_3->punkte = $punkte; - $pr_3->pruefungstyp_kurzbz = $typ; - $pr_3->datum = $datum; - $pr_3->anmerkung = ""; - $pr_3->insertamum = $jetzt; - $pr_3->insertvon = getAuthUID(); - $pr_3->updateamum = null; - $pr_3->updatevon = null; - $pr_3->ext_id = null; - $pr_3->new = true; - $old_note = -1; - $pruefungenChanged['savedPruefung'] = $pr_3; -// $savedPruefung = $pr_3; //"new T3"; + + } else if(!isError($result3) && !hasData($result3)) { + // insert new termin3 + + $id = $this->LePruefungModel->insert( + array( + 'lehreinheit_id' => $lehreinheit_id, + 'student_uid' => $student_uid, + 'mitarbeiter_uid' => getAuthUID(), + 'note' => $note, + 'punkte' => null,//$punkte, + 'pruefungstyp_kurzbz' => $typ, + 'datum' => $datum, + 'anmerkung' => "", + 'insertamum' => $jetzt, + 'insertvon' => getAuthUID(), + 'updateamum' => null, + 'updatevon' => null, + 'ext_id' => null + ) + ); + if($id) { + $res = $this->LePruefungModel->load($id->retval); + if(hasData($res)) $pruefungenChanged['savedPruefung'] = getData($res); } - $pr_3->save(); + } } else { // TODO: proper error phrase that explains better why we terminated with error @@ -517,48 +583,53 @@ class Noten extends FHCAPI_Controller $student_uid = $result->student_uid; $sem_kurzbz = $result->sem_kurzbz; $note = $result->note; - - $lvgesamtnote = new lvgesamtnote(); - if (!$lvgesamtnote->load($lv_id, $student_uid, $sem_kurzbz)) - { - $lvgesamtnote->student_uid = $student_uid; - $lvgesamtnote->lehrveranstaltung_id = $lv_id; - $lvgesamtnote->studiensemester_kurzbz = $sem_kurzbz; - $lvgesamtnote->note = trim($note); - $lvgesamtnote->mitarbeiter_uid = getAuthUID(); - $lvgesamtnote->benotungsdatum = date("Y-m-d H:i:s"); - $lvgesamtnote->freigabedatum = null; - $lvgesamtnote->freigabevon_uid = null; - $lvgesamtnote->bemerkung = null; - $lvgesamtnote->updateamum = null; - $lvgesamtnote->updatevon = null; - $lvgesamtnote->insertamum = date("Y-m-d H:i:s"); - $lvgesamtnote->insertvon = getAuthUID(); - $lvgesamtnote->punkte =// TODO: deprecated? - $new = true; - $response = "neu"; - } - else - { - $lvgesamtnote->note = trim($note); - $lvgesamtnote->punkte = null; // TODO: deprecated? - $lvgesamtnote->benotungsdatum = date("Y-m-d H:i:s"); - $lvgesamtnote->updateamum = date("Y-m-d H:i:s"); - $lvgesamtnote->updatevon = getAuthUID(); - $new = false; - if ($lvgesamtnote->freigabedatum) - $response = "update_f"; - else - $response = "update"; - } - if (!$lvgesamtnote->save($new)) - $ret = $lvgesamtnote->errormsg; - else - $ret = $response; - $lvgesamtnote->load($lv_id, $student_uid, $sem_kurzbz); + $result = $this->LvgesamtnoteModel->getLvGesamtNoten($lv_id, $student_uid, $sem_kurzbz); + $this->addMeta('getLvGesamtNoten', $result); + if(!isError($result) && hasData($result)) { + $lvgesamtnote = getData($result)[0]; + + $id = $this->LvgesamtnoteModel->update( + [$lvgesamtnote->student_uid, $lvgesamtnote->studiensemester_kurzbz, $lvgesamtnote->lehrveranstaltung_id], + array( + 'note' => $note, + 'punkte' => null, + 'benotungsdatum' => date("Y-m-d H:i:s"), + 'updateamum' => date("Y-m-d H:i:s"), + 'updatevon' => getAuthUID() + ) + ); + + if($id) { + $res = $this->LvgesamtnoteModel->load($id->retval); + if(hasData($res)) $lvgesamtnote = getData($res)[0]; + } + } else if(!isError($result) && !hasData($result)) { + $id = $this->LvgesamtnoteModel->insert( + array( + 'student_uid' => $student_uid, + 'lehrveranstaltung_id' => $lv_id, + 'studiensemester_kurzbz' => $sem_kurzbz, + 'note' => $note, + 'punkte' => null, + 'mitarbeiter_uid' => getAuthUID(), + 'benotungsdatum' => date("Y-m-d H:i:s"), + 'freigabedatum' => null, + 'freigabevon_uid' => null, + 'bemerkung' => null, + 'updateamum' => null, + 'updatevon' => null, + 'insertamum' => date("Y-m-d H:i:s"), + 'insertvon' => getAuthUID() + ) + ); + if($id) { + $res = $this->LvgesamtnoteModel->load($id->retval); + if(hasData($res)) $lvgesamtnote = getData($res)[0]; + } + } - $this->terminateWithSuccess(array($ret, $lvgesamtnote)); + $this->terminateWithSuccess(array($lvgesamtnote)); } public function saveNotenvorschlagBulk() { @@ -578,53 +649,56 @@ class Noten extends FHCAPI_Controller foreach($noten as $note) { - $lvgesamtnote = new lvgesamtnote(); - if (!$lvgesamtnote->load($lv_id, $note->uid, $sem_kurzbz)) - { - $lvgesamtnote->student_uid = $note->uid; - $lvgesamtnote->lehrveranstaltung_id = $lv_id; - $lvgesamtnote->studiensemester_kurzbz = $sem_kurzbz; - $lvgesamtnote->note = trim($note->note); - $lvgesamtnote->mitarbeiter_uid = getAuthUID(); - $lvgesamtnote->benotungsdatum = date("Y-m-d H:i:s"); - $lvgesamtnote->freigabedatum = null; - $lvgesamtnote->freigabevon_uid = null; - $lvgesamtnote->bemerkung = null; - $lvgesamtnote->updateamum = null; - $lvgesamtnote->updatevon = null; - $lvgesamtnote->insertamum = date("Y-m-d H:i:s"); - $lvgesamtnote->insertvon = getAuthUID(); - $lvgesamtnote->punkte =// TODO: deprecated? - $new = true; - $response = "neu"; - } - else - { - $lvgesamtnote->note = trim($note->note); - $lvgesamtnote->punkte = null; // TODO: deprecated? - $lvgesamtnote->benotungsdatum = date("Y-m-d H:i:s"); - $lvgesamtnote->updateamum = date("Y-m-d H:i:s"); - $lvgesamtnote->updatevon = getAuthUID(); - $new = false; - if ($lvgesamtnote->freigabedatum) - $response = "update_f"; - else - $response = "update"; - } - if (!$lvgesamtnote->save($new)) - $responseMsgs[] = $lvgesamtnote->errormsg; - else - $responseMsgs[] = $response; - - - $lvgesamtnote->load($lv_id, $note->uid, $sem_kurzbz); + $result = $this->LvgesamtnoteModel->getLvGesamtNoten($lv_id, $note->uid, $sem_kurzbz); + $this->addMeta('getLvGesamtNoten', $result); + if(!isError($result) && hasData($result)) { + $lvgesamtnote = getData($result)[0]; + + $id = $this->LvgesamtnoteModel->update( + [$lvgesamtnote->student_uid, $lvgesamtnote->studiensemester_kurzbz, $lvgesamtnote->lehrveranstaltung_id], + array( + 'note' => trim($note->note), + 'punkte' => null, + 'benotungsdatum' => date("Y-m-d H:i:s"), + 'updateamum' => date("Y-m-d H:i:s"), + 'updatevon' => getAuthUID() + ) + ); + + if($id) { + $res = $this->LvgesamtnoteModel->load($id->retval); + if(hasData($res)) $lvgesamtnote = getData($res)[0]; + } + } else if(!isError($result) && !hasData($result)) { + $id = $this->LvgesamtnoteModel->insert( + array( + 'student_uid' => $note->uid, + 'lehrveranstaltung_id' => $lv_id, + 'studiensemester_kurzbz' => $sem_kurzbz, + 'note' => trim($note->note), + 'punkte' => null, + 'mitarbeiter_uid' => getAuthUID(), + 'benotungsdatum' => date("Y-m-d H:i:s"), + 'freigabedatum' => null, + 'freigabevon_uid' => null, + 'bemerkung' => null, + 'updateamum' => null, + 'updatevon' => null, + 'insertamum' => date("Y-m-d H:i:s"), + 'insertvon' => getAuthUID() + ) + ); + if($id) { + $res = $this->LvgesamtnoteModel->load($id->retval); + if(hasData($res)) $lvgesamtnote = getData($res)[0]; + } + } $retLvNoten[] = $lvgesamtnote; } - $this->terminateWithSuccess(array($retLvNoten, $responseMsgs)); - + $this->terminateWithSuccess($retLvNoten); } public function createPruefungen() { diff --git a/application/models/crm/Prestudent_model.php b/application/models/crm/Prestudent_model.php index ff56c3268..04ee23b9a 100644 --- a/application/models/crm/Prestudent_model.php +++ b/application/models/crm/Prestudent_model.php @@ -782,4 +782,8 @@ class Prestudent_model extends DB_Model return $this->execQuery($query, array($person_id)); } + + public function getMobilityPrestudent(){ + + } } diff --git a/application/models/education/LePruefung_model.php b/application/models/education/LePruefung_model.php index 6e51f1975..f2da314bc 100644 --- a/application/models/education/LePruefung_model.php +++ b/application/models/education/LePruefung_model.php @@ -52,4 +52,53 @@ class LePruefung_model extends DB_Model 'student_uid' => $student_uid ]); } + + public function getPruefungenByLvStudiensemester($lv_id, $sem_kurzbz) { + $qry = "SELECT tbl_pruefung.*, tbl_lehrveranstaltung.bezeichnung as lehrveranstaltung_bezeichnung, tbl_lehrveranstaltung.lehrveranstaltung_id, + tbl_note.bezeichnung as note_bezeichnung, tbl_pruefungstyp.beschreibung as typ_beschreibung, tbl_lehreinheit.studiensemester_kurzbz as studiensemester_kurzbz + FROM lehre.tbl_pruefung, lehre.tbl_lehreinheit, lehre.tbl_lehrveranstaltung, lehre.tbl_note, lehre.tbl_pruefungstyp + WHERE tbl_pruefung.lehreinheit_id=tbl_lehreinheit.lehreinheit_id + AND tbl_lehreinheit.lehrveranstaltung_id=tbl_lehrveranstaltung.lehrveranstaltung_id + AND tbl_pruefung.note = tbl_note.note + AND tbl_pruefung.pruefungstyp_kurzbz=tbl_pruefungstyp.pruefungstyp_kurzbz + AND tbl_lehrveranstaltung.lehrveranstaltung_id = ? + AND tbl_lehreinheit.studiensemester_kurzbz = ? + ORDER BY datum DESC;"; + + return $this->execReadOnlyQuery($qry, array($lv_id, $sem_kurzbz)); + } + + public function getPruefungenByUidTypLvStudiensemester($uid, $typ = null, $lv_id = null, $sem_kurzbz = null) { + $params = [$uid]; + $qry = "SELECT tbl_pruefung.*, tbl_lehrveranstaltung.bezeichnung as lehrveranstaltung_bezeichnung, tbl_lehrveranstaltung.lehrveranstaltung_id, + tbl_note.bezeichnung as note_bezeichnung, tbl_pruefungstyp.beschreibung as typ_beschreibung, tbl_lehreinheit.studiensemester_kurzbz as studiensemester_kurzbz + FROM lehre.tbl_pruefung, lehre.tbl_lehreinheit, lehre.tbl_lehrveranstaltung, lehre.tbl_note, lehre.tbl_pruefungstyp + WHERE student_uid= ? + AND tbl_pruefung.lehreinheit_id=tbl_lehreinheit.lehreinheit_id + AND tbl_lehreinheit.lehrveranstaltung_id=tbl_lehrveranstaltung.lehrveranstaltung_id + AND tbl_pruefung.note = tbl_note.note + AND tbl_pruefung.pruefungstyp_kurzbz=tbl_pruefungstyp.pruefungstyp_kurzbz"; + if ($typ != null) + { + $qry .= " AND tbl_pruefungstyp.pruefungstyp_kurzbz = ?"; + $params[] = $typ; + } + + if ($lv_id != null) + { + $qry .= " AND tbl_lehrveranstaltung.lehrveranstaltung_id = ?"; + $params[] = $lv_id; + } + + if ($sem_kurzbz != null) + { + $qry .= " AND tbl_lehreinheit.studiensemester_kurzbz = ?"; + $params[] = $sem_kurzbz; + } + + + $qry .= " ORDER BY datum DESC"; + + return $this->execReadOnlyQuery($qry, $params); + } } diff --git a/application/models/education/Pruefung_model.php b/application/models/education/Pruefung_model.php index d3cc72d69..2f7ba8cf8 100644 --- a/application/models/education/Pruefung_model.php +++ b/application/models/education/Pruefung_model.php @@ -307,18 +307,4 @@ class Pruefung_model extends DB_Model return $this->loadWhereCommitteeExamsFailed(); } - public function getPruefungenByLvStudiensemester($lv_id, $sem_kurzbz) { - $qry = "SELECT tbl_pruefung.*, tbl_lehrveranstaltung.bezeichnung as lehrveranstaltung_bezeichnung, tbl_lehrveranstaltung.lehrveranstaltung_id, - tbl_note.bezeichnung as note_bezeichnung, tbl_pruefungstyp.beschreibung as typ_beschreibung, tbl_lehreinheit.studiensemester_kurzbz as studiensemester_kurzbz - FROM lehre.tbl_pruefung, lehre.tbl_lehreinheit, lehre.tbl_lehrveranstaltung, lehre.tbl_note, lehre.tbl_pruefungstyp - WHERE tbl_pruefung.lehreinheit_id=tbl_lehreinheit.lehreinheit_id - AND tbl_lehreinheit.lehrveranstaltung_id=tbl_lehrveranstaltung.lehrveranstaltung_id - AND tbl_pruefung.note = tbl_note.note - AND tbl_pruefung.pruefungstyp_kurzbz=tbl_pruefungstyp.pruefungstyp_kurzbz - AND tbl_lehrveranstaltung.lehrveranstaltung_id = ? - AND tbl_lehreinheit.studiensemester_kurzbz = ? - ORDER BY datum DESC;"; - - return $this->execReadOnlyQuery($qry, array($lv_id, $sem_kurzbz)); - } } diff --git a/public/js/components/Cis/Benotungstool/Benotungstool.js b/public/js/components/Cis/Benotungstool/Benotungstool.js index 0b61cfb2d..38198df5a 100644 --- a/public/js/components/Cis/Benotungstool/Benotungstool.js +++ b/public/js/components/Cis/Benotungstool/Benotungstool.js @@ -248,7 +248,7 @@ export const Benotungstool = { this.$api.call(ApiNoten.saveNotenvorschlagBulk(this.lv_id, this.sem_kurzbz, notenbulk)).then(res => { if(res.meta.status === 'success') { this.$fhcAlert.alertWarning('Noten erfolgreich importiert') // TODO: phrase - const lvNoten = res.data[0] + const lvNoten = res.data lvNoten.forEach(lvn => { @@ -605,8 +605,8 @@ export const Benotungstool = { if (res.meta.status === 'success') { const s = this.studenten.find(s => s.uid === data.uid) this.teilnoten[s.uid].note_lv = data.note_vorschlag - s.freigabedatum = this.parseDate(res.data[1]['freigabedatum']) - s.benotungsdatum = this.parseDate(res.data[1]['benotungsdatum']) + s.freigabedatum = this.parseDate(res.data[0]['freigabedatum']) + s.benotungsdatum = this.parseDate(res.data[0]['benotungsdatum']) s.freigegeben = this.checkFreigabe(s.freigabedatum, s.benotungsdatum, s.uid); @@ -992,10 +992,6 @@ export const Benotungstool = { data.push(entry) } }) - - data.forEach(entry => { - entry.infoString += ' | 👥' + entry.studentcount + ' | 📅' + entry.termincount - }) this.lehreinheiten = [...data] @@ -1068,10 +1064,6 @@ export const Benotungstool = { } }) - data.forEach(entry => { - entry.infoString += ' | 👥' + entry.studentcount + ' | 📅' + entry.termincount - }) - this.lehreinheiten = [...data] }) @@ -1135,10 +1127,6 @@ export const Benotungstool = { } }) - data.forEach(entry => { - entry.infoString += ' | 👥' + entry.studentcount + ' | 📅' + entry.termincount - }) - this.lehreinheiten = [...data] }) @@ -1614,8 +1602,14 @@ export const Benotungstool = {
- diff --git a/public/js/components/Cis/Studium/Studium.js b/public/js/components/Cis/Studium/Studium.js index 1b7aff81b..68221af62 100644 --- a/public/js/components/Cis/Studium/Studium.js +++ b/public/js/components/Cis/Studium/Studium.js @@ -195,7 +195,11 @@ export default { }, studiengangTitel(studiengang) { if (!studiengang) return ""; - return `${studiengang?.kurzbzlang} (${studiengang?.bezeichnung})`; + if(this.isGermanLanguage){ + return `${studiengang?.kurzbzlang} (${studiengang?.bezeichnung})`; + }else{ + return `${studiengang?.kurzbzlang} (${studiengang?.english})`; + } }, studiensemesterTitel(studiensemester){ if (!studiensemester) return ""; @@ -213,9 +217,12 @@ export default { }, computed:{ + isGermanLanguage(){ + return this.$p.user_language.value == "German" + }, selectedLehrveranstaltungTitel(){ const studiengang = this.studiengaenge.find((studiengang) => studiengang.studiengang_kz == this.selectedStudiengang); - return `${this.selectedLehrveranstaltung?.bezeichnung} ${this.selectedLehrveranstaltung?.lehrform_kurzbz} / ${studiengang.kurzbzlang}-${this.selectedSemester} ${this.selectedLehrveranstaltung?.orgform_kurzbz} (${this.selectedStudiensemester})`; + return `${this.isGermanLanguage ? this.selectedLehrveranstaltung?.bezeichnung : this.selectedLehrveranstaltung?.bezeichnung_english} ${this.selectedLehrveranstaltung?.lehrform_kurzbz} / ${studiengang.kurzbzlang}-${this.selectedSemester} ${this.selectedLehrveranstaltung?.orgform_kurzbz} (${this.selectedStudiensemester})`; }, computedStudienOrdnung(){ if(!this.studienOrdnung) return null; @@ -232,14 +239,14 @@ export default { let result = []; Object.entries(this.computedStudienOrdnung).forEach(([key,value])=>{ result.push({ - bezeichnung: `Studienordnung: ${key}`, + bezeichnung: `${this.$p.t('studium', 'studienordnung') }: ${key}`, disabled: true, }); value.forEach((studienplan)=>{ result.push({ studienplan:studienplan, diabled: false, - bezeichnung: `${studienplan?.bezeichnung}-${studienplan?.orgform_kurzbz} ( ${studienplan?.orgform_bezeichnung}, ${studienplan?.sprache} )` + bezeichnung: `${studienplan?.bezeichnung}-${studienplan?.orgform_kurzbz} ( ${this.isGermanLanguage ? studienplan?.orgform_bezeichnung : studienplan?.orgform_bezeichnung_english}, ${studienplan?.sprache} )` }); }) @@ -264,14 +271,14 @@ export default { }) }, - template: ` + template: /*html*/`
-

Studium

+

{{$p.t('studium','studium')}}