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 */`
+
+
+ {{ $p.t('gruppenmanagement/add_group') }}
+
+
+
+
+
+
+
+
+ `
+};
\ 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(