diff --git a/application/controllers/api/frontend/v1/stv/Config.php b/application/controllers/api/frontend/v1/stv/Config.php
index d52016943..739c4c38f 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'),
@@ -205,6 +205,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/application/controllers/api/frontend/v1/stv/Student.php b/application/controllers/api/frontend/v1/stv/Student.php
index 0cfd82c36..5cff2b173 100644
--- a/application/controllers/api/frontend/v1/stv/Student.php
+++ b/application/controllers/api/frontend/v1/stv/Student.php
@@ -36,6 +36,7 @@ class Student extends FHCAPI_Controller
parent::__construct([
'get' => ['admin:r', 'assistenz:r'],
'save' => ['admin:rw', 'assistenz:rw'],
+ 'saveStudent' => ['admin:rw', 'assistenz:rw'],
'check' => ['admin:rw', 'assistenz:rw'],
'add' => ['admin:rw', 'assistenz:rw'] // TODO(chris): extra permissions
]);
@@ -414,6 +415,31 @@ class Student extends FHCAPI_Controller
), ''));
}
+ /**
+ * Saves data to a prestudent using their student_uid
+ *
+ * @param string $student_uid
+ * @param string $studiensemester_kurzbz
+ * @return void
+ */
+ public function saveStudent($student_uid, $studiensemester_kurzbz)
+ {
+ $this->load->model('crm/Student_model', 'StudentModel');
+
+ $result = $this->StudentModel->load([$student_uid]);
+
+ $data = $this->getDataOrTerminateWithError($result);
+
+ if (!$data)
+ show_404(); // No Student with that ID
+
+ $student = current($data);
+
+ $this->checkPermissionsForPrestudent($student->prestudent_id, ['admin:rw', 'assistenz:rw']);
+
+ return $this->save($student->prestudent_id, $studiensemester_kurzbz);
+ }
+
public function check()
{
$this->load->library('form_validation');
diff --git a/application/controllers/api/frontend/v1/stv/Verband.php b/application/controllers/api/frontend/v1/stv/Verband.php
index 6487b6263..9fcd97c91 100644
--- a/application/controllers/api/frontend/v1/stv/Verband.php
+++ b/application/controllers/api/frontend/v1/stv/Verband.php
@@ -272,6 +272,7 @@ class Verband extends FHCAPI_Controller
$this->StudiengangModel->addSelect("CONCAT(UPPER(CONCAT(typ, kurzbz)), '-', semester, verband, (SELECT CASE WHEN bezeichnung IS NULL OR bezeichnung='' THEN ''::TEXT ELSE CONCAT(' (', bezeichnung, ')') END FROM public.tbl_lehrverband WHERE studiengang_kz=v.studiengang_kz AND semester=v.semester AND verband=v.verband ORDER BY gruppe LIMIT 1)) AS name", false);
$this->StudiengangModel->addSelect("CASE WHEN MAX(gruppe)='' OR MAX(gruppe)=' ' THEN TRUE ELSE FALSE END AS leaf");
+ $this->StudiengangModel->addSelect($this->StudiengangModel->escape($semester) . ' AS semester');
$this->StudiengangModel->addSelect('verband');
$this->StudiengangModel->addSelect($this->StudiengangModel->escape($studiengang_kz) . '::integer AS stg_kz', false);
@@ -320,6 +321,8 @@ class Verband extends FHCAPI_Controller
$this->StudiengangModel->addSelect("CONCAT(UPPER(CONCAT(typ, kurzbz)), '-', semester, verband, gruppe, (SELECT CASE WHEN bezeichnung IS NULL OR bezeichnung='' THEN ''::TEXT ELSE CONCAT(' (', bezeichnung, ')') END FROM public.tbl_lehrverband WHERE studiengang_kz=v.studiengang_kz AND semester=v.semester AND verband=v.verband AND gruppe=v.gruppe ORDER BY gruppe LIMIT 1)) AS name", false);
$this->StudiengangModel->addSelect("TRUE AS leaf", false);
+ $this->StudiengangModel->addSelect('v.semester');
+ $this->StudiengangModel->addSelect('v.verband');
$this->StudiengangModel->addSelect('gruppe');
$this->StudiengangModel->addSelect($this->StudiengangModel->escape($studiengang_kz) . '::integer AS stg_kz', false);
diff --git a/application/helpers/hlp_common_helper.php b/application/helpers/hlp_common_helper.php
index 00c0a1b93..edbde5272 100644
--- a/application/helpers/hlp_common_helper.php
+++ b/application/helpers/hlp_common_helper.php
@@ -515,3 +515,31 @@ function has_permissions_for_stg($studiengang_kz, $permissions = '')
return false;
}
+
+/**
+ * check if an entry exists in the database
+ */
+function is_in_db($key, $model = '')
+{
+ if (!$model)
+ return false;
+
+ $field = strstr($model, ":");
+ if ($field) {
+ $model = strstr($model, ":", true);
+ $field = substr($field, 1);
+ }
+
+ $CI =& get_instance();
+ $CI->load->model($model, $model);
+
+ if ($field) {
+ $result = $CI->$model->loadWhere([
+ $field => $key
+ ]);
+ } else {
+ $result = $CI->$model->load($key);
+ }
+
+ return (isSuccess($result) && hasData($result));
+}
diff --git a/application/language/english/form_validation_lang.php b/application/language/english/form_validation_lang.php
index b8918a721..2777cd05e 100644
--- a/application/language/english/form_validation_lang.php
+++ b/application/language/english/form_validation_lang.php
@@ -41,3 +41,4 @@ if (!defined('BASEPATH')) exit('No direct script access allowed');
$lang['form_validation_has_write_permissions'] = 'You have no rights to edit {field} field.';
$lang['form_validation_is_valid_date'] = 'The date format is invalid or out of range.';
$lang['form_validation_has_permissions_for_stg'] = 'You have no rights for stg {field}.';
+$lang['form_validation_is_in_db'] = '{field} does not exist.';
diff --git a/public/css/components/primevue.css b/public/css/components/primevue.css
index 5949d6ecc..ac1950334 100644
--- a/public/css/components/primevue.css
+++ b/public/css/components/primevue.css
@@ -5690,3 +5690,32 @@
outline: 0;
box-shadow: 0 0 0 .25rem rgba(13,110,253,.25);
}
+
+/* input-group */
+/* autocomplete */
+.input-group:not(.has-validation) > .dropdown-toggle:nth-last-child(n+3) > .p-autocomplete-input,
+.input-group:not(.has-validation) > .form-floating:not(:last-child) > .form-control > .p-autocomplete-input,
+.input-group:not(.has-validation) > .form-floating:not(:last-child) > .form-select > .p-autocomplete-input,
+.input-group:not(.has-validation) > :not(:last-child):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating) > .p-autocomplete-input,
+.input-group:not(.has-validation) > .dropdown-toggle:nth-last-child(n+3) > .p-autocomplete > .p-autocomplete-input,
+.input-group:not(.has-validation) > .form-floating:not(:last-child) > .form-control > .p-autocomplete > .p-autocomplete-input,
+.input-group:not(.has-validation) > .form-floating:not(:last-child) > .form-select > .p-autocomplete > .p-autocomplete-input,
+.input-group:not(.has-validation) > :not(:last-child):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating) > .p-autocomplete > .p-autocomplete-input {
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 0;
+}
+.input-group > :not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback) > .p-autocomplete-input,
+.input-group > :not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback) > .p-autocomplete > .p-autocomplete-input {
+ margin-left: calc(var(--bs-border-width) * -1);
+ border-top-left-radius: 0;
+ border-bottom-left-radius: 0;
+}
+
+.p-inputtext.is-invalid:enabled:hover {
+ border-color: var(--bs-form-invalid-border-color);
+}
+.p-inputtext.is-invalid:enabled:focus,
+.was-validated .p-inputtext:invalid:focus {
+ border-color: var(--bs-form-invalid-border-color);
+ box-shadow: 0 0 0 .25rem rgba(var(--bs-danger-rgb),.25);
+}
diff --git a/public/js/api/factory/stv/details.js b/public/js/api/factory/stv/details.js
index 1077de22d..577bc14fb 100644
--- a/public/js/api/factory/stv/details.js
+++ b/public/js/api/factory/stv/details.js
@@ -35,4 +35,14 @@ export default {
params
};
},
+ saveStudent(student_uid, studiensemester_kurzbz, params) {
+ return {
+ method: 'post',
+ url: 'api/frontend/v1/stv/student/saveStudent/'
+ + encodeURIComponent(student_uid)
+ + '/'
+ + encodeURIComponent(studiensemester_kurzbz),
+ params
+ };
+ }
};
\ No newline at end of file
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