diff --git a/application/controllers/api/frontend/v1/funktionen/Funktionen.php b/application/controllers/api/frontend/v1/funktionen/Funktionen.php
new file mode 100644
index 000000000..d6d4a0ae4
--- /dev/null
+++ b/application/controllers/api/frontend/v1/funktionen/Funktionen.php
@@ -0,0 +1,385 @@
+ ['admin:r', 'assistenz:r'],
+ 'getContractFunctions' => ['admin:r', 'assistenz:r'],
+ 'getCurrentFunctions' => ['admin:r', 'assistenz:r'],
+ 'getAllUserFunctions' => ['admin:r', 'assistenz:r'],
+ 'getOrgHeads' => ['admin:r', 'assistenz:r'],
+ 'getOrgetsForCompany' => ['admin:r', 'assistenz:r'],
+ 'loadAllOes' => ['admin:r', 'assistenz:r'],
+ 'searchOes' => ['admin:r', 'assistenz:r'],
+ 'searchFunctions' => ['admin:r', 'assistenz:r'],
+ 'loadFunction' => ['admin:r', 'assistenz:r'],
+ 'insertFunction' => ['admin:rw', 'assistenz:rw'],
+ 'updateFunction' => ['admin:rw', 'assistenz:rw'],
+ 'deleteFunction' => ['admin:rw', 'assistenz:rw'],
+ )
+ );
+
+ // Load Libraries
+ $this->load->library('VariableLib', ['uid' => getAuthUID()]);
+ $this->load->library('form_validation');
+
+ // Load language phrases
+ $this->loadPhrases([
+ 'ui',
+ ]);
+
+ // Load models
+ $this->load->model('extensions/FHC-Core-Personalverwaltung/Api_model', 'ApiModel');
+ $this->load->model('ressource/Funktion_model', 'FunktionModel');
+ $this->load->model('person/Benutzerfunktion_model', 'BenutzerfunktionModel');
+
+ $this->load->model('organisation/Organisationseinheit_model', 'OrganisationseinheitModel');
+ }
+
+ public function getOrgHeads()
+ {
+ $result = $this->OrganisationseinheitModel->getHeads();
+
+ $data = $this->getDataOrTerminateWithError($result);
+
+ $this->terminateWithSuccess($data);
+ }
+
+ public function getAllUserFunctions($uid)
+ {
+ if(!$uid)
+ {
+ $this->terminateWithError($this->p->t('ui', 'error_missingId', ['id'=> 'UID']), self::ERROR_TYPE_GENERAL);
+ }
+
+ $sql = "
+ SELECT
+ dv.dienstverhaeltnis_id,
+ un.bezeichnung || ' (' || TO_CHAR(dv.von, 'DD.MM.YYYY') || CASE WHEN dv.bis IS NOT NULL THEN ' - '
+ || TO_CHAR(dv.bis, 'DD.MM.YYYY') ELSE '' END || ')' AS dienstverhaeltnis_unternehmen ,
+ '[' || oet.bezeichnung || '] ' || oe.bezeichnung AS funktion_oebezeichnung,
+ f.beschreibung AS funktion_beschreibung,
+ bf.*,
+ fb.bezeichnung AS fachbereich_bezeichnung,
+ CASE
+ WHEN
+ bf.datum_bis IS NOT NULL AND bf.datum_bis::date < now()::date
+ THEN
+ false
+ ELSE
+ true
+ END aktiv
+ FROM
+ public.tbl_benutzerfunktion bf
+ JOIN
+ public.tbl_organisationseinheit oe ON oe.oe_kurzbz = bf.oe_kurzbz
+ JOIN
+ public.tbl_organisationseinheittyp oet ON oe.organisationseinheittyp_kurzbz = oet.organisationseinheittyp_kurzbz
+ JOIN
+ public.tbl_funktion f ON f.funktion_kurzbz = bf.funktion_kurzbz
+ LEFT JOIN
+ hr.tbl_vertragsbestandteil_funktion vf ON vf.benutzerfunktion_id = bf.benutzerfunktion_id
+ LEFT JOIN
+ hr.tbl_vertragsbestandteil v ON vf.vertragsbestandteil_id = v.vertragsbestandteil_id
+ LEFT JOIN
+ hr.tbl_dienstverhaeltnis dv ON v.dienstverhaeltnis_id = dv.dienstverhaeltnis_id
+ LEFT JOIN
+ public.tbl_organisationseinheit un ON dv.oe_kurzbz = un.oe_kurzbz
+ LEFT JOIN
+ public.tbl_fachbereich fb ON fb.fachbereich_kurzbz = bf.fachbereich_kurzbz
+ WHERE
+ bf.uid = ?
+ ORDER BY
+ f.beschreibung, bf.datum_von ASC";
+
+ $benutzerfunktionen = $this->BenutzerfunktionModel->execReadOnlyQuery($sql, array($uid));
+ $data = $this->getDataOrTerminateWithError($benutzerfunktionen);
+
+ $this->terminateWithSuccess($data);
+ }
+
+ //TODO(Manu) DELETE
+ public function DEPR_getCurrentFunctions($uid, $companyOrgetkurzbz)
+ {
+ $sql = "
+ SELECT
+ bf.benutzerfunktion_id, f.beschreibung || ', '
+ || oe.bezeichnung || ' [' || oet.bezeichnung || '], '
+ || COALESCE(to_char(bf.datum_von, 'dd.mm.YYYY'), 'n/a')
+ || ' - ' || COALESCE(to_char(bf.datum_bis, 'dd.mm.YYYY'), 'n/a')
+ || COALESCE(dvu.attachedtovb, '') AS label
+
+ FROM (
+ WITH RECURSIVE oes(oe_kurzbz, oe_parent_kurzbz) as
+ (
+ SELECT oe_kurzbz, oe_parent_kurzbz FROM public.tbl_organisationseinheit
+ WHERE oe_kurzbz = ?
+ UNION ALL
+ SELECT o.oe_kurzbz, o.oe_parent_kurzbz FROM public.tbl_organisationseinheit o, oes
+ WHERE o.oe_parent_kurzbz=oes.oe_kurzbz
+ )
+ SELECT oe_kurzbz
+ FROM oes
+ GROUP BY oe_kurzbz
+ ) c
+ JOIN public.tbl_organisationseinheit oe ON oe.oe_kurzbz = c.oe_kurzbz
+ JOIN public.tbl_organisationseinheittyp oet ON oe.organisationseinheittyp_kurzbz = oet.organisationseinheittyp_kurzbz
+ JOIN public.tbl_benutzerfunktion bf ON bf.oe_kurzbz = oe.oe_kurzbz
+ JOIN public.tbl_funktion f ON f.funktion_kurzbz = bf.funktion_kurzbz
+ LEFT JOIN (
+ SELECT
+ benutzerfunktion_id, ' [DV]' AS attachedtovb
+ FROM
+ hr.tbl_vertragsbestandteil_funktion
+ GROUP BY
+ benutzerfunktion_id
+ ) dvu ON dvu.benutzerfunktion_id = bf.benutzerfunktion_id
+ WHERE bf.uid = ?
+ ORDER BY f.beschreibung ASC";
+
+ $benutzerfunktionen = $this->BenutzerfunktionModel->execReadOnlyQuery($sql, array( $companyOrgetkurzbz,$uid));
+ $data = $this->getDataOrTerminateWithError($benutzerfunktionen);
+
+ $this->terminateWithSuccess($data);
+ }
+
+ /*
+ * return list of child orgets for a given company orget_kurzbz
+ * as key value list to be used in select or autocomplete
+ */
+ public function getOrgetsForCompany($companyOrgetkurzbz=null)
+ {
+ $sql = "
+ SELECT
+ oe.oe_kurzbz,
+ '[' || COALESCE(oet.bezeichnung, oet.organisationseinheittyp_kurzbz) ||
+ '] ' || COALESCE(oe.bezeichnung, oe.oe_kurzbz) AS label
+ FROM (
+ WITH RECURSIVE oes(oe_kurzbz, oe_parent_kurzbz) as
+ (
+ SELECT oe_kurzbz, oe_parent_kurzbz FROM public.tbl_organisationseinheit
+ WHERE oe_kurzbz=?
+ UNION ALL
+ SELECT o.oe_kurzbz, o.oe_parent_kurzbz FROM public.tbl_organisationseinheit o, oes
+ WHERE o.oe_parent_kurzbz=oes.oe_kurzbz
+ )
+ SELECT oe_kurzbz
+ FROM oes
+ GROUP BY oe_kurzbz
+ ) c
+ JOIN public.tbl_organisationseinheit oe ON oe.oe_kurzbz = c.oe_kurzbz
+ JOIN public.tbl_organisationseinheittyp oet ON oe.organisationseinheittyp_kurzbz = oet.organisationseinheittyp_kurzbz
+ ORDER BY oet.bezeichnung ASC, oe.bezeichnung ASC";
+
+ $childorgets = $this->OrganisationseinheitModel->execReadOnlyQuery($sql, array($companyOrgetkurzbz));
+ $data = $this->getDataOrTerminateWithError($childorgets);
+
+ $this->terminateWithSuccess($data);
+ }
+
+ //TODO(Manu) DELETE
+ /*
+ * List of Oes for autocomplete field organisation unit
+ */
+ public function DEPR_loadAllOes($filterStudent=false, $aktiv=true)
+ {
+ $sql = "
+ SELECT
+ oe_kurzbz,
+ CONCAT('[', organisationseinheittyp_kurzbz, '] ', bezeichnung) as label
+ FROM public.tbl_organisationseinheit
+ WHERE aktiv = ?";
+
+ if($filterStudent)
+ $sql .= " AND organisationseinheittyp_kurzbz in ('Studiengang','Lehrgang')";
+
+ $sql .= " ORDER BY organisationseinheittyp_kurzbz, bezeichnung";
+
+ $result = $this->OrganisationseinheitModel->execReadOnlyQuery($sql, array($aktiv));
+ $data = $this->getDataOrTerminateWithError($result);
+
+ $this->terminateWithSuccess($data);
+ }
+
+ public function searchOes($companyOrgetKurzbz, $searchString = null)
+ {
+ $result = $this->OrganisationseinheitModel->getAutocompleteSuggestionsWithCompany($companyOrgetKurzbz, $searchString);
+ if (isError($result)) {
+ $this->terminateWithError($result, self::ERROR_TYPE_GENERAL);
+ }
+ $this->terminateWithSuccess($result ?: []);
+ }
+
+ public function searchFunctions($searchString = null)
+ {
+
+ $result = $this->FunktionModel->getAutocompleteSuggestions($searchString);
+ if (isError($result)) {
+ $this->terminateWithError($result, self::ERROR_TYPE_GENERAL);
+ }
+ $this->terminateWithSuccess($result ?: []);
+ }
+
+ public function loadFunction($benutzerfunktion_id)
+ {
+ $this->BenutzerfunktionModel->addSelect("*");
+ $result = $this->BenutzerfunktionModel->loadWhere(
+ array('benutzerfunktion_id' => $benutzerfunktion_id)
+ );
+ $data = $this->getDataOrTerminateWithError($result);
+
+ $this->terminateWithSuccess(current($data));
+ }
+
+ public function insertFunction()
+ {
+ $this->load->library('form_validation');
+ $authUID = getAuthUID();
+
+ $uid = $this->input->post('uid');
+
+ if(!$uid)
+ {
+ return $this->terminateWithError($this->p->t('ui', 'error_missingId', ['id'=> 'UID']), self::ERROR_TYPE_GENERAL);
+ }
+
+ $formData = $this->input->post('formData');
+
+ $datum_von = $formData['datum_von'] ?? null;
+ $datum_bis = $formData['datum_bis'] ?? null;
+ $formData['oe_kurzbz'] = is_array($formData['oe_kurzbz']) ? $formData['oe_kurzbz']['oe_kurzbz'] : $formData['oe_kurzbz'];
+ $formData['funktion_kurzbz'] = is_array($formData['funktion_kurzbz'])
+ ? $formData['funktion_kurzbz']['funktion_kurzbz']
+ : $formData['funktion_kurzbz'];
+ $bezeichnung = $formData['bezeichnung'] ?? null;
+ $wochenstunden = $formData['wochenstunden'] ?? null;
+
+ $this->form_validation->set_data($formData);
+ $this->form_validation->set_rules('datum_von', 'VonDatum', 'required|is_valid_date', [
+ 'required' => $this->p->t('ui', 'error_fieldRequired', ['field' => 'VonDatum']),
+ 'is_valid_date' => $this->p->t('ui', 'error_notValidDate', ['field' => 'VonDatum'])
+ ]);
+ $this->form_validation->set_rules('datum_bis', 'BisDatum', 'is_valid_date', [
+ 'is_valid_date' => $this->p->t('ui', 'error_notValidDate', ['field' => 'BisDatum'])
+ ]);
+ $this->form_validation->set_rules('oe_kurzbz', 'Organisationseinheit', 'required', [
+ 'required' => $this->p->t('ui', 'error_fieldRequired', ['field' => 'Organisationseinheit'])
+ ]);
+ $this->form_validation->set_rules('funktion_kurzbz', 'Funktion', 'required', [
+ 'required' => $this->p->t('ui', 'error_fieldRequired', ['field' => 'Funktion'])
+ ]);
+ $this->form_validation->set_rules('wochenstunden', 'Wochenstunden', 'numeric', [
+ 'numeric' => $this->p->t('ui', 'error_fieldNotNumeric', ['field' => 'Wochenstunden'])
+ ]);
+
+ if ($this->form_validation->run() == false)
+ {
+ $this->terminateWithValidationErrors($this->form_validation->error_array());
+ }
+
+ $result = $this->BenutzerfunktionModel->insert([
+ 'uid' => $uid,
+ 'datum_von' => $datum_von,
+ 'datum_bis' => $datum_bis ,
+ 'oe_kurzbz' => $formData['oe_kurzbz'],
+ 'funktion_kurzbz' => $formData['funktion_kurzbz'],
+ 'bezeichnung' => $bezeichnung,
+ 'wochenstunden' => $wochenstunden,
+ 'insertamum' => date('c'),
+ 'insertvon' => $authUID,
+ ]);
+
+ $data = $this->getDataOrTerminateWithError($result);
+ $this->terminateWithSuccess($data);
+ }
+
+ public function updateFunction()
+ {
+ $this->load->library('form_validation');
+ $authUID = getAuthUID();
+
+ $uid = $this->input->post('uid');
+
+ if(!$uid)
+ {
+ return $this->terminateWithError($this->p->t('ui', 'error_missingId', ['id'=> 'UID']), self::ERROR_TYPE_GENERAL);
+ }
+ $benutzerfunktion_id = $this->input->post('benutzerfunktion_id');
+
+ if(!$benutzerfunktion_id)
+ {
+ return $this->terminateWithError($this->p->t('ui', 'error_missingId', ['id'=> 'Benutzerfunktion ID']), self::ERROR_TYPE_GENERAL);
+ }
+
+ $formData = $this->input->post('formData');
+
+ $datum_von = $formData['datum_von'] ?? null;
+ $datum_bis = $formData['datum_bis'] ?? null;
+ $formData['oe_kurzbz'] = is_array($formData['oe_kurzbz']) ? $formData['oe_kurzbz']['oe_kurzbz'] : $formData['oe_kurzbz'];
+ $formData['funktion_kurzbz'] = is_array($formData['funktion_kurzbz'])
+ ? $formData['funktion_kurzbz']['funktion_kurzbz']
+ : $formData['funktion_kurzbz'];
+ $bezeichnung = $formData['bezeichnung'] ?? null;
+ $wochenstunden = $formData['wochenstunden'] ?? null;
+
+ $this->form_validation->set_data($formData);
+ $this->form_validation->set_rules('datum_von', 'VonDatum', 'required|is_valid_date', [
+ 'required' => $this->p->t('ui', 'error_fieldRequired', ['field' => 'VonDatum']),
+ 'is_valid_date' => $this->p->t('ui', 'error_notValidDate', ['field' => 'VonDatum'])
+ ]);
+ $this->form_validation->set_rules('datum_bis', 'BisDatum', 'is_valid_date', [
+ 'is_valid_date' => $this->p->t('ui', 'error_notValidDate', ['field' => 'BisDatum'])
+ ]);
+ $this->form_validation->set_rules('oe_kurzbz', 'Organisationseinheit', 'required', [
+ 'required' => $this->p->t('ui', 'error_fieldRequired', ['field' => 'Organisationseinheit'])
+ ]);
+ $this->form_validation->set_rules('funktion_kurzbz', 'Funktion', 'required', [
+ 'required' => $this->p->t('ui', 'error_fieldRequired', ['field' => 'Funktion'])
+ ]);
+ $this->form_validation->set_rules('wochenstunden', 'Wochenstunden', 'numeric', [
+ 'numeric' => $this->p->t('ui', 'error_fieldNotNumeric', ['field' => 'Wochenstunden'])
+ ]);
+
+ if ($this->form_validation->run() == false)
+ {
+ $this->terminateWithValidationErrors($this->form_validation->error_array());
+ }
+
+ $result = $this->BenutzerfunktionModel->update(
+ [
+ 'benutzerfunktion_id' => $benutzerfunktion_id,
+ ],
+ [
+ 'uid' => $uid,
+ 'datum_von' => $datum_von,
+ 'datum_bis' => $datum_bis ,
+ 'oe_kurzbz' => $formData['oe_kurzbz'],
+ 'funktion_kurzbz' => $formData['funktion_kurzbz'],
+ 'bezeichnung' => $bezeichnung,
+ 'wochenstunden' => $wochenstunden,
+ 'updateamum' => date('c'),
+ 'updatevon' => $authUID,
+ ]
+ );
+
+ $data = $this->getDataOrTerminateWithError($result);
+ $this->terminateWithSuccess($data);
+ }
+
+ public function deleteFunction($benutzerfunktion_id)
+ {
+ $result = $this->BenutzerfunktionModel->delete(
+ array('benutzerfunktion_id' => $benutzerfunktion_id)
+ );
+
+ $data = $this->getDataOrTerminateWithError($result);
+ $this->terminateWithSuccess($data);
+ }
+}
diff --git a/application/controllers/api/frontend/v1/stv/Config.php b/application/controllers/api/frontend/v1/stv/Config.php
index d37a6d6ec..f22f888de 100644
--- a/application/controllers/api/frontend/v1/stv/Config.php
+++ b/application/controllers/api/frontend/v1/stv/Config.php
@@ -132,6 +132,11 @@ class Config extends FHCAPI_Controller
'title' => $this->p->t('stv', 'tab_functions'),
'component' => './Stv/Studentenverwaltung/Details/Funktionen.js'
];
+/* TODO(Manu) REMOVE just for Testing*/
+ $result['functions_composition'] = [
+ 'title' => 'F_PV21',
+ 'component' => './Stv/Studentenverwaltung/Details/FunktionenOld.js'
+ ];
Events::trigger('stv_conf_student', function & () use (&$result) {
return $result;
diff --git a/application/models/organisation/Organisationseinheit_model.php b/application/models/organisation/Organisationseinheit_model.php
index 1b1a826aa..b146ee7e5 100644
--- a/application/models/organisation/Organisationseinheit_model.php
+++ b/application/models/organisation/Organisationseinheit_model.php
@@ -217,4 +217,72 @@ class Organisationseinheit_model extends DB_Model
oe_kurzbz ILIKE '%". $this->escapeLike($eventQuery). "%'
");
}
+
+ /**
+ * Get OEs by eventQuery string and companyOrgetKurzbz
+ * Use with autocomplete event queries in Function Component
+ * @param $searchString String
+ * @param $companyOrgetKurzbz String oe_kurzbz of the company (gst vs gmbh)
+ * @return array
+ */
+ public function getAutocompleteSuggestionsWithCompany($companyOrgetKurzbz, $searchString)
+ {
+ $sql = "
+ WITH RECURSIVE oes(oe_kurzbz, oe_parent_kurzbz) AS (
+ SELECT oe_kurzbz, oe_parent_kurzbz
+ FROM public.tbl_organisationseinheit
+ WHERE oe_kurzbz = ?
+ UNION ALL
+ SELECT o.oe_kurzbz, o.oe_parent_kurzbz
+ FROM public.tbl_organisationseinheit o
+ INNER JOIN oes ON o.oe_parent_kurzbz = oes.oe_kurzbz
+ )
+ SELECT
+ oe.oe_kurzbz, oe.aktiv,
+ '[' || COALESCE(oet.bezeichnung, oet.organisationseinheittyp_kurzbz) ||
+ '] ' || COALESCE(oe.bezeichnung, oe.oe_kurzbz) AS label
+ FROM (
+ SELECT oe_kurzbz FROM oes GROUP BY oe_kurzbz
+ ) c
+ JOIN public.tbl_organisationseinheit oe ON oe.oe_kurzbz = c.oe_kurzbz
+ JOIN public.tbl_organisationseinheittyp oet ON oe.organisationseinheittyp_kurzbz = oet.organisationseinheittyp_kurzbz
+ ";
+
+ $params = [$companyOrgetKurzbz];
+
+ if (!empty($searchString)) {
+ $escaped = $this->escapeLike($searchString);
+ $ilike = '%' . $escaped . '%';
+
+ $sql .= "
+ WHERE
+ oe.oe_kurzbz ILIKE ? OR
+ oe.bezeichnung ILIKE ? OR
+ oe.organisationseinheittyp_kurzbz ILIKE ?
+ ";
+
+ $params[] = $ilike;
+ $params[] = $ilike;
+ $params[] = $ilike;
+ }
+
+ $sql .= " ORDER BY oet.bezeichnung ASC, oe.bezeichnung ASC";
+
+ $result = $this->execQuery($sql, $params);
+
+ return $result;
+ }
+
+
+ /**
+ * get highest organisation units
+ */
+ public function getHeads()
+ {
+ $this->addSelect('*');
+ $this->addSelect('oe_kurzbz as head');
+ $result = $this->loadWhere(array('oe_parent_kurzbz' => null, 'aktiv' => true));
+
+ return $result;
+ }
}
diff --git a/application/models/ressource/Funktion_model.php b/application/models/ressource/Funktion_model.php
index 43908167c..60965b438 100644
--- a/application/models/ressource/Funktion_model.php
+++ b/application/models/ressource/Funktion_model.php
@@ -11,4 +11,26 @@ class Funktion_model extends DB_Model
$this->dbTable = 'public.tbl_funktion';
$this->pk = 'funktion_kurzbz';
}
+
+ /**
+ * Get Functions by eventQuery string. Use with autocomplete event queries in Function Component
+ * @param $eventQuery String
+ * @return array
+ */
+ public function getAutocompleteSuggestions($eventQuery)
+ {
+ $this->addSelect('funktion_kurzbz, beschreibung, aktiv');
+ $this->addSelect("beschreibung AS label");
+ $this->addOrder('beschreibung', 'ASC');
+
+ if($eventQuery === null)
+ {
+ return $this->load();
+ }
+
+ return $this->loadWhere("
+ funktion_kurzbz ILIKE '%". $this->escapeLike($eventQuery). "%'
+ OR beschreibung ILIKE '%". $this->escapeLike($eventQuery). "%'
+ ");
+ }
}
diff --git a/application/views/Studentenverwaltung.php b/application/views/Studentenverwaltung.php
index 398b921d2..a1b7fe347 100644
--- a/application/views/Studentenverwaltung.php
+++ b/application/views/Studentenverwaltung.php
@@ -18,7 +18,8 @@
#datepicker fuer component functions
'public/css/components/vue-datepicker.css',
'public/css/components/primevue.css',
- 'public/css/Studentenverwaltung.css'
+ 'public/css/Studentenverwaltung.css',
+ 'public/css/components/function.css'
],
'customJSs' => [
'vendor/vuejs/vuedatepicker_js/vue-datepicker.iife.js'
diff --git a/public/css/components/function.css b/public/css/components/function.css
new file mode 100644
index 000000000..3e5e4a632
--- /dev/null
+++ b/public/css/components/function.css
@@ -0,0 +1,3 @@
+.item-inactive {
+ text-decoration: line-through;
+}
\ No newline at end of file
diff --git a/public/js/api/factory/functions.js b/public/js/api/factory/functions.js
new file mode 100644
index 000000000..ac717050a
--- /dev/null
+++ b/public/js/api/factory/functions.js
@@ -0,0 +1,124 @@
+/**
+ * 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 {
+ getContractFunctions(filter) {
+ var url = 'api/frontend/v1/funktionen/Funktionen/getContractFunctions';
+ if( typeof filter !== 'undefined' && filter !== null ) {
+ url = url + '/' + filter;
+ }
+ return {
+ method: 'get',
+ url,
+ };
+
+ },
+ getOrgHeads() {
+ var url = 'api/frontend/v1/funktionen/Funktionen/getOrgHeads';
+ return {
+ method: 'get',
+ url,
+ };
+ },
+ getOrgetsForCompany(unternehmen) {
+ var url = 'api/frontend/v1/funktionen/Funktionen/getOrgetsForCompany'
+ + '/' + unternehmen;
+ return {
+ method: 'get',
+ url,
+ };
+ },
+ loadAllOes(filterStudent) {
+ var url = 'api/frontend/v1/funktionen/Funktionen/loadAllOes'
+ + '/' + filterStudent;
+ return {
+ method: 'get',
+ url,
+ };
+ },
+ getCompanyByOrget(orget) {
+ var url = 'api/frontend/v1/funktionen/Funktionen/getCompanyByOrget'
+ + '/' + orget;
+ return {
+ method: 'get',
+ url,
+ };
+ },
+ getCurrentFunctions(mitarbeiter_uid, unternehmen) {
+ var url = 'api/frontend/v1/funktionen/Funktionen/getCurrentFunctions'
+ + '/' + mitarbeiter_uid + '/' + unternehmen;
+ return {
+ method: 'get',
+ url,
+ };
+ } ,
+ getAllUserFunctions(mitarbeiter_uid) {
+ var url = 'api/frontend/v1/funktionen/Funktionen/getAllUserFunctions'
+ + '/' + mitarbeiter_uid;
+ return {
+ method: 'get',
+ url,
+ };
+ },
+ getAllFunctions() {
+ var url = 'api/frontend/v1/funktionen/Funktionen/getAllFunctions';
+ return {
+ method: 'get',
+ url,
+ };
+ },
+ addFunction(params) {
+ return {
+ method: 'post',
+ url: 'api/frontend/v1/funktionen/Funktionen/insertFunction/',
+ params
+ };
+ },
+ loadFunction(benutzerfunktion_id) {
+ var url = 'api/frontend/v1/funktionen/Funktionen/loadFunction'
+ + '/' + benutzerfunktion_id;
+ return {
+ method: 'get',
+ url,
+ };
+ },
+ updateFunction(params) {
+ return {
+ method: 'post',
+ url: 'api/frontend/v1/funktionen/Funktionen/updateFunction/',
+ params
+ };
+ },
+ deleteFunction(benutzerfunktion_id) {
+ return {
+ method: 'post',
+ url: 'api/frontend/v1/funktionen/Funktionen/deleteFunction/' + benutzerfunktion_id
+ };
+ },
+ getOes(head, searchString) {
+ return {
+ method: 'get',
+ url: 'api/frontend/v1/funktionen/Funktionen/searchOes/' + head + '/' + searchString
+ };
+ },
+ getFunctions(searchString) {
+ return {
+ method: 'get',
+ url: 'api/frontend/v1/funktionen/Funktionen/searchFunctions/' + searchString
+ };
+ }
+
+};
\ No newline at end of file
diff --git a/public/js/components/Funktionen/Funktionen.js b/public/js/components/Funktionen/Funktionen.js
index 335edd906..9618bdf9b 100644
--- a/public/js/components/Funktionen/Funktionen.js
+++ b/public/js/components/Funktionen/Funktionen.js
@@ -1,623 +1,547 @@
-import { Modal } from './Modal.js';
-import { ModalDialog } from './ModalDialog.js';
-import { Toast } from './Toast.js';
-import {OrgChooser} from "./OrgChooser.js";
-import { usePhrasen } from '../../mixins/Phrasen.js';
+import {CoreFilterCmpt} from "../filter/Filter.js";
+import FormInput from "../Form/Input.js";
+import FormForm from "../Form/Form.js";
+import BsModal from "../Bootstrap/Modal.js";
+import PvAutoComplete from "../../../../index.ci.php/public/js/components/primevue/autocomplete/autocomplete.esm.min.js";
-import ApiFunktion from '../../api/factory/funktionen/person.js';
-import ApiPerson from "../../../extensions/FHC-Core-Personalverwaltung/js/api/factory/person.js";
+import ApiCoreFunktion from '../../api/factory/functions.js';
-export const Funktionen = {
+export default {
name: 'FunctionComponent',
components: {
- Modal,
- ModalDialog,
- Toast,
- OrgChooser,
- "datepicker": VueDatePicker
+ CoreFilterCmpt,
+ FormInput,
+ FormForm,
+ BsModal,
+ PvAutoComplete
},
props: {
- modelValue: { type: Object, default: () => ({}), required: false},
- config: { type: Object, default: () => ({}), required: false},
- readonlyMode: { type: Boolean, required: false, default: false },
- personID: { type: Number, required: false },
- personUID: { type: String, required: false },
- writePermission: { type: Boolean, required: false },
- showDvCompany: { tpye: Boolean, required: false, default: true }
+ modelValue: {
+ type: Object,
+ default: () => ({}),
+ required: false
+ },
+ config: {type: Object, default: () => ({}), required: false},
+ readonlyMode: {type: Boolean, required: false, default: false},
+ personID: {type: Number, required: true},
+ personUID: {type: String, required: true},
+ writePermission: {type: Boolean, required: false},
+ showDvCompany: {type: Boolean, required: false, default: true},
+ saveFunctionAsCopy: {type: Boolean, required: false, default: false},
+ stylePv21: {type: Boolean, required: false, default: false},
+ companyLinkFormatter: {type: Function || null, default: null}
},
- emits: ['updateHeader'],
- setup( props, { emit } ) {
-
- const $api = Vue.inject('$api');
- //const fhcAlert = Vue.inject('$fhcAlert');
-
-
- const readonly = Vue.ref(false);
-
- const { t } = usePhrasen();
-
- //const { personID: currentPersonID , personUID: currentPersonUID } = Vue.toRefs(props);
- const currentPersonID = Vue.computed(() => { return props.personID });
- const currentPersonUID = Vue.computed(() => { return props.personUID });
-
- const dialogRef = Vue.ref();
-
- const isFetching = Vue.ref(false);
-
- const theModel = Vue.computed({
- get: () => props.modelValue,
- set: (value) => emit('update:modelValue', value),
- });
-
- const unternehmen = Vue.ref();
- const jobfunctionList = Vue.ref([]);
- const jobfunctionDefList = Vue.ref([]);
- const orgUnitList = Vue.ref([{value: '', label: '-'}]);
-
- const aktivChecked = Vue.ref(true);
-
- const table = Vue.ref(null); // reference to your table element
- const tabulator = Vue.ref(null); // variable to hold your table
- const tableData = Vue.reactive([]); // data for table to display
-
-
- const full = FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router;
- const route = ( props.readonlyMode !== true ) ? VueRouter.useRoute() : null;
-
- const convertArrayToObject = (array, key) => {
- const initialValue = {};
- return array.reduce((obj, item) => {
- return {
- ...obj,
- [item[key]]: item,
- };
- }, initialValue);
- };
-
- const fetchData = async () => {
- if (currentPersonID.value==null && theModel.value.personID==null) {
- jobfunctionList.value = [];
- return;
- }
- isFetching.value = true
-
- // fetch data and map them for easier access
- try {
- const response = await $api.call(
- ApiFunktion.getAllUserFunctions(theModel.value.personUID || currentPersonUID.value));
- if(response.error === 1) {
- let rawList = [];
- tableData.value = [];
- jobfunctionList.value = convertArrayToObject(rawList, 'benutzerfunktion_id');
- } else {
- let rawList = response.retval;
- tableData.value = response.retval;
- jobfunctionList.value = convertArrayToObject(rawList, 'benutzerfunktion_id');
- }
- } catch (error) {
- console.log(error)
- } finally {
- isFetching.value = false
- }
-
- }
-
- const createShape = () => {
- return {
- benutzerfunktion_id: 0,
- uid: theModel.value.personUID || currentPersonUID.value,
- oe_kurzbz: "",
- funktion_kurzbz: "",
- datum_von: "",
- datum_bis: "",
- bezeichnung: "",
- wochenstunden: 0,
- }
- }
-
- const currentValue = Vue.ref(createShape());
- const preservedValue = Vue.ref(createShape());
-
- Vue.watch([currentPersonID, currentPersonUID], ([id,uid]) => {
- fetchData();
- });
-
- Vue.watch(unternehmen, (unternehmen_kurzbz) => {
- fetchOrgUnits(unternehmen_kurzbz);
- fetchFunctions();
- });
-
- const toggleMode = async () => {
- if (!readonly.value) {
- // cancel changes?
- if (hasChanged.value) {
- const ok = await dialogRef.value.show();
- if (ok) {
- console.log("ok=", ok);
- currentValue.value = preservedValue.value;
- } else {
- return
- }
- }
- } else {
- // switch to edit mode and preserve data
- preservedValue.value = {...currentValue.value};
- }
- readonly.value = !readonly.value;
- }
-
- Vue.onMounted(async () => {
- currentValue.value = createShape();
- await fetchData();
-
- const dateFormatter = (cell) => {
- return cell.getValue()?.replace(/(.*)-(.*)-(.*)/, '$3.$2.$1');
- }
-
- const dvFormatter = (cell) => {
- if( props.readonlyMode === true ) {
- return (cell.getValue() != null) ? cell.getValue() : '';
- }
- const url = fullPath + route.params.id + '/' + route.params.uid + '/contract/' + cell.getRow().getData().dienstverhaeltnis_id;
- return cell.getValue() != null ? `` + cell.getValue() + '' : '';
- }
-
- // helper
- const createDomButton = (classValue, clickHandler) => {
- const nodeBtn = document.createElement("button");
- const classAttrBtn = document.createAttribute("class");
- classAttrBtn.value = "btn btn-outline-secondary btn-sm";
- nodeBtn.setAttributeNode(classAttrBtn);
- nodeBtn.addEventListener("click", clickHandler);
- const nodeI = document.createElement("i");
- const classAttrI = document.createAttribute("class");
- classAttrI.value = classValue;
- nodeI.setAttributeNode(classAttrI);
- nodeBtn.appendChild(nodeI);
- return nodeBtn;
- }
-
- const btnFormatter = (cell) => {
-
- const nodeDiv = document.createElement("div");
- const classAttrDiv = document.createAttribute("class");
- classAttrDiv.value = "d-grid gap-2 d-md-flex justify-content-end align-middle";
- nodeDiv.setAttributeNode(classAttrDiv);
- // delete button
- const nodeBtnDel = createDomButton("fa fa-xmark",() => { showDeleteModal(cell.getValue()) })
- // edit button
- const nodeBtnEdit = createDomButton("fa fa-pen",() => { showEditModal(cell.getValue()) })
-
- if( cell.getRow().getData().dienstverhaeltnis_unternehmen === null ) {
- nodeDiv.appendChild(nodeBtnEdit);
- nodeDiv.appendChild(nodeBtnDel);
- }
-
- return nodeDiv;
- }
-
-
- const columnsDef = [
- { title: t('person','dv_unternehmen'), field: "dienstverhaeltnis_unternehmen", formatter: dvFormatter, sorter:"string", headerFilter:"list", headerFilterParams: {valuesLookup:true, autocomplete:true, sort:"asc"}, visible: props.showDvCompany },
- { title: t('person','zuordnung_taetigkeit'), field: "funktion_beschreibung", hozAlign: "left", headerFilter:"list", headerFilterParams: {valuesLookup:true, autocomplete:true, sort:"asc"} },
- { title: t('lehre','organisationseinheit'), field: "funktion_oebezeichnung", headerFilter:"list", headerFilterParams: {valuesLookup:true, autocomplete:true, sort:"asc"} },
- { title: t('person','wochenstunden'), field: "wochenstunden", hozAlign: "right", width: 140, headerFilter:true },
- { title: t('ui','from'), field: "datum_von", hozAlign: "center", formatter: dateFormatter, width: 140, sorter:"string", headerFilter:true, headerFilterFunc:customHeaderFilter },
- { title: t('global','bis'), field: "datum_bis", hozAlign: "center", formatter: dateFormatter, width: 140, sorter:"string", headerFilter:true, headerFilterFunc:customHeaderFilter },
- { title: t('ui','bezeichnung'), field: "bezeichnung", hozAlign: "left", headerFilter:"list", headerFilterParams: {valuesLookup:true, autocomplete:true, sort:"asc"} }
- ];
-
- if( props.readonlyMode === false) {
- columnsDef.push({ title: "", field: "benutzerfunktion_id", formatter: btnFormatter, hozAlign: "right", width: 100, headerSort: false, frozen: true });
- }
-
- let tabulatorOptions = {
- height: "100%",
- layout: "fitColumns",
- movableColumns: true,
- reactiveData: true,
- columns: columnsDef,
- //data: tableData.value,
- data: jobfunctionListArray.value,
- };
-
- tabulator.value = new Tabulator(
- table.value,
- tabulatorOptions
- );
-
- function customHeaderFilter(headerValue, rowValue, rowData, filterParams){
- //headerValue - the value of the header filter element
- //rowValue - the value of the column in this row
- //rowData - the data for the row being filtered
- //filterParams - params object passed to the headerFilterFuncParams property
-
- const validDate = function(d){
- return d instanceof Date && isFinite(d);
- }
-
- const date1 = new Date(rowValue);
- date1.setHours(0,0,0,0);
- let [day, month, year] = headerValue.split('.')
- if (year < 1000) return true; // prevents dates like 17.5.2
- const date2 = new Date(+year, +month - 1, +day);
-
- return !(validDate(date2)) || ((date2 - date1) == 0); //must return a boolean, true if it passes the filter.
- }
-
- tabulator.value.on('tableBuilt', () => {
- //tabulator.value.setData(tableData.value);
-
- })
-
- })
-
- const jobfunctionListArray = Vue.computed(() => {
- let temp = (jobfunctionList.value ? Object.values(jobfunctionList.value) : []);
- let filtered = temp.filter((e) => ( !aktivChecked.value || (aktivChecked.value && e.aktiv) ));
- return filtered;
- });
-
- // Workaround to update tabulator
- Vue.watch(jobfunctionListArray, (newVal, oldVal) => {
- console.log('jobfunctionList changed');
- tabulator.value?.setData(jobfunctionListArray.value);
- }, {deep: true})
-
- // Modal
- const modalRef = Vue.ref();
- const confirmDeleteRef = Vue.ref();
-
- const showAddModal = () => {
- currentValue.value = createShape();
- // reset form state
- frmState.orgetBlurred=false;
- frmState.funktionBlurred=false;
- frmState.beginnBlurred=false;
- // call bootstrap show function
- modalRef.value.show();
- }
-
- const hideModal = () => {
- modalRef.value.hide();
- }
-
- const showEditModal = async (id) => {
- currentValue.value = { ...jobfunctionList.value[id] };
- // fetch company
- isFetching.value = true;
- try {
- const res = await $api.call(ApiFunktion.getCompanyByOrget(currentValue.value.oe_kurzbz));
- if (res.error == 0) {
- unternehmen.value = res.retval[0].oe_kurzbz;
- } else {
- console.log('company not found for orget!');
- }
-
- } catch (error) {
- console.log(error)
- } finally {
- isFetching.value = false
- }
- //delete currentValue.value.bezeichnung;
- modalRef.value.show();
- }
-
- const showDeleteModal = async (id) => {
- currentValue.value = { ...jobfunctionList.value[id] };
- const ok = await confirmDeleteRef.value.show();
-
- if (ok) {
-
- try {
- const res = await $api.call(ApiPerson.deletePersonJobFunction(id));
- if (res.error == 0) {
- delete jobfunctionList.value[id];
- showDeletedToast();
- theModel.value.updateHeader();
- }
- } catch (error) {
- console.log(error)
- } finally {
- isFetching.value = false
- }
-
- }
- }
-
-
- const okHandler = async () => {
- if (!validate()) {
-
- console.log("form invalid");
-
- } else {
-
- // submit
- try {
- let payload = {...currentValue.value};
- delete payload.dienstverhaeltnis_id;
- delete payload.dienstverhaeltnis_unternehmen;
- delete payload.fachbereich_bezeichnung;
- delete payload.funktion_oebezeichnung;
- delete payload.aktiv;
- delete payload.funktion_beschreibung;
- const r = await $api.call(ApiPerson.upsertPersonJobFunction(payload));
- if (r.error == 0) {
- // fetch all data because of all the references in the changed record
- await fetchData();
- console.log('job function successfully saved');
- theModel.value.updateHeader();
- showToast();
- }
- } catch (error) {
- console.log(error)
- } finally {
- isFetching.value = false
- }
-
- hideModal();
- }
- }
-
- // -------------
- // form handling
- // -------------
-
- const jobFunctionFrm = Vue.ref();
-
- const frmState = Vue.reactive({ beginnBlurred: false, orgetBlurred: false, funktionBlurred: false, wasValidated: false });
-
- const notEmpty = (n) => {
- return !!n && n.trim() != "";
- }
-
- const validate = () => {
- frmState.orgetBlurred = true;
- frmState.funktionBlurred = true;
- frmState.beginnBlurred = true;
- return notEmpty(currentValue.value.datum_von) &&
- notEmpty(currentValue.value.oe_kurzbz) &&
- notEmpty(currentValue.value.funktion_kurzbz);
- }
-
-
- const hasChanged = Vue.computed(() => {
- return Object.keys(currentValue.value).some(field => currentValue.value[field] !== preservedValue.value[field])
- });
-
- const formatDate = (d) => {
- if (d != null && d != '') {
- return d.substring(8, 10) + "." + d.substring(5, 7) + "." + d.substring(0, 4);
- } else {
- return ''
- }
- }
-
- // Toast
- const toastRef = Vue.ref();
- const deleteToastRef = Vue.ref();
-
- const showToast = () => {
- toastRef.value.show();
- }
-
- const showDeletedToast = () => {
- deleteToastRef.value.show();
- }
-
- const fetchOrgUnits = async (unternehmen_kurzbz) => {
- if( unternehmen_kurzbz === '' ) {
- return;
- }
- const response = await $api.call(
- ApiFunktion.getOrgetsForCompany(unternehmen_kurzbz));
- const orgets = response.retval;
- orgets.unshift({
- value: '',
- label: t('ui','bitteWaehlen'),
- });
- orgUnitList.value = await orgets;
- }
-
- const fetchFunctions = async (uid, unternehmen_kurzbz) => {
- if(unternehmen_kurzbz === '' || uid === '' ) {
- return;
- }
- const response = await $api.call(ApiFunktion.getAllFunctions());
- const benutzerfunktionen = response.retval;
- benutzerfunktionen.unshift({
- value: '',
- label: t('ui','bitteWaehlen'),
- });
- jobfunctionDefList.value = benutzerfunktionen;
- }
-
- const unternehmenSelectedHandler = (e) => {
- console.log('unternehmen selected: ',e);
- unternehmen.value = e;
- }
-
- const ciPath = FHC_JS_DATA_STORAGE_OBJECT.app_root.replace(/(https:|)(^|\/\/)(.*?\/)/g, '') + FHC_JS_DATA_STORAGE_OBJECT.ci_router;
- const fullPath = `/${ciPath}/extensions/FHC-Core-Personalverwaltung/Employees/`;
-
+ data(){
return {
- jobfunctionList, orgUnitList, jobfunctionListArray,
- jobfunctionDefList,
- currentValue,
- readonly,
- frmState,
- dialogRef,
- toastRef, deleteToastRef,
- jobFunctionFrm,
- modalRef,
- fullPath,
- route,
- aktivChecked,
- unternehmen,
- tabulator,
- table,
+ tabulatorOptions: {
+ ajaxURL: 'dummy',
+ ajaxRequestFunc: () => this.$api.call(
+ ApiCoreFunktion.getAllUserFunctions(this.personUID)
+ ),
+ ajaxResponse: (url, params, response) => response.data,
+ columns: [
+ {
+ title: "dienstverhaeltnis_unternehmen",
+ field: "dienstverhaeltnis_unternehmen",
+ width: 50,
+ headerFilter: "list",
+ headerFilterParams: {valuesLookup: true, autocomplete: true, sort: "asc"},
+ formatter: this.companyLinkFormatter
+ },
+ {
+ title: "funktion_beschreibung", field: "funktion_beschreibung", headerFilter: "list",
+ headerFilterParams: {valuesLookup: true, autocomplete: true, sort: "asc"},
+ },
+ {
+ title: "funktion_oebezeichnung", field: "funktion_oebezeichnung", headerFilter: "list",
+ headerFilterParams: {valuesLookup: true, autocomplete: true, sort: "asc"}
+ },
+ {title: "wochenstunden", field: "wochenstunden", headerFilter: true},
+ {
+ title: "Von",
+ field: "datum_von",
+ headerFilter: true,
+ formatter: function (cell) {
+ const dateStr = cell.getValue();
+ if (!dateStr) return "";
- toggleMode, formatDate, notEmpty,
- showToast, showDeletedToast,
- showAddModal, hideModal, okHandler,
- showDeleteModal, showEditModal, confirmDeleteRef, t, unternehmenSelectedHandler,
+ const date = new Date(dateStr);
+ return date.toLocaleString("de-DE", {
+ day: "2-digit",
+ month: "2-digit",
+ year: "numeric",
+ });
+ },
+ },
+ {
+ title: "Bis",
+ field: "datum_bis",
+ headerFilter: true,
+ formatter: function (cell) {
+ const dateStr = cell.getValue();
+ if (!dateStr) return "";
+
+ const date = new Date(dateStr);
+ return date.toLocaleString("de-DE", {
+ day: "2-digit",
+ month: "2-digit",
+ year: "numeric",
+ });
+ },
+ },
+ {title: "bezeichnung", field: "bezeichnung", headerFilter: true},
+ {title: "aktiv", field: "aktiv", visible: false},
+ {title: "benutzerfunktion_id", field: "benutzerfunktion_id", visible: false},
+ {title: "uid", field: "uid", visible: false},
+ {
+ //title: 'Aktionen', field: 'actions',
+ minWidth: 150, // Ensures Action-buttons will be always fully displayed
+ formatter: (cell, formatterParams, onRendered) => {
+ let container = document.createElement('div');
+ container.className = "d-flex gap-2";
+
+
+ if( cell.getRow().getData().dienstverhaeltnis_unternehmen === null ) {
+ let button = document.createElement('button');
+ button.className = 'btn btn-outline-secondary btn-action';
+ if(this.stylePv21)
+ button.innerHTML = '';
+ else
+ button.innerHTML = '';
+ button.title = this.$p.t('ui', 'bearbeiten');
+ button.addEventListener('click', (event) =>
+ this.actionEditFunction(cell.getData().benutzerfunktion_id)
+ );
+ if(this.readonlyMode === true) button.disabled = true;
+ container.append(button);
+ }
+ if( cell.getRow().getData().dienstverhaeltnis_unternehmen === null ) {
+ let 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.actionDeleteFunction(cell.getData().benutzerfunktion_id)
+ );
+ if(this.readonlyMode === true) button.disabled = true;
+ container.append(button);
+ }
+
+ if (cell.getRow().getData().dienstverhaeltnis_unternehmen === null && this.saveFunctionAsCopy) {
+ let button = document.createElement('button');
+ button.className = 'btn btn-outline-secondary btn-action';
+ button.innerHTML = '';
+ button.title = this.$p.t('ui', 'saveAsCopy');
+ button.addEventListener('click', () =>
+ this.actionCopyFunction(cell.getData().benutzerfunktion_id)
+ );
+ if(this.readonlyMode === true) button.disabled = true;
+ container.append(button);
+ }
+
+ return container;
+ },
+ frozen: true
+ }
+ ],
+ layout: 'fitDataFill',
+ layoutColumnsOnNewData: false,
+ height: '300',
+ persistenceID: 'core-functions',
+ },
+ tabulatorEvents: [
+ {
+ event: 'tableBuilt',
+ handler: async () => {
+ await this.$p.loadCategory(['global', 'lehre', 'person', 'ui']);
+ let cm = this.$refs.table.tabulator.columnManager;
+
+ //Field Company: if visible show link to dv
+ const column = cm.getColumnByField('dienstverhaeltnis_unternehmen');
+ const companyDv = {
+ title: this.$p.t('person', 'dv_unternehmen'),
+ width: 100,
+ visible: this.showDvCompany,
+ };
+ column.component.updateDefinition(companyDv);
+
+ cm.getColumnByField('funktion_beschreibung').component.updateDefinition({
+ title: this.$p.t('person', 'zuordnung_taetigkeit'),
+ width: 100
+ });
+ cm.getColumnByField('funktion_oebezeichnung').component.updateDefinition({
+ title: this.$p.t('lehre', 'organisationseinheit'),
+ width: 100
+ });
+ cm.getColumnByField('wochenstunden').component.updateDefinition({
+ title: this.$p.t('person', 'wochenstunden')
+ });
+
+ const columnDatumVon = cm.getColumnByField('datum_von');
+ const fieldVonDatum = {
+ title: this.$p.t('ui', 'from')
+ };
+
+ columnDatumVon.component.updateDefinition(fieldVonDatum);
+
+ const columnDatumBis = cm.getColumnByField('datum_bis');
+ const fieldBisDatum = {
+ title: this.$p.t('global', 'bis'),
+ };
+ columnDatumBis.component.updateDefinition(fieldBisDatum);
+
+ cm.getColumnByField('bezeichnung').component.updateDefinition({
+ title: this.$p.t('ui', 'bezeichnung'),
+ width: 100
+ });
+
+ }
+ }
+ ],
+ isFilterSet: false,
+ listOrgHeads: [],
+ listOrgUnits: [],
+ formData: {
+ head: 'gst',
+ oe_kurzbz: ''
+ },
+ statusNew: true,
+ listAllFunctions: [],
+ abortController: {
+ oes: null,
+ functions: null
+ },
+ filteredOes: [],
+ filteredFunctions: [],
+ newBtnStyle: ''
}
},
+ computed: {
+ },
+ methods: {
+ onSwitchChange() {
+ if (this.isFilterSet) {
+ this.$refs.table.tabulator.setFilter("aktiv", "=", true);
+ }
+ else {
+ this.$refs.table.tabulator.clearFilter();
+ this.isFilterSet = false;
+ }
+ },
+ actionNewFunction(){
+ this.resetModal();
+ this.statusNew = true;
+ this.$refs.functionModal.show();
+ },
+ actionCopyFunction(benutzerfunktion_id) {
+ this.statusNew = true;
+ this.loadFunction(benutzerfunktion_id).then(() => {
+ this.$refs.functionModal.show();
+ });
+ },
+ actionDeleteFunction(benutzerfunktion_id) {
+ this.$fhcAlert
+ .confirmDelete()
+ .then(result => result
+ ? benutzerfunktion_id
+ : Promise.reject({handled: true}))
+ .then(this.deleteFunction)
+ .catch(this.$fhcAlert.handleSystemError);
+ },
+ actionEditFunction(benutzerfunktion_id) {
+ this.resetModal();
+ this.statusNew = false;
+ this.loadFunction(benutzerfunktion_id);
+ this.$refs.functionModal.show();
+ },
+ addFunction() {
+ const dataToSend = {
+ uid: this.personUID,
+ formData: this.formData
+ };
+ return this.$refs.functionData
+ .call(ApiCoreFunktion.addFunction(dataToSend))
+ .then(response => {
+ this.$fhcAlert.alertSuccess(this.$p.t('ui', 'successSave'));
+ this.hideModal('functionModal');
+ this.resetModal();
+ }).catch(this.$fhcAlert.handleSystemError)
+ .finally(() => {
+ this.reload();
+ });
+ },
+ loadFunction(benutzerfunktion_id) {
+ return this.$api
+ .call(ApiCoreFunktion.loadFunction(benutzerfunktion_id))
+ .then(result => {
+ this.formData = result.data;
+ })
+ .catch(this.$fhcAlert.handleSystemError);
+ },
+ updateFunction(benutzerfunktion_id){
+ const dataToSend = {
+ uid: this.personUID,
+ formData: this.formData,
+ benutzerfunktion_id: benutzerfunktion_id
+ };
+ return this.$refs.functionData
+ .call(ApiCoreFunktion.updateFunction(dataToSend))
+ .then(response => {
+ this.$fhcAlert.alertSuccess(this.$p.t('ui', 'successSave'));
+ this.hideModal('functionModal');
+ this.resetModal();
+ }).catch(this.$fhcAlert.handleSystemError)
+ .finally(() => {
+ this.reload();
+ });
+ },
+ deleteFunction(benutzerfunktion_id) {
+ return this.$api
+ .call(ApiCoreFunktion.deleteFunction(benutzerfunktion_id))
+ .then(response => {
+ this.$fhcAlert.alertSuccess(this.$p.t('ui', 'successDelete'));
+ })
+ .catch(this.$fhcAlert.handleSystemError)
+ .finally(() => {
+ this.reload();
+ });
+ },
+ getOrgetsForCompany(){
+ return this.$api
+ .call(ApiCoreFunktion.getOrgetsForCompany(this.formData.head))
+ .then(result => {
+ this.listOrgUnits = result.data;
+ })
+ .catch(this.$fhcAlert.handleSystemError);
+ },
+ hideModal(modalRef) {
+ this.$refs[modalRef].hide();
+ },
+ reload() {
+ this.$refs.table.reloadTable();
+ },
+ resetModal(){
+ this.formData = {};
+ this.formData.head = 'gst';
+ this.formData.oe_kurzbz = '';
+ this.formData.funktion_kurzbz = '';
+ },
+ searchOe(event) {
+ if (this.abortController.oes) {
+ this.abortController.oes.abort();
+ }
+
+ this.abortController.oes = new AbortController();
+
+ return this.$api
+ .call(ApiCoreFunktion.getOes(this.formData.head, event.query))
+ .then(result => {
+ this.filteredOes = result.data.retval;
+ });
+ },
+ searchFunctions(event) {
+ if (this.abortController.functions) {
+ this.abortController.functions.abort();
+ }
+
+ this.abortController.functions = new AbortController();
+
+ return this.$api
+ .call(ApiCoreFunktion.getFunctions(event.query))
+ .then(result => {
+ this.filteredFunctions = result.data.retval;
+ });
+ },
+ styleNewButton(){
+ if(this.stylePv21) {
+ this.newBtnStyle = "btn-sm";
+ }
+ },
+ //helper function: workaround to trigger validation if input is not a number
+ normalizeStunden() {
+ if (this.formData.wochenstunden === null || this.formData.wochenstunden === '') {
+ this.formData.wochenstunden = 'xxx'
+ }
+ }
+ },
+ created() {
+ this.$api
+ .call(ApiCoreFunktion.getOrgHeads())
+ .then(result => {
+ this.listOrgHeads = result.data;
+ })
+ .catch(this.$fhcAlert.handleSystemError);
+
+ this.styleNewButton();
+ },
template: `
-
+
-
-
- {{ t('person','funktionGespeichert') }}
-
-
+
+
+
+
+
+
+
+
-
-
- {{ t('person', 'funktionGeloescht') }}
-
-
-
-
-
-
-
+
+
-
-
-
-
-
-
-
-
+
+
+
+ {{ $p.t('funktion', 'addFunktion') }}
+ {{ $p.t('funktion', 'editFunktion') }}
+
-
-
-
-
-
-
-
-
+
+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+ {{slotProps.option.label}}
+
+
+
-
+
+
+
+
+ {{slotProps.option.label}}
+
+
+
-
-
- {{ t('person','funktionNochNichtGespeichert') }}
-
-
+
+
+
-
-
- {{ t('person','funktion') }} '{{ currentValue?.funktion_kurzbz }} ({{ currentValue?.datum_von }}-{{ currentValue?.datum_bis }})' {{ t('person', 'wirklichLoeschen') }}?
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
`
}
-
-export default Funktionen;
\ No newline at end of file
diff --git a/public/js/components/Funktionen/Funktionen_composition_api.js b/public/js/components/Funktionen/Funktionen_composition_api.js
new file mode 100644
index 000000000..d48e77fd3
--- /dev/null
+++ b/public/js/components/Funktionen/Funktionen_composition_api.js
@@ -0,0 +1,622 @@
+import { Modal } from './Modal.js';
+import { ModalDialog } from './ModalDialog.js';
+import { Toast } from './Toast.js';
+import {OrgChooser} from "./OrgChooser.js";
+import { usePhrasen } from '../../mixins/Phrasen.js';
+
+import ApiFunktion from '../../api/factory/funktionen/person.js';
+import ApiPerson from "../../../extensions/FHC-Core-Personalverwaltung/js/api/factory/person.js";
+
+export const Funktionen = {
+ name: 'FunctionComponentOld',
+ components: {
+ Modal,
+ ModalDialog,
+ Toast,
+ OrgChooser,
+ "datepicker": VueDatePicker
+ },
+ props: {
+ modelValue: { type: Object, default: () => ({}), required: false},
+ config: { type: Object, default: () => ({}), required: false},
+ readonlyMode: { type: Boolean, required: false, default: false },
+ personID: { type: Number, required: false },
+ personUID: { type: String, required: false },
+ writePermission: { type: Boolean, required: false },
+ showDvCompany: { type: Boolean, required: false, default: true }
+ },
+ emits: ['updateHeader'],
+ setup( props, { emit } ) {
+
+ const $api = Vue.inject('$api');
+ //const fhcAlert = Vue.inject('$fhcAlert');
+
+
+ const readonly = Vue.ref(false);
+
+ const { t } = usePhrasen();
+
+ //const { personID: currentPersonID , personUID: currentPersonUID } = Vue.toRefs(props);
+ const currentPersonID = Vue.computed(() => { return props.personID });
+ const currentPersonUID = Vue.computed(() => { return props.personUID });
+
+ const dialogRef = Vue.ref();
+
+ const isFetching = Vue.ref(false);
+
+ const theModel = Vue.computed({
+ get: () => props.modelValue,
+ set: (value) => emit('update:modelValue', value),
+ });
+
+ const unternehmen = Vue.ref();
+ const jobfunctionList = Vue.ref([]);
+ const jobfunctionDefList = Vue.ref([]);
+ const orgUnitList = Vue.ref([{value: '', label: '-'}]);
+
+ const aktivChecked = Vue.ref(true);
+
+ const table = Vue.ref(null); // reference to your table element
+ const tabulator = Vue.ref(null); // variable to hold your table
+ const tableData = Vue.reactive([]); // data for table to display
+
+
+ const full = FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router;
+ const route = ( props.readonlyMode !== true ) ? VueRouter.useRoute() : null;
+
+ const convertArrayToObject = (array, key) => {
+ const initialValue = {};
+ return array.reduce((obj, item) => {
+ return {
+ ...obj,
+ [item[key]]: item,
+ };
+ }, initialValue);
+ };
+
+ const fetchData = async () => {
+ if (currentPersonID.value==null && theModel.value.personID==null) {
+ jobfunctionList.value = [];
+ return;
+ }
+ isFetching.value = true
+
+ // fetch data and map them for easier access
+ try {
+ const response = await $api.call(
+ ApiFunktion.getAllUserFunctions(theModel.value.personUID || currentPersonUID.value));
+ if(response.error === 1) {
+ let rawList = [];
+ tableData.value = [];
+ jobfunctionList.value = convertArrayToObject(rawList, 'benutzerfunktion_id');
+ } else {
+ let rawList = response.retval;
+ tableData.value = response.retval;
+ jobfunctionList.value = convertArrayToObject(rawList, 'benutzerfunktion_id');
+ }
+ } catch (error) {
+ console.log(error)
+ } finally {
+ isFetching.value = false
+ }
+
+ }
+
+ const createShape = () => {
+ return {
+ benutzerfunktion_id: 0,
+ uid: theModel.value.personUID || currentPersonUID.value,
+ oe_kurzbz: "",
+ funktion_kurzbz: "",
+ datum_von: "",
+ datum_bis: "",
+ bezeichnung: "",
+ wochenstunden: 0,
+ }
+ }
+
+ const currentValue = Vue.ref(createShape());
+ const preservedValue = Vue.ref(createShape());
+
+ Vue.watch([currentPersonID, currentPersonUID], ([id,uid]) => {
+ fetchData();
+ });
+
+ Vue.watch(unternehmen, (unternehmen_kurzbz) => {
+ fetchOrgUnits(unternehmen_kurzbz);
+ fetchFunctions();
+ });
+
+ const toggleMode = async () => {
+ if (!readonly.value) {
+ // cancel changes?
+ if (hasChanged.value) {
+ const ok = await dialogRef.value.show();
+ if (ok) {
+ console.log("ok=", ok);
+ currentValue.value = preservedValue.value;
+ } else {
+ return
+ }
+ }
+ } else {
+ // switch to edit mode and preserve data
+ preservedValue.value = {...currentValue.value};
+ }
+ readonly.value = !readonly.value;
+ }
+
+ Vue.onMounted(async () => {
+ currentValue.value = createShape();
+ await fetchData();
+
+ const dateFormatter = (cell) => {
+ return cell.getValue()?.replace(/(.*)-(.*)-(.*)/, '$3.$2.$1');
+ }
+
+ const dvFormatter = (cell) => {
+ if( props.readonlyMode === true ) {
+ return (cell.getValue() != null) ? cell.getValue() : '';
+ }
+ const url = fullPath + route.params.id + '/' + route.params.uid + '/contract/' + cell.getRow().getData().dienstverhaeltnis_id;
+ return cell.getValue() != null ? `` + cell.getValue() + '' : '';
+ }
+
+ // helper
+ const createDomButton = (classValue, clickHandler) => {
+ const nodeBtn = document.createElement("button");
+ const classAttrBtn = document.createAttribute("class");
+ classAttrBtn.value = "btn btn-outline-secondary btn-sm";
+ nodeBtn.setAttributeNode(classAttrBtn);
+ nodeBtn.addEventListener("click", clickHandler);
+ const nodeI = document.createElement("i");
+ const classAttrI = document.createAttribute("class");
+ classAttrI.value = classValue;
+ nodeI.setAttributeNode(classAttrI);
+ nodeBtn.appendChild(nodeI);
+ return nodeBtn;
+ }
+
+ const btnFormatter = (cell) => {
+
+ const nodeDiv = document.createElement("div");
+ const classAttrDiv = document.createAttribute("class");
+ classAttrDiv.value = "d-grid gap-2 d-md-flex justify-content-end align-middle";
+ nodeDiv.setAttributeNode(classAttrDiv);
+ // delete button
+ const nodeBtnDel = createDomButton("fa fa-xmark",() => { showDeleteModal(cell.getValue()) })
+ // edit button
+ const nodeBtnEdit = createDomButton("fa fa-pen",() => { showEditModal(cell.getValue()) })
+
+ if( cell.getRow().getData().dienstverhaeltnis_unternehmen === null ) {
+ nodeDiv.appendChild(nodeBtnEdit);
+ nodeDiv.appendChild(nodeBtnDel);
+ }
+
+ return nodeDiv;
+ }
+
+
+ const columnsDef = [
+ { title: t('person','dv_unternehmen'), field: "dienstverhaeltnis_unternehmen", formatter: dvFormatter, sorter:"string", headerFilter:"list", headerFilterParams: {valuesLookup:true, autocomplete:true, sort:"asc"}, visible: props.showDvCompany },
+ { title: t('person','zuordnung_taetigkeit'), field: "funktion_beschreibung", hozAlign: "left", headerFilter:"list", headerFilterParams: {valuesLookup:true, autocomplete:true, sort:"asc"} },
+ { title: t('lehre','organisationseinheit'), field: "funktion_oebezeichnung", headerFilter:"list", headerFilterParams: {valuesLookup:true, autocomplete:true, sort:"asc"} },
+ { title: t('person','wochenstunden'), field: "wochenstunden", hozAlign: "right", width: 140, headerFilter:true },
+ { title: t('ui','from'), field: "datum_von", hozAlign: "center", formatter: dateFormatter, width: 140, sorter:"string", headerFilter:true, headerFilterFunc:customHeaderFilter },
+ { title: t('global','bis'), field: "datum_bis", hozAlign: "center", formatter: dateFormatter, width: 140, sorter:"string", headerFilter:true, headerFilterFunc:customHeaderFilter },
+ { title: t('ui','bezeichnung'), field: "bezeichnung", hozAlign: "left", headerFilter:"list", headerFilterParams: {valuesLookup:true, autocomplete:true, sort:"asc"} }
+ ];
+
+ if( props.readonlyMode === false) {
+ columnsDef.push({ title: "", field: "benutzerfunktion_id", formatter: btnFormatter, hozAlign: "right", width: 100, headerSort: false, frozen: true });
+ }
+
+ let tabulatorOptions = {
+ height: "100%",
+ layout: "fitColumns",
+ movableColumns: true,
+ reactiveData: true,
+ columns: columnsDef,
+ //data: tableData.value,
+ data: jobfunctionListArray.value,
+ };
+
+ tabulator.value = new Tabulator(
+ table.value,
+ tabulatorOptions
+ );
+
+ function customHeaderFilter(headerValue, rowValue, rowData, filterParams){
+ //headerValue - the value of the header filter element
+ //rowValue - the value of the column in this row
+ //rowData - the data for the row being filtered
+ //filterParams - params object passed to the headerFilterFuncParams property
+
+ const validDate = function(d){
+ return d instanceof Date && isFinite(d);
+ }
+
+ const date1 = new Date(rowValue);
+ date1.setHours(0,0,0,0);
+ let [day, month, year] = headerValue.split('.')
+ if (year < 1000) return true; // prevents dates like 17.5.2
+ const date2 = new Date(+year, +month - 1, +day);
+
+ return !(validDate(date2)) || ((date2 - date1) == 0); //must return a boolean, true if it passes the filter.
+ }
+
+ tabulator.value.on('tableBuilt', () => {
+ //tabulator.value.setData(tableData.value);
+
+ })
+
+ })
+
+ const jobfunctionListArray = Vue.computed(() => {
+ let temp = (jobfunctionList.value ? Object.values(jobfunctionList.value) : []);
+ let filtered = temp.filter((e) => ( !aktivChecked.value || (aktivChecked.value && e.aktiv) ));
+ return filtered;
+ });
+
+ // Workaround to update tabulator
+ Vue.watch(jobfunctionListArray, (newVal, oldVal) => {
+ console.log('jobfunctionList changed');
+ tabulator.value?.setData(jobfunctionListArray.value);
+ }, {deep: true})
+
+ // Modal
+ const modalRef = Vue.ref();
+ const confirmDeleteRef = Vue.ref();
+
+ const showAddModal = () => {
+ currentValue.value = createShape();
+ // reset form state
+ frmState.orgetBlurred=false;
+ frmState.funktionBlurred=false;
+ frmState.beginnBlurred=false;
+ // call bootstrap show function
+ modalRef.value.show();
+ }
+
+ const hideModal = () => {
+ modalRef.value.hide();
+ }
+
+ const showEditModal = async (id) => {
+ currentValue.value = { ...jobfunctionList.value[id] };
+ // fetch company
+ isFetching.value = true;
+ try {
+ const res = await $api.call(ApiFunktion.getCompanyByOrget(currentValue.value.oe_kurzbz));
+ if (res.error == 0) {
+ unternehmen.value = res.retval[0].oe_kurzbz;
+ } else {
+ console.log('company not found for orget!');
+ }
+
+ } catch (error) {
+ console.log(error)
+ } finally {
+ isFetching.value = false
+ }
+ //delete currentValue.value.bezeichnung;
+ modalRef.value.show();
+ }
+
+ const showDeleteModal = async (id) => {
+ currentValue.value = { ...jobfunctionList.value[id] };
+ const ok = await confirmDeleteRef.value.show();
+
+ if (ok) {
+
+ try {
+ const res = await $api.call(ApiPerson.deletePersonJobFunction(id));
+ if (res.error == 0) {
+ delete jobfunctionList.value[id];
+ showDeletedToast();
+ theModel.value.updateHeader();
+ }
+ } catch (error) {
+ console.log(error)
+ } finally {
+ isFetching.value = false
+ }
+
+ }
+ }
+
+
+ const okHandler = async () => {
+ if (!validate()) {
+
+ console.log("form invalid");
+
+ } else {
+
+ // submit
+ try {
+ let payload = {...currentValue.value};
+ delete payload.dienstverhaeltnis_id;
+ delete payload.dienstverhaeltnis_unternehmen;
+ delete payload.fachbereich_bezeichnung;
+ delete payload.funktion_oebezeichnung;
+ delete payload.aktiv;
+ delete payload.funktion_beschreibung;
+ const r = await $api.call(ApiPerson.upsertPersonJobFunction(payload));
+ if (r.error == 0) {
+ // fetch all data because of all the references in the changed record
+ await fetchData();
+ console.log('job function successfully saved');
+ theModel.value.updateHeader();
+ showToast();
+ }
+ } catch (error) {
+ console.log(error)
+ } finally {
+ isFetching.value = false
+ }
+
+ hideModal();
+ }
+ }
+
+ // -------------
+ // form handling
+ // -------------
+
+ const jobFunctionFrm = Vue.ref();
+
+ const frmState = Vue.reactive({ beginnBlurred: false, orgetBlurred: false, funktionBlurred: false, wasValidated: false });
+
+ const notEmpty = (n) => {
+ return !!n && n.trim() != "";
+ }
+
+ const validate = () => {
+ frmState.orgetBlurred = true;
+ frmState.funktionBlurred = true;
+ frmState.beginnBlurred = true;
+ return notEmpty(currentValue.value.datum_von) &&
+ notEmpty(currentValue.value.oe_kurzbz) &&
+ notEmpty(currentValue.value.funktion_kurzbz);
+ }
+
+
+ const hasChanged = Vue.computed(() => {
+ return Object.keys(currentValue.value).some(field => currentValue.value[field] !== preservedValue.value[field])
+ });
+
+ const formatDate = (d) => {
+ if (d != null && d != '') {
+ return d.substring(8, 10) + "." + d.substring(5, 7) + "." + d.substring(0, 4);
+ } else {
+ return ''
+ }
+ }
+
+ // Toast
+ const toastRef = Vue.ref();
+ const deleteToastRef = Vue.ref();
+
+ const showToast = () => {
+ toastRef.value.show();
+ }
+
+ const showDeletedToast = () => {
+ deleteToastRef.value.show();
+ }
+
+ const fetchOrgUnits = async (unternehmen_kurzbz) => {
+ if( unternehmen_kurzbz === '' ) {
+ return;
+ }
+ const response = await $api.call(
+ ApiFunktion.getOrgetsForCompany(unternehmen_kurzbz));
+ const orgets = response.retval;
+ orgets.unshift({
+ value: '',
+ label: t('ui','bitteWaehlen'),
+ });
+ orgUnitList.value = await orgets;
+ }
+
+ const fetchFunctions = async (uid, unternehmen_kurzbz) => {
+ if(unternehmen_kurzbz === '' || uid === '' ) {
+ return;
+ }
+ const response = await $api.call(ApiFunktion.getAllFunctions());
+ const benutzerfunktionen = response.retval;
+ benutzerfunktionen.unshift({
+ value: '',
+ label: t('ui','bitteWaehlen'),
+ });
+ jobfunctionDefList.value = benutzerfunktionen;
+ }
+
+ const unternehmenSelectedHandler = (e) => {
+ console.log('unternehmen selected: ',e);
+ unternehmen.value = e;
+ }
+
+ const ciPath = FHC_JS_DATA_STORAGE_OBJECT.app_root.replace(/(https:|)(^|\/\/)(.*?\/)/g, '') + FHC_JS_DATA_STORAGE_OBJECT.ci_router;
+ const fullPath = `/${ciPath}/extensions/FHC-Core-Personalverwaltung/Employees/`;
+
+ return {
+ jobfunctionList, orgUnitList, jobfunctionListArray,
+ jobfunctionDefList,
+ currentValue,
+ readonly,
+ frmState,
+ dialogRef,
+ toastRef, deleteToastRef,
+ jobFunctionFrm,
+ modalRef,
+ fullPath,
+ route,
+ aktivChecked,
+ unternehmen,
+ tabulator,
+ table,
+
+ toggleMode, formatDate, notEmpty,
+ showToast, showDeletedToast,
+ showAddModal, hideModal, okHandler,
+ showDeleteModal, showEditModal, confirmDeleteRef, t, unternehmenSelectedHandler
+ }
+ },
+ template: `
+
+
+
+
+ {{ t('person','funktionGespeichert') }}
+
+
+
+
+
+ {{ t('person', 'funktionGeloescht') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ t('person','funktionNochNichtGespeichert') }}
+
+
+
+
+
+ {{ t('person','funktion') }} '{{ currentValue?.funktion_kurzbz }} ({{ currentValue?.datum_von }}-{{ currentValue?.datum_bis }})' {{ t('person', 'wirklichLoeschen') }}?
+
+
+ `
+}
+
+export default Funktionen;
\ No newline at end of file
diff --git a/public/js/components/Stv/Studentenverwaltung/Details/Funktionen.js b/public/js/components/Stv/Studentenverwaltung/Details/Funktionen.js
index b58c0805c..3e82cc48b 100644
--- a/public/js/components/Stv/Studentenverwaltung/Details/Funktionen.js
+++ b/public/js/components/Stv/Studentenverwaltung/Details/Funktionen.js
@@ -9,16 +9,17 @@ export default {
},
template: `
-
-
+
{{ $p.t('person', 'funktionen') }}
diff --git a/public/js/components/Stv/Studentenverwaltung/Details/FunktionenOld.js b/public/js/components/Stv/Studentenverwaltung/Details/FunktionenOld.js
new file mode 100644
index 000000000..5188c8d1b
--- /dev/null
+++ b/public/js/components/Stv/Studentenverwaltung/Details/FunktionenOld.js
@@ -0,0 +1,28 @@
+import PersonFunctions from "../../../Funktionen/Funktionen_composition_api";
+
+//import PersonFunctions from "../../../Funktionen/Funktionen.js";
+
+export default {
+ components: {
+ PersonFunctions,
+ },
+ props: {
+ modelValue: Object,
+ },
+ template: `
+
`,
+};
diff --git a/system/phrasesupdate.php b/system/phrasesupdate.php
index 6c52517b6..ed6f71a7f 100644
--- a/system/phrasesupdate.php
+++ b/system/phrasesupdate.php
@@ -25079,7 +25079,7 @@ array(
'phrases' => array(
array(
'sprache' => 'German',
- 'text' => 'Das Eingabefeld {field} darf nur Zahlen enthalten.',
+ 'text' => 'Das Eingabefeld {field} darf nur Ziffern enthalten.',
'description' => '',
'insertvon' => 'system'
),
@@ -41476,6 +41476,106 @@ and represent the current state of research on the topic. The prescribed citatio
)
)
),
+ array(
+ 'app' => 'core',
+ 'category' => 'funktion',
+ 'phrase' => 'filter_active',
+ 'insertvon' => 'system',
+ 'phrases' => array(
+ array(
+ 'sprache' => 'German',
+ 'text' => 'Nur aktive anzeigen',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ array(
+ 'sprache' => 'English',
+ 'text' => 'Display only active',
+ 'description' => '',
+ 'insertvon' => 'system'
+ )
+ )
+ ),
+ array(
+ 'app' => 'core',
+ 'category' => 'funktion',
+ 'phrase' => 'addFunktion',
+ 'insertvon' => 'system',
+ 'phrases' => array(
+ array(
+ 'sprache' => 'German',
+ 'text' => 'Funktion hinzufügen',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ array(
+ 'sprache' => 'English',
+ 'text' => 'Add function',
+ 'description' => '',
+ 'insertvon' => 'system'
+ )
+ )
+ ),
+ array(
+ 'app' => 'core',
+ 'category' => 'funktion',
+ 'phrase' => 'editFunktion',
+ 'insertvon' => 'system',
+ 'phrases' => array(
+ array(
+ 'sprache' => 'German',
+ 'text' => 'Funktion bearbeiten',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ array(
+ 'sprache' => 'English',
+ 'text' => 'Edit function',
+ 'description' => '',
+ 'insertvon' => 'system'
+ )
+ )
+ ),
+ array(
+ 'app' => 'core',
+ 'category' => 'ui',
+ 'phrase' => 'saveAsCopy',
+ 'insertvon' => 'system',
+ 'phrases' => array(
+ array(
+ 'sprache' => 'German',
+ 'text' => 'Als Kopie speichern',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ array(
+ 'sprache' => 'English',
+ 'text' => 'Save as copy',
+ 'description' => '',
+ 'insertvon' => 'system'
+ )
+ )
+ ),
+ array(
+ 'app' => 'core',
+ 'category' => 'ui',
+ 'phrase' => 'error_fieldMustBePositiveInteger',
+ 'insertvon' => 'system',
+ 'phrases' => array(
+ array(
+ 'sprache' => 'German',
+ 'text' => 'Das Eingabefeld {field} darf nur positive Ganzzahlen enthalten.',
+ 'description' => '',
+ 'insertvon' => 'system'
+ ),
+ array(
+ 'sprache' => 'English',
+ 'text' => 'The input field {field} may only contain positive integers',
+ 'description' => '',
+ 'insertvon' => 'system'
+ )
+ )
+ ),
// FHC-4 Studierendenverwaltung FUNCTIONS END
);