From 86641ea02d82c84df9d6b4e03e00b4b2456b3fbe Mon Sep 17 00:00:00 2001 From: chfhtw Date: Tue, 2 Sep 2025 16:06:04 +0200 Subject: [PATCH] StV: Lehrverband- & Special-groups --- .../api/frontend/v1/stv/Config.php | 6 +- .../api/frontend/v1/stv/Gruppen.php | 192 ++++++++++++-- .../api/frontend/v1/stv/Lehrverband.php | 63 +++++ public/js/api/factory/stv/group.js | 21 ++ public/js/api/factory/stv/lehrverband.js | 31 +++ .../Stv/Studentenverwaltung/Details/Groups.js | 216 ++++++++++++++++ .../Details/Groups/List.js | 171 +++++++++++++ .../Studentenverwaltung/Details/Groups/Lvb.js | 242 ++++++++++++++++++ .../Details/Groups/Special.js | 97 +++++++ .../Details/Gruppen/Gruppen.js | 187 -------------- system/phrasesupdate.php | 161 ++++++++++++ 11 files changed, 1177 insertions(+), 210 deletions(-) create mode 100644 application/controllers/api/frontend/v1/stv/Lehrverband.php create mode 100644 public/js/api/factory/stv/lehrverband.js create mode 100644 public/js/components/Stv/Studentenverwaltung/Details/Groups.js create mode 100644 public/js/components/Stv/Studentenverwaltung/Details/Groups/List.js create mode 100644 public/js/components/Stv/Studentenverwaltung/Details/Groups/Lvb.js create mode 100644 public/js/components/Stv/Studentenverwaltung/Details/Groups/Special.js delete mode 100644 public/js/components/Stv/Studentenverwaltung/Details/Gruppen/Gruppen.js diff --git a/application/controllers/api/frontend/v1/stv/Config.php b/application/controllers/api/frontend/v1/stv/Config.php index 2fb436384..5898e1094 100644 --- a/application/controllers/api/frontend/v1/stv/Config.php +++ b/application/controllers/api/frontend/v1/stv/Config.php @@ -110,7 +110,7 @@ class Config extends FHCAPI_Controller ]; $result['groups'] = [ 'title' => $this->p->t('stv', 'tab_groups'), - 'component' => './Stv/Studentenverwaltung/Details/Gruppen.js' + 'component' => './Stv/Studentenverwaltung/Details/Groups.js' ]; $result['messages'] = [ 'title' => $this->p->t('stv', 'tab_messages'), @@ -203,6 +203,10 @@ class Config extends FHCAPI_Controller 'additionalCols' => [] ] ]; + $result['groups'] = [ + 'title' => $this->p->t('stv', 'tab_groups'), + 'component' => './Stv/Studentenverwaltung/Details/Groups.js' + ]; $result['status'] = [ 'title' => 'Status', 'component' => './Stv/Studentenverwaltung/Details/MultiStatus.js', diff --git a/application/controllers/api/frontend/v1/stv/Gruppen.php b/application/controllers/api/frontend/v1/stv/Gruppen.php index c30816f2a..c45165b41 100644 --- a/application/controllers/api/frontend/v1/stv/Gruppen.php +++ b/application/controllers/api/frontend/v1/stv/Gruppen.php @@ -9,6 +9,8 @@ class Gruppen extends FHCAPI_Controller public function __construct() { parent::__construct([ + 'add' => ['admin:rw', 'assistenz:rw'], + 'search' => ['admin:r', 'assistenz:r'], 'getGruppen' => ['admin:r', 'assistenz:r'], 'deleteGruppe' => ['admin:rw', 'assistenz:rw'], ]); @@ -18,7 +20,9 @@ class Gruppen extends FHCAPI_Controller // Load language phrases $this->loadPhrases([ - 'ui', 'gruppenmanagement' + 'ui', + 'gruppenmanagement', + 'lehre' ]); // Load models @@ -26,15 +30,140 @@ class Gruppen extends FHCAPI_Controller $this->load->model('organisation/Gruppe_model', 'GruppeModel'); } + public function add() + { + $this->load->library("form_validation"); + + $this->form_validation->set_rules( + 'gruppe_kurzbz', + $this->p->t('gruppenmanagement', 'gruppe'), + 'required|is_in_db[organisation/Gruppe_model]', + [ + 'required' => $this->p->t('ui', 'error_fieldRequired'), + 'is_in_db' => $this->p->t('ui', 'error_fieldNotFound') + ] + ); + $this->form_validation->set_rules( + 'uid', + $this->p->t('ui', 'student_uid'), + 'required|is_in_db[crm/Student_model:student_uid]', + [ + 'required' => $this->p->t('ui', 'error_fieldRequired'), + 'is_in_db' => $this->p->t('ui', 'error_fieldNotFound') + ] + ); + $this->form_validation->set_rules( + 'studiensemester_kurzbz', + $this->p->t('lehre', 'studiensemester'), + 'required|is_in_db[organisation/Studiensemester_model]', + [ + 'required' => $this->p->t('ui', 'error_fieldRequired'), + 'is_in_db' => $this->p->t('ui', 'error_fieldNotFound') + ] + ); + + if (!$this->form_validation->run()) + $this->terminateWithValidationErrors($this->form_validation->error_array()); + + $gruppe_kurzbz = $this->input->post('gruppe_kurzbz'); + $uid = $this->input->post('uid'); + $studiensemester_kurzbz = $this->input->post('studiensemester_kurzbz'); + + $result = $this->BenutzergruppeModel->load([ + $gruppe_kurzbz, + $uid + ]); + $benutzergruppe = $this->getDataOrTerminateWithError($result); + + if ($benutzergruppe) { + $this->terminateWithError( + $this->p->t('gruppenmanagement', 'error_alreadyInGroup', [ + 'uid' => $uid, + 'studiensemester_kurzbz' => current($benutzergruppe)->studiensemester_kurzbz + ]), + self::ERROR_TYPE_GENERAL + ); + } + + $result = $this->BenutzergruppeModel->insert([ + 'uid' => $uid, + 'gruppe_kurzbz' => $gruppe_kurzbz, + 'studiensemester_kurzbz' => $studiensemester_kurzbz, + 'insertamum' => date('c'), + 'insertvon' => getAuthUID() + ]); + + $this->getDataOrTerminateWithError($result); + + $this->terminateWithSuccess(); + } + + public function search() + { + $query = $this->input->post('query'); + if (!$query) + $this->terminateWithSuccess([]); + + // add query to where clause + $query = strtoupper($query); + $query = $this->GruppeModel->db->escape_like_str($query); + $query = '%' . str_replace(' ', '%', $query) . '%'; + + $this->GruppeModel->db->group_start(); + $this->GruppeModel->db->or_like('UPPER(gruppe_kurzbz)', $query, 'none', false); + $this->GruppeModel->db->or_like('UPPER(bezeichnung)', $query, 'none', false); + $this->GruppeModel->db->or_like('UPPER(beschreibung)', $query, 'none', false); + $this->GruppeModel->db->group_end(); + + // add stg sorting 1 + $studiengang_kz = $this->input->post('studiengang_kz'); + $sort_stg = $studiengang_kz ? "WHEN studiengang_kz = " . $this->GruppeModel->escape($studiengang_kz) . " THEN 0" : ""; + + // add stg sorting 2 + $studiengang_kzs = []; + $result = $this->permissionlib->getSTG_isEntitledFor('admin'); + if ($result) + $studiengang_kzs = array_merge($studiengang_kzs, $result); + $result = $this->permissionlib->getSTG_isEntitledFor('assistenz'); + if ($result) + $studiengang_kzs = array_merge($studiengang_kzs, $result); + + // selects + $this->GruppeModel->addSelect("*"); + $this->GruppeModel->addSelect("CASE + " . $sort_stg . " + WHEN studiengang_kz IN (" . implode(",", $this->GruppeModel->db->escape($studiengang_kzs)) . ") + THEN 1 + ELSE 2 + END AS sort_stg"); + + // ordering + $this->GruppeModel->addOrder("sort_stg"); + $this->GruppeModel->addOrder("sort"); + $this->GruppeModel->addOrder("gruppe_kurzbz"); + + // default where clause & execute + $result = $this->GruppeModel->loadWhere([ + 'lehre' => true, + 'sichtbar' => true, + 'aktiv' => true, + 'direktinskription' => false + ]); + + $data = $this->getDataOrTerminateWithError($result); + + $this->terminateWithSuccess($data); + } + public function getGruppen($student_uid) { - $this->BenutzergruppeModel ->addSelect('gruppe_kurzbz'); - $this->BenutzergruppeModel ->addSelect('bezeichnung'); - $this->BenutzergruppeModel ->addSelect('generiert'); - $this->BenutzergruppeModel ->addSelect('uid'); - $this->BenutzergruppeModel ->addSelect('studiensemester_kurzbz'); - $this->BenutzergruppeModel ->addJoin('public.tbl_gruppe', 'gruppe_kurzbz'); - $this->BenutzergruppeModel-> addOrder('bezeichnung', 'ASC'); + $this->BenutzergruppeModel->addSelect('gruppe_kurzbz'); + $this->BenutzergruppeModel->addSelect('bezeichnung'); + $this->BenutzergruppeModel->addSelect('generiert'); + $this->BenutzergruppeModel->addSelect('uid'); + $this->BenutzergruppeModel->addSelect('studiensemester_kurzbz'); + $this->BenutzergruppeModel->addJoin('public.tbl_gruppe', 'gruppe_kurzbz'); + $this->BenutzergruppeModel->addOrder('bezeichnung', 'ASC'); $result = $this->BenutzergruppeModel->loadWhere( array( @@ -49,29 +178,48 @@ class Gruppen extends FHCAPI_Controller public function deleteGruppe() { - $student_uid = $this->input->post('id'); + $this->load->library("form_validation"); + + $this->form_validation->set_rules( + 'uid', + $this->p->t('person', 'UID'), + 'required', + [ + 'required' => $this->p->t('ui', 'error_fieldRequired') + ] + ); + + $this->form_validation->set_rules( + 'gruppe_kurzbz', + $this->p->t('gruppenmanagement', 'gruppe'), + 'required', + [ + 'required' => $this->p->t('ui', 'error_fieldRequired') + ] + ); + + if (!$this->form_validation->run()) + $this->terminateWithValidationErrors($this->form_validation->error_array()); + + $uid = $this->input->post('uid'); $gruppe_kurzbz = $this->input->post('gruppe_kurzbz'); - //Validate if automatic group generation - $result = $this->GruppeModel-> loadWhere( - array( - 'gruppe_kurzbz' => $gruppe_kurzbz - ) - ); + // Validate if automatic group generation + $result = $this->GruppeModel->loadWhere([ + 'gruppe_kurzbz' => $gruppe_kurzbz + ]); $data = $this->getDataOrTerminateWithError($result); $generation = current($data); - if($generation->generiert) + if ($generation->generiert) { $this->terminateWithError($this->p->t('gruppenmanagement', 'error_deleteGeneratedGroups'), self::ERROR_TYPE_GENERAL); } - $result = $this->BenutzergruppeModel->delete( - array( - 'gruppe_kurzbz' => $gruppe_kurzbz, - 'uid' => $student_uid - ) - ); + $result = $this->BenutzergruppeModel->delete([ + 'gruppe_kurzbz' => $gruppe_kurzbz, + 'uid' => $uid + ]); $data = $this->getDataOrTerminateWithError($result); diff --git a/application/controllers/api/frontend/v1/stv/Lehrverband.php b/application/controllers/api/frontend/v1/stv/Lehrverband.php new file mode 100644 index 000000000..72610dd63 --- /dev/null +++ b/application/controllers/api/frontend/v1/stv/Lehrverband.php @@ -0,0 +1,63 @@ + ['admin:r', 'assistenz:r'], + 'getTree' => ['admin:r', 'assistenz:r'], + 'getSpecialgroups' => ['admin:r', 'assistenz:r'] + ]); + } + + public function hasOrgforms($studiengang_kz) + { + $this->load->model('organisation/Studiengang_model', 'StudiengangModel'); + + $result = $this->StudiengangModel->load($studiengang_kz); + + $data = $this->getDataOrTerminateWithError($result); + if ($data) { + $data = current($data)->mischform; + } + + $this->terminateWithSuccess($data); + } + + public function getTree($studiengang_kz) + { + $this->load->model('organisation/Lehrverband_model', 'LehrverbandModel'); + + $result = $this->LehrverbandModel->loadWhere([ + 'studiengang_kz' => $studiengang_kz, + 'aktiv' => true + ]); + + $data = $this->getDataOrTerminateWithError($result); + + $this->terminateWithSuccess($data); + } + + public function getSpecialgroups($studiengang_kz) + { + $this->load->model('organisation/Gruppe_model', 'GruppeModel'); + + $where = [ + 'studiengang_kz' => $studiengang_kz, + 'lehre' => true, + 'sichtbar' => true, + 'aktiv' => true, + 'direktinskription' => false + ]; + + $result = $this->GruppeModel->loadWhere($where); + + $data = $this->getDataOrTerminateWithError($result); + + $this->terminateWithSuccess($data); + } +} diff --git a/public/js/api/factory/stv/group.js b/public/js/api/factory/stv/group.js index b395fbf43..18fcb4443 100644 --- a/public/js/api/factory/stv/group.js +++ b/public/js/api/factory/stv/group.js @@ -16,6 +16,27 @@ */ export default { + add(uid, gruppe_kurzbz, studiensemester_kurzbz) { + return { + method: 'post', + url: 'api/frontend/v1/stv/gruppen/add/', + params: { + uid, + gruppe_kurzbz, + studiensemester_kurzbz + } + }; + }, + search(query, studiengang_kz) { + return { + method: 'post', + url: 'api/frontend/v1/stv/gruppen/search/', + params: { + query, + studiengang_kz + } + }; + }, getGruppen(id) { return { method: 'get', diff --git a/public/js/api/factory/stv/lehrverband.js b/public/js/api/factory/stv/lehrverband.js new file mode 100644 index 000000000..676422087 --- /dev/null +++ b/public/js/api/factory/stv/lehrverband.js @@ -0,0 +1,31 @@ +/** + * 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 { + hasOrgforms(studiengang_kz) { + return { + method: 'get', + url: 'api/frontend/v1/stv/lehrverband/hasOrgforms/' + studiengang_kz + }; + }, + getTree(studiengang_kz) { + return { + method: 'get', + url: 'api/frontend/v1/stv/lehrverband/getTree/' + studiengang_kz + }; + } +}; diff --git a/public/js/components/Stv/Studentenverwaltung/Details/Groups.js b/public/js/components/Stv/Studentenverwaltung/Details/Groups.js new file mode 100644 index 000000000..5b2c6fc73 --- /dev/null +++ b/public/js/components/Stv/Studentenverwaltung/Details/Groups.js @@ -0,0 +1,216 @@ +import GroupsLvb from './Groups/Lvb.js'; +import GroupsSpecial from './Groups/Special.js'; +import GroupsList from './Groups/List.js'; + +import ApiStvGroups from '../../../../api/factory/stv/group.js'; +import ApiStvDetails from '../../../../api/factory/stv/details.js'; + +export default { + name: 'TabGroups', + components: { + GroupsLvb, + GroupsSpecial, + GroupsList + }, + inject: { + $reloadList: { + from: '$reloadList', + required: true + }, + currentSemester: { + form: 'currentSemester', + required: true + } + }, + props: { + modelValue: [Object, Array] + }, + data() { + return { + hasOrgforms: false, + lvbList: [], + specialGroups: [], + selectedOrgform: false, + selectedSemester: false, + selectedVerband: false, + selectedGruppe: false, + multiFormHandler: (form, errors) => { + function _split_errors(result, [uid, errors]) { + const gruppe_kurzbz = []; + const studiensemester_kurzbz = []; + const others = {}; + errors.forEach(error => { + _split_messages(error.messages, gruppe_kurzbz, studiensemester_kurzbz, others); + }); + if (gruppe_kurzbz.length) { + if (!result.formFeedback.gruppe_kurzbz) + result.formFeedback.gruppe_kurzbz = []; + result.formFeedback.gruppe_kurzbz.push(...gruppe_kurzbz); + } + if (studiensemester_kurzbz.length) { + if (!result.formFeedback.studiensemester_kurzbz) + result.formFeedback.studiensemester_kurzbz = []; + result.formFeedback.studiensemester_kurzbz.push(...studiensemester_kurzbz); + } + if (Object.keys(others).length) { + result.toast[uid] = [ + { type: 'validation', messages: others } + ]; + } + return result; + } + function _split_messages(messages, gruppe_kurzbz, studiensemester_kurzbz, others) { + Object.entries(messages).forEach(([field, msg]) => { + if (field == 'gruppe_kurzbz') { + gruppe_kurzbz.push(msg); + } else if (field == 'studiensemester_kurzbz') { + studiensemester_kurzbz.push(msg); + } else { + if (!others[field]) + others[field] = []; + others[field].push(msg); + } + }); + } + const { formFeedback, toast } = Object.entries(errors) + .reduce(_split_errors, { formFeedback: {}, toast: {} }); + + if (formFeedback.gruppe_kurzbz) + formFeedback.gruppe_kurzbz = formFeedback.gruppe_kurzbz + .filter((v,k,a) => a.indexOf(v) == k); + if (formFeedback.studiensemester_kurzbz) + formFeedback.studiensemester_kurzbz = formFeedback.studiensemester_kurzbz + .filter((v,k,a) => a.indexOf(v) == k); + + form.clearValidation(); + if (Object.keys(formFeedback)) { + form.setFeedback(false, formFeedback); + } + + if (Object.keys(toast).length) { + console.log(toast); + this.$api.getErrorHandler().handler.toast(toast); + } + } + }; + }, + computed: { + allAreStudents() { + if (Array.isArray(this.modelValue)) + return this.modelValue.every(ps => ps.uid); + return this.modelValue.uid; + }, + sharedStg() { + if (Array.isArray(this.modelValue)) { + const first = this.modelValue.find(Boolean); + if (this.modelValue.every(ps => ps.studiengang_kz === first.studiengang_kz)) + return first.studiengang_kz; + return false; + } + return this.modelValue.studiengang_kz; + } + }, + methods: { + showNewGroupModal() { + this.$refs.newGroupModal.show() + }, + changeLvb(params) { + let data = { semester: params.semester }; + if (params.verband && params.verband != " ") { + data.verband = params.verband; + if (params.gruppe && params.gruppe != " ") + data.gruppe = params.gruppe; + } + + let endpoint; + + if (Array.isArray(this.modelValue)) { + endpoint = this.modelValue.map(student => [ + student.uid + ' (' + student.vorname + ' ' + student.nachname + ')', + ApiStvDetails.save( + student.prestudent_id, + this.currentSemester, + data + ) + ]); + } else { + endpoint = ApiStvDetails.save( + this.modelValue.prestudent_id, + this.currentSemester, + data + ); + } + this.$api + .call(endpoint) + .then(result => { + this.$fhcAlert.alertSuccess(this.$p.t('ui', 'successSave')); + this.$reloadList(); + }) + .catch(this.$fhcAlert.handleSystemError); + }, + addSpecialGroup(params) { + const gruppe_kurzbz = this.$refs.newGroupModal.value.gruppe_kurzbz || this.$refs.newGroupModal.value; + + if (Array.isArray(this.modelValue)) { + this.$refs.newGroupModal.$refs.form + .call( + this.modelValue.map(student => [ + student.uid + ' (' + student.vorname + ' ' + student.nachname + ')', + ApiStvGroups.add( + student.uid, + gruppe_kurzbz, + this.currentSemester + ) + ]), + { + errorHandling: { + combine: { form: ['validation'] }, + handler: { form: this.multiFormHandler } + } + } + ) + .then(result => { + const successes = result.filter(res => res.status == 'fulfilled'); + if (result.length == successes.length) { + this.$refs.newGroupModal.hide(); + } + if (successes.length) { + this.$fhcAlert.alertSuccess(this.$p.t('gruppenmanagement/groups_added', { n: successes.length })); + this.$refs.list.reload(); + } + }) + .catch(this.$fhcAlert.handleSystemError); + } else { + this.$refs.newGroupModal.$refs.form + .call(ApiStvGroups.add(this.modelValue.uid, gruppe_kurzbz, this.currentSemester)) + .then(result => { + this.$refs.newGroupModal.hide(); + this.$fhcAlert.alertSuccess(this.$p.t('gruppenmanagement/groups_added', { n: 1 })); + this.$refs.list.reload(); + }) + .catch(this.$fhcAlert.handleSystemError); + } + } + }, + template: /* html */` +
+

{{ $p.t('gruppenmanagement/special_groups') }}

+ + + +

{{ $p.t('lehre/lehrverband') }}

+ +
` +}; \ No newline at end of file diff --git a/public/js/components/Stv/Studentenverwaltung/Details/Groups/List.js b/public/js/components/Stv/Studentenverwaltung/Details/Groups/List.js new file mode 100644 index 000000000..bc4b5a86a --- /dev/null +++ b/public/js/components/Stv/Studentenverwaltung/Details/Groups/List.js @@ -0,0 +1,171 @@ +import { CoreFilterCmpt } from "../../../../filter/Filter.js"; + +import ApiStvGroups from '../../../../../api/factory/stv/group.js'; + +export default { + name: 'TabGroupsList', + components: { + CoreFilterCmpt + }, + inject: [ + "currentSemester" + ], + props: { + students: Object + }, + emits: [ + "new" + ], + data() { + return { + phrasenLoaded: false, + optionsReady: true + }; + }, + computed: { + tabulatorOptions() { + let ajaxRequestFunc, ajaxResponse, initialFilter; + if (Array.isArray(this.students)) { + ajaxRequestFunc = () => { + return this.$api.call( + this.students.map(student => ApiStvGroups.getGruppen(student.uid)) + ); + }; + ajaxResponse = (url, params, response) => { + return response.reduce((data, result) => [ + ...data, + ...result.value.data + ], []); + }; + initialFilter = [ + this.students.map(student => { + return { field: "uid", type: "=", value: student.uid }; + }), + [ + { field: "studiensemester_kurzbz", type: "=", value: null }, + { field: "studiensemester_kurzbz", type: "=", value: this.currentSemester } + ] + ]; + } else { + ajaxRequestFunc = () => { + return this.$api.call( + ApiStvGroups.getGruppen(this.students.uid) + ); + }; + ajaxResponse = (url, params, response) => { + return response.data; + }; + initialFilter = [ + { field: "uid", type: "=", value: this.students.uid }, + [ + { field: "studiensemester_kurzbz", type: "=", value: null }, + { field: "studiensemester_kurzbz", type: "=", value: this.currentSemester } + ] + ]; + } + return { + ajaxURL: 'dummy', + ajaxRequestFunc, + ajaxResponse, + initialFilter, + columns: [ + { title: this.$p.t('gruppenmanagement/gruppe'), field: "gruppe_kurzbz" }, + { title: this.$p.t('ui/bezeichnung'), field: "bezeichnung" }, + { title: this.$p.t('lehre/studiensemester'), field: "studiensemester_kurzbz" }, + { + title: this.$p.t('gruppenmanagement/automatisch_generiert'), + field: "generiert", + formatter: "tickCross", + hozAlign: "center", + formatterParams: { + tickElement: '', + crossElement: '' + } + }, + { title: this.$p.t('ui/student_uid'), field: "uid" }, + { + title: this.$p.t('global/actions'), + field: 'actions', + minWidth: 150, // Ensures Action-buttons will be always fully displayed + formatter: (cell, formatterParams, onRendered) => { + const container = document.createElement('div'); + container.className = "d-flex gap-2"; + + const data = cell.getData(); + + const button = document.createElement('button'); + button.className = 'btn btn-outline-secondary btn-action'; + button.innerHTML = ''; + button.title = this.$p.t('ui', 'loeschen'); + button.addEventListener('click', () => + this.actionDeleteGroup(data) + ); + if (data.generiert) + button.disabled = true; + container.append(button); + + return container; + }, + frozen: true + } + ], + height: 'auto', + index: 'group_id', + persistenceID: 'stv-details-group-list' + }; + } + }, + watch: { + tabulatorOptions() { + // Refresh Tabulator if options have changed + this.optionsReady = false; + this.$nextTick(() => this.optionsReady = true); + } + }, + methods: { + actionDeleteGroup(data) { + this.$fhcAlert + .confirmDelete() + .then(result => result + ? data + : Promise.reject({handled: true})) + .then(this.deleteGroup) + .catch(this.$fhcAlert.handleSystemError); + }, + deleteGroup(params) { + return this.$api + .call(ApiStvGroups.deleteGroup(params)) + .then(response => { + this.$fhcAlert.alertSuccess(this.$p.t('ui', 'successDelete')); + this.reload(); + }) + .catch(this.$fhcAlert.handleSystemError); + }, + reload() { + this.$refs.table.reloadTable(); + } + }, + mounted() { + this.$p + .loadCategory(['global', 'lehre', 'ui', 'gruppenmanagement']) + .then(() => { + this.phrasenLoaded = true; + }); + }, + template: /* html */` +
+ + +
` +}; \ No newline at end of file diff --git a/public/js/components/Stv/Studentenverwaltung/Details/Groups/Lvb.js b/public/js/components/Stv/Studentenverwaltung/Details/Groups/Lvb.js new file mode 100644 index 000000000..db94d359c --- /dev/null +++ b/public/js/components/Stv/Studentenverwaltung/Details/Groups/Lvb.js @@ -0,0 +1,242 @@ +import FhcForm from "../../../../Form/Form.js"; + +import ApiStvLvb from '../../../../../api/factory/stv/lehrverband.js'; + +export default { + name: 'TabGroupsLvb', + components: { + FhcForm + }, + props: { + students: Object + }, + emits: [ + "submit" + ], + data() { + return { + lvbList: [], + selectedSemester: false, + selectedVerband: false, + selectedGruppe: false + }; + }, + computed: { + allAreStudents() { + if (Array.isArray(this.students)) + return this.students.every(ps => ps.uid); + return this.students.uid; + }, + studiengang_kz() { + if (Array.isArray(this.students)) { + const first = this.students.find(Boolean); + if (this.students.every(ps => ps.studiengang_kz === first.studiengang_kz)) + return first.studiengang_kz; + return false; + } + return this.students.studiengang_kz; + }, + semester: { + get() { + if (this.selectedSemester !== false) { + if (this.lvbList.some(item => item.semester == this.selectedSemester)) + return this.selectedSemester; + return false; + } + if (Array.isArray(this.students)) { + const first = this.students.find(Boolean); + if (this.lvbList.every(item => item.semester != first.semester)) + return false; + if (this.students.every(ps => ps.semester === first.semester)) + return first.semester; + return false; + } + if (this.lvbList.some(item => item.semester == this.students.semester)) + return this.students.semester; + return false; + }, + set(value) { + this.selectedSemester = value; + } + }, + verband: { + get() { + if (this.semester === false) + return false; + if (this.selectedVerband !== false) { + if (this.lvbListVerband.some(item => item.verband == this.selectedVerband)) + return this.selectedVerband; + return false; + } + if (Array.isArray(this.students)) { + const first = this.students.find(Boolean); + if (this.lvbListVerband.every(item => item.verband != first.verband)) + return false; + if (this.students.every(ps => ps.verband === first.verband)) + return first.verband; + return false; + } + if (this.lvbListVerband.some(item => item.verband == this.students.verband)) + return this.students.verband; + return false; + }, + set(value) { + this.selectedVerband = value; + } + }, + gruppe: { + get() { + if (this.verband === false) + return false; + if (this.selectedGruppe !== false) { + if (this.lvbListGruppe.some(item => item.gruppe == this.selectedGruppe)) + return this.selectedGruppe; + return false; + } + if (Array.isArray(this.students)) { + const first = this.students.find(Boolean); + if (this.lvbListGruppe.every(item => item.gruppe != first.gruppe)) + return false; + if (this.students.every(ps => ps.gruppe === first.gruppe)) + return first.gruppe; + return false; + } + if (this.lvbListGruppe.some(item => item.gruppe == this.students.gruppe)) + return this.students.gruppe; + return false; + }, + set(value) { + this.selectedGruppe = value; + } + }, + stgSemester() { + if (!this.lvbList.length) + return []; + + const semester = new Set(this.lvbList.map(lvb => lvb.semester)); + + return Array.from(semester).sort((a, b) => a - b); + }, + lvbListVerband() { + if (!this.lvbList.length) + return []; + if (this.semester === false) + return []; + + return this.lvbList.filter(lvb => this.semester == lvb.semester); + }, + semesterVerband() { + if (!this.lvbListVerband.length) + return []; + + const verband = new Set(this.lvbListVerband.map(lvb => lvb.verband.replace(/ /g, ''))); + + return Array.from(verband).filter(Boolean).sort(); + }, + lvbListGruppe() { + if (!this.lvbListVerband.length) + return []; + if (this.verband === false) + return []; + + return this.lvbListVerband.filter(lvb => this.verband == lvb.verband); + }, + verbandGruppe() { + if (!this.lvbListGruppe.length) + return []; + + const gruppe = new Set(this.lvbListGruppe.map(lvb => lvb.gruppe.replace(/ /g, ''))); + + return Array.from(gruppe).filter(Boolean).sort(); + } + }, + watch: { + studiengang_kz() { + this.loadGroupsForStg(); + } + }, + methods: { + loadGroupsForStg() { + this.lvbList = []; + + if (this.studiengang_kz === false) + return; + + let lvbList; + this.$api + .call(ApiStvLvb.getTree(this.studiengang_kz)) + .then(result => this.lvbList = result.data) + .catch(this.$fhcAlert.handleSystemError) + }, + onSubmit() { + let params = { + studiengang_kz: this.studiengang_kz, + semester: this.semester, + verband: this.verband, + gruppe: this.gruppe + }; + this.$emit("submit", params); + } + }, + created() { + this.loadGroupsForStg(); + }, + template: /* html */` +
+ + + {{ $p.t('lehre/semester') }} + + + + {{ $p.t('lehre/verband') }} + + + + {{ $p.t('lehre/gruppe') }} + + + + +
+ {{ $p.t('stv/groups_error_notallstudents') }} +
+
+ {{ $p.t('stv/groups_error_notsamestg') }} +
+
` +}; \ No newline at end of file diff --git a/public/js/components/Stv/Studentenverwaltung/Details/Groups/Special.js b/public/js/components/Stv/Studentenverwaltung/Details/Groups/Special.js new file mode 100644 index 000000000..90b4c15ec --- /dev/null +++ b/public/js/components/Stv/Studentenverwaltung/Details/Groups/Special.js @@ -0,0 +1,97 @@ +import BsModal from "../../../../Bootstrap/Modal.js"; +import FhcForm from "../../../../Form/Form.js"; +import FormValidation from "../../../../Form/Validation.js"; +import FormInput from "../../../../Form/Input.js"; + +import ApiStvGroups from '../../../../../api/factory/stv/group.js'; + +export default { + name: 'TabGroupsSpecial', + components: { + BsModal, + FhcForm, + FormValidation, + FormInput, + PvAutocomplete: primevue.autocomplete + }, + props: { + defaultStg: Number + }, + emits: [ + "chosen" + ], + data() { + return { + value: '', + groupSuggestions: [] + }; + }, + methods: { + show() { + this.$refs.popup.show(); + }, + hide() { + this.$refs.popup.hide(); + }, + getGroupSuggestions({ query }) { + this.$api + .call(ApiStvGroups.search(query, this.defaultStg)) + .then(result => this.groupSuggestions = result.data) + .catch(this.$fhcAlert.handleSystemError); + }, + onSubmit(evt) { + if (!evt.defaultPrevented) { + evt.preventDefault(); + this.hide(); + } + }, + onEnter(evt) { + /** + * NOTE(chris): PrimeVue: AutoComplete: Enter does not submit form #5618 + * @see https://github.com/primefaces/primevue/issues/5618 + * + * this is fixed in 3.52.0 + * until then this function fill fix it + */ + if (!this.$refs.autocomplete.$refs.input.overlayVisible) { + this.$refs.form.$el.requestSubmit(); + } + }, + modalOpened() { + this.$refs.autocomplete.$refs.input.$refs.focusInput.focus(); + }, + modalClosed() { + this.value = ''; + this.$refs.form.clearValidation(); + } + }, + template: /* html */` + + + + +
+ + +
+
+
` +}; \ No newline at end of file diff --git a/public/js/components/Stv/Studentenverwaltung/Details/Gruppen/Gruppen.js b/public/js/components/Stv/Studentenverwaltung/Details/Gruppen/Gruppen.js deleted file mode 100644 index ab0c8d8e4..000000000 --- a/public/js/components/Stv/Studentenverwaltung/Details/Gruppen/Gruppen.js +++ /dev/null @@ -1,187 +0,0 @@ -import {CoreFilterCmpt} from "../../../../filter/Filter.js"; - -import ApiStvGroups from '../../../../../api/factory/stv/group.js'; - -export default { - name: 'TblGroups', - components: { - CoreFilterCmpt, - }, - inject: { - currentSemester: { - from: 'currentSemester', - }, - }, - props: { - student: Object - }, - data() { - return { - tabulatorOptions: { - ajaxURL: 'dummy', - ajaxRequestFunc: () => this.$api.call( - ApiStvGroups.getGruppen(this.student.uid) - ), - ajaxResponse: (url, params, response) => response.data, - initialFilter: { - logic: "and", - filters: [ - { field: "uid", operator: "eq", value: this.student.uid }, - { - logic: "or", - filters: [ - { field: "studiensemester_kurzbz", operator: "eq", value: null }, - { field: "studiensemester_kurzbz", operator: "eq", value: this.currentSemester } - ] - } - ] - }, - columns: [ - {title: "Gruppe", field: "gruppe_kurzbz"}, - {title: "Bezeichnung", field: "bezeichnung"}, - {title: "Semester", field: "studiensemester_kurzbz"}, - { - title: "automatisch generiert", - field: "generiert", - formatter: "tickCross", - hozAlign: "center", - formatterParams: { - tickElement: '', - crossElement: '' - } - }, - {title: "UID", field: "uid"}, - { - title: 'Aktionen', field: 'actions', - minWidth: 150, // Ensures Action-buttons will be always fully displayed - formatter: (cell, formatterParams, onRendered) => { - const container = document.createElement('div'); - container.className = "d-flex gap-2"; - - const data = cell.getData(); - - const button = document.createElement('button'); - button.className = 'btn btn-outline-secondary btn-action'; - button.innerHTML = ''; - button.title = this.$p.t('ui', 'loeschen'); - button.addEventListener('click', () => - this.actionDeleteGroup(data.gruppe_kurzbz) - ); - if (data.generiert) - button.disabled = true; - container.append(button); - - return container; - }, - frozen: true - }, - ], - layout: 'fitDataFill', - height: 'auto', - index: 'group_id', - persistenceID: 'stv-details-gruppe' - }, - tabulatorEvents: [ - { - event: 'tableBuilt', - handler: async () => { - - await this.$p.loadCategory(['global', 'person', 'stv', 'ui', 'gruppenmanagement']); - - let cm = this.$refs.table.tabulator.columnManager; - - cm.getColumnByField('gruppe_kurzbz').component.updateDefinition({ - title: this.$p.t('gruppenmanagement', 'gruppe') - }); - - cm.getColumnByField('bezeichnung').component.updateDefinition({ - title: this.$p.t('ui', 'bezeichnung') - }); - - cm.getColumnByField('generiert').component.updateDefinition({ - title: this.$p.t('gruppenmanagement', 'automatisch_generiert') - }); - - cm.getColumnByField('uid').component.updateDefinition({ - title: this.$p.t('ui', 'student_uid') - }); - - //Interference with Filter if not commented out - /* - cm.getColumnByField('studiensemester_kurzbz').component.updateDefinition({ - title: this.$p.t('lehre', 'studiensemester') - });*/ - - } - } - ], - } - }, - methods: { - actionDeleteGroup(gruppe_kurzbz) { - this.$fhcAlert - .confirmDelete() - .then(result => result - ? gruppe_kurzbz - : Promise.reject({handled: true})) - .then(this.deleteGroup) - .catch(this.$fhcAlert.handleSystemError); - - }, - deleteGroup(gruppe_kurzbz) { - const group_id = { - id: this.student.uid, - gruppe_kurzbz: gruppe_kurzbz - }; - - return this.$api - .call(ApiStvGroups.deleteGroup(group_id)) - .then(response => { - this.$fhcAlert.alertSuccess(this.$p.t('ui', 'successDelete')); - }).catch(this.$fhcAlert.handleSystemError) - .finally(() => { - window.scrollTo(0, 0); - this.reload(); - }); - }, - reload() { - this.$refs.table.reloadTable(); - }, - }, - watch: { - currentSemester(newVal) { - if (newVal) { - this.$refs.table.tabulator.clearFilter(); // Clear old filters - - this.$refs.table.tabulator.setFilter((data) => { - return ( - data.uid === this.student.uid && - ( - data.studiensemester_kurzbz === newVal || - data.studiensemester_kurzbz === null - ) - ); - }); - } - }, - student() { - this.$refs.table.reloadTable(); - } - }, - template: ` -
-
{{$p.t('stv', 'tab_groups')}}
- - - -
- ` -} \ No newline at end of file diff --git a/system/phrasesupdate.php b/system/phrasesupdate.php index 82ea03ba0..61fa4ef04 100644 --- a/system/phrasesupdate.php +++ b/system/phrasesupdate.php @@ -1515,6 +1515,26 @@ $phrases = array( ) ) ), + array( + 'app' => 'core', + 'category' => 'ui', + 'phrase' => 'change', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Ändern', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Change', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), array( 'app' => 'core', 'category' => 'ui', @@ -25371,6 +25391,26 @@ array( ) ) ), + array( + 'app' => 'core', + 'category' => 'ui', + 'phrase' => 'error_fieldNotFound', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => '{field} nicht gefunden', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => '{field} not found', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), array( 'app' => 'core', 'category' => 'notiz', @@ -39164,6 +39204,47 @@ array( ) ) ), + // groups + array( + 'app' => 'core', + 'category' => 'stv', + 'phrase' => 'groups_error_notallstudents', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Fehler: Es können nur Studierende mit UID verschoben werden.', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Error: Only students with UID can be moved.', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'stv', + 'phrase' => 'groups_error_notsamestg', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Fehler: Alle Studierenden müssen im selben Studiengang sein.', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Error: All students must be in the same program.', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), //**************************** CORE/document_export array( @@ -43097,6 +43178,66 @@ array( ) ) ), + array( + 'app' => 'core', + 'category' => 'gruppenmanagement', + 'phrase' => 'special_groups', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Spezialgruppen', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Special groups', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'gruppenmanagement', + 'phrase' => 'add_group', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Gruppe hinzufügen', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Add group', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'gruppenmanagement', + 'phrase' => 'groups_added', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => '{n} Gruppen hinzufügen', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Added {n} groups', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), array( 'app' => 'core', 'category' => 'gruppenmanagement', @@ -43137,6 +43278,26 @@ array( ) ) ), + array( + 'app' => 'core', + 'category' => 'gruppenmanagement', + 'phrase' => 'error_alreadyInGroup', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Der Student {uid} ist bereits im {studiensemester_kurzbz} dieser Gruppe zugeteilt. Entfernen Sie vorher diese Zuteilung.', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Student {uid} is already assigned to this group in {studiensemester_kurzbz}. Remove this assignment beforehand.', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), //////////// FHC4 Phrases Gruppen End //////////// array(