From 1d63b8de4417f86d3d742c6e614300861a52b7a6 Mon Sep 17 00:00:00 2001 From: ma0048 Date: Thu, 24 Jul 2025 11:18:58 +0200 Subject: [PATCH 01/22] schluesselverwaltung & kontaktdatenverwaltung gui hinzugefuegt betriebsmittel einschraenkung ueber prop moeglich (nur anzeige) --- .../v1/betriebsmittel/BetriebsmittelP.php | 12 +- .../ressource/Betriebsmittelperson_model.php | 15 ++- .../js/api/factory/betriebsmittel/person.js | 14 ++- .../Betriebsmittel/Betriebsmittel.js | 16 ++- system/filtersupdate.php | 103 ++++++++++++++++++ 5 files changed, 149 insertions(+), 11 deletions(-) diff --git a/application/controllers/api/frontend/v1/betriebsmittel/BetriebsmittelP.php b/application/controllers/api/frontend/v1/betriebsmittel/BetriebsmittelP.php index 8e9d931f2..b8979d99a 100644 --- a/application/controllers/api/frontend/v1/betriebsmittel/BetriebsmittelP.php +++ b/application/controllers/api/frontend/v1/betriebsmittel/BetriebsmittelP.php @@ -60,7 +60,11 @@ class BetriebsmittelP extends FHCAPI_Controller public function getAllBetriebsmittel($type_id, $id) { - $result = $this->BetriebsmittelpersonModel->getBetriebsmittelData($id, $type_id); + $betriebsmitteltypes = null; + if ($this->input->get('betriebsmitteltypes') !== null && !isEmptyArray($this->input->get('betriebsmitteltypes'))) + $betriebsmitteltypes = $this->input->get('betriebsmitteltypes'); + + $result = $this->BetriebsmittelpersonModel->getBetriebsmittelData($id, $type_id, $betriebsmitteltypes); if (isError($result)) { $this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL); @@ -366,6 +370,12 @@ class BetriebsmittelP extends FHCAPI_Controller $this->load->model('ressource/Betriebsmitteltyp_model', 'BetriebsmitteltypModel'); $this->BetriebsmitteltypModel->addOrder('beschreibung', 'ASC'); + + if ($this->input->get('betriebsmitteltypes') !== null && !isEmptyArray($this->input->get('betriebsmitteltypes'))) + { + $this->BetriebsmitteltypModel->db->where_in('betriebsmitteltyp', $this->input->get('betriebsmitteltypes')); + } + $result = $this->BetriebsmitteltypModel->load(); // load All if (isError($result)) { diff --git a/application/models/ressource/Betriebsmittelperson_model.php b/application/models/ressource/Betriebsmittelperson_model.php index 6da9a384d..219af51b8 100644 --- a/application/models/ressource/Betriebsmittelperson_model.php +++ b/application/models/ressource/Betriebsmittelperson_model.php @@ -97,7 +97,7 @@ class Betriebsmittelperson_model extends DB_Model return $this->loadWhere($condition); } - public function getBetriebsmittelData($id, $type_id) + public function getBetriebsmittelData($id, $type_id, $betriesmitteltypes = null) { switch ($type_id) { case 'person_id': @@ -113,6 +113,15 @@ class Betriebsmittelperson_model extends DB_Model return error("ID nicht gültig"); } + $cond .= " = ? "; + $params[] = $id; + + if ($betriesmitteltypes && !isEmptyArray($betriesmitteltypes)) + { + $cond .= " AND bm.betriebsmitteltyp IN ?"; + $params[] = $betriesmitteltypes; + } + $query = " SELECT bm.nummer, bmp.person_id, bm.betriebsmitteltyp, bmp.anmerkung as anmerkung, @@ -126,9 +135,9 @@ class Betriebsmittelperson_model extends DB_Model JOIN wawi.tbl_betriebsmittel bm ON (bmp.betriebsmittel_id = bm.betriebsmittel_id) WHERE - " . $cond . " = ? "; + " . $cond; - return $this->execQuery($query, array($id)); + return $this->execQuery($query, $params); } /** diff --git a/public/js/api/factory/betriebsmittel/person.js b/public/js/api/factory/betriebsmittel/person.js index 912a5df31..504e755b6 100644 --- a/public/js/api/factory/betriebsmittel/person.js +++ b/public/js/api/factory/betriebsmittel/person.js @@ -16,10 +16,13 @@ */ export default { - getAllBetriebsmittel(type, id) { + getAllBetriebsmittel(type, id, betriebsmitteltypes) { return { method: 'get', - url: 'api/frontend/v1/betriebsmittel/betriebsmittelP/getAllBetriebsmittel/' + type + '/' + id + url: 'api/frontend/v1/betriebsmittel/betriebsmittelP/getAllBetriebsmittel/' + type + '/' + id, + params: { + betriebsmitteltypes + } }; }, addNewBetriebsmittel(person_id, params) { @@ -48,10 +51,13 @@ export default { url: 'api/frontend/v1/betriebsmittel/betriebsmittelP/deleteBetriebsmittel/' + betriebsmittelperson_id }; }, - getTypenBetriebsmittel() { + getTypenBetriebsmittel(betriebsmitteltypes) { return { method: 'get', - url: 'api/frontend/v1/betriebsmittel/betriebsmittelP/getTypenBetriebsmittel/' + url: 'api/frontend/v1/betriebsmittel/betriebsmittelP/getTypenBetriebsmittel/', + params: { + betriebsmitteltypes + } }; }, loadInventarliste(query) { diff --git a/public/js/components/Betriebsmittel/Betriebsmittel.js b/public/js/components/Betriebsmittel/Betriebsmittel.js index 39681ffb4..d1188b371 100644 --- a/public/js/components/Betriebsmittel/Betriebsmittel.js +++ b/public/js/components/Betriebsmittel/Betriebsmittel.js @@ -29,14 +29,24 @@ export default { uid: { type: [Number, String], required: true - } + }, + /** List of types to allow for creation */ + betriebsmittelTypes: { + type: Array, + default: null + }, + /** + * If true: only show the types specified in 'betriebsmittelTypes'. + * If false: show all available types. + */ + filterByProvidedTypes: Boolean }, data() { return { tabulatorOptions: { ajaxURL: 'dummy', ajaxRequestFunc: () => this.$api.call( - this.endpoint.getAllBetriebsmittel(this.typeId, this.id) + this.endpoint.getAllBetriebsmittel(this.typeId, this.id, (this.filterByProvidedTypes ? this.betriebsmittelTypes : null)) ), ajaxResponse: (url, params, response) => response.data, columns: [ @@ -296,7 +306,7 @@ export default { }, created() { return this.$api - .call(this.endpoint.getTypenBetriebsmittel()) + .call(this.endpoint.getTypenBetriebsmittel(this.betriebsmittelTypes)) .then(result => { this.listBetriebsmitteltyp = result.data; }) diff --git a/system/filtersupdate.php b/system/filtersupdate.php index 51dd86314..9e3c62a33 100644 --- a/system/filtersupdate.php +++ b/system/filtersupdate.php @@ -1456,6 +1456,109 @@ $filters = array( ', 'oe_kurzbz' => null, ), + array( + 'app' => 'personalverwaltung', + 'dataset_name' => 'schluesselverwaltung', + 'filter_kurzbz' => 'ma4schluesselverwaltung', + 'description' => '{MA Schluesselverwaltung}', + 'sort' => 1, + 'default_filter' => true, + 'filter' => ' + { + "name": "MA Schlüsselverwaltung", + "columns": [ + {"name": "UID"}, + {"name": "PersonId"}, + {"name": "Vorname"}, + {"name": "Nachname"}, + {"name": "EMail"}, + {"name": "Unternehmen"}, + {"name": "Vertragsart"}, + {"name": "DV_von"}, + {"name": "DV_bis"}, + {"name": "Wochenstunden"}, + {"name": "WS_von"}, + {"name": "WS_bis"}, + {"name": "Standardkostenstelle"}, + {"name": "DV_status"} + ], + "filters": [ + { + "name": "DV_status", + "option": "", + "operation": "nequal", + "condition": "beendet" + } + ] + } + ', + 'oe_kurzbz' => null, + ), + array( + 'app' => 'personalverwaltung', + 'dataset_name' => 'schluesselverwaltung', + 'filter_kurzbz' => 'ma4schluesselverwaltung_beendet', + 'description' => '{MA Schluesselverwaltung (DV beendet)}', + 'sort' => 2, + 'default_filter' => false, + 'filter' => ' + { + "name": "MA Schlüsselverwaltung (DV beendet)", + "columns": [ + {"name": "UID"}, + {"name": "PersonId"}, + {"name": "Vorname"}, + {"name": "Nachname"}, + {"name": "EMail"}, + {"name": "Unternehmen"}, + {"name": "Vertragsart"}, + {"name": "DV_von"}, + {"name": "DV_bis"}, + {"name": "Wochenstunden"}, + {"name": "WS_von"}, + {"name": "WS_bis"}, + {"name": "Standardkostenstelle"}, + {"name": "DV_status"} + ], + "filters": [ + { + "name": "DV_status", + "option": "", + "operation": "equal", + "condition": "beendet" + } + ] + } + ', + 'oe_kurzbz' => null, + ), + array( + 'app' => 'personalverwaltung', + 'dataset_name' => 'kontaktdatenverwaltung', + 'filter_kurzbz' => 'ma4kontaktdaten', + 'description' => '{MA Kontaktdatenverwaltung}', + 'sort' => 1, + 'default_filter' => true, + 'filter' => ' + { + "name": "MA Kontaktdatenverwaltung", + "columns": [ + {"name": "UID"}, + {"name": "PersonId"}, + {"name": "Vorname"}, + {"name": "Nachname"}, + {"name": "Unternehmen"}, + {"name": "Vertragsart"}, + {"name": "DV_von"}, + {"name": "DV_bis"}, + {"name": "Disziplinaere_Zuordnung"}, + {"name": "DV_status"} + ], + "filters": [] + }', + 'oe_kurzbz' => null, + ), + ); // Loop through the filters array From 49ddf568e101f9c7d89a571a5e6bd6ada3ed3d1f Mon Sep 17 00:00:00 2001 From: chfhtw Date: Wed, 6 Aug 2025 08:46:02 +0200 Subject: [PATCH 02/22] filter component: keep column visibility on reload --- public/js/components/filter/Filter.js | 58 ++++++++++++++++++++++----- 1 file changed, 49 insertions(+), 9 deletions(-) diff --git a/public/js/components/filter/Filter.js b/public/js/components/filter/Filter.js index fc4ed81c5..07f88cccc 100644 --- a/public/js/components/filter/Filter.js +++ b/public/js/components/filter/Filter.js @@ -91,7 +91,6 @@ export const CoreFilterCmpt = { dataset: null, datasetMetadata: null, selectedFields: null, - notSelectedFields: null, filterFields: null, availableFilters: null, @@ -103,6 +102,8 @@ export const CoreFilterCmpt = { fetchCmptApiFunctionParams: null, fetchCmptDataFetched: null, + fetchResult: null, + tabulator: null, tableBuilt: false, tabulatorHasSelector: false, @@ -118,6 +119,11 @@ export const CoreFilterCmpt = { }; }, computed: { + notSelectedFields() { + if (!this.fields || !this.selectedFields) + return null; + return this.fields.filter(x => this.selectedFields.indexOf(x) === -1) + }, filteredData() { if (!this.dataset) return []; @@ -215,6 +221,32 @@ export const CoreFilterCmpt = { await this.$p.loadCategory('ui'); placeholder = this.$p.t('ui/keineDatenVorhanden'); } + + if (!this.tableOnly) { + // prefetch data to get fields & selectedFields for filteredColumns & filteredData + await new Promise(resolve => { + const filterId = window.location.hash ? window.location.hash.slice(1) : null; + + const resolvePromiseFunc = data => { + this.setRenderData(data); + resolve(); + }; + // get the filter data + if (filterId === null) + this.startFetchCmpt( + wsParams => this.$api.call(ApiFilter.getFilter(wsParams)), + null, + resolvePromiseFunc + ); + else + this.startFetchCmpt( + wsParams => this.$api.call(ApiFilter.getFilterById(wsParams)), + { filterId }, + resolvePromiseFunc + ); + }); + } + // Define a default tabulator options in case it was not provided let tabulatorOptions = {...{ layout: "fitDataStretchFrozen", @@ -236,6 +268,11 @@ export const CoreFilterCmpt = { if (!this.tableOnly) { tabulatorOptions.data = this.filteredData; tabulatorOptions.columns = this.filteredColumns; + } else { + tabulatorOptions.columns.forEach(col => { + if (col.visible === undefined) + col.visible = true; + }); } if (tabulatorOptions.selectable || (tabulatorOptions.columns && tabulatorOptions.columns.filter(el => el.formatter == 'rowSelection').length)) @@ -355,18 +392,14 @@ export const CoreFilterCmpt = { this.render ); }, - /** - * - */ - render(response) { - let data = response; + setRenderData(data) { + this.fetchResult = data; this.filterName = data.filterName; this.dataset = data.dataset; this.datasetMetadata = data.datasetMetadata; this.fields = data.fields; this.selectedFields = data.selectedFields; - this.notSelectedFields = this.fields.filter(x => this.selectedFields.indexOf(x) === -1); this.filterFields = []; for (let i = 0; i < data.datasetMetadata.length; i++) @@ -383,6 +416,14 @@ export const CoreFilterCmpt = { } } } + }, + /** + * + */ + render(response) { + let data = response; + + this.setRenderData(data); // If the side menu is active if (this.sideMenu === true) @@ -623,11 +664,10 @@ export const CoreFilterCmpt = { this.$emit('uuidDefined', this.uuid) }, mounted() { - this.initTabulator().then(() => { if (!this.tableOnly) { this.selectedFilter = window.location.hash ? window.location.hash.slice(1) : null; - this.getFilter(); // get the filter data + this.render(this.fetchResult); } }); From 42a40072fa0eb741a767b3fb00dc9b3f272d7e46 Mon Sep 17 00:00:00 2001 From: ma0068 Date: Wed, 6 Aug 2025 10:55:58 +0200 Subject: [PATCH 03/22] show all errors in one alert - refactor Dropdown.js: use new factory for overwriting errors - move functionaliy to show Errors and InfoAlerts to Api.js - extend FhcAlert (primeVue) with slot for toast for ability to use html --- .../api/frontend/v1/stv/Status.php | 7 +- .../Details/Status/Dropdown.js | 49 +++++------- public/js/plugins/Api.js | 76 ++++++++++++++++++- public/js/plugins/FhcAlert.js | 19 +++-- 4 files changed, 110 insertions(+), 41 deletions(-) diff --git a/application/controllers/api/frontend/v1/stv/Status.php b/application/controllers/api/frontend/v1/stv/Status.php index fcc8007cd..7e649f3cf 100644 --- a/application/controllers/api/frontend/v1/stv/Status.php +++ b/application/controllers/api/frontend/v1/stv/Status.php @@ -24,7 +24,6 @@ class Status extends FHCAPI_Controller 'updateStatus' => ['admin:rw', 'assistenz:rw'], 'advanceStatus' => ['admin:rw', 'assistenz:rw'], 'confirmStatus' => ['admin:rw', 'assistenz:rw'], - ]); //Load Models @@ -435,9 +434,10 @@ class Status extends FHCAPI_Controller ]); if (!$this->form_validation->run()) + { $this->terminateWithValidationErrors($this->form_validation->error_array()); + } - $this->load->library('PrestudentLib'); $this->db->trans_start(); @@ -623,8 +623,9 @@ class Status extends FHCAPI_Controller ]); if (!$this->form_validation->run()) + { $this->terminateWithValidationErrors($this->form_validation->error_array()); - + } // Start DB transaction $this->db->trans_start(); diff --git a/public/js/components/Stv/Studentenverwaltung/Details/Status/Dropdown.js b/public/js/components/Stv/Studentenverwaltung/Details/Status/Dropdown.js index ab8f83fab..cff6b2be6 100644 --- a/public/js/components/Stv/Studentenverwaltung/Details/Status/Dropdown.js +++ b/public/js/components/Stv/Studentenverwaltung/Details/Status/Dropdown.js @@ -142,14 +142,15 @@ export default { this.addStudent({status_kurzbz: 'student', statusgrund_id}); }, addStudent(data) { - Promise - .allSettled( - this.prestudentIds.map(prestudent_id => this.$api.call( - ApiStvStatus.addStudent(prestudent_id, data), - { errorHeader: prestudent_id } - )) - ) - .then(res => this.showFeedback(res, data.status_kurzbz)); + this.$api.call(this.prestudentIds.map(prestudent_id => [ + prestudent_id, + ApiStvStatus.addStudent(prestudent_id, data), + { errorHeader: prestudent_id } + ])) + .then(() => { + this.$emit('reloadTable'); + this.$reloadList(); + }); }, changeStatusToAbbrecher(statusgrund_id) { this @@ -242,31 +243,15 @@ export default { return askForSemester(); }, changeStatus(data) { - Promise - .allSettled( - this.prestudentIds.map(prestudent_id => this.$api.call( - ApiStvStatus.changeStatus(prestudent_id, data), - { errorHeader: prestudent_id } - )) - ) - .then(res => this.showFeedback(res, data.status_kurzbz)); - }, - showFeedback(results, status_kurzbz) { - const countSuccess = results.filter(result => result.status == "fulfilled").length; - const countError = results.length - countSuccess; - - //Feedback Success als infoalert - this.$fhcAlert.alertInfo(this.$p.t('ui', 'successNewStatus', { - countSuccess, - status: status_kurzbz, - countError - })); - - if(results.length == 1 && countSuccess > 0){ + this.$api.call(this.prestudentIds.map(prestudent_id => [ + prestudent_id, + ApiStvStatus.changeStatus(prestudent_id, data) + ])) + .then(() => { this.$emit('reloadTable'); - } - this.$reloadList(); - } + this.$reloadList(); + }); + }, }, created() { this.$api diff --git a/public/js/plugins/Api.js b/public/js/plugins/Api.js index d0ffaa439..47139b79f 100644 --- a/public/js/plugins/Api.js +++ b/public/js/plugins/Api.js @@ -42,10 +42,13 @@ export default { } function _clean_return_value(response) { + if (typeof response.data === 'string' || response.data instanceof String) + return _clean_return_value({ data: response }); + const result = response.data; delete response.data; if (!result.meta) - result.meta = {response}; + result.meta = { response }; else result.meta.response = response; return result; @@ -159,6 +162,77 @@ export default { return fhcApiAxios.post(uri, data, config); }, call(factory, configoverwrite, form) { + if (Array.isArray(factory)) { + const $fhcAlert = app.config.globalProperties.$fhcAlert; + const $api = app.config.globalProperties.$api; + + Promise + .allSettled(factory.map((config, index) => { + if (Array.isArray(config)) + return $api.call(config[1], { + errorHeader: config[0], + errorHandling: false + }); + else + return $api.call(config, { + errorHeader: '#' + index, + errorHandling: false + }); + })) + .then(res => { + // TODO(chris): obey form & configoverwrite + let messagesError = []; + let messagesSuccessful = []; + + res.forEach(result => { + if (result.status === 'fulfilled') { + //console.log(JSON.parse(result.value.data)); + const successTitle = "
" + result.value.data + "
"; + messagesSuccessful.push(successTitle + "ok"); + } else { + const errorTitle = "
" + result.reason.config.errorHeader + "
"; + const errorMsg = JSON.parse(result.reason.request.response); + const fullMessage = errorMsg.errors.map(error => { + if (error.type == 'validation') { + // TODO(chris): do we want the keys? + return '
' + Object.values(error.messages).join("
") + '
'; + } + // TODO(chris): other types + if (error.message) + return '
' + error.message + '
'; + if (error.messages) + return '
' + error.messages.join("\n") + '
'; + // TODO(chris): what to do here + return '
' + "Generic Error" + '
'; // TODO(chris): translate + }).join("\n"); + messagesError.push(errorTitle + fullMessage); + } + }); + + if (messagesError.length) + { + const test = document.createElement('b'); + $fhcAlert.alertDefault( + 'error', + messagesError.length + " Fehler", // TODO(chris): translate + '
' + messagesError.join("") + '
', + true, + true + ); + } + if (messagesSuccessful.length) + { + const test = document.createElement('b'); + $fhcAlert.alertDefault( + 'info', + 'Feedback', + messagesSuccessful.length + " erfolgreiche Statusänderung(en) durchgeführt", // TODO(chris): translate + false, + true + ); + } + }); + } let {method, url, params, config} = factory; if (configoverwrite !== undefined) { config = configoverwrite; diff --git a/public/js/plugins/FhcAlert.js b/public/js/plugins/FhcAlert.js index f90b61ac2..5d84b1de9 100644 --- a/public/js/plugins/FhcAlert.js +++ b/public/js/plugins/FhcAlert.js @@ -140,7 +140,16 @@ const helperApp = Vue.createApp({ } }, template: /* html */` - + + +