StV: Lehrverband- & Special-groups

This commit is contained in:
chfhtw
2025-09-02 16:06:04 +02:00
parent eae79e9f5e
commit 86641ea02d
11 changed files with 1177 additions and 210 deletions
@@ -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',
@@ -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);
@@ -0,0 +1,63 @@
<?php
if (! defined('BASEPATH')) exit('No direct script access allowed');
class Lehrverband extends FHCAPI_Controller
{
public function __construct()
{
parent::__construct([
'hasOrgforms' => ['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);
}
}
+21
View File
@@ -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',
+31
View File
@@ -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 <https://www.gnu.org/licenses/>.
*/
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
};
}
};
@@ -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 */`
<div class="stv-details-groups h-100 d-flex flex-column">
<h3>{{ $p.t('gruppenmanagement/special_groups') }}</h3>
<groups-special
ref="newGroupModal"
:default-stg="sharedStg"
@submit.capture.prevent="addSpecialGroup"
/>
<groups-list
ref="list"
class="mb-3"
:students="modelValue"
@new="showNewGroupModal"
/>
<h3>{{ $p.t('lehre/lehrverband') }}</h3>
<groups-lvb
:students="modelValue"
@submit="changeLvb"
/>
</div>`
};
@@ -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: '<i class="fa fa-check text-success"></i>',
crossElement: '<i class="fa fa-xmark text-danger"></i>'
}
},
{ 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 = '<i class="fa fa-xmark"></i>';
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 */`
<div class="stv-details-groups-list">
<core-filter-cmpt
v-if="phrasenLoaded && optionsReady"
ref="table"
:tabulator-options="tabulatorOptions"
table-only
:side-menu="false"
reload
:reload-btn-infotext="$p.t('table/reload')"
new-btn-show
:new-btn-label="$p.t('lehre/gruppe')"
@click:new="$emit('new')"
>
</core-filter-cmpt>
</div>`
};
@@ -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 */`
<div class="stv-details-groups-lvb">
<fhc-form
v-if="allAreStudents && studiengang_kz"
ref="form"
class="input-group"
@submit.prevent="onSubmit"
>
<span class="input-group-text">
{{ $p.t('lehre/semester') }}
</span>
<select
v-model="semester"
class="form-select"
>
<option v-for="semester in stgSemester">{{ semester }}</option>
</select>
<span
class="input-group-text"
:class="{'text-muted': semester === false}"
>
{{ $p.t('lehre/verband') }}
</span>
<select
v-model="verband"
class="form-select"
:disabled="semester === false"
>
<option v-for="verband in semesterVerband">{{ verband }}</option>
</select>
<span
class="input-group-text"
:class="{'text-muted': verband === false}"
>
{{ $p.t('lehre/gruppe') }}
</span>
<select
v-model="gruppe"
class="form-select"
:disabled="verband === false"
>
<option v-for="gruppe in verbandGruppe">{{ gruppe }}</option>
</select>
<button
type="submit"
class="btn btn-primary"
:disabled="gruppe === false && verband === false"
>
{{ $p.t('ui/change') }}
</button>
</fhc-form>
<div v-if="!allAreStudents" class="alert alert-danger">
{{ $p.t('stv/groups_error_notallstudents') }}
</div>
<div v-if="!studiengang_kz" class="alert alert-danger">
{{ $p.t('stv/groups_error_notsamestg') }}
</div>
</div>`
};
@@ -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 */`
<bs-modal
ref="popup"
class="stv-details-groups-special"
@hide-bs-modal="modalClosed"
@shown-bs-modal="modalOpened"
>
<template #title>
{{ $p.t('gruppenmanagement/add_group') }}
</template>
<fhc-form ref="form" @submit="onSubmit">
<form-validation />
<div class="input-group">
<form-input
ref="autocomplete"
type="autocomplete"
name="gruppe_kurzbz"
v-model="value"
container-class="flex-grow-1"
input-class="w-100"
:suggestions="groupSuggestions"
:option-label="el => el.gruppe_kurzbz + (el.bezeichnung ? ' (' + el.bezeichnung + ')' : '')"
@complete="getGroupSuggestions"
@keydown.enter.capture="onEnter"
/>
<button type="submit" class="btn btn-primary">{{ $p.t('ui/hinzufuegen') }}</button>
</div>
</fhc-form>
</bs-modal>`
};
@@ -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: '<i class="fa fa-check text-success"></i>',
crossElement: '<i class="fa fa-xmark text-danger"></i>'
}
},
{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 = '<i class="fa fa-xmark"></i>';
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: `
<div class="stv-details-gruppen h-100 pb-3">
<h5>{{$p.t('stv', 'tab_groups')}}</h5>
<core-filter-cmpt
ref="table"
:tabulator-options="tabulatorOptions"
:tabulator-events="tabulatorEvents"
table-only
:side-menu="false"
reload
:reload-btn-infotext="this.$p.t('table', 'reload')"
>
</core-filter-cmpt>
</div>
`
}
+161
View File
@@ -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(