From c5c29df6ccd6415607e813fc1d1df7152f793420 Mon Sep 17 00:00:00 2001 From: Alexei Karpenko Date: Tue, 14 Jul 2026 20:51:41 +0200 Subject: [PATCH 01/32] Gehaltsbestandteil, Gehaltsbestandteil_model: added gehaltstyp_bezeichnung --- .../vertragsbestandteil/Gehaltsbestandteil.php | 11 ++++++++++- .../vertragsbestandteil/Gehaltsbestandteil_model.php | 9 ++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/application/libraries/vertragsbestandteil/Gehaltsbestandteil.php b/application/libraries/vertragsbestandteil/Gehaltsbestandteil.php index 95bbdd908..70c362ed7 100644 --- a/application/libraries/vertragsbestandteil/Gehaltsbestandteil.php +++ b/application/libraries/vertragsbestandteil/Gehaltsbestandteil.php @@ -21,6 +21,7 @@ class Gehaltsbestandteil extends AbstractBestandteil implements \JsonSerializabl protected $valorisierungssperre; protected $valorisierung; protected $auszahlungen; + protected $gehaltstyp_bezeichnung; protected $insertamum; protected $insertvon; @@ -47,7 +48,8 @@ class Gehaltsbestandteil extends AbstractBestandteil implements \JsonSerializabl isset($data->valorisierungssperre) && $this->setValorisierungssperre($data->valorisierungssperre); isset($data->valorisierung) && $this->setValorisierung($data->valorisierung); isset($data->auszahlungen) && $this->setAuszahlungen($data->auszahlungen); - + isset($data->gehaltstyp_bezeichnung) && $this->setGehaltstyp_bezeichnung($data->gehaltstyp_bezeichnung); + isset($data->insertamum) && $this->setInsertamum($data->insertamum); isset($data->insertvon) && $this->setInsertvon($data->insertvon); isset($data->updateamum) && $this->setUpdateamum($data->updateamum); @@ -242,6 +244,13 @@ class Gehaltsbestandteil extends AbstractBestandteil implements \JsonSerializabl return $this; } + public function setGehaltstyp_bezeichnung($gehaltstyp_bezeichnung) + { + $this->markDirty('gehaltstyp_bezeichnung', $this->gehaltstyp_bezeichnung, $gehaltstyp_bezeichnung); + $this->gehaltstyp_bezeichnung = $gehaltstyp_bezeichnung; + return $this; + } + public function setInsertamum($insertamum) { $this->markDirty('insertamum', $this->insertamum, $insertamum); diff --git a/application/models/vertragsbestandteil/Gehaltsbestandteil_model.php b/application/models/vertragsbestandteil/Gehaltsbestandteil_model.php index 2669105a1..57031ac08 100644 --- a/application/models/vertragsbestandteil/Gehaltsbestandteil_model.php +++ b/application/models/vertragsbestandteil/Gehaltsbestandteil_model.php @@ -177,7 +177,8 @@ LEFT JOIN $stichtagclause .= ')'; } - $this->addSelect('*'); + $this->addSelect('tbl_gehaltsbestandteil.*, tbl_gehaltstyp.bezeichnung AS gehaltstyp_bezeichnung'); + $this->addJoin('hr.tbl_gehaltstyp', 'gehaltstyp_kurzbz'); $where = <<escape($dienstverhaeltnis_id)} {$stichtagclause} @@ -213,9 +214,11 @@ SELECT g.updateamum, g.updatevon, g.valorisierung, - g.auszahlungen + g.auszahlungen, + gt.bezeichnung AS gehaltstyp_bezeichnung FROM - hr.tbl_gehaltsbestandteil g + hr.tbl_gehaltsbestandteil g + JOIN hr.tbl_gehaltstyp gt USING (gehaltstyp_kurzbz) LEFT JOIN hr.tbl_valorisierung_historie vh ON vh.gehaltsbestandteil_id = g.gehaltsbestandteil_id AND vh.valorisierungsdatum = ( SELECT From 13515b25227ab2a4c6c8857ee7a6b490881f8bca Mon Sep 17 00:00:00 2001 From: chfhtw Date: Tue, 4 Aug 2026 09:40:47 +0200 Subject: [PATCH 02/32] Bugfix: after a drag and drop operation, when resizing the page, the moved item stays in the drop position even if it has had a position in the resized layout --- public/js/components/Drop/Grid.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/public/js/components/Drop/Grid.js b/public/js/components/Drop/Grid.js index 6d9777c28..d4d8c90d9 100644 --- a/public/js/components/Drop/Grid.js +++ b/public/js/components/Drop/Grid.js @@ -233,6 +233,14 @@ export default { if (value == oldValue) return; + /** NOTE(chris): tempPositionUpdates depends on the cols not to be + * updated. So if they do we reset to get a clean value. In case the + * drag and drop operation is still motion on the next tick this + * will be correct again and if there is no such operation we clean + * up. + */ + this.tempPositionUpdates = null; + this.reinitGrid(); }, rows: { @@ -246,6 +254,11 @@ export default { }, indexedItems: { handler(value) { + /** NOTE(chris): there was an update of the items, so in case + * it came from a drag and drop operation we need to clean up + */ + this.tempPositionUpdates = null; + this.reinitGrid(); }, immediate: true, From d824334538bd7f12df511413c601a18c6906196c Mon Sep 17 00:00:00 2001 From: chfhtw Date: Tue, 4 Aug 2026 09:42:18 +0200 Subject: [PATCH 03/32] Hiding widgets should look like deleting and recovering is moved to the "add widget" popup --- public/js/components/Dashboard/Dashboard.js | 43 +++++++++++---- public/js/components/Dashboard/Item.js | 28 +--------- public/js/components/Dashboard/Section.js | 19 +------ .../js/components/Dashboard/Widget/Picker.js | 55 +++++++++++++++---- 4 files changed, 81 insertions(+), 64 deletions(-) diff --git a/public/js/components/Dashboard/Dashboard.js b/public/js/components/Dashboard/Dashboard.js index bf45ad227..b88765856 100644 --- a/public/js/components/Dashboard/Dashboard.js +++ b/public/js/components/Dashboard/Dashboard.js @@ -62,6 +62,9 @@ export default { return [type, result]; })); + }, + hiddenWidgets() { + return this.widgets.filter(widget => widget.hidden); } }, methods: { @@ -69,6 +72,9 @@ export default { this.$refs.widgetpicker .getWidget() .then(widget_id => { + if (widget_id.hidden) + this.widgetUnhide(widget_id, widget.place); + widget.widget = widget_id; // NOTE(chris): min size widget.place = Object.fromEntries(Object.entries(widget.place).map(([key, value]) => { @@ -131,17 +137,6 @@ export default { this.widgets.forEach((widget, i) => { if (failed.includes(widget.id)) { this.widgets[i] = structuredClone(ObjectUtils.deepToRaw(this.originalWidgets[widget.id])); - /** NOTE(chris): if you wanna hide or unhide a - * preset and it fails: switch around the hidden - * value to revert it properly (checkboxes can't - * really handle it otherwise) - */ - if (payload[widget.id].hidden !== undefined) { - this.widgets[i].hidden = payload[widget.id].hidden; - this.$nextTick(() => { - this.widgets[i] = structuredClone(ObjectUtils.deepToRaw(this.originalWidgets[widget.id])); - }); - } } else if (payload[widget.id]) { payload[widget.id].id = widget.id; payload[widget.id].index = widget.index; @@ -160,6 +155,31 @@ export default { }) .catch(this.$fhcAlert.handleSystemError); }, + widgetUnhide(orig, place) { + place = Object.fromEntries(Object.entries(place).map(([key, value]) => { + value.w = this.sizeLimits[orig.widget].width.min; + value.h = this.sizeLimits[orig.widget].height.min; + // NOTE(chris): unpin pinned widgets because they got a new placement + value.pinned = false; + return [key, value]; + })); + // NOTE(chris): clear placement in other screensizes + Object.keys(orig.place).forEach(key => { + if (!place[key]) { + place[key] = { + x: undefined, + y: undefined, + pinned: false + }; + } + }); + let payload = {}; + payload[orig.id] = { + hidden: false, + place + }; + return this.widgetUpdate(payload); + }, async fetchViewData() { let viewDataResult = await this.$api.call(ApiDashboard.getViewData()); const viewData = viewDataResult.data; @@ -223,6 +243,7 @@ export default { ` } diff --git a/public/js/components/Dashboard/Item.js b/public/js/components/Dashboard/Item.js index 20c195178..160b34e91 100644 --- a/public/js/components/Dashboard/Item.js +++ b/public/js/components/Dashboard/Item.js @@ -36,7 +36,6 @@ export default { "width", "height", "custom", - "hidden", "editMode", "loading", // widget got added and is waiting for backend to save in db "item_data", @@ -115,14 +114,6 @@ export default { }, ready() { return this.component && this.arguments !== null; - }, - visible: { - get() { - return !this.hidden; - }, - set(value) { - this.$emit('remove', this.hidden); - } } }, methods: { @@ -202,10 +193,8 @@ export default { }, template: /*html*/ `
@@ -299,9 +288,9 @@ export default { > - + - - -
- -
-
diff --git a/public/js/components/Dashboard/Section.js b/public/js/components/Dashboard/Section.js index e8c56fd4e..b55eefb7a 100644 --- a/public/js/components/Dashboard/Section.js +++ b/public/js/components/Dashboard/Section.js @@ -124,8 +124,6 @@ export default { return { ...item, ...placement, weight }; }); - if (this.editModeIsActive) - return placedItems; return placedItems.filter(item => !item.hidden); } }, @@ -141,22 +139,12 @@ export default { handleConfigClosed() { this.configOpened = false }, - removeWidget(item, revert) { + removeWidget(item) { if (item.custom) { BsConfirm.popup(this.$p.t('dashboard', 'alert_deleteWidget')).then(() => this.$emit('widgetRemove', item.id, this.name)); } else { let update = {}; - update[item.id] = { hidden: !revert }; - - if (!revert) { - // NOTE(chris): move to last line - update[item.id].place = []; - let y = this.gridHeight; - if (this.additionalRow) - y--; - update[item.id].place[this.gridWidth] = { x: 0, y }; - } - + update[item.id] = { hidden: true }; this.updatePreset(update); } }, @@ -251,13 +239,12 @@ export default { :loading="item.loading" :config="item.config" :custom="item.custom" - :hidden="item.hidden" :editMode="editModeIsActive" :place="item.place[gridWidth]" :widget-template="indexedWidgetsTemplates[item.widget]" :source="adminMode ? null : item.source || 'custom'" @change="saveConfig($event, item)" - @remove="removeWidget(item, $event)" + @remove="removeWidget(item)" @config-opened="handleConfigOpened" @config-closed="handleConfigClosed" @pin-item="updatePositions" diff --git a/public/js/components/Dashboard/Widget/Picker.js b/public/js/components/Dashboard/Widget/Picker.js index 260b95874..284eb551c 100644 --- a/public/js/components/Dashboard/Widget/Picker.js +++ b/public/js/components/Dashboard/Widget/Picker.js @@ -7,11 +7,21 @@ export default { WidgetIcon, }, props: [ - "widgets" + "widgets", + "hiddenWidgets" ], data: () => ({ callbacks: {} }), + computed: { + hasAnyWidgets() { + if (!this.widgets) + return false; + if (!this.widgets.length && !this.hiddenWidgets?.length) + return false; + return true; + } + }, methods: { getWidget() { return new Promise((resolve,reject) => { @@ -37,23 +47,46 @@ export default {
From 2f86a1b0171738b58baef4a114d82115e1967cb3 Mon Sep 17 00:00:00 2001 From: chfhtw Date: Tue, 4 Aug 2026 10:46:08 +0200 Subject: [PATCH 05/32] Changing the edit button into a toggle --- public/js/components/Dashboard/Dashboard.js | 26 ++++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/public/js/components/Dashboard/Dashboard.js b/public/js/components/Dashboard/Dashboard.js index b88765856..e390f16d4 100644 --- a/public/js/components/Dashboard/Dashboard.js +++ b/public/js/components/Dashboard/Dashboard.js @@ -224,15 +224,23 @@ export default { }, template: /* html */`
-

- {{ userFirstName ? $p.t('global/personalGreeting', [ userFirstName ]) : '' }} - -

+
+

+ {{ userFirstName ? $p.t('global/personalGreeting', [ userFirstName ]) : '' }} +

+
+ + +
+
Date: Mon, 10 Aug 2026 12:20:30 +0200 Subject: [PATCH 06/32] add db schema for gehaltsanpassungtyp --- system/dbupdate_3.4/28260_vertraege.php | 40 +++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/system/dbupdate_3.4/28260_vertraege.php b/system/dbupdate_3.4/28260_vertraege.php index 1e0e4e048..5615dc1f4 100644 --- a/system/dbupdate_3.4/28260_vertraege.php +++ b/system/dbupdate_3.4/28260_vertraege.php @@ -49,6 +49,7 @@ if ($result = $db->db_query("SELECT * FROM information_schema.tables WHERE table dienstverhaeltnis_id integer NOT NULL, vertragsbestandteil_id integer, gehaltstyp_kurzbz character varying(32) NOT NULL, + gehaltsanpassungtyp_kurzbz character varying(32), von date, bis date, anmerkung text, @@ -69,6 +70,7 @@ if ($result = $db->db_query("SELECT * FROM information_schema.tables WHERE table COMMENT ON COLUMN hr.tbl_gehaltsbestandteil.auszahlungen IS E'Wie oft im Jahr wird das Gehalt bezahlt. zb 14x oder nur 12x'; COMMENT ON COLUMN hr.tbl_gehaltsbestandteil.valorisierung IS E'Wird dieser Bestandteil mitvalorisiert'; COMMENT ON COLUMN hr.tbl_gehaltsbestandteil.valorisierungssperre IS E'Bis zu welchem Datum ist dieser Bestandteil von der Valorisierung ausgenommen'; + COMMENT ON COLUMN hr.tbl_gehaltsbestandteil.gehaltsanpassungtyp_kurzbz IS E'Ersteinstufung, indiv. Erhöhung, etc.'; ALTER TABLE hr.tbl_gehaltsbestandteil ADD CONSTRAINT tbl_dienstverhaeltnis_fk FOREIGN KEY (dienstverhaeltnis_id) REFERENCES hr.tbl_dienstverhaeltnis (dienstverhaeltnis_id) MATCH FULL @@ -120,6 +122,21 @@ if ($result = $db->db_query("SELECT * FROM information_schema.tables WHERE table REFERENCES hr.tbl_gehaltstyp (gehaltstyp_kurzbz) MATCH FULL ON DELETE RESTRICT ON UPDATE CASCADE; + CREATE TABLE hr.tbl_gehaltsanpassungtyp + ( + gehaltsanpassungtyp_kurzbz character varying(32) NOT NULL, + bezeichnung varchar(256), + sort smallint, + aktiv boolean NOT NULL DEFAULT true, + CONSTRAINT tbl_gehaltsanpassungtyp_pk PRIMARY KEY (gehaltsanpassungtyp_kurzbz) + ); + + COMMENT ON TABLE hr.tbl_gehaltsanpassungtyp IS E'Key-Table of Salary Adaption Types'; + + ALTER TABLE hr.tbl_gehaltsbestandteil ADD CONSTRAINT tbl_gehaltsanpassungtyp_fk FOREIGN KEY (gehaltsanpassungtyp_kurzbz) + REFERENCES hr.tbl_gehaltsanpassungtyp (gehaltsanpassungtyp_kurzbz) MATCH FULL + ON DELETE RESTRICT ON UPDATE CASCADE; + CREATE TABLE hr.tbl_vertragsart ( vertragsart_kurzbz varchar(32) NOT NULL, @@ -368,6 +385,7 @@ if ($result = $db->db_query("SELECT * FROM information_schema.tables WHERE table GRANT SELECT, UPDATE, INSERT, DELETE ON hr.tbl_gehaltsbestandteil TO vilesci; GRANT SELECT, UPDATE, INSERT, DELETE ON hr.tbl_gehaltshistorie TO vilesci; GRANT SELECT, UPDATE, INSERT, DELETE ON hr.tbl_gehaltstyp TO vilesci; + GRANT SELECT, UPDATE, INSERT, DELETE ON hr.tbl_gehaltsanpassungtyp TO vilesci; GRANT USAGE ON SCHEMA hr TO vilesci; GRANT USAGE ON SCHEMA hr TO web; @@ -398,6 +416,10 @@ if ($result = $db->db_query("SELECT * FROM information_schema.tables WHERE table INSERT INTO hr.tbl_gehaltstyp(gehaltstyp_kurzbz, bezeichnung, valorisierung, sort, aktiv) VALUES('lohnausgleichatz','Lohnausgleich ATZ', false, 5, true); INSERT INTO hr.tbl_gehaltstyp(gehaltstyp_kurzbz, bezeichnung, valorisierung, sort, aktiv) VALUES('zusatzvereinbarung','Zusatzvereinbarung',false, 6, true); + INSERT INTO hr.tbl_gehaltsanpassungtyp(gehaltsanpassungtyp_kurzbz, bezeichnung, sort, aktiv) VALUES('ersteinstufung','Ersteinstufung', 1, true); + INSERT INTO hr.tbl_gehaltsanpassungtyp(gehaltsanpassungtyp_kurzbz, bezeichnung, sort, aktiv) VALUES('individuelle_anpassung','Indiv. Anpassung', 2, true); + INSERT INTO hr.tbl_gehaltsanpassungtyp(gehaltsanpassungtyp_kurzbz, bezeichnung, sort, aktiv) VALUES('strukturelle_anpassung','Strukturelle Anpassung', 3, true); + INSERT INTO hr.tbl_vertragsbestandteil_freitexttyp(freitexttyp_kurzbz, bezeichnung, ueberlappend, kuendigungsrelevant) VALUES('allin','All-In', false, false); INSERT INTO hr.tbl_vertragsbestandteil_freitexttyp(freitexttyp_kurzbz, bezeichnung, ueberlappend, kuendigungsrelevant) VALUES('ersatzarbeitskraft','Ersatzarbeitskraft', false, true); INSERT INTO hr.tbl_vertragsbestandteil_freitexttyp(freitexttyp_kurzbz, bezeichnung, ueberlappend, kuendigungsrelevant) VALUES('zusatzvereinbarung','Zusatzvereinbarung', true, false); @@ -607,6 +629,24 @@ if ($result = $db->db_query("SELECT * FROM information_schema.columns WHERE colu } } + +if ($result = $db->db_query("SELECT * FROM information_schema.columns WHERE column_name='gehaltsanpassungtyp_kurzbz' AND table_name='tbl_gehaltsbestandteil' AND table_schema='hr'")) +{ + if ($db->db_num_rows($result) == 0) + { + $qry = " + ALTER TABLE + hr.tbl_gehaltsbestandteil + ADD COLUMN + gehaltsanpassungtyp_kurzbz character varying(32) + "; + if (! $db->db_query($qry)) + echo 'Vertraege: ' . $db->db_last_error() . '
'; + else + echo 'Spalte gehaltsanpassungtyp_kurzbz wurde in hr.tbl_gehaltsbestandteil neu erstellt
'; + } +} + if ($result = $db->db_query("SELECT * FROM information_schema.columns WHERE column_name='betrag' AND table_name='tbl_sachaufwand' AND table_schema='hr'")) { if ($db->db_num_rows($result) == 0) From 83b06b6c504d4e19c150694cd36ec94a2ac2bcb7 Mon Sep 17 00:00:00 2001 From: ma0068 Date: Mon, 10 Aug 2026 14:04:45 +0200 Subject: [PATCH 07/32] - Redesign Tags - use Slot in Filter Component - use Phrase for relaoding Tag in Detail Header --- public/css/components/Detailheader.css | 13 +++++ public/css/tags.css | 5 ++ .../components/DetailHeader/DetailHeader.js | 43 +++++++------- .../Stv/Studentenverwaltung/List.js | 56 ++++++++++--------- public/js/components/Tag/Tag.js | 7 ++- public/js/components/filter/Filter.js | 3 +- 6 files changed, 80 insertions(+), 47 deletions(-) diff --git a/public/css/components/Detailheader.css b/public/css/components/Detailheader.css index dcfcf6806..e9ebddfc1 100644 --- a/public/css/components/Detailheader.css +++ b/public/css/components/Detailheader.css @@ -62,3 +62,16 @@ top:13%;" z-index: 104; font-size: 1rem; width: 2.5rem; height: 2.5rem; opacity:0; transition: opacity 0.2s; top:13%;" }*/ + +.tag-hover:hover { + background-color: #e9ecef; +} + +.green { + background-color: #e9ecef; +} + +.btn-hover { + --bs-btn-hover-bg: #e9ecef; /* Neue Hintergrundfarbe beim Hover */ + --bs-btn-hover-border-color: #e9ecef; /* Neue Rahmenfarbe beim Hover */ +} diff --git a/public/css/tags.css b/public/css/tags.css index 0634c5ce3..0230cfbda 100644 --- a/public/css/tags.css +++ b/public/css/tags.css @@ -145,3 +145,8 @@ float: right; margin-top: 3px; } + +.btn-hover { + --bs-btn-hover-bg: #e9ecef; + --bs-btn-hover-border-color: #e9ecef; +} diff --git a/public/js/components/DetailHeader/DetailHeader.js b/public/js/components/DetailHeader/DetailHeader.js index 396e1245a..d4d9d4cc1 100644 --- a/public/js/components/DetailHeader/DetailHeader.js +++ b/public/js/components/DetailHeader/DetailHeader.js @@ -397,32 +397,37 @@ export default {
-

+

{{headerData[0].titelpre}} {{headerData[0].vorname}} {{headerData[0].nachname}} , {{headerData[0].titelpost}}

- -
- - {{currentSemester}} + +
+ +
+ + {{currentSemester}} +
+
unruly
diff --git a/public/js/components/Stv/Studentenverwaltung/List.js b/public/js/components/Stv/Studentenverwaltung/List.js index 997b1f499..b100f3e62 100644 --- a/public/js/components/Stv/Studentenverwaltung/List.js +++ b/public/js/components/Stv/Studentenverwaltung/List.js @@ -841,33 +841,37 @@ export default {
-