Compare commits

...
Author SHA1 Message Date
Johann Hoffmann ec59af8d5f fixed date format timzeone issue since native js date was still in use there; consolidated date helpers to use the luxon approach from abgabetool datehelpers and put them into the generic DateHelpers.js file; also replaced a few other sketchy date formats with the consolidated helpers function; 2026-09-02 13:52:47 +02:00
Harald Bamberger 6d641bbd06 pass in uid by reference 2026-08-24 13:56:55 +02:00
Harald Bamberger 3455aed3f5 Merge branch 'bhdev_master_stundenplan' 2026-08-24 13:46:09 +02:00
Harald Bamberger f84d12277d remove debug code 2026-08-24 07:31:47 +02:00
Harald Bamberger 9bf5bc2ad9 more generic bugfix, to use studentlehrverband only with date_range for the same studiensemester_kurzbz 2026-08-24 07:26:12 +02:00
Harald Bamberger 27765606e3 bufix akadGrad by sex if exists 2026-08-20 19:31:37 +02:00
Harald Bamberger 75d2cabdbe bugfix lv-plan 2026-08-20 15:10:26 +02:00
Harald Bamberger bae9d6d9a0 Merge branch 'feature-78257/StudVW_Und_Tags_Finetuning' 2026-08-19 17:11:12 +02:00
Harald Bamberger 43d6a882ce add padding to tags buttons, remove orphanded template closing tag 2026-08-19 17:07:25 +02:00
Harald Bamberger 7f3b4100ab Merge branch 'master' into feature-78257/StudVW_Und_Tags_Finetuning 2026-08-19 15:07:07 +02:00
Harald Bamberger 9f54ecce54 Merge branch 'epic/LVEvaluierung-Neu' 2026-08-19 13:59:00 +02:00
Cristina 7af0f4a478 Merge remote-tracking branch 'origin/epic/LVEvaluierung-Neu' into epic/LVEvaluierung-Neu 2026-08-19 13:35:25 +02:00
Cristina 0f843db33d Merge branch 'master' into epic/LVEvaluierung-Neu 2026-08-19 13:34:53 +02:00
Cristina 3e59648d5b Merge branch 'master' into epic/LVEvaluierung-Neu 2026-08-19 12:55:38 +02:00
Cristina 5923991848 Merge branch 'master' of https://github.com/FH-Complete/FHC-Core 2026-08-19 12:55:03 +02:00
Harald Bamberger 55a6ec9d0d fix handling of grundbetrag and betrag_valorisiert 2026-08-19 11:48:22 +02:00
chfhtw 78505589c8 Bugfix: racecondition renderers 2026-08-19 09:45:15 +02:00
Harald Bamberger 0e985113ec Merge branch 'feature-77666/Details_anzeigen_der_Valorisierung_pro_Person' 2026-08-19 08:20:42 +02:00
Harald Bamberger 8d9113d5c8 Merge branch 'master' into feature-77666/Details_anzeigen_der_Valorisierung_pro_Person 2026-08-19 08:06:16 +02:00
Harald Bamberger 7ce81ad4bd Merge branch 'feature-78292/dv_gehaltsbestandteil_ersteinstufung' 2026-08-19 07:57:07 +02:00
Harald Bamberger ea56ec8daa change wording 2026-08-19 07:50:50 +02:00
Harald Bamberger bf5ee2f64b Merge branch 'feature-78139/CIS4_Dashboard_ausgeblendete_Widgets' 2026-08-18 17:51:40 +02:00
Harald Bamberger 55d6d90fa6 Merge branch 'master' into feature-78139/CIS4_Dashboard_ausgeblendete_Widgets 2026-08-18 17:47:13 +02:00
Harald Bamberger 6adc55a843 Merge branch 'feature/78128-quick-copy-full-name-and-title' 2026-08-18 17:39:44 +02:00
Harald Bamberger 52c1fba17b Merge branch 'bugfix/78127-degree-programs-page-localization-ineffective' 2026-08-18 16:53:49 +02:00
Cristina 5e15d4b219 Add Distinct to getLeitung in Studiengang_model.php
Avoid multiple records for same active STGL.
e.g. double entries
2026-08-18 16:51:39 +02:00
Harald Bamberger 00954f680b Merge branch 'bugfix/78392-missing-searchbar-is-mobile-injectable' 2026-08-18 16:48:43 +02:00
Harald Bamberger 92d27fc242 Merge branch 'master' into feature/78128-quick-copy-full-name-and-title 2026-08-18 14:52:41 +02:00
Harald Bamberger 558963cd74 Merge branch 'master' into feature-78139/CIS4_Dashboard_ausgeblendete_Widgets 2026-08-18 14:42:44 +02:00
Werner Masik d8355ba3f5 added Funktionsänderung to gehaltsanpassungtyp 2026-08-13 14:56:04 +02:00
ma0068 afb8011347 bugfix manual rebuilding of Tag ZGV Missing 2026-08-13 12:57:49 +02:00
Werner Masik d85a3cb08f extract DB migration for gehaltsanpassungtyp into separate file 2026-08-12 15:18:35 +02:00
ma0068 15beaa9be8 Merge branch 'feature-78257/StudVW_Und_Tags_Finetuning' of github.com:FH-Complete/FHC-Core into feature-78257/StudVW_Und_Tags_Finetuning 2026-08-12 12:42:10 +02:00
ma0068 574057da88 add masterzgv to CoremissingZgvLib 2026-08-12 12:41:39 +02:00
Harald Bamberger c81b5338de Merge branch 'master' into feature-78292/dv_gehaltsbestandteil_ersteinstufung 2026-08-12 11:39:49 +02:00
Harald Bamberger c433ff5c3a remove public/vendor symlink since created by composer install 2026-08-12 11:39:16 +02:00
Harald Bamberger a5797a0bde Merge branch 'master' into feature-78257/StudVW_Und_Tags_Finetuning 2026-08-11 18:00:17 +02:00
Cristina 37d5f2da5d Merge branch 'master' into epic/LVEvaluierung-Neu 2026-08-11 15:45:27 +02:00
Cristina 27c39cfdca Merge branch 'master' of https://github.com/FH-Complete/FHC-Core 2026-08-11 15:45:08 +02:00
Werner Masik d66fe72cc9 add field for salary adaption type (Ersteinstufung, indiv. Erhöhung, ..) 2026-08-11 11:18:36 +02:00
adisposkofh 59ac56374e removed redundant response meta used for dev 2026-08-11 09:44:41 +02:00
adisposkofh a397460e0e Merge branch 'master' into feature/78128-quick-copy-full-name-and-title 2026-08-11 09:43:27 +02:00
adisposkofh 108f91ea1a quick copy option on full name and title in profile and other lv plan 2026-08-10 17:09:31 +02:00
adisposkofh dc5a846c10 defaulting searchbar isMobile injectable to false when not provided 2026-08-10 16:24:58 +02:00
ma0068 06895510e0 add reloadList if language is changed 2026-08-10 14:25:07 +02:00
ma0068 83b06b6c50 - Redesign Tags
- use Slot in Filter Component
- use Phrase  for relaoding Tag in Detail Header
2026-08-10 14:04:45 +02:00
Werner Masik bd99f3f927 add db schema for gehaltsanpassungtyp 2026-08-10 12:20:30 +02:00
Cristina eeb1529b64 Merge branch 'master' into epic/LVEvaluierung-Neu 2026-08-10 12:07:45 +02:00
Cristina 98c3707802 Merge branch 'master' of https://github.com/FH-Complete/FHC-Core 2026-08-10 12:07:01 +02:00
Cristina e5c8807f17 Merge branch 'master' of https://github.com/FH-Complete/FHC-Core 2026-08-05 15:13:35 +02:00
chfhtw 2f86a1b017 Changing the edit button into a toggle 2026-08-04 10:46:08 +02:00
chfhtw 75ddbdf7c1 Recovery items on top 2026-08-04 10:24:29 +02:00
chfhtw d824334538 Hiding widgets should look like deleting and recovering is moved to the "add widget" popup 2026-08-04 09:42:18 +02:00
chfhtw 13515b2522 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 2026-08-04 09:40:47 +02:00
Alexei Karpenko c5c29df6cc Gehaltsbestandteil, Gehaltsbestandteil_model: added gehaltstyp_bezeichnung 2026-07-14 20:51:41 +02:00
44 changed files with 595 additions and 389 deletions
@@ -114,6 +114,7 @@ class LvPlan extends FHCAPI_Controller
// fetching lvplan events
$result = $this->stundenplanlib->getEventsUser($start_date, $end_date, $uid);
$lvplanEvents = $this->getDataOrTerminateWithError($result);
// fetching moodle events
@@ -62,6 +62,8 @@ class OtherLvPlan extends FHCAPI_Controller
"foto" => $profileData->data->foto,
"vorname" => $profileData->data->vorname,
"nachname" => $profileData->data->nachname,
"titel" => $profileData->data->titel,
"postnomen" => $profileData->data->postnomen,
],
];
+3 -2
View File
@@ -94,13 +94,13 @@ class StundenplanLib
if (isError($benutzer_gruppen))
return $benutzer_gruppen;
$benutzer_gruppen = getData($benutzer_gruppen);
$this->_ci->addMeta('spezialgruppen', $benutzer_gruppen);
// getting the student_lehrverbaende of the student in the different studiensemester
$student_lehrverband = $this->fetchStudentlehrverbandFromStudiensemester($student_uid, $semester_range);
if (isError($student_lehrverband))
return $student_lehrverband;
$student_lehrverband = getData($student_lehrverband);
$this->_ci->addMeta('studentlehrvebaende', $student_lehrverband);
$stundenplan_query = $this->_ci->StundenplanModel->getStundenplanQuery(
$start,
$end,
@@ -632,6 +632,7 @@ class StundenplanLib
$result->semester = $item->semester;
$result->verband = $item->verband;
$result->gruppe = $item->gruppe;
$result->studiensemester_kurzbz = $item->studiensemester_kurzbz;
return $result;
},
$lehrverband_query_result);
@@ -28,10 +28,21 @@ class CoreMissingZgvTagLib
$this->ci->PrestudentModel->addJoin('public.tbl_prestudentstatus', 'prestudent_id');
$this->ci->PrestudentModel->addJoin('public.tbl_benutzer bn', 'person_id');
$this->ci->PrestudentModel->addJoin('public.tbl_studiengang', 'studiengang_kz');
$this->ci->PrestudentModel->db->group_start();
$this->ci->PrestudentModel->db->where('zgvdatum', null);
$this->ci->PrestudentModel->db->where('typ', 'b');
$this->ci->PrestudentModel->db->or_group_start();
$this->ci->PrestudentModel->db->where('zgvmadatum', null);
$this->ci->PrestudentModel->db->where('typ', 'm');
$this->ci->PrestudentModel->db->group_end();
$this->ci->PrestudentModel->db->group_end();
$result = $this->ci->PrestudentModel-> loadWhere(array(
'bn.aktiv' => true, //check if necessary
'zgvdatum' => null,
'typ' => 'b',
'bn.aktiv' => true,
'studiensemester_kurzbz' => $semester
));
@@ -60,16 +71,36 @@ class CoreMissingZgvTagLib
$semester = $params['studiensemester_kurzbz'];
$prestudent_id = $params['id'];
$this->ci->PrestudentModel->addSelect('prestudent_id');
$this->ci->PrestudentModel->addSelect('start as von');
$this->ci->PrestudentModel->addSelect('ende as bis');
$this->ci->PrestudentModel->addJoin('public.tbl_prestudentstatus', 'prestudent_id');
//add studiensemester also here for manual rebuild
$this->ci->PrestudentModel->addJoin('public.tbl_studiensemester', 'studiensemester_kurzbz');
$this->ci->PrestudentModel->addJoin('public.tbl_benutzer bn', 'person_id');
$this->ci->PrestudentModel->addJoin('public.tbl_studiengang', 'studiengang_kz');
$result = $this->ci->PrestudentModel->loadWhere(array(
'bn.aktiv' => true, //check if necessary
'zgvdatum' => null,
'typ' => 'b',
$this->ci->PrestudentModel->db->group_start();
$this->ci->PrestudentModel->db->where('zgvdatum', null);
$this->ci->PrestudentModel->db->where('typ', 'b');
$this->ci->PrestudentModel->db->or_group_start();
$this->ci->PrestudentModel->db->where('zgvmadatum', null);
$this->ci->PrestudentModel->db->where('typ', 'm');
$this->ci->PrestudentModel->db->group_end();
$this->ci->PrestudentModel->db->group_end();
$this->ci->PrestudentModel->db->limit(1);
$result = $this->ci->PrestudentModel-> loadWhere(array(
'bn.aktiv' => true,
'studiensemester_kurzbz' => $semester,
'prestudent_id' => $prestudent_id
));
if(hasData($result))
{
return $result;
@@ -13,6 +13,7 @@ class Gehaltsbestandteil extends AbstractBestandteil implements \JsonSerializabl
protected $dienstverhaeltnis_id;
protected $vertragsbestandteil_id;
protected $gehaltstyp_kurzbz;
protected $gehaltsanpassungtyp_kurzbz = '';
protected $von;
protected $bis;
protected $anmerkung;
@@ -21,6 +22,7 @@ class Gehaltsbestandteil extends AbstractBestandteil implements \JsonSerializabl
protected $valorisierungssperre;
protected $valorisierung;
protected $auszahlungen;
protected $gehaltstyp_bezeichnung;
protected $insertamum;
protected $insertvon;
@@ -39,6 +41,10 @@ class Gehaltsbestandteil extends AbstractBestandteil implements \JsonSerializabl
isset($data->dienstverhaeltnis_id) && $this->setDienstverhaeltnis_id($data->dienstverhaeltnis_id);
isset($data->vertragsbestandteil_id) && $this->setVertragsbestandteil_id($data->vertragsbestandteil_id);
isset($data->gehaltstyp_kurzbz) && $this->setGehaltstyp_kurzbz($data->gehaltstyp_kurzbz);
//isset($data->gehaltsanpassungtyp_kurzbz) && $this->setGehaltsanpassungtyp_kurzbz($data->gehaltsanpassungtyp_kurzbz);
if (!empty($data->gehaltsanpassungtyp_kurzbz)) {
$this->setGehaltsanpassungtyp_kurzbz($data->gehaltsanpassungtyp_kurzbz);
}
isset($data->von) && $this->setVon($data->von);
isset($data->bis) && $this->setBis($data->bis);
isset($data->anmerkung) && $this->setAnmerkung($data->anmerkung);
@@ -47,7 +53,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);
@@ -75,6 +82,11 @@ class Gehaltsbestandteil extends AbstractBestandteil implements \JsonSerializabl
return $this->gehaltstyp_kurzbz;
}
public function getGehaltsanpassungtyp_kurzbz()
{
return $this->gehaltsanpassungtyp_kurzbz;
}
public function getVon()
{
return $this->von;
@@ -178,6 +190,13 @@ class Gehaltsbestandteil extends AbstractBestandteil implements \JsonSerializabl
return $this;
}
public function setGehaltsanpassungtyp_kurzbz($gehaltsanpassungtyp_kurzbz)
{
$this->markDirty('gehaltsanpassungtyp_kurzbz', $this->gehaltsanpassungtyp_kurzbz, $gehaltsanpassungtyp_kurzbz);
$this->gehaltsanpassungtyp_kurzbz = $gehaltsanpassungtyp_kurzbz;
return $this;
}
public function setVon($von)
{
$this->markDirty('von', $this->von, $von);
@@ -206,6 +225,10 @@ class Gehaltsbestandteil extends AbstractBestandteil implements \JsonSerializabl
$grundbetrag = number_format($grundbetrag, 2, '.', '');
}
$this->markDirty('grundbetrag', $this->grundbetrag, $grundbetrag);
if(in_array('grundbetrag', $this->modifiedcolumns))
{
$this->setBetrag_valorisiert($grundbetrag);
}
$this->grundbetrag = $grundbetrag;
return $this;
}
@@ -242,6 +265,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);
@@ -284,6 +314,7 @@ class Gehaltsbestandteil extends AbstractBestandteil implements \JsonSerializabl
'dienstverhaeltnis_id' => $this->getDienstverhaeltnis_id(),
'vertragsbestandteil_id' => $this->getVertragsbestandteil_id(),
'gehaltstyp_kurzbz' => $this->getGehaltstyp_kurzbz(),
'gehaltsanpassungtyp_kurzbz' => $this->getGehaltsanpassungtyp_kurzbz(),
'von' => $this->getVon(),
'bis' => $this->getBis(),
'anmerkung' => $this->getAnmerkung(),
@@ -312,6 +343,7 @@ class Gehaltsbestandteil extends AbstractBestandteil implements \JsonSerializabl
dienstverhaeltnis_id: {$this->getDienstverhaeltnis_id()}
vertragsbestandteil_id: {$this->getVertragsbestandteil_id()}
gehaltstyp_kurzbz: {$this->getGehaltstyp_kurzbz()}
gehaltsanpassungtyp_kurzbz: {$this->getGehaltsanpassungtyp_kurzbz()}
von: {$this->getVon()}
bis: {$this->getBis()}
anmerkung: {$this->getAnmerkung()}
+1 -1
View File
@@ -204,7 +204,7 @@ class Student_model extends DB_Model
Events::trigger(
'generate_student_uid',
function ($value) use ($uid) {
function ($value) use (&$uid) {
$uid = $value;
},
$stgkzl,
@@ -457,6 +457,7 @@ class Studiengang_model extends DB_Model
*/
public function getLeitung($studiengang_kz = null)
{
$this->addDistinct();
$this->addSelect('uid, studiengang_kz, oe_kurzbz, vorname, nachname, email, titelpre, titelpost, alias');
$this->addJoin('public.tbl_benutzerfunktion', 'oe_kurzbz');
$this->addJoin('public.tbl_benutzer', 'uid');
@@ -604,11 +604,17 @@ class Stundenplan_model extends DB_Model
{
foreach($semester_date_range as $sem_date => $sem_date_range)
{
if(!array_key_exists($sem_date,$studentlehrverbaende) || count($studentlehrverbaende[$sem_date]) == 0)
if(!array_key_exists($sem,$studentlehrverbaende) || count($studentlehrverbaende[$sem]) == 0)
{
continue;
}
foreach($studentlehrverbaende[$sem_date] as $key=>$lehrverband)
$studlvbds = array_filter(
$studentlehrverbaende[$sem],
function($value) use ($sem_date) {
return $value->studiensemester_kurzbz === $sem_date;
}
);
foreach($studlvbds as $key=>$lehrverband)
{
$query .= "(((sp.studiengang_kz = ".$this->escape($lehrverband->studiengang_kz)." AND sp.semester = ".$this->escape($lehrverband->semester)." AND sp.verband = ".$this->escape($lehrverband->verband)." AND sp.gruppe = ".$this->escape($lehrverband->gruppe)." AND sp.datum BETWEEN ".$this->escape($sem_date_range->start)." AND ".$this->escape($sem_date_range->ende).")";
// Eintraege fuer den ganzen Verband
@@ -41,6 +41,7 @@ with gbt as (
gb.anmerkung,
gb.dienstverhaeltnis_id,
gb.gehaltstyp_kurzbz,
gb.gehaltsanpassungtyp_kurzbz,
gb.valorisierungssperre,
gb.valorisierung,
gb.grundbetrag as grund_betrag_decrypted,
@@ -76,11 +77,13 @@ select
gbt.anmerkung,
gbt.dienstverhaeltnis_id,
gbt.gehaltstyp_kurzbz,
gbt.gehaltsanpassungtyp_kurzbz,
gbt.valorisierungssperre,
gbt.valorisierung,
gbt.grund_betrag_decrypted,
gbt.betrag_val_decrypted,
gt.bezeichnung as gehaltstyp_bezeichnung,
ga.bezeichnung as gehaltsanpassungtyp_bezeichnung,
vb.vertragsbestandteiltyp_kurzbz,
bf.funktion_kurzbz,
bf.oe_kurzbz,
@@ -93,6 +96,8 @@ from
gbt
LEFT JOIN
hr.tbl_gehaltstyp gt using(gehaltstyp_kurzbz)
LEFT JOIN
hr.tbl_gehaltsanpassungtyp ga using(gehaltsanpassungtyp_kurzbz)
LEFT JOIN
hr.tbl_vertragsbestandteil vb using(vertragsbestandteil_id)
LEFT JOIN
@@ -177,7 +182,8 @@ LEFT JOIN
$stichtagclause .= ')';
}
$this->addSelect('*');
$this->addSelect('tbl_gehaltsbestandteil.*, tbl_gehaltstyp.bezeichnung AS gehaltstyp_bezeichnung');
$this->addJoin('hr.tbl_gehaltstyp', 'gehaltstyp_kurzbz');
$where = <<<EOSQL
dienstverhaeltnis_id = {$this->escape($dienstverhaeltnis_id)}
{$stichtagclause}
@@ -202,6 +208,7 @@ SELECT
g.dienstverhaeltnis_id,
g.vertragsbestandteil_id,
g.gehaltstyp_kurzbz,
g.gehaltsanpassungtyp_kurzbz,
g.von,
g.bis,
g.anmerkung,
@@ -213,9 +220,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
@@ -263,7 +272,7 @@ EOSQL;
// Note: replaced gb.betrag_valorisiert with vh.betrag_valorisiert!
$qry = "
SELECT
gb.gehaltsbestandteil_id,gb.dienstverhaeltnis_id,gb.vertragsbestandteil_id,gb.gehaltstyp_kurzbz,
gb.gehaltsbestandteil_id,gb.dienstverhaeltnis_id,gb.vertragsbestandteil_id,gb.gehaltstyp_kurzbz,gb.gehaltsanpassungtyp_kurzbz,
gb.von,gb.bis,gb.anmerkung,gb.grundbetrag as grundbetrag,gb.valorisierungssperre,gb.insertamum,
gb.insertvon,gb.updateamum,gb.updatevon,gb.valorisierung,gb.auszahlungen,
vh.valorisierungsdatum, vh.betrag_valorisiert as betrag_valorisiert
+13
View File
@@ -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 */
}
+5
View File
@@ -145,3 +145,8 @@
float: right;
margin-top: 3px;
}
.btn-hover {
--bs-btn-hover-bg: #e9ecef;
--bs-btn-hover-border-color: #e9ecef;
}
+4 -4
View File
@@ -22,6 +22,7 @@ import OtherLvPlan from "../../components/Cis/LvPlan/OtherLvPlan.js";
import ApiRouteInfo from '../../api/factory/routeinfo.js';
import {capitalize} from "../../helpers/StringHelpers.js";
import { getTodayISO, toDateTime } from "../../helpers/DateHelpers.js";
const ciPath = FHC_JS_DATA_STORAGE_OBJECT.app_root.replace(/(https:|)(^|\/\/)(.*?\/)/g, '') + FHC_JS_DATA_STORAGE_OBJECT.ci_router;
const isMobile = window.matchMedia("(max-width: 767px)").matches;
@@ -88,7 +89,7 @@ const router = VueRouter.createRouter({
params: { // in this case always populate other params since they are not optional
ort_kurzbz: to.params.ort_kurzbz,
mode: isMobile ? DEFAULT_MODE_RAUMINFO_MOBILE : DEFAULT_MODE_RAUMINFO_DESKTOP,
focus_date: new Date().toISOString().split("T")[0]
focus_date: getTodayISO()
},
};
},
@@ -107,8 +108,7 @@ const router = VueRouter.createRouter({
: (isMobile ? DEFAULT_MODE_RAUMINFO_MOBILE : DEFAULT_MODE_RAUMINFO_DESKTOP);
// default to today date if not provided
const d = new Date(route.params.focus_date)
const focus_date = !isNaN(d) ? route.params.focus_date : new Date().toISOString().split("T")[0];
const focus_date = toDateTime(route.params.focus_date) ? route.params.focus_date : getTodayISO();
// for consistency reasons format the props into one object but actually use a new name to we dont collide with
// existing viewData declaration written from codeigniter 3 into routerview tag
@@ -127,7 +127,7 @@ const router = VueRouter.createRouter({
name: "RoomInformation",
params: {
mode: to.params.mode || DEFAULT_MODE_RAUMINFO,
focus_date: to.params.focus_date || new Date().toISOString().split("T")[0],
focus_date: to.params.focus_date || getTodayISO(),
ort_kurzbz: route.params.ort_kurzbz
}
});
+3
View File
@@ -174,6 +174,9 @@ export default {
>
{{ $p.t('lehre/noLvFound') }}
</div>
<div v-else-if="!renderers || !renderers[event.type]" class="placeholder-glow">
<span class="placeholder col-12"></span>
</div>
<div
v-else
:class="'event-type-' + event.type + ' ' + mode + 'PageContainer'"
+3
View File
@@ -79,6 +79,9 @@ export default {
>
{{ $p.t('lehre/noLvFound') }}
</div>
<div v-else-if="!renderers || !renderers[event.type]" class="placeholder-glow">
<span class="placeholder col-12"></span>
</div>
<component
v-else-if="mode == 'eventheader'"
:is="renderers[event.type]?.modalTitle"
@@ -2,7 +2,7 @@ import BsModal from '../../Bootstrap/Modal.js';
import VueDatePicker from '../../vueDatepicker.js.php';
import ApiAbgabe from '../../../api/factory/abgabe.js'
import { getDateStyleClass } from "./getDateStyleClass.js";
import {compareISODateValues, formatDateTime, formatISODate, getViennaTodayISO} from "./dateUtils.js";
import { compareISODateValues, formatDate, formatDateTime, getTodayISO } from "../../../helpers/DateHelpers.js";
export const AbgabeMitarbeiterDetail = {
@@ -174,7 +174,7 @@ export const AbgabeMitarbeiterDetail = {
'fixtermin': false,
'invertedFixtermin': true,
'kurzbz': '', // todo kurzbz textfield value vorschlag für qualgates
'datum': getViennaTodayISO(),
'datum': getTodayISO(),
'note': this.allowedNotenOptions.find(opt => opt.note == 9),
'beurteilungsnotiz': '',
'upload_allowed': false,
@@ -351,9 +351,7 @@ export const AbgabeMitarbeiterDetail = {
}
}
},
formatDate(dateParam) {
return formatISODate(dateParam)
},
formatDate,
openCreateNewAbgabeModal() {
if(this.projektarbeit?.betreuerart_kurzbz == 'Zweitbegutachter') {
// maybe alert that this is not allowed or smth
@@ -370,7 +368,7 @@ export const AbgabeMitarbeiterDetail = {
'fixtermin': false,
'invertedFixtermin': true,
'kurzbz': '',
'datum': getViennaTodayISO(),
'datum': getTodayISO(),
'note': this.allowedNotenOptions.find(opt => opt.note == 9),
'beurteilungsnotiz': '',
'upload_allowed': typ.upload_allowed_default,
@@ -404,7 +402,7 @@ export const AbgabeMitarbeiterDetail = {
'fixtermin': false,
'invertedFixtermin': true,
'kurzbz': '',
'datum': getViennaTodayISO(),
'datum': getTodayISO(),
'note': this.allowedNotenOptions.find(opt => opt.note == 9),
'beurteilungsnotiz': '',
'upload_allowed': false,
@@ -603,7 +601,7 @@ export const AbgabeMitarbeiterDetail = {
'fixtermin': false,
'invertedFixtermin': true,
'kurzbz': '',
'datum': getViennaTodayISO(),
'datum': getTodayISO(),
'note': this.allowedNotenOptions.find(opt => opt.note == 9),
'beurteilungsnotiz': '',
'upload_allowed': typ.upload_allowed_default,
@@ -3,7 +3,7 @@ import BsModal from '../../Bootstrap/Modal.js';
import VueDatePicker from '../../vueDatepicker.js.php';
import ApiAbgabe from '../../../api/factory/abgabe.js'
import FhcOverlay from "../../Overlay/FhcOverlay.js";
import { formatISODate, getViennaTodayISO } from "./dateUtils.js";
import { formatDate, getTodayISO } from "../../../helpers/DateHelpers.js";
import { validateThesisTitle } from './titleValidation.js'
@@ -248,9 +248,7 @@ export const AbgabeStudentDetail = {
window.open(FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + url)
},
formatDate(dateParam) {
return formatISODate(dateParam)
},
formatDate,
async upload(termin) {
if (! await this.validate(termin))
@@ -286,7 +284,7 @@ export const AbgabeStudentDetail = {
if(res.meta.status == "success") {
this.$fhcAlert.alertSuccess(this.$capitalize(this.$p.t('abgabetool/c4fileUploadSuccessv3')))
termin.abgabedatum = getViennaTodayISO();
termin.abgabedatum = getTodayISO();
if(res?.data?.signatur !== undefined) {
termin.signatur = res.data.signatur
}
@@ -1,5 +1,5 @@
import AbgabeterminStatusLegende from "./StatusLegende.js";
import {formatDateTime} from "./dateUtils.js";
import { formatDateTime } from "../../../helpers/DateHelpers.js";
export const AbgabeStudentTimeline = {
name: "AbgabeStudentTimeline",
@@ -11,7 +11,7 @@ import AbgabeStudentTimeline from "./AbgabeStudentTimeline.js";
import { splitMailsHelper } from "../../../helpers/EmailHelpers.js"
import { getDateStyleClass} from "./getDateStyleClass.js";
import { dateFilter } from '../../../tabulator/filters/DatesManual.js';
import { compareISODateValues, formatISODate, getViennaTodayISO, toViennaDate } from "./dateUtils.js";
import { compareISODateValues, formatDate, getNow, getTodayISO, toDateTime } from "../../../helpers/DateHelpers.js";
export const AbgabetoolAssistenz = {
name: "AbgabetoolAssistenz",
@@ -114,7 +114,7 @@ export const AbgabetoolAssistenz = {
fixtermin: false,
},
serienTermin: Vue.reactive({
datum: getViennaTodayISO(),
datum: getTodayISO(),
bezeichnung: {
paabgabetyp_kurzbz: 'zwischen',
bezeichnung: 'Zwischenabgabe'
@@ -643,7 +643,7 @@ export const AbgabetoolAssistenz = {
upload_allowed: false,
fixtermin: false,
}
this.serienEdit.datum = getViennaTodayISO()
this.serienEdit.datum = getTodayISO()
this.serienEdit.bezeichnung = this.abgabeTypeOptions.find(opt => opt.paabgabetyp_kurzbz === 'zwischen')
this.serienEdit.kurzbz = ''
this.serienEdit.upload_allowed = false
@@ -1091,23 +1091,9 @@ export const AbgabetoolAssistenz = {
return false;
}
const toLuxon = (val) => {
if (!val) return null;
let dt;
if (val instanceof Date) {
dt = luxon.DateTime.fromJSDate(val);
} else if (typeof val === "string") {
dt = toViennaDate(val);
} else { // fallback
dt = luxon.DateTime.fromMillis(Number(val));
}
return dt.isValid ? dt : null;
};
const rowDate = toLuxon(rowVal);
const von = toLuxon(filterVal[0]);
const bis = toLuxon(filterVal[1]);
const rowDate = toDateTime(rowVal);
const von = toDateTime(filterVal[0]);
const bis = toDateTime(filterVal[1]);
// specific day
if (von && !bis) {
@@ -1128,22 +1114,8 @@ export const AbgabetoolAssistenz = {
const rowDate = rowVal.luxonDate;
const toLuxon = (val) => {
if (!val) return null;
let dt;
if (val instanceof Date) {
dt = luxon.DateTime.fromJSDate(val);
} else if (typeof val === "string") {
dt = toViennaDate(val);
} else { // fallback
dt = luxon.DateTime.fromMillis(Number(val));
}
return dt.isValid ? dt : null;
};
const von = toLuxon(filterVal[0]);
const bis = toLuxon(filterVal[1]);
const von = toDateTime(filterVal[0]);
const bis = toDateTime(filterVal[1]);
// specific day
if (von && !bis) {
@@ -1372,7 +1344,7 @@ export const AbgabetoolAssistenz = {
},
checkAbgabetermineProjektarbeit(projekt) {
const now = luxon.DateTime.now()
const now = getNow()
// calculate Abgabetermin time diff to now and assign last and next to projekt
projekt.abgabetermine.forEach(termin => {
@@ -1387,7 +1359,7 @@ export const AbgabetoolAssistenz = {
// while already looping through each termin, calculate datestyle beforehand
termin.dateStyle = getDateStyleClass(termin, this.notenOptions)
const date = toViennaDate(termin.datum).endOf('day')
const date = toDateTime(termin.datum).endOf('day')
termin.luxonDate = date
termin.diffMs = date.toMillis() - now.toMillis(); // positive = future, negative = past
@@ -1654,9 +1626,7 @@ export const AbgabetoolAssistenz = {
getNotenFilterOptionLabel(option) {
return option.bezeichnung
},
formatDate(dateParam) {
return formatISODate(dateParam);
},
formatDate,
formAction(cell) {
const actionButtons = document.createElement('div');
actionButtons.className = "d-flex gap-3";
@@ -1851,9 +1821,7 @@ export const AbgabetoolAssistenz = {
return str
},
isPastDate(date) {
const deadline = luxon.DateTime.fromISO(date, { zone: 'Europe/Vienna' }).endOf('day');
const nowInVienna = luxon.DateTime.now().setZone('Europe/Vienna');
return nowInVienna > deadline;
return getNow() > toDateTime(date).endOf("day");
},
setDetailComponent(details){
const pa = this.projektarbeiten.find(projektarbeit => projektarbeit.projektarbeit_id == details.projektarbeit_id)
@@ -7,7 +7,7 @@ import FhcOverlay from "../../Overlay/FhcOverlay.js";
import { getDateStyleClass } from "./getDateStyleClass.js";
import { dateFilter } from '../../../tabulator/filters/DatesManual.js';
import {splitMailsHelper} from "../../../helpers/EmailHelpers.js";
import { formatISODate, getViennaTodayISO, toViennaDate } from "./dateUtils.js";
import { formatDate, getNow, getTodayISO, toDateTime } from "../../../helpers/DateHelpers.js";
export const AbgabetoolMitarbeiter = {
name: "AbgabetoolMitarbeiter",
@@ -54,7 +54,7 @@ export const AbgabetoolMitarbeiter = {
allowedNotenOptions: null,
notenOptionsNonFinal: null,
serienTermin: Vue.reactive({
datum: getViennaTodayISO(),
datum: getTodayISO(),
bezeichnung: {
paabgabetyp_kurzbz: 'zwischen',
bezeichnung: 'Zwischenabgabe'
@@ -668,22 +668,8 @@ export const AbgabetoolMitarbeiter = {
const rowDate = rowVal.luxonDate;
const toLuxon = (val) => {
if (!val) return null;
let dt;
if (val instanceof Date) {
dt = luxon.DateTime.fromJSDate(val);
} else if (typeof val === "string") {
dt = toViennaDate(val);
} else { // fallback
dt = luxon.DateTime.fromMillis(Number(val));
}
return dt.isValid ? dt : null;
};
const von = toLuxon(filterVal[0]);
const bis = toLuxon(filterVal[1]);
const von = toDateTime(filterVal[0]);
const bis = toDateTime(filterVal[1]);
// specific day
if (von && !bis) {
@@ -899,14 +885,14 @@ export const AbgabetoolMitarbeiter = {
}
},
checkAbgabetermineProjektarbeit(projekt) {
const now = luxon.DateTime.now()
const now = getNow()
// calculate Abgabetermin time diff to now and assign last and next to projekt
projekt.abgabetermine.forEach(termin => {
// while already looping through each termin, calculate datestyle beforehand
termin.dateStyle = getDateStyleClass(termin, this.notenOptions)
const date = toViennaDate(termin.datum).endOf('day')
const date = toDateTime(termin.datum).endOf('day')
termin.luxonDate = date
termin.diffMs = date.toMillis() - now.toMillis(); // positive = future, negative = past
@@ -1017,9 +1003,7 @@ export const AbgabetoolMitarbeiter = {
getOptionLabelAbgabetyp(option){
return this.$p.t('abgabetool/c4paatyp' + option.paabgabetyp_kurzbz)
},
formatDate(dateParam) {
return formatISODate(dateParam);
},
formatDate,
undoSelection(cell) {
// checks if cells row is selected and unselects -> imitates columns which dont trigger row selection
// but actually just revert it after the fact
@@ -1104,9 +1088,7 @@ export const AbgabetoolMitarbeiter = {
return str
},
isPastDate(date) {
const deadline = luxon.DateTime.fromISO(date, { zone: 'Europe/Vienna' }).endOf('day');
const nowInVienna = luxon.DateTime.now().setZone('Europe/Vienna');
return nowInVienna > deadline;
return getNow() > toDateTime(date).endOf("day");
},
setDetailComponent(details){
this.loading=true
@@ -4,6 +4,7 @@ import ApiAuthinfo from '../../../api/factory/authinfo.js';
import BsModal from "../../Bootstrap/Modal.js";
import FhcOverlay from "../../Overlay/FhcOverlay.js";
import { getDateStyleClass} from "./getDateStyleClass.js";
import { getNow, toDateTime } from "../../../helpers/DateHelpers.js";
import { validateThesisTitle } from './titleValidation.js'
export const AbgabetoolStudent = {
@@ -171,9 +172,7 @@ export const AbgabetoolStudent = {
return qgate1positiv && qgate2positiv
},
isPastDate(date) {
const deadline = luxon.DateTime.fromISO(date, { zone: 'Europe/Vienna' }).endOf('day');
const nowInVienna = luxon.DateTime.now().setZone('Europe/Vienna');
return nowInVienna > deadline;
return getNow() > toDateTime(date).endOf("day");
},
setDetailComponent(details){
this.loading = true
@@ -1,33 +0,0 @@
const zone = 'Europe/Vienna';
export function getViennaTodayISO() {
return luxon.DateTime.now().setZone(zone).toISODate();
}
export function formatISODate(dateParam) {
if (!dateParam) return '';
const date = luxon.DateTime.fromISO(String(dateParam), { zone });
return date.isValid ? date.toFormat('dd.MM.yyyy') : '';
}
export function formatDateTime(dateParam) {
if (!dateParam) return '';
const date = luxon.DateTime.fromSQL(dateParam);
return date.isValid ? date.toFormat("dd.MM.yyyy HH:mm") : '';
}
export function toViennaDate(dateParam) {
if (!dateParam) return null;
return luxon.DateTime.fromISO(String(dateParam), { zone });
}
export function compareISODateValues(a, b) {
if (!a && !b) return 0;
if (!a) return 1;
if (!b) return -1;
return String(a).localeCompare(String(b));
}
@@ -1,12 +1,12 @@
const zone = 'Europe/Vienna';
import { getNow, toDateTime } from "../../../helpers/DateHelpers.js";
export function getDateStyleClass(termin, notenOptions) {
const today = luxon.DateTime.now().setZone(zone);
const datum = luxon.DateTime.fromISO(termin.datum, { zone }).endOf('day');
const abgabedatum = termin.abgabedatum ? luxon.DateTime.fromISO(termin.abgabedatum, { zone }) : null;
termin.diffindays = datum.diff(today, 'days').days;
const isLate = abgabedatum && abgabedatum > datum;
const today = getNow();
const datum = toDateTime(termin.datum)?.endOf('day');
const abgabedatum = toDateTime(termin.abgabedatum);
termin.diffindays = datum ? datum.diff(today, 'days').days : NaN;
const isLate = abgabedatum && datum && abgabedatum > datum;
// GRADE STATUS
if (termin.note) {
+27 -8
View File
@@ -32,7 +32,7 @@ export default {
listGroup: [],
rangeIntervalFirst: null,
otherPersonData: {
fullName: "",
fullNameWithTitle: "",
photo: "",
},
timezone: FHC_JS_DATA_STORAGE_OBJECT.timezone,
@@ -210,10 +210,17 @@ export default {
this.isOtherPersonMitarbeiter =
!!viewData?.user_data?.is_mitarbeiter;
this.isOtherPersonStudent = !!viewData?.user_data?.is_student;
this.otherPersonData.fullName =
viewData?.user_data?.vorname +
" " +
viewData?.user_data?.nachname;
let fullNameWithTitleFragments = [
viewData?.user_data?.titel,
viewData?.user_data?.vorname,
viewData?.user_data?.nachname,
viewData?.user_data?.postnomen,
];
fullNameWithTitleFragments = fullNameWithTitleFragments.filter(
(fragment) => !!fragment,
);
this.otherPersonData.fullNameWithTitle =
fullNameWithTitleFragments.join(" ");
this.otherPersonData.photo = viewData?.user_data?.foto;
},
async redirectToMyLvPlanIfAuthUid() {
@@ -225,12 +232,21 @@ export default {
this.$router.push({ name: "MyLvPlan" });
}
},
copyFullNameWithTitleToClipboard() {
navigator.clipboard.writeText(
this.otherPersonData.fullNameWithTitle,
);
this.$fhcAlert.alertSuccess(
this.$p.t("profil/name_title_clipboard_copy_confirmation"),
);
},
},
async created() {
await this.redirectToMyLvPlanIfAuthUid();
await this.fetchViewData();
await this.$p.loadCategory(["profil"]);
},
template: `
template: /*html*/ `
<div class="d-flex flex-column h-100">
<h2>
<div class="d-flex flex-row justify-content-between align-items-center">
@@ -238,8 +254,11 @@ export default {
{{ $p.t('lehre/stundenplan') + (studiensemester_kurzbz ? " " + studiensemester_kurzbz : "") }}
</span>
<div @click="this.$router.push({name: 'ProfilView', params: {uid: propsViewData.otherUid}})" type="button" class="d-flex flex-row align-items-center gap-3">
<span v-if="otherPersonData.fullName?.length">
{{ otherPersonData.fullName }}
<span v-if="otherPersonData.fullNameWithTitle?.length">
{{ otherPersonData.fullNameWithTitle }}
<span @click.stop="copyFullNameWithTitleToClipboard()" :title="$p.t('LvPlan/copy')" class="fs-4 h-100">
<i class="fa-regular fa-copy"></i>
</span>
</span>
<img v-if="otherPersonData.photo?.length" alt="profile picture" class=" img-thumbnail " style=" max-height:60px; " :src="get_image_base64_src"/>
</div>
+4 -17
View File
@@ -1,4 +1,4 @@
import { numberPadding, formatDate } from "../../../helpers/DateHelpers.js"
import { formatDate, formatTime } from "../../../helpers/DateHelpers.js"
export default {
props: {
@@ -25,27 +25,14 @@ export default {
return FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + `/CisVue/Cms/content/${this.event.ort_content_id}`
},
start_time: function () {
if (!this.event.start) return 'N/A';
if (!this.event.start instanceof Date) {
return this.event.start;
}
return numberPadding(this.event.start.getHours()) + ":" + numberPadding(this.event.start.getMinutes());
return this.event.start ? formatTime(this.event.start) : 'N/A';
},
end_time: function () {
if (!this.event.end) return 'N/A';
if (!this.event.end instanceof Date) {
return this.event.end;
}
return numberPadding(this.event.end.getHours()) + ":" + numberPadding(this.event.end.getMinutes());
return this.event.end ? formatTime(this.event.end) : 'N/A';
}
},
methods: {
mehtodNumberPadding: function (number) {
return numberPadding(number);
},
methodFormatDate: function (d) {
return formatDate(d);
},
methodFormatDate: formatDate,
},
template:/*html*/`
<table class="table table-hover mb-4">
@@ -1,6 +1,7 @@
import FhcCalendar from "../../Calendar/LvPlan.js";
import ApiLvPlan from '../../../api/factory/lvPlan.js';
import { getTodayISO } from "../../../helpers/DateHelpers.js";
export const DEFAULT_MODE_RAUMINFO_MOBILE = 'List';
export const DEFAULT_MODE_RAUMINFO_DESKTOP = 'Week';
@@ -17,7 +18,7 @@ export default {
inject: ["isMobile"],
computed: {
currentDay() {
return this.propsViewData?.focus_date || luxon.DateTime.now().setZone(FHC_JS_DATA_STORAGE_OBJECT.timezone).toISODate();
return this.propsViewData?.focus_date || getTodayISO();
},
currentMode() {
const defaultMode = this.isMobile ? DEFAULT_MODE_RAUMINFO_MOBILE : DEFAULT_MODE_RAUMINFO_DESKTOP;
@@ -67,6 +67,21 @@ export default {
return res;
},
},
methods: {
copyFullNameWithTitleToClipboard() {
let fragments = [
this.$props.data.Titel,
this.$props.data.Vorname,
this.$props.data.Nachname,
this.$props.data.Postnomen,
];
fragments = fragments.filter((fragment) => !!fragment);
navigator.clipboard.writeText(fragments.join(" "));
this.$fhcAlert.alertSuccess(
this.$p.t("profil/name_title_clipboard_copy_confirmation"),
);
},
},
template: /*html*/ `
<div class="card h-100">
@@ -124,6 +139,16 @@ export default {
<span class="form-underline-content">{{wert?wert:'-'}} </span>
</div>
</div>
<a
@click="copyFullNameWithTitleToClipboard()"
href="#"
class="col-12 fhc-link-color d-flex flex-row gap-2 align-items-center justify-content-center mt-3"
>
<span>
<i class="fa-regular fa-copy ms-2 text-decoration-none"></i>
</span>
<span>{{ $p.t("profil/name_title_copy") }}</span>
</a>
</div>
</div>
</div>
+13 -20
View File
@@ -1,6 +1,7 @@
import {CoreFilterCmpt} from "../../../components/filter/Filter.js";
import VueDatePicker from '../../vueDatepicker.js.php';
import ApiOrt from '../../../api/factory/ort.js'
import { getNow, getTodayISO } from "../../../helpers/DateHelpers.js";
export const Raumsuche = {
name: "Raumsuche",
components: {
@@ -10,6 +11,8 @@ export const Raumsuche = {
},
inject: ["isMobile"],
data() {
const now = getNow();
return {
phrasenPromise: null,
phrasenResolved: false,
@@ -23,14 +26,14 @@ export const Raumsuche = {
},
anzahl: 1,
selectedType: null,
datum: new Date(),
datum: getTodayISO(),
von: Vue.ref({
hours: new Date().getHours(),
minutes: new Date().getMinutes()
hours: now.hour,
minutes: now.minute
}),
bis: Vue.ref({
hours: new Date().getHours() + 1,
minutes: new Date().getMinutes()
hours: now.plus({ hours: 1 }).hour,
minutes: now.minute
}),
datepickerTextInputOptions: {
enterSubmit: true,
@@ -136,7 +139,7 @@ export const Raumsuche = {
})
},
loadRooms() {
this.$api.call(ApiOrt.getRooms(this.datum.toISOString(), this.getTimeString(this.von), this.getTimeString(this.bis), this.selectedType?.raumtyp_kurzbz ?? '', this.anzahl))
this.$api.call(ApiOrt.getRooms(this.datum, this.getTimeString(this.von), this.getTimeString(this.bis), this.selectedType?.raumtyp_kurzbz ?? '', this.anzahl))
.then(res => {
if(res?.data?.retval) this.setupData(res.data.retval)
})
@@ -150,17 +153,6 @@ export const Raumsuche = {
setRoute(val) {
// TODO: router push
},
dateFormat(date) {
const day = String(date.getDate()).padStart(2, '0');
const month = String(date.getMonth() + 1).padStart(2, '0');
const year = date.getFullYear();
return `${day}.${month}.${year}`
},
timeFormat(date) {
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
return `${hours}:${minutes}`;
},
async setupMounted() {
this.tableBuiltPromise = new Promise(this.tableResolve)
@@ -199,7 +191,8 @@ export const Raumsuche = {
:dark="isDarkMode"
:clearable="false"
:enable-time-picker="false"
:format="dateFormat"
format="dd.MM.yyyy"
model-type="yyyy-MM-dd"
:text-input="datepickerTextInputOptions"
:min-date="new Date()"
date-picker
@@ -213,7 +206,7 @@ export const Raumsuche = {
v-model="von"
:dark="isDarkMode"
:clearable="false"
:format="timeFormat"
format="HH:mm"
:text-input="timepickerTextInputOptions"
:is-24="true"
time-picker
@@ -227,7 +220,7 @@ export const Raumsuche = {
v-model="bis"
:dark="isDarkMode"
:clearable="false"
:format="timeFormat"
format="HH:mm"
:text-input="timepickerTextInputOptions"
:is-24="true"
time-picker
+49 -20
View File
@@ -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;
@@ -204,15 +224,23 @@ export default {
},
template: /* html */`
<div class="core-dashboard">
<h3>
{{ userFirstName ? $p.t('global/personalGreeting', [ userFirstName ]) : '' }}
<button
class="btn ms-2"
aria-label="edit dashboard"
v-tooltip="{ showDelay: 1000, value: $p.t('dashboard/edit') }"
@click="editMode = !editMode"
><i class="fa-solid fa-gear" aria-hidden="true"></i></button>
</h3>
<header class="d-flex justify-content-between align-items-baseline">
<h3>
{{ userFirstName ? $p.t('global/personalGreeting', [ userFirstName ]) : '' }}
</h3>
<div class="form-check form-check-reverse form-switch">
<label class="form-check-label" for="switchEditMode">
{{ $p.t('dashboard/edit') }}
</label>
<input
class="form-check-input"
type="checkbox"
role="switch"
id="switchEditMode"
v-model="editMode"
>
</div>
</header>
<dashboard-section
name="general"
:widgets="widgets"
@@ -223,6 +251,7 @@ export default {
<dashboard-widget-picker
ref="widgetpicker"
:widgets="widgetsSetup"
:hidden-widgets="hiddenWidgets"
></dashboard-widget-picker>
</div>`
}
+2 -26
View File
@@ -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*/ `
<article
v-if="!hidden || editMode"
class="dashboard-item card overflow-hidden h-100 position-relative"
:class="{
'hidden-widget': hidden,
[arguments?.className]: arguments && arguments.className
}"
>
@@ -299,9 +288,9 @@ export default {
>
<i class="fa-solid fa-gear" aria-hidden="true"></i>
</a>
<!-- delete button -->
<!-- delete/hide button -->
<a
v-if="custom && editMode"
v-if="editMode"
href="#"
class="col-auto px-1"
:aria-label="$p.t('dashboard/widget_delete')"
@@ -310,19 +299,6 @@ export default {
>
<i class="fa-solid fa-trash" aria-hidden="true"></i>
</a>
<!-- hide button -->
<Transition>
<div v-if="!custom && editMode" class="col-auto px-1 form-switch">
<input
type="checkbox"
role="switch"
v-model="visible"
class="form-check-input ms-0"
:value="true"
:aria-label="$p.t('dashboard/widget_toggle_visibility')"
>
</div>
</Transition>
</header>
<!-- TODO Manu rename/remove-->
<div v-if="ready" class="card-body overflow-hidden p-0">
+3 -16
View File
@@ -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"
+44 -11
View File
@@ -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 {
<bs-modal
ref="modal"
class="fade"
:dialog-class="{ 'modal-fullscreen-sm-down': 1, 'modal-xl': widgets && widgets.length > 0 }"
:dialog-class="{ 'modal-fullscreen-sm-down': 1, 'modal-xl': hasAnyWidgets }"
@hiddenBsModal="close"
>
<template v-slot:title>{{ $p.t('dashboard/createWidget') }}</template>
<template v-slot:default>
<div v-if="widgets" class="row g-2">
<div v-if="!widgets.length">
{{ $p.t('dashboard/noWidgetsAvailable') }}
</div>
<template v-if="widgets && hiddenWidgets">
<div
v-for="widget in widgets"
:key="widget.widget_id"
class="widget-icon-container col-sm-6 col-md-4 col-lg-3 col-xl-2"
v-if="!widgets.length && !hiddenWidgets.length"
class="row g-2"
>
<widget-icon @select="pick" :widget="widget"></widget-icon>
<div>{{ $p.t('dashboard/noWidgetsAvailable') }}</div>
</div>
</div>
<template v-else>
<div
v-if="hiddenWidgets.length"
class="row g-2"
:class="widgets.length ? 'border-bottom pb-3 mb-3' : ''"
>
<div
v-for="widget in hiddenWidgets"
:key="widget.widget_id"
class="widget-icon-container col-sm-6 col-md-4 col-lg-3 col-xl-2"
>
<widget-icon @select="pick(widget)" :widget="widgets.find(w => w.widget_id == widget.widget)"></widget-icon>
</div>
</div>
<div
v-if="widgets.length"
class="row g-2"
>
<div
v-for="widget in widgets"
:key="widget.widget_id"
class="widget-icon-container col-sm-6 col-md-4 col-lg-3 col-xl-2"
>
<widget-icon @select="pick" :widget="widget"></widget-icon>
</div>
</div>
</template>
</template>
<div v-else class="text-center">
<i class="fa-solid fa-spinner fa-pulse fa-3x"></i>
</div>
@@ -1,3 +1,5 @@
import { formatDate, formatDateTime } from "../../helpers/DateHelpers.js";
export default {
props: [
"config",
@@ -30,14 +32,7 @@ export default {
},
},
methods: {
formatDateTime: function(dateTime) {
const dt = new Date(dateTime);
return dt.getDate() + '.' + (dt.getMonth()+1) + '.' + dt.getFullYear() + ' | '
+ dt.getHours() + ':' + dt.getMinutes();
},
getDate: function(dateTime){
const dt = new Date(dateTime);
return dt.getDate() + '.' + (dt.getMonth()+1) + '.' + dt.getFullYear();
}
formatDateTime,
getDate: formatDate
}
}
+3 -9
View File
@@ -1,6 +1,6 @@
import AbstractWidget from './Abstract.js';
import BsModal from '../Bootstrap/Modal.js';
import { numberPadding } from '../../helpers/DateHelpers.js';
import { formatDate, formatTime } from '../../helpers/DateHelpers.js';
import ApiCms from '../../api/factory/cms.js';
const MAX_LOADED_NEWS = 30;
@@ -84,14 +84,8 @@ export default {
});
})
},
formatDate: function (dateTime) {
const dt = new Date(dateTime);
return numberPadding(dt.getDate()) + '.' + numberPadding((dt.getMonth() + 1)) + '.' + dt.getFullYear();
},
formatTime: function (dateTime) {
const dt = new Date(dateTime);
return numberPadding(dt.getHours()) + ':' + numberPadding(dt.getMinutes());
},
formatDate,
formatTime,
isString(value){
return Object.prototype.toString.call(value) === '[object String]';
},
@@ -400,32 +400,37 @@ export default {
<div v-if="headerData.length == 1">
<div v-if="!isLoading" class="d-flex align-items-center gap-3">
<h2 class="h4">
<h2 class="h4 mt-2">
{{headerData[0].titelpre}}
{{headerData[0].vorname}}
{{headerData[0].nachname}}
<span v-if="headerData[0].titelpost">, </span>
{{headerData[0].titelpost}}
</h2>
<core-tag ref="tagComponent"
v-if="tagsEnabled"
:endpoint="tagEndpoint"
:values="prestudentIds"
@added="addedTag"
@deleted="deletedTag"
@updated="updatedTag"
zuordnung_typ="prestudent_id"
></core-tag>
<div
role="button"
v-if="tagsEnabled"
@click="rebuildPrestudentTags"
class="btn btn-outline btn-light mb-1"
:title="'Automatische Tags fuer ' + currentSemester + ' neu laden'"
>
<i class="fa-solid fa-refresh pe-1"></i>
<span>{{currentSemester}}</span>
<div class="d-flex border rounded align-items-center ps-1">
<core-tag ref="tagComponent"
v-if="tagsEnabled"
:endpoint="tagEndpoint"
:values="prestudentIds"
:show-hover="true"
@added="addedTag"
@deleted="deletedTag"
@updated="updatedTag"
zuordnung_typ="prestudent_id"
></core-tag>
<div
role="button"
v-if="tagsEnabled"
@click="rebuildPrestudentTags"
class="btn btn-outline btn-sm btn-hover m-1"
:title="$p.t('tag','rebuild_tags') + ' ' + currentSemester"
>
<i class="fa-solid fa-refresh pe-1"></i>
<span>{{currentSemester}}</span>
</div>
</div>
<h6 v-if="headerData[0].unruly" class="badge" :class="'bg-unruly rounded-0'"><strong>unruly</strong></h6>
</div>
<div v-else class="d-flex align-items-center gap-3">
+13
View File
@@ -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,
@@ -553,8 +553,12 @@ export default {
}
if (!this.formData.akadgrad_id && this.arrAkadGrad.length > 0) {
if(this.student.geschlecht == 'w')
this.formData.akadgrad_id = this.arrAkadGrad[1].akadgrad_id;
const akadGradByGeschlecht = this.arrAkadGrad.find((item) => {
return item.geschlecht === this.student.geschlecht;
});
if(akadGradByGeschlecht)
this.formData.akadgrad_id = akadGradByGeschlecht.akadgrad_id;
else
this.formData.akadgrad_id = this.arrAkadGrad[0].akadgrad_id;
}
@@ -317,6 +317,7 @@ export default {
"$p.user_language.value"(n, o) {
if (n !== o && o !== undefined && this.$refs.table.tableBuilt) {
this.translateTabulator();
this.$reloadList();
}
},
"tagFilterState.selectedOptions": {
@@ -842,35 +843,39 @@ export default {
</div>
</template>
<template #actions>
<core-tag ref="tagComponent"
v-if="tagsEnabled"
:endpoint="tagEndpoint"
:values="selectedColumnValues"
@added="addedTag"
@deleted="deletedTag"
@updated="updatedTag"
zuordnung_typ="prestudent_id"
></core-tag>
<template #tags>
<div class="d-flex border rounded align-items-center ps-1">
<core-tag ref="tagComponent"
v-if="tagsEnabled"
:endpoint="tagEndpoint"
:values="selectedColumnValues"
@added="addedTag"
@deleted="deletedTag"
@updated="updatedTag"
zuordnung_typ="prestudent_id"
show-hover
></core-tag>
<button
v-if="!selected.length"
class="btn btn-outline btn-light mb-1"
@click="rebuild(selected)"
:title="$p.t('tag','rebuild_tags') + ' Stg ' + currentSemester"
>
<i class="fa-solid fa-refresh pe-1"></i> STG
</button>
<button
v-else
class="btn btn-outline btn-light mb-1"
@click="rebuild(selected)"
:title="$p.t('tag','rebuild_tags') + ' ' + $p.t('ui','selection') + ' ' + currentSemester"
>
<i class="fa-solid fa-refresh pe-1"></i> {{selected.length}}
</button>
<button
v-if="!selected.length"
class="btn btn-outline btn-sm m-1 btn-hover"
@click="rebuild(selected)"
:title="$p.t('tag','rebuild_tags') + ' Stg ' + currentSemester"
>
<i class="fa-solid fa-refresh pe-1"></i> STG
</button>
<button
v-else
class="btn btn-outline btn-sm m-1 btn-hover"
@click="rebuild(selected)"
:title="$p.t('tag','rebuild_tags') + ' ' + $p.t('ui','selection') + ' ' + currentSemester"
>
<i class="fa-solid fa-refresh pe-1"></i> {{selected.length}}
</button>
</div>
</template>
<template v-if="filter.length || headerFilterActive">
<template v-if="filter.length || headerFilterActive">
<div class="d-flex justify-content-center align-items-center gap-2 ps-4 position-absolute start-50 translate-middle-x">
<p class="text-danger mb-0">
<strong>{{$p.t('filter','filterActive')}}</strong>
@@ -884,8 +889,6 @@ export default {
<span class="fa-solid fa-filter-circle-xmark"></span>
</button>
</div>
</template>
</template>
<template #filter>
@@ -895,6 +898,7 @@ export default {
</div>
</div>
</template>
</core-filter-cmpt>
</div>
<list-new ref="new" :studiengang-kz="studiengangKz" :studiensemester-kurzbz="studiensemesterKurzbz"></list-new>
+6 -1
View File
@@ -28,6 +28,10 @@ export default {
confirmLimit: {
type: Number,
default: 20
},
showHover: {
type: Boolean,
default: false
}
},
data() {
@@ -228,7 +232,8 @@ export default {
<span :title="values.length === 0 ? 'Bitte Zeilen markieren' : ''">
<button @mouseover="showList = true"
:disabled="!values || values.length === 0"
class="btn btn-sm">
class="btn btn-sm"
:class="{'btn-hover': showHover}">
<i class="fa-solid fa-tag fa-xl"></i>
</button>
</span>
+2 -1
View File
@@ -1094,7 +1094,7 @@ export const CoreFilterCmpt = {
<div :id="'filterCollapsables' + idExtra">
<div class="d-flex flex-row justify-content-between flex-wrap">
<div v-if="newBtnShow || reload || $slots.search || $slots.actions" class="d-flex gap-2 align-items-baseline flex-wrap">
<div v-if="newBtnShow || reload || $slots.search || $slots.actions || $slots.tags" class="d-flex gap-2 align-items-baseline flex-wrap">
<button v-if="newBtnShow" class="btn btn-primary" :class="newBtnClass" :title="newBtnLabel ? undefined : 'New'" :aria-label="newBtnLabel ? undefined : 'New'" @click="$emit('click:new', $event)" :disabled="newBtnDisabled">
<span class="fa-solid fa-plus" aria-hidden="true"></span>
{{ newBtnLabel }}
@@ -1107,6 +1107,7 @@ export const CoreFilterCmpt = {
<span v-else id="selected-info-text"> Mit {{ selectedData.length }} ausgewählten:</span>
</span>
<slot name="actions" v-bind="{selected: tabulatorHasSelector ? selectedData : []}"></slot>
<slot name="tags"></slot>
<slot name="search"></slot>
</div>
<div class="d-flex gap-1 align-items-baseline flex-grow-1 justify-content-end">
+1 -1
View File
@@ -40,7 +40,7 @@ export default {
query: Vue.computed(() => this.lastQuery),
};
},
inject: ["isMobile"],
inject: { isMobile: { default: false } },
data: function () {
return {
searchtimer: null,
+60 -52
View File
@@ -1,61 +1,69 @@
// HELPER FILE -- that contains multiple functions which create, handle and manipulate js dates
// Everything parses and formats with luxon in the server timezone. new Date("2026-09-02")
// reads UTC midnight and shows the day before west of Greenwich.
// custom Error class for DateHelpers.js misuse
class DateHelperError extends Error {
constructor(message) {
super(message);
this.name = "DateHelperError";
}
function getZone() {
return globalThis.FHC_JS_DATA_STORAGE_OBJECT?.timezone || 'Europe/Vienna';
}
/**
* adds padding to a number
* converts a date value into a luxon DateTime in the server timezone
*
* @param {number|string} number - the number on which padding should be added.
* @returns {string} number with padding.
* @param {string|number|Date|Object} value - ISO string, SQL string, milliseconds, Date or luxon DateTime.
* @returns {Object|null} luxon DateTime, or null if the value is empty or unparsable.
*/
export function toDateTime(value) {
if (!value) return null;
const zone = getZone();
let date;
if (value instanceof luxon.DateTime) date = value.setZone(zone);
else if (value instanceof Date) date = luxon.DateTime.fromJSDate(value, { zone });
else if (typeof value === 'number') date = luxon.DateTime.fromMillis(value, { zone });
// fromISO rejects the space that SQL puts between date and time
else if (typeof value === 'string') date = value.includes(' ')
? luxon.DateTime.fromSQL(value, { zone })
: luxon.DateTime.fromISO(value, { zone });
else return null;
return date.isValid ? date : null;
}
// current time in the server timezone
export function getNow() {
return luxon.DateTime.now().setZone(getZone());
}
// today in the server timezone, as yyyy-MM-dd
export function getTodayISO() {
return getNow().toISODate();
}
// the three formatters return '' for an empty or unparsable value
export function formatDate(value) {
return toDateTime(value)?.toFormat('dd.MM.yyyy') ?? '';
}
export function formatTime(value) {
return toDateTime(value)?.toFormat('HH:mm') ?? '';
}
export function formatDateTime(value) {
return toDateTime(value)?.toFormat('dd.MM.yyyy HH:mm') ?? '';
}
// tabulator sorter for ISO dates, sorts empty values last
export function compareISODateValues(a, b) {
if (!a && !b) return 0;
if (!a) return 1;
if (!b) return -1;
return String(a).localeCompare(String(b));
}
// pads hours and minutes to two digits
export function numberPadding(number) {
if(typeof number !== "string" && typeof number !== "number")
{
throw new TypeError("function numberPadding in file DateHelpers.js is only usable with strings or numbers");
}
if(number.toString().length > 2)
{
throw new DateHelperError("The number on which the padding should be added should not be longer than to 2 characters, please refere to the function numberPadding in the helper file DateHelpers.js");
}
return number.toString().length == 1 ? '0' + number.toString() : number.toString();
}
/**
* formats date to dd.mm.yyyy
*
* @param {string|Date} d - the date that should be formatted.
* @returns {string} formatted date string.
*/
export function formatDate(d) {
// parameter is of type Date
if(d instanceof Date)
{
if (isNaN(date.valueOf())) {
return 'N/A';
}
// if the date is an invalid string then creating a date from the string will fail and N/A is returned
return `${numberPadding(d.getDate())}.${numberPadding(d.getMonth() + 1)}.${d.getFullYear()}`;
}
// parameter is of type string
else if (typeof d === "string")
{
let date = new Date(d);
// if the date is an invalid string then creating a date from the string will fail and N/A is returned
if (isNaN(date.valueOf())) {
return 'N/A';
}
return `${numberPadding(date.getDate())}.${numberPadding(date.getMonth() + 1)}.${date.getFullYear()}`;
}
// parameter is not of type string or Date and an exception is thrown
else
{
throw new TypeError("The parameter provided for this function is not a string or a Date object, please refere to the function formatDate in the DateHelpers.js file");
}
return String(number).padStart(2, '0');
}
+1
View File
@@ -100,6 +100,7 @@ require_once('dbupdate_3.4/68530_Dashboard_Cleanup.php');
require_once('dbupdate_3.4/75959_StudVw_Automatische_Tags.php');
require_once('dbupdate_3.4/76160_lvv_favorites.php');
require_once('dbupdate_3.4/77080_tabulator_presets_table.php');
require_once('dbupdate_3.4/78292_gehaltsanpassungtyp.php');
// *** Pruefung und hinzufuegen der neuen Attribute und Tabellen
echo '<H2>Pruefe Tabellen und Attribute!</H2>';
@@ -0,0 +1,47 @@
<?php
if (! defined('DB_NAME')) exit('No direct script access allowed');
if ($result = $db->db_query("SELECT * FROM information_schema.tables WHERE table_name='tbl_gehaltsanpassungtyp' AND table_schema='hr'"))
{
if ($db->db_num_rows($result) == 0)
{
$qry = "
ALTER TABLE
hr.tbl_gehaltsbestandteil
ADD COLUMN IF NOT EXISTS
gehaltsanpassungtyp_kurzbz character varying(32);
COMMENT ON COLUMN hr.tbl_gehaltsbestandteil.gehaltsanpassungtyp_kurzbz IS E'Ersteinstufung, indiv. Erhöhung, etc.';
CREATE TABLE IF NOT EXISTS 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 DROP CONSTRAINT IF EXISTS tbl_gehaltsanpassungtyp_fk;
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;
GRANT SELECT, UPDATE, INSERT, DELETE ON hr.tbl_gehaltsanpassungtyp TO vilesci;
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_erhoehung','Indiv. Gehaltserhöhung', 2, true);
INSERT INTO hr.tbl_gehaltsanpassungtyp(gehaltsanpassungtyp_kurzbz, bezeichnung, sort, aktiv) VALUES('strukturelle_anpassung','Strukturelle Anpassung', 3, true);
INSERT INTO hr.tbl_gehaltsanpassungtyp(gehaltsanpassungtyp_kurzbz, bezeichnung, sort, aktiv) VALUES('funktionsaenderung','Funktionsänderung', 4, true);
";
if (! $db->db_query($qry))
echo '<strong>Gehaltsanpassungtyp: ' . $db->db_last_error() . '</strong><br>';
else
echo 'Tabelle und Feld für Gehaltsanpassungtyp wurden neu erstellt<br>';
}
}
+60
View File
@@ -29742,6 +29742,46 @@ array(
)
)
),
array(
'app' => 'core',
'category' => 'profil',
'phrase' => 'name_title_clipboard_copy_confirmation',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Name mit Titel in die Zwischenablage kopiert.',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Full name with title copied to clipboard.',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'profil',
'phrase' => 'name_title_copy',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Name mit Titel kopieren',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Copy full name with title',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'profil',
@@ -60852,6 +60892,26 @@ I have been informed that I am under no obligation to consent to the transmissio
)
)
),
array(
'app' => 'core',
'category' => 'LvPlan',
'phrase' => 'copy',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Kopieren',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Copy',
'description' => '',
'insertvon' => 'system'
)
)
),
// ### LvPlanStgOrg END ###
// DMS-Link Phrasen Start
array(